History

module: add in-kernel support for decompressing

Current scheme of having userspace decompress kernel modules before loading them into the kernel runs afoul of LoadPin security policy, as it loses link between the source of kernel module on the disk and binary blob that is being loaded into the kernel. To solve this issue let's implement decompression in kernel, so that we can pass a file descriptor of compressed module file into finit_module() which will keep LoadPin happy. To let userspace know what compression/decompression scheme kernel supports it will create /sys/module/compression attribute. kmod can read this attribute and decide if it can pass compressed file to finit_module(). New MODULE_INIT_COMPRESSED_DATA flag indicates that the kernel should attempt to decompress the data read from file descriptor prior to trying load the module. To simplify things kernel will only implement single decompression method matching compression method selected when generating modules. This patch implements gzip and xz; more can be added later,

Dmitry Torokhov committed 4 years ago

Commit b1ae6dc1 parent ef307fc6 files · +329 −11Browse files

Showing the changes to kernel (4 of 6 files). Show all

Unified · Split

4 files
  1. kernel/Makefile +1 −0
  2. kernel/module-internal.h +19 −0
  3. kernel/module.c +24 −11
  4. kernel/module_decompress.c +271 −0
kernel/Makefile+1−0
@@ -67,6 +67,7 @@ obj-y += up.o · 66 unmodified lines
67endif67endif
68obj-$(CONFIG_UID16) += uid16.o68obj-$(CONFIG_UID16) += uid16.o
69obj-$(CONFIG_MODULES) += module.o69obj-$(CONFIG_MODULES) += module.o
70obj-$(CONFIG_MODULE_DECOMPRESS) += module_decompress.o
70obj-$(CONFIG_MODULE_SIG) += module_signing.o71obj-$(CONFIG_MODULE_SIG) += module_signing.o
71obj-$(CONFIG_MODULE_SIG_FORMAT) += module_signature.o72obj-$(CONFIG_MODULE_SIG_FORMAT) += module_signature.o
72obj-$(CONFIG_KALLSYMS) += kallsyms.o73obj-$(CONFIG_KALLSYMS) += kallsyms.o
kernel/module-internal.h+19−0
@@ -22,6 +22,11 @@ struct load_info { · 21 unmodified lines
22 bool sig_ok;22 bool sig_ok;
23#ifdef CONFIG_KALLSYMS23#ifdef CONFIG_KALLSYMS
24 unsigned long mod_kallsyms_init_off;24 unsigned long mod_kallsyms_init_off;
25#endif
26#ifdef CONFIG_MODULE_DECOMPRESS
27 struct page **pages;
28 unsigned int max_pages;
29 unsigned int used_pages;
25#endif30#endif
26 struct {31 struct {
27 unsigned int sym, str, mod, vers, info, pcpu;32 unsigned int sym, str, mod, vers, info, pcpu;
@@ -29,3 +34,17 @@ struct load_info { · 1 unmodified line
29};34};
30 35
31extern int mod_verify_sig(const void *mod, struct load_info *info);36extern int mod_verify_sig(const void *mod, struct load_info *info);
37
38#ifdef CONFIG_MODULE_DECOMPRESS
39int module_decompress(struct load_info *info, const void *buf, size_t size);
40void module_decompress_cleanup(struct load_info *info);
41#else
42static inline int module_decompress(struct load_info *info,
43 const void *buf, size_t size)
44{
45 return -EOPNOTSUPP;
46}
47static inline void module_decompress_cleanup(struct load_info *info)
48{
49}
50#endif
kernel/module.c+24−11
@@ -3173,9 +3173,12 @@ out: · 3172 unmodified lines
3173 return err;3173 return err;
3174}3174}
3175 3175
3176static void free_copy(struct load_info *info)3176static void free_copy(struct load_info *info, int flags)
3177{3177{
3178 vfree(info->hdr);3178 if (flags & MODULE_INIT_COMPRESSED_FILE)
3179 module_decompress_cleanup(info);
3180 else
3181 vfree(info->hdr);
3179}3182}
3180 3183
3181static int rewrite_section_headers(struct load_info *info, int flags)3184static int rewrite_section_headers(struct load_info *info, int flags)
@@ -4124,7 +4127,7 @@ static int load_module(struct load_info *info, const char __user *uargs, · 942 unmodified lines
4124 }4127 }
4125 4128
4126 /* Get rid of temporary copy. */4129 /* Get rid of temporary copy. */
4127 free_copy(info);4130 free_copy(info, flags);
4128 4131
4129 /* Done! */4132 /* Done! */
4130 trace_module_load(mod);4133 trace_module_load(mod);
@@ -4173,7 +4176,7 @@ static int load_module(struct load_info *info, const char __user *uargs, · 42 unmodified lines
4173 4176
4174 module_deallocate(mod, info);4177 module_deallocate(mod, info);
4175 free_copy:4178 free_copy:
4176 free_copy(info);4179 free_copy(info, flags);
4177 return err;4180 return err;
4178}4181}
4179 4182
@@ -4200,7 +4203,8 @@ SYSCALL_DEFINE3(init_module, void __user *, umod, · 20 unmodified lines
4200SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)4203SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)
4201{4204{
4202 struct load_info info = { };4205 struct load_info info = { };
4203 void *hdr = NULL;4206 void *buf = NULL;
4207 int len;
4204 int err;4208 int err;
4205 4209
4206 err = may_init_module();4210 err = may_init_module();
@@ -4210,15 +4214,24 @@ SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags) · 3 unmodified lines
4210 pr_debug("finit_module: fd=%d, uargs=%p, flags=%i\n", fd, uargs, flags);4214 pr_debug("finit_module: fd=%d, uargs=%p, flags=%i\n", fd, uargs, flags);
4211 4215
4212 if (flags & ~(MODULE_INIT_IGNORE_MODVERSIONS4216 if (flags & ~(MODULE_INIT_IGNORE_MODVERSIONS
4213 |MODULE_INIT_IGNORE_VERMAGIC))4217 |MODULE_INIT_IGNORE_VERMAGIC
4218 |MODULE_INIT_COMPRESSED_FILE))
4214 return -EINVAL;4219 return -EINVAL;
4215 4220
4216 err = kernel_read_file_from_fd(fd, 0, &hdr, INT_MAX, NULL,4221 len = kernel_read_file_from_fd(fd, 0, &buf, INT_MAX, NULL,
4217 READING_MODULE);4222 READING_MODULE);
4218 if (err < 0)4223 if (len < 0)
4219 return err;4224 return len;
4220 info.hdr = hdr;4225
4221 info.len = err;4226 if (flags & MODULE_INIT_COMPRESSED_FILE) {
4227 err = module_decompress(&info, buf, len);
4228 vfree(buf); /* compressed data is no longer needed */
4229 if (err)
4230 return err;
4231 } else {
4232 info.hdr = buf;
4233 info.len = len;
4234 }
4222 4235
4223 return load_module(&info, uargs, flags);4236 return load_module(&info, uargs, flags);
4224}4237}
kernel/module_decompress.c+271−0
@@ -0,0 +1,271 @@
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright 2021 Google LLC.
4 */
5
6#include <linux/init.h>
7#include <linux/highmem.h>
8#include <linux/kobject.h>
9#include <linux/mm.h>
10#include <linux/module.h>
11#include <linux/slab.h>
12#include <linux/sysfs.h>
13#include <linux/vmalloc.h>
14
15#include "module-internal.h"
16
17static int module_extend_max_pages(struct load_info *info, unsigned int extent)
18{
19 struct page **new_pages;
20
21 new_pages = kvmalloc_array(info->max_pages + extent,
22 sizeof(info->pages), GFP_KERNEL);
23 if (!new_pages)
24 return -ENOMEM;
25
26 memcpy(new_pages, info->pages, info->max_pages * sizeof(info->pages));
27 kvfree(info->pages);
28 info->pages = new_pages;
29 info->max_pages += extent;
30
31 return 0;
32}
33
34static struct page *module_get_next_page(struct load_info *info)
35{
36 struct page *page;
37 int error;
38
39 if (info->max_pages == info->used_pages) {
40 error = module_extend_max_pages(info, info->used_pages);
41 if (error)
42 return ERR_PTR(error);
43 }
44
45 page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
46 if (!page)
47 return ERR_PTR(-ENOMEM);
48
49 info->pages[info->used_pages++] = page;
50 return page;
51}
52
53#ifdef CONFIG_MODULE_COMPRESS_GZIP
54#include <linux/zlib.h>
55#define MODULE_COMPRESSION gzip
56#define MODULE_DECOMPRESS_FN module_gzip_decompress
57
58/*
59 * Calculate length of the header which consists of signature, header
60 * flags, time stamp and operating system ID (10 bytes total), plus
61 * an optional filename.
62 */
63static size_t module_gzip_header_len(const u8 *buf, size_t size)
64{
65 const u8 signature[] = { 0x1f, 0x8b, 0x08 };
66 size_t len = 10;
67
68 if (size < len || memcmp(buf, signature, sizeof(signature)))
69 return 0;
70
71 if (buf[3] & 0x08) {
72 do {
73 /*
74 * If we can't find the end of the file name we must
75 * be dealing with a corrupted file.
76 */
77 if (len == size)
78 return 0;
79 } while (buf[len++] != '\0');
80 }
81
82 return len;
83}
84
85static ssize_t module_gzip_decompress(struct load_info *info,
86 const void *buf, size_t size)
87{
88 struct z_stream_s s = { 0 };
89 size_t new_size = 0;
90 size_t gzip_hdr_len;
91 ssize_t retval;
92 int rc;
93
94 gzip_hdr_len = module_gzip_header_len(buf, size);
95 if (!gzip_hdr_len) {
96 pr_err("not a gzip compressed module\n");
97 return -EINVAL;
98 }
99
100 s.next_in = buf + gzip_hdr_len;
101 s.avail_in = size - gzip_hdr_len;
102
103 s.workspace = kmalloc(zlib_inflate_workspacesize(), GFP_KERNEL);
104 if (!s.workspace)
105 return -ENOMEM;
106
107 rc = zlib_inflateInit2(&s, -MAX_WBITS);
108 if (rc != Z_OK) {
109 pr_err("failed to initialize decompresser: %d\n", rc);
110 retval = -EINVAL;
111 goto out;
112 }
113
114 do {
115 struct page *page = module_get_next_page(info);
116 if (!page) {
117 retval = -ENOMEM;
118 goto out_inflate_end;
119 }
120
121 s.next_out = kmap(page);
122 s.avail_out = PAGE_SIZE;
123 rc = zlib_inflate(&s, 0);
124 kunmap(page);
125
126 new_size += PAGE_SIZE - s.avail_out;
127 } while (rc == Z_OK);
128
129 if (rc != Z_STREAM_END) {
130 pr_err("decompression failed with status %d\n", rc);
131 retval = -EINVAL;
132 goto out_inflate_end;
133 }
134
135 retval = new_size;
136
137out_inflate_end:
138 zlib_inflateEnd(&s);
139out:
140 kfree(s.workspace);
141 return retval;
142}
143#elif CONFIG_MODULE_COMPRESS_XZ
144#include <linux/xz.h>
145#define MODULE_COMPRESSION xz
146#define MODULE_DECOMPRESS_FN module_xz_decompress
147
148static ssize_t module_xz_decompress(struct load_info *info,
149 const void *buf, size_t size)
150{
151 static const u8 signature[] = { 0xfd, '7', 'z', 'X', 'Z', 0 };
152 struct xz_dec *xz_dec;
153 struct xz_buf xz_buf;
154 enum xz_ret xz_ret;
155 size_t new_size = 0;
156 ssize_t retval;
157
158 if (size < sizeof(signature) ||
159 memcmp(buf, signature, sizeof(signature))) {
160 pr_err("not an xz compressed module\n");
161 return -EINVAL;
162 }
163
164 xz_dec = xz_dec_init(XZ_DYNALLOC, (u32)-1);
165 if (!xz_dec)
166 return -ENOMEM;
167
168 xz_buf.in_size = size;
169 xz_buf.in = buf;
170 xz_buf.in_pos = 0;
171
172 do {
173 struct page *page = module_get_next_page(info);
174 if (!page) {
175 retval = -ENOMEM;
176 goto out;
177 }
178
179 xz_buf.out = kmap(page);
180 xz_buf.out_pos = 0;
181 xz_buf.out_size = PAGE_SIZE;
182 xz_ret = xz_dec_run(xz_dec, &xz_buf);
183 kunmap(page);
184
185 new_size += xz_buf.out_pos;
186 } while (xz_buf.out_pos == PAGE_SIZE && xz_ret == XZ_OK);
187
188 if (xz_ret != XZ_STREAM_END) {
189 pr_err("decompression failed with status %d\n", xz_ret);
190 retval = -EINVAL;
191 goto out;
192 }
193
194 retval = new_size;
195
196 out:
197 xz_dec_end(xz_dec);
198 return retval;
199}
200#else
201#error "Unexpected configuration for CONFIG_MODULE_DECOMPRESS"
202#endif
203
204int module_decompress(struct load_info *info, const void *buf, size_t size)
205{
206 unsigned int n_pages;
207 ssize_t data_size;
208 int error;
209
210 /*
211 * Start with number of pages twice as big as needed for
212 * compressed data.
213 */
214 n_pages = DIV_ROUND_UP(size, PAGE_SIZE) * 2;
215 error = module_extend_max_pages(info, n_pages);
216
217 data_size = MODULE_DECOMPRESS_FN(info, buf, size);
218 if (data_size < 0) {
219 error = data_size;
220 goto err;
221 }
222
223 info->hdr = vmap(info->pages, info->used_pages, VM_MAP, PAGE_KERNEL);
224 if (!info->hdr) {
225 error = -ENOMEM;
226 goto err;
227 }
228
229 info->len = data_size;
230 return 0;
231
232err:
233 module_decompress_cleanup(info);
234 return error;
235}
236
237void module_decompress_cleanup(struct load_info *info)
238{
239 int i;
240
241 if (info->hdr)
242 vunmap(info->hdr);
243
244 for (i = 0; i < info->used_pages; i++)
245 __free_page(info->pages[i]);
246
247 kvfree(info->pages);
248
249 info->pages = NULL;
250 info->max_pages = info->used_pages = 0;
251}
252
253static ssize_t compression_show(struct kobject *kobj,
254 struct kobj_attribute *attr, char *buf)
255{
256 return sysfs_emit(buf, "%s\n", __stringify(MODULE_COMPRESSION));
257}
258static struct kobj_attribute module_compression_attr = __ATTR_RO(compression);
259
260static int __init module_decompress_sysfs_init(void)
261{
262 int error;
263
264 error = sysfs_create_file(&module_kset->kobj,
265 &module_compression_attr.attr);
266 if (error)
267 pr_warn("Failed to create 'compression' attribute");
268
269 return 0;
270}
271late_initcall(module_decompress_sysfs_init);