2022-01-06 00:55:12 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2021 Google LLC .
*/
# include <linux/init.h>
# include <linux/highmem.h>
# include <linux/kobject.h>
# include <linux/mm.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/sysfs.h>
# include <linux/vmalloc.h>
2022-03-22 17:03:31 +03:00
# include "internal.h"
2022-01-06 00:55:12 +03:00
static int module_extend_max_pages ( struct load_info * info , unsigned int extent )
{
struct page * * new_pages ;
new_pages = kvmalloc_array ( info - > max_pages + extent ,
sizeof ( info - > pages ) , GFP_KERNEL ) ;
if ( ! new_pages )
return - ENOMEM ;
memcpy ( new_pages , info - > pages , info - > max_pages * sizeof ( info - > pages ) ) ;
kvfree ( info - > pages ) ;
info - > pages = new_pages ;
info - > max_pages + = extent ;
return 0 ;
}
static struct page * module_get_next_page ( struct load_info * info )
{
struct page * page ;
int error ;
if ( info - > max_pages = = info - > used_pages ) {
error = module_extend_max_pages ( info , info - > used_pages ) ;
if ( error )
return ERR_PTR ( error ) ;
}
page = alloc_page ( GFP_KERNEL | __GFP_HIGHMEM ) ;
if ( ! page )
return ERR_PTR ( - ENOMEM ) ;
info - > pages [ info - > used_pages + + ] = page ;
return page ;
}
# ifdef CONFIG_MODULE_COMPRESS_GZIP
# include <linux/zlib.h>
# define MODULE_COMPRESSION gzip
# define MODULE_DECOMPRESS_FN module_gzip_decompress
/*
* Calculate length of the header which consists of signature , header
* flags , time stamp and operating system ID ( 10 bytes total ) , plus
* an optional filename .
*/
static size_t module_gzip_header_len ( const u8 * buf , size_t size )
{
const u8 signature [ ] = { 0x1f , 0x8b , 0x08 } ;
size_t len = 10 ;
if ( size < len | | memcmp ( buf , signature , sizeof ( signature ) ) )
return 0 ;
if ( buf [ 3 ] & 0x08 ) {
do {
/*
* If we can ' t find the end of the file name we must
* be dealing with a corrupted file .
*/
if ( len = = size )
return 0 ;
} while ( buf [ len + + ] ! = ' \0 ' ) ;
}
return len ;
}
static ssize_t module_gzip_decompress ( struct load_info * info ,
const void * buf , size_t size )
{
struct z_stream_s s = { 0 } ;
size_t new_size = 0 ;
size_t gzip_hdr_len ;
ssize_t retval ;
int rc ;
gzip_hdr_len = module_gzip_header_len ( buf , size ) ;
if ( ! gzip_hdr_len ) {
pr_err ( " not a gzip compressed module \n " ) ;
return - EINVAL ;
}
s . next_in = buf + gzip_hdr_len ;
s . avail_in = size - gzip_hdr_len ;
s . workspace = kmalloc ( zlib_inflate_workspacesize ( ) , GFP_KERNEL ) ;
if ( ! s . workspace )
return - ENOMEM ;
rc = zlib_inflateInit2 ( & s , - MAX_WBITS ) ;
if ( rc ! = Z_OK ) {
2022-01-13 12:24:17 +03:00
pr_err ( " failed to initialize decompressor: %d \n " , rc ) ;
2022-01-06 00:55:12 +03:00
retval = - EINVAL ;
goto out ;
}
do {
struct page * page = module_get_next_page ( info ) ;
2022-03-22 17:03:33 +03:00
2022-11-10 05:58:34 +03:00
if ( IS_ERR ( page ) ) {
retval = PTR_ERR ( page ) ;
2022-01-06 00:55:12 +03:00
goto out_inflate_end ;
}
2022-07-20 19:19:32 +03:00
s . next_out = kmap_local_page ( page ) ;
2022-01-06 00:55:12 +03:00
s . avail_out = PAGE_SIZE ;
rc = zlib_inflate ( & s , 0 ) ;
2022-07-20 19:19:32 +03:00
kunmap_local ( s . next_out ) ;
2022-01-06 00:55:12 +03:00
new_size + = PAGE_SIZE - s . avail_out ;
} while ( rc = = Z_OK ) ;
if ( rc ! = Z_STREAM_END ) {
pr_err ( " decompression failed with status %d \n " , rc ) ;
retval = - EINVAL ;
goto out_inflate_end ;
}
retval = new_size ;
out_inflate_end :
zlib_inflateEnd ( & s ) ;
out :
kfree ( s . workspace ) ;
return retval ;
}
# elif CONFIG_MODULE_COMPRESS_XZ
# include <linux/xz.h>
# define MODULE_COMPRESSION xz
# define MODULE_DECOMPRESS_FN module_xz_decompress
static ssize_t module_xz_decompress ( struct load_info * info ,
const void * buf , size_t size )
{
static const u8 signature [ ] = { 0xfd , ' 7 ' , ' z ' , ' X ' , ' Z ' , 0 } ;
struct xz_dec * xz_dec ;
struct xz_buf xz_buf ;
enum xz_ret xz_ret ;
size_t new_size = 0 ;
ssize_t retval ;
if ( size < sizeof ( signature ) | |
memcmp ( buf , signature , sizeof ( signature ) ) ) {
pr_err ( " not an xz compressed module \n " ) ;
return - EINVAL ;
}
xz_dec = xz_dec_init ( XZ_DYNALLOC , ( u32 ) - 1 ) ;
if ( ! xz_dec )
return - ENOMEM ;
xz_buf . in_size = size ;
xz_buf . in = buf ;
xz_buf . in_pos = 0 ;
do {
struct page * page = module_get_next_page ( info ) ;
2022-03-22 17:03:33 +03:00
2022-11-10 05:58:34 +03:00
if ( IS_ERR ( page ) ) {
retval = PTR_ERR ( page ) ;
2022-01-06 00:55:12 +03:00
goto out ;
}
2022-07-20 19:19:32 +03:00
xz_buf . out = kmap_local_page ( page ) ;
2022-01-06 00:55:12 +03:00
xz_buf . out_pos = 0 ;
xz_buf . out_size = PAGE_SIZE ;
xz_ret = xz_dec_run ( xz_dec , & xz_buf ) ;
2022-07-20 19:19:32 +03:00
kunmap_local ( xz_buf . out ) ;
2022-01-06 00:55:12 +03:00
new_size + = xz_buf . out_pos ;
} while ( xz_buf . out_pos = = PAGE_SIZE & & xz_ret = = XZ_OK ) ;
if ( xz_ret ! = XZ_STREAM_END ) {
pr_err ( " decompression failed with status %d \n " , xz_ret ) ;
retval = - EINVAL ;
goto out ;
}
retval = new_size ;
out :
xz_dec_end ( xz_dec ) ;
return retval ;
}
# else
# error "Unexpected configuration for CONFIG_MODULE_DECOMPRESS"
# endif
int module_decompress ( struct load_info * info , const void * buf , size_t size )
{
unsigned int n_pages ;
ssize_t data_size ;
int error ;
/*
* Start with number of pages twice as big as needed for
* compressed data .
*/
n_pages = DIV_ROUND_UP ( size , PAGE_SIZE ) * 2 ;
error = module_extend_max_pages ( info , n_pages ) ;
data_size = MODULE_DECOMPRESS_FN ( info , buf , size ) ;
if ( data_size < 0 ) {
error = data_size ;
goto err ;
}
info - > hdr = vmap ( info - > pages , info - > used_pages , VM_MAP , PAGE_KERNEL ) ;
if ( ! info - > hdr ) {
error = - ENOMEM ;
goto err ;
}
info - > len = data_size ;
return 0 ;
err :
module_decompress_cleanup ( info ) ;
return error ;
}
void module_decompress_cleanup ( struct load_info * info )
{
int i ;
if ( info - > hdr )
vunmap ( info - > hdr ) ;
for ( i = 0 ; i < info - > used_pages ; i + + )
__free_page ( info - > pages [ i ] ) ;
kvfree ( info - > pages ) ;
info - > pages = NULL ;
info - > max_pages = info - > used_pages = 0 ;
}
2022-02-16 00:11:42 +03:00
# ifdef CONFIG_SYSFS
2022-01-06 00:55:12 +03:00
static ssize_t compression_show ( struct kobject * kobj ,
struct kobj_attribute * attr , char * buf )
{
2022-09-06 11:03:18 +03:00
return sysfs_emit ( buf , __stringify ( MODULE_COMPRESSION ) " \n " ) ;
2022-01-06 00:55:12 +03:00
}
2022-03-22 17:03:33 +03:00
2022-01-06 00:55:12 +03:00
static struct kobj_attribute module_compression_attr = __ATTR_RO ( compression ) ;
static int __init module_decompress_sysfs_init ( void )
{
int error ;
error = sysfs_create_file ( & module_kset - > kobj ,
& module_compression_attr . attr ) ;
if ( error )
pr_warn ( " Failed to create 'compression' attribute " ) ;
return 0 ;
}
late_initcall ( module_decompress_sysfs_init ) ;
2022-02-16 00:11:42 +03:00
# endif