1cfc1d277SAaron Tomlin // SPDX-License-Identifier: GPL-2.0-or-later 2cfc1d277SAaron Tomlin /* 3cfc1d277SAaron Tomlin * Copyright 2021 Google LLC. 4cfc1d277SAaron Tomlin */ 5cfc1d277SAaron Tomlin 6cfc1d277SAaron Tomlin #include <linux/init.h> 7cfc1d277SAaron Tomlin #include <linux/highmem.h> 8cfc1d277SAaron Tomlin #include <linux/kobject.h> 9cfc1d277SAaron Tomlin #include <linux/mm.h> 10cfc1d277SAaron Tomlin #include <linux/module.h> 11cfc1d277SAaron Tomlin #include <linux/slab.h> 12cfc1d277SAaron Tomlin #include <linux/sysfs.h> 13cfc1d277SAaron Tomlin #include <linux/vmalloc.h> 14cfc1d277SAaron Tomlin 15cfc1d277SAaron Tomlin #include "internal.h" 16cfc1d277SAaron Tomlin 17cfc1d277SAaron Tomlin static int module_extend_max_pages(struct load_info *info, unsigned int extent) 18cfc1d277SAaron Tomlin { 19cfc1d277SAaron Tomlin struct page **new_pages; 20cfc1d277SAaron Tomlin 21cfc1d277SAaron Tomlin new_pages = kvmalloc_array(info->max_pages + extent, 22cfc1d277SAaron Tomlin sizeof(info->pages), GFP_KERNEL); 23cfc1d277SAaron Tomlin if (!new_pages) 24cfc1d277SAaron Tomlin return -ENOMEM; 25cfc1d277SAaron Tomlin 26cfc1d277SAaron Tomlin memcpy(new_pages, info->pages, info->max_pages * sizeof(info->pages)); 27cfc1d277SAaron Tomlin kvfree(info->pages); 28cfc1d277SAaron Tomlin info->pages = new_pages; 29cfc1d277SAaron Tomlin info->max_pages += extent; 30cfc1d277SAaron Tomlin 31cfc1d277SAaron Tomlin return 0; 32cfc1d277SAaron Tomlin } 33cfc1d277SAaron Tomlin 34cfc1d277SAaron Tomlin static struct page *module_get_next_page(struct load_info *info) 35cfc1d277SAaron Tomlin { 36cfc1d277SAaron Tomlin struct page *page; 37cfc1d277SAaron Tomlin int error; 38cfc1d277SAaron Tomlin 39cfc1d277SAaron Tomlin if (info->max_pages == info->used_pages) { 40cfc1d277SAaron Tomlin error = module_extend_max_pages(info, info->used_pages); 41cfc1d277SAaron Tomlin if (error) 42cfc1d277SAaron Tomlin return ERR_PTR(error); 43cfc1d277SAaron Tomlin } 44cfc1d277SAaron Tomlin 45cfc1d277SAaron Tomlin page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM); 46cfc1d277SAaron Tomlin if (!page) 47cfc1d277SAaron Tomlin return ERR_PTR(-ENOMEM); 48cfc1d277SAaron Tomlin 49cfc1d277SAaron Tomlin info->pages[info->used_pages++] = page; 50cfc1d277SAaron Tomlin return page; 51cfc1d277SAaron Tomlin } 52cfc1d277SAaron Tomlin 53169a58adSStephen Boyd #if defined(CONFIG_MODULE_COMPRESS_GZIP) 54cfc1d277SAaron Tomlin #include <linux/zlib.h> 55cfc1d277SAaron Tomlin #define MODULE_COMPRESSION gzip 56cfc1d277SAaron Tomlin #define MODULE_DECOMPRESS_FN module_gzip_decompress 57cfc1d277SAaron Tomlin 58cfc1d277SAaron Tomlin /* 59cfc1d277SAaron Tomlin * Calculate length of the header which consists of signature, header 60cfc1d277SAaron Tomlin * flags, time stamp and operating system ID (10 bytes total), plus 61cfc1d277SAaron Tomlin * an optional filename. 62cfc1d277SAaron Tomlin */ 63cfc1d277SAaron Tomlin static size_t module_gzip_header_len(const u8 *buf, size_t size) 64cfc1d277SAaron Tomlin { 65cfc1d277SAaron Tomlin const u8 signature[] = { 0x1f, 0x8b, 0x08 }; 66cfc1d277SAaron Tomlin size_t len = 10; 67cfc1d277SAaron Tomlin 68cfc1d277SAaron Tomlin if (size < len || memcmp(buf, signature, sizeof(signature))) 69cfc1d277SAaron Tomlin return 0; 70cfc1d277SAaron Tomlin 71cfc1d277SAaron Tomlin if (buf[3] & 0x08) { 72cfc1d277SAaron Tomlin do { 73cfc1d277SAaron Tomlin /* 74cfc1d277SAaron Tomlin * If we can't find the end of the file name we must 75cfc1d277SAaron Tomlin * be dealing with a corrupted file. 76cfc1d277SAaron Tomlin */ 77cfc1d277SAaron Tomlin if (len == size) 78cfc1d277SAaron Tomlin return 0; 79cfc1d277SAaron Tomlin } while (buf[len++] != '\0'); 80cfc1d277SAaron Tomlin } 81cfc1d277SAaron Tomlin 82cfc1d277SAaron Tomlin return len; 83cfc1d277SAaron Tomlin } 84cfc1d277SAaron Tomlin 85cfc1d277SAaron Tomlin static ssize_t module_gzip_decompress(struct load_info *info, 86cfc1d277SAaron Tomlin const void *buf, size_t size) 87cfc1d277SAaron Tomlin { 88cfc1d277SAaron Tomlin struct z_stream_s s = { 0 }; 89cfc1d277SAaron Tomlin size_t new_size = 0; 90cfc1d277SAaron Tomlin size_t gzip_hdr_len; 91cfc1d277SAaron Tomlin ssize_t retval; 92cfc1d277SAaron Tomlin int rc; 93cfc1d277SAaron Tomlin 94cfc1d277SAaron Tomlin gzip_hdr_len = module_gzip_header_len(buf, size); 95cfc1d277SAaron Tomlin if (!gzip_hdr_len) { 96cfc1d277SAaron Tomlin pr_err("not a gzip compressed module\n"); 97cfc1d277SAaron Tomlin return -EINVAL; 98cfc1d277SAaron Tomlin } 99cfc1d277SAaron Tomlin 100cfc1d277SAaron Tomlin s.next_in = buf + gzip_hdr_len; 101cfc1d277SAaron Tomlin s.avail_in = size - gzip_hdr_len; 102cfc1d277SAaron Tomlin 103cfc1d277SAaron Tomlin s.workspace = kmalloc(zlib_inflate_workspacesize(), GFP_KERNEL); 104cfc1d277SAaron Tomlin if (!s.workspace) 105cfc1d277SAaron Tomlin return -ENOMEM; 106cfc1d277SAaron Tomlin 107cfc1d277SAaron Tomlin rc = zlib_inflateInit2(&s, -MAX_WBITS); 108cfc1d277SAaron Tomlin if (rc != Z_OK) { 109cfc1d277SAaron Tomlin pr_err("failed to initialize decompressor: %d\n", rc); 110cfc1d277SAaron Tomlin retval = -EINVAL; 111cfc1d277SAaron Tomlin goto out; 112cfc1d277SAaron Tomlin } 113cfc1d277SAaron Tomlin 114cfc1d277SAaron Tomlin do { 115cfc1d277SAaron Tomlin struct page *page = module_get_next_page(info); 1165aff4dfdSAaron Tomlin 11745af1d7aSMiaoqian Lin if (IS_ERR(page)) { 11845af1d7aSMiaoqian Lin retval = PTR_ERR(page); 119cfc1d277SAaron Tomlin goto out_inflate_end; 120cfc1d277SAaron Tomlin } 121cfc1d277SAaron Tomlin 122554694baSFabio M. De Francesco s.next_out = kmap_local_page(page); 123cfc1d277SAaron Tomlin s.avail_out = PAGE_SIZE; 124cfc1d277SAaron Tomlin rc = zlib_inflate(&s, 0); 125554694baSFabio M. De Francesco kunmap_local(s.next_out); 126cfc1d277SAaron Tomlin 127cfc1d277SAaron Tomlin new_size += PAGE_SIZE - s.avail_out; 128cfc1d277SAaron Tomlin } while (rc == Z_OK); 129cfc1d277SAaron Tomlin 130cfc1d277SAaron Tomlin if (rc != Z_STREAM_END) { 131cfc1d277SAaron Tomlin pr_err("decompression failed with status %d\n", rc); 132cfc1d277SAaron Tomlin retval = -EINVAL; 133cfc1d277SAaron Tomlin goto out_inflate_end; 134cfc1d277SAaron Tomlin } 135cfc1d277SAaron Tomlin 136cfc1d277SAaron Tomlin retval = new_size; 137cfc1d277SAaron Tomlin 138cfc1d277SAaron Tomlin out_inflate_end: 139cfc1d277SAaron Tomlin zlib_inflateEnd(&s); 140cfc1d277SAaron Tomlin out: 141cfc1d277SAaron Tomlin kfree(s.workspace); 142cfc1d277SAaron Tomlin return retval; 143cfc1d277SAaron Tomlin } 144169a58adSStephen Boyd #elif defined(CONFIG_MODULE_COMPRESS_XZ) 145cfc1d277SAaron Tomlin #include <linux/xz.h> 146cfc1d277SAaron Tomlin #define MODULE_COMPRESSION xz 147cfc1d277SAaron Tomlin #define MODULE_DECOMPRESS_FN module_xz_decompress 148cfc1d277SAaron Tomlin 149cfc1d277SAaron Tomlin static ssize_t module_xz_decompress(struct load_info *info, 150cfc1d277SAaron Tomlin const void *buf, size_t size) 151cfc1d277SAaron Tomlin { 152cfc1d277SAaron Tomlin static const u8 signature[] = { 0xfd, '7', 'z', 'X', 'Z', 0 }; 153cfc1d277SAaron Tomlin struct xz_dec *xz_dec; 154cfc1d277SAaron Tomlin struct xz_buf xz_buf; 155cfc1d277SAaron Tomlin enum xz_ret xz_ret; 156cfc1d277SAaron Tomlin size_t new_size = 0; 157cfc1d277SAaron Tomlin ssize_t retval; 158cfc1d277SAaron Tomlin 159cfc1d277SAaron Tomlin if (size < sizeof(signature) || 160cfc1d277SAaron Tomlin memcmp(buf, signature, sizeof(signature))) { 161cfc1d277SAaron Tomlin pr_err("not an xz compressed module\n"); 162cfc1d277SAaron Tomlin return -EINVAL; 163cfc1d277SAaron Tomlin } 164cfc1d277SAaron Tomlin 165cfc1d277SAaron Tomlin xz_dec = xz_dec_init(XZ_DYNALLOC, (u32)-1); 166cfc1d277SAaron Tomlin if (!xz_dec) 167cfc1d277SAaron Tomlin return -ENOMEM; 168cfc1d277SAaron Tomlin 169cfc1d277SAaron Tomlin xz_buf.in_size = size; 170cfc1d277SAaron Tomlin xz_buf.in = buf; 171cfc1d277SAaron Tomlin xz_buf.in_pos = 0; 172cfc1d277SAaron Tomlin 173cfc1d277SAaron Tomlin do { 174cfc1d277SAaron Tomlin struct page *page = module_get_next_page(info); 1755aff4dfdSAaron Tomlin 17645af1d7aSMiaoqian Lin if (IS_ERR(page)) { 17745af1d7aSMiaoqian Lin retval = PTR_ERR(page); 178cfc1d277SAaron Tomlin goto out; 179cfc1d277SAaron Tomlin } 180cfc1d277SAaron Tomlin 181554694baSFabio M. De Francesco xz_buf.out = kmap_local_page(page); 182cfc1d277SAaron Tomlin xz_buf.out_pos = 0; 183cfc1d277SAaron Tomlin xz_buf.out_size = PAGE_SIZE; 184cfc1d277SAaron Tomlin xz_ret = xz_dec_run(xz_dec, &xz_buf); 185554694baSFabio M. De Francesco kunmap_local(xz_buf.out); 186cfc1d277SAaron Tomlin 187cfc1d277SAaron Tomlin new_size += xz_buf.out_pos; 188cfc1d277SAaron Tomlin } while (xz_buf.out_pos == PAGE_SIZE && xz_ret == XZ_OK); 189cfc1d277SAaron Tomlin 190cfc1d277SAaron Tomlin if (xz_ret != XZ_STREAM_END) { 191cfc1d277SAaron Tomlin pr_err("decompression failed with status %d\n", xz_ret); 192cfc1d277SAaron Tomlin retval = -EINVAL; 193cfc1d277SAaron Tomlin goto out; 194cfc1d277SAaron Tomlin } 195cfc1d277SAaron Tomlin 196cfc1d277SAaron Tomlin retval = new_size; 197cfc1d277SAaron Tomlin 198cfc1d277SAaron Tomlin out: 199cfc1d277SAaron Tomlin xz_dec_end(xz_dec); 200cfc1d277SAaron Tomlin return retval; 201cfc1d277SAaron Tomlin } 202169a58adSStephen Boyd #elif defined(CONFIG_MODULE_COMPRESS_ZSTD) 203169a58adSStephen Boyd #include <linux/zstd.h> 204169a58adSStephen Boyd #define MODULE_COMPRESSION zstd 205169a58adSStephen Boyd #define MODULE_DECOMPRESS_FN module_zstd_decompress 206169a58adSStephen Boyd 207169a58adSStephen Boyd static ssize_t module_zstd_decompress(struct load_info *info, 208169a58adSStephen Boyd const void *buf, size_t size) 209169a58adSStephen Boyd { 210169a58adSStephen Boyd static const u8 signature[] = { 0x28, 0xb5, 0x2f, 0xfd }; 211169a58adSStephen Boyd ZSTD_outBuffer zstd_dec; 212169a58adSStephen Boyd ZSTD_inBuffer zstd_buf; 213169a58adSStephen Boyd zstd_frame_header header; 214169a58adSStephen Boyd size_t wksp_size; 215169a58adSStephen Boyd void *wksp = NULL; 216169a58adSStephen Boyd ZSTD_DStream *dstream; 217169a58adSStephen Boyd size_t ret; 218169a58adSStephen Boyd size_t new_size = 0; 219169a58adSStephen Boyd int retval; 220169a58adSStephen Boyd 221169a58adSStephen Boyd if (size < sizeof(signature) || 222169a58adSStephen Boyd memcmp(buf, signature, sizeof(signature))) { 223169a58adSStephen Boyd pr_err("not a zstd compressed module\n"); 224169a58adSStephen Boyd return -EINVAL; 225169a58adSStephen Boyd } 226169a58adSStephen Boyd 227169a58adSStephen Boyd zstd_buf.src = buf; 228169a58adSStephen Boyd zstd_buf.pos = 0; 229169a58adSStephen Boyd zstd_buf.size = size; 230169a58adSStephen Boyd 231169a58adSStephen Boyd ret = zstd_get_frame_header(&header, zstd_buf.src, zstd_buf.size); 232169a58adSStephen Boyd if (ret != 0) { 233169a58adSStephen Boyd pr_err("ZSTD-compressed data has an incomplete frame header\n"); 234169a58adSStephen Boyd retval = -EINVAL; 235169a58adSStephen Boyd goto out; 236169a58adSStephen Boyd } 237169a58adSStephen Boyd if (header.windowSize > (1 << ZSTD_WINDOWLOG_MAX)) { 238169a58adSStephen Boyd pr_err("ZSTD-compressed data has too large a window size\n"); 239169a58adSStephen Boyd retval = -EINVAL; 240169a58adSStephen Boyd goto out; 241169a58adSStephen Boyd } 242169a58adSStephen Boyd 243169a58adSStephen Boyd wksp_size = zstd_dstream_workspace_bound(header.windowSize); 244169a58adSStephen Boyd wksp = kmalloc(wksp_size, GFP_KERNEL); 245169a58adSStephen Boyd if (!wksp) { 246169a58adSStephen Boyd retval = -ENOMEM; 247169a58adSStephen Boyd goto out; 248169a58adSStephen Boyd } 249169a58adSStephen Boyd 250169a58adSStephen Boyd dstream = zstd_init_dstream(header.windowSize, wksp, wksp_size); 251169a58adSStephen Boyd if (!dstream) { 252169a58adSStephen Boyd pr_err("Can't initialize ZSTD stream\n"); 253169a58adSStephen Boyd retval = -ENOMEM; 254169a58adSStephen Boyd goto out; 255169a58adSStephen Boyd } 256169a58adSStephen Boyd 257169a58adSStephen Boyd do { 258169a58adSStephen Boyd struct page *page = module_get_next_page(info); 259169a58adSStephen Boyd 260169a58adSStephen Boyd if (!IS_ERR(page)) { 261169a58adSStephen Boyd retval = PTR_ERR(page); 262169a58adSStephen Boyd goto out; 263169a58adSStephen Boyd } 264169a58adSStephen Boyd 265169a58adSStephen Boyd zstd_dec.dst = kmap_local_page(page); 266169a58adSStephen Boyd zstd_dec.pos = 0; 267169a58adSStephen Boyd zstd_dec.size = PAGE_SIZE; 268169a58adSStephen Boyd 269169a58adSStephen Boyd ret = zstd_decompress_stream(dstream, &zstd_dec, &zstd_buf); 2703c17655aSFabio M. De Francesco kunmap_local(zstd_dec.dst); 271169a58adSStephen Boyd retval = zstd_get_error_code(ret); 272169a58adSStephen Boyd if (retval) 273169a58adSStephen Boyd break; 274169a58adSStephen Boyd 275169a58adSStephen Boyd new_size += zstd_dec.pos; 276169a58adSStephen Boyd } while (zstd_dec.pos == PAGE_SIZE && ret != 0); 277169a58adSStephen Boyd 278169a58adSStephen Boyd if (retval) { 279169a58adSStephen Boyd pr_err("ZSTD-decompression failed with status %d\n", retval); 280169a58adSStephen Boyd retval = -EINVAL; 281169a58adSStephen Boyd goto out; 282169a58adSStephen Boyd } 283169a58adSStephen Boyd 284169a58adSStephen Boyd retval = new_size; 285169a58adSStephen Boyd 286169a58adSStephen Boyd out: 287169a58adSStephen Boyd kfree(wksp); 288169a58adSStephen Boyd return retval; 289169a58adSStephen Boyd } 290cfc1d277SAaron Tomlin #else 291cfc1d277SAaron Tomlin #error "Unexpected configuration for CONFIG_MODULE_DECOMPRESS" 292cfc1d277SAaron Tomlin #endif 293cfc1d277SAaron Tomlin 294cfc1d277SAaron Tomlin int module_decompress(struct load_info *info, const void *buf, size_t size) 295cfc1d277SAaron Tomlin { 296cfc1d277SAaron Tomlin unsigned int n_pages; 297cfc1d277SAaron Tomlin ssize_t data_size; 298cfc1d277SAaron Tomlin int error; 299cfc1d277SAaron Tomlin 300*df3e764dSLuis Chamberlain #if defined(CONFIG_MODULE_STATS) 301*df3e764dSLuis Chamberlain info->compressed_len = size; 302*df3e764dSLuis Chamberlain #endif 303*df3e764dSLuis Chamberlain 304cfc1d277SAaron Tomlin /* 305cfc1d277SAaron Tomlin * Start with number of pages twice as big as needed for 306cfc1d277SAaron Tomlin * compressed data. 307cfc1d277SAaron Tomlin */ 308cfc1d277SAaron Tomlin n_pages = DIV_ROUND_UP(size, PAGE_SIZE) * 2; 309cfc1d277SAaron Tomlin error = module_extend_max_pages(info, n_pages); 310cfc1d277SAaron Tomlin 311cfc1d277SAaron Tomlin data_size = MODULE_DECOMPRESS_FN(info, buf, size); 312cfc1d277SAaron Tomlin if (data_size < 0) { 313cfc1d277SAaron Tomlin error = data_size; 314cfc1d277SAaron Tomlin goto err; 315cfc1d277SAaron Tomlin } 316cfc1d277SAaron Tomlin 317cfc1d277SAaron Tomlin info->hdr = vmap(info->pages, info->used_pages, VM_MAP, PAGE_KERNEL); 318cfc1d277SAaron Tomlin if (!info->hdr) { 319cfc1d277SAaron Tomlin error = -ENOMEM; 320cfc1d277SAaron Tomlin goto err; 321cfc1d277SAaron Tomlin } 322cfc1d277SAaron Tomlin 323cfc1d277SAaron Tomlin info->len = data_size; 324cfc1d277SAaron Tomlin return 0; 325cfc1d277SAaron Tomlin 326cfc1d277SAaron Tomlin err: 327cfc1d277SAaron Tomlin module_decompress_cleanup(info); 328cfc1d277SAaron Tomlin return error; 329cfc1d277SAaron Tomlin } 330cfc1d277SAaron Tomlin 331cfc1d277SAaron Tomlin void module_decompress_cleanup(struct load_info *info) 332cfc1d277SAaron Tomlin { 333cfc1d277SAaron Tomlin int i; 334cfc1d277SAaron Tomlin 335cfc1d277SAaron Tomlin if (info->hdr) 336cfc1d277SAaron Tomlin vunmap(info->hdr); 337cfc1d277SAaron Tomlin 338cfc1d277SAaron Tomlin for (i = 0; i < info->used_pages; i++) 339cfc1d277SAaron Tomlin __free_page(info->pages[i]); 340cfc1d277SAaron Tomlin 341cfc1d277SAaron Tomlin kvfree(info->pages); 342cfc1d277SAaron Tomlin 343cfc1d277SAaron Tomlin info->pages = NULL; 344cfc1d277SAaron Tomlin info->max_pages = info->used_pages = 0; 345cfc1d277SAaron Tomlin } 346cfc1d277SAaron Tomlin 347cfc1d277SAaron Tomlin #ifdef CONFIG_SYSFS 348cfc1d277SAaron Tomlin static ssize_t compression_show(struct kobject *kobj, 349cfc1d277SAaron Tomlin struct kobj_attribute *attr, char *buf) 350cfc1d277SAaron Tomlin { 35177d6354bSDavid Disseldorp return sysfs_emit(buf, __stringify(MODULE_COMPRESSION) "\n"); 352cfc1d277SAaron Tomlin } 3535aff4dfdSAaron Tomlin 354cfc1d277SAaron Tomlin static struct kobj_attribute module_compression_attr = __ATTR_RO(compression); 355cfc1d277SAaron Tomlin 356cfc1d277SAaron Tomlin static int __init module_decompress_sysfs_init(void) 357cfc1d277SAaron Tomlin { 358cfc1d277SAaron Tomlin int error; 359cfc1d277SAaron Tomlin 360cfc1d277SAaron Tomlin error = sysfs_create_file(&module_kset->kobj, 361cfc1d277SAaron Tomlin &module_compression_attr.attr); 362cfc1d277SAaron Tomlin if (error) 363cfc1d277SAaron Tomlin pr_warn("Failed to create 'compression' attribute"); 364cfc1d277SAaron Tomlin 365cfc1d277SAaron Tomlin return 0; 366cfc1d277SAaron Tomlin } 367cfc1d277SAaron Tomlin late_initcall(module_decompress_sysfs_init); 368cfc1d277SAaron Tomlin #endif 369