diff options
| author | Kees Cook <keescook@chromium.org> | 2012-10-15 17:01:07 -0400 |
|---|---|---|
| committer | Rusty Russell <rusty@rustcorp.com.au> | 2012-12-13 21:35:22 -0500 |
| commit | 34e1169d996ab148490c01b65b4ee371cf8ffba2 (patch) | |
| tree | 3380af46682ce4396c1524bdba8badcab8a51046 | |
| parent | 84ecfd15f5547c992c901df6ec14b4d507eb2c6e (diff) | |
module: add syscall to load module from fd
As part of the effort to create a stronger boundary between root and
kernel, Chrome OS wants to be able to enforce that kernel modules are
being loaded only from our read-only crypto-hash verified (dm_verity)
root filesystem. Since the init_module syscall hands the kernel a module
as a memory blob, no reasoning about the origin of the blob can be made.
Earlier proposals for appending signatures to kernel modules would not be
useful in Chrome OS, since it would involve adding an additional set of
keys to our kernel and builds for no good reason: we already trust the
contents of our root filesystem. We don't need to verify those kernel
modules a second time. Having to do signature checking on module loading
would slow us down and be redundant. All we need to know is where a
module is coming from so we can say yes/no to loading it.
If a file descriptor is used as the source of a kernel module, many more
things can be reasoned about. In Chrome OS's case, we could enforce that
the module lives on the filesystem we expect it to live on. In the case
of IMA (or other LSMs), it would be possible, for example, to examine
extended attributes that may contain signatures over the contents of
the module.
This introduces a new syscall (on x86), similar to init_module, that has
only two arguments. The first argument is used as a file descriptor to
the module and the second argument is a pointer to the NULL terminated
string of module arguments.
Signed-off-by: Kees Cook <keescook@chromium.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> (merge fixes)
| -rw-r--r-- | arch/x86/syscalls/syscall_32.tbl | 1 | ||||
| -rw-r--r-- | arch/x86/syscalls/syscall_64.tbl | 1 | ||||
| -rw-r--r-- | include/linux/syscalls.h | 1 | ||||
| -rw-r--r-- | kernel/module.c | 367 | ||||
| -rw-r--r-- | kernel/sys_ni.c | 1 |
5 files changed, 223 insertions, 148 deletions
diff --git a/arch/x86/syscalls/syscall_32.tbl b/arch/x86/syscalls/syscall_32.tbl index a47103fbc692..83b3838417ed 100644 --- a/arch/x86/syscalls/syscall_32.tbl +++ b/arch/x86/syscalls/syscall_32.tbl | |||
| @@ -356,3 +356,4 @@ | |||
| 356 | 347 i386 process_vm_readv sys_process_vm_readv compat_sys_process_vm_readv | 356 | 347 i386 process_vm_readv sys_process_vm_readv compat_sys_process_vm_readv |
| 357 | 348 i386 process_vm_writev sys_process_vm_writev compat_sys_process_vm_writev | 357 | 348 i386 process_vm_writev sys_process_vm_writev compat_sys_process_vm_writev |
| 358 | 349 i386 kcmp sys_kcmp | 358 | 349 i386 kcmp sys_kcmp |
| 359 | 350 i386 finit_module sys_finit_module | ||
diff --git a/arch/x86/syscalls/syscall_64.tbl b/arch/x86/syscalls/syscall_64.tbl index a582bfed95bb..7c58c84b7bc8 100644 --- a/arch/x86/syscalls/syscall_64.tbl +++ b/arch/x86/syscalls/syscall_64.tbl | |||
| @@ -319,6 +319,7 @@ | |||
| 319 | 310 64 process_vm_readv sys_process_vm_readv | 319 | 310 64 process_vm_readv sys_process_vm_readv |
| 320 | 311 64 process_vm_writev sys_process_vm_writev | 320 | 311 64 process_vm_writev sys_process_vm_writev |
| 321 | 312 common kcmp sys_kcmp | 321 | 312 common kcmp sys_kcmp |
| 322 | 313 common finit_module sys_finit_module | ||
| 322 | 323 | ||
| 323 | # | 324 | # |
| 324 | # x32-specific system call numbers start at 512 to avoid cache impact | 325 | # x32-specific system call numbers start at 512 to avoid cache impact |
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index 727f0cd73921..32bc035bcd68 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h | |||
| @@ -868,4 +868,5 @@ asmlinkage long sys_process_vm_writev(pid_t pid, | |||
| 868 | 868 | ||
| 869 | asmlinkage long sys_kcmp(pid_t pid1, pid_t pid2, int type, | 869 | asmlinkage long sys_kcmp(pid_t pid1, pid_t pid2, int type, |
| 870 | unsigned long idx1, unsigned long idx2); | 870 | unsigned long idx1, unsigned long idx2); |
| 871 | asmlinkage long sys_finit_module(int fd, const char __user *uargs); | ||
| 871 | #endif | 872 | #endif |
diff --git a/kernel/module.c b/kernel/module.c index 6e48c3a43599..6d2c4e4ca1f5 100644 --- a/kernel/module.c +++ b/kernel/module.c | |||
| @@ -21,6 +21,7 @@ | |||
| 21 | #include <linux/ftrace_event.h> | 21 | #include <linux/ftrace_event.h> |
| 22 | #include <linux/init.h> | 22 | #include <linux/init.h> |
| 23 | #include <linux/kallsyms.h> | 23 | #include <linux/kallsyms.h> |
| 24 | #include <linux/file.h> | ||
| 24 | #include <linux/fs.h> | 25 | #include <linux/fs.h> |
| 25 | #include <linux/sysfs.h> | 26 | #include <linux/sysfs.h> |
| 26 | #include <linux/kernel.h> | 27 | #include <linux/kernel.h> |
| @@ -2425,18 +2426,17 @@ static inline void kmemleak_load_module(const struct module *mod, | |||
| 2425 | #endif | 2426 | #endif |
| 2426 | 2427 | ||
| 2427 | #ifdef CONFIG_MODULE_SIG | 2428 | #ifdef CONFIG_MODULE_SIG |
| 2428 | static int module_sig_check(struct load_info *info, | 2429 | static int module_sig_check(struct load_info *info) |
| 2429 | const void *mod, unsigned long *_len) | ||
| 2430 | { | 2430 | { |
| 2431 | int err = -ENOKEY; | 2431 | int err = -ENOKEY; |
| 2432 | unsigned long markerlen = sizeof(MODULE_SIG_STRING) - 1; | 2432 | const unsigned long markerlen = sizeof(MODULE_SIG_STRING) - 1; |
| 2433 | unsigned long len = *_len; | 2433 | const void *mod = info->hdr; |
| 2434 | 2434 | ||
| 2435 | if (len > markerlen && | 2435 | if (info->len > markerlen && |
| 2436 | memcmp(mod + len - markerlen, MODULE_SIG_STRING, markerlen) == 0) { | 2436 | memcmp(mod + info->len - markerlen, MODULE_SIG_STRING, markerlen) == 0) { |
| 2437 | /* We truncate the module to discard the signature */ | 2437 | /* We truncate the module to discard the signature */ |
| 2438 | *_len -= markerlen; | 2438 | info->len -= markerlen; |
| 2439 | err = mod_verify_sig(mod, _len); | 2439 | err = mod_verify_sig(mod, &info->len); |
| 2440 | } | 2440 | } |
| 2441 | 2441 | ||
| 2442 | if (!err) { | 2442 | if (!err) { |
| @@ -2454,59 +2454,97 @@ static int module_sig_check(struct load_info *info, | |||
| 2454 | return err; | 2454 | return err; |
| 2455 | } | 2455 | } |
| 2456 | #else /* !CONFIG_MODULE_SIG */ | 2456 | #else /* !CONFIG_MODULE_SIG */ |
| 2457 | static int module_sig_check(struct load_info *info, | 2457 | static int module_sig_check(struct load_info *info) |
| 2458 | void *mod, unsigned long *len) | ||
| 2459 | { | 2458 | { |
| 2460 | return 0; | 2459 | return 0; |
| 2461 | } | 2460 | } |
| 2462 | #endif /* !CONFIG_MODULE_SIG */ | 2461 | #endif /* !CONFIG_MODULE_SIG */ |
| 2463 | 2462 | ||
| 2464 | /* Sets info->hdr, info->len and info->sig_ok. */ | 2463 | /* Sanity checks against invalid binaries, wrong arch, weird elf version. */ |
| 2465 | static int copy_and_check(struct load_info *info, | 2464 | static int elf_header_check(struct load_info *info) |
| 2466 | const void __user *umod, unsigned long len, | ||
| 2467 | const char __user *uargs) | ||
| 2468 | { | 2465 | { |
| 2469 | int err; | 2466 | if (info->len < sizeof(*(info->hdr))) |
| 2470 | Elf_Ehdr *hdr; | 2467 | return -ENOEXEC; |
| 2468 | |||
| 2469 | if (memcmp(info->hdr->e_ident, ELFMAG, SELFMAG) != 0 | ||
| 2470 | || info->hdr->e_type != ET_REL | ||
| 2471 | || !elf_check_arch(info->hdr) | ||
| 2472 | || info->hdr->e_shentsize != sizeof(Elf_Shdr)) | ||
| 2473 | return -ENOEXEC; | ||
| 2474 | |||
| 2475 | if (info->hdr->e_shoff >= info->len | ||
| 2476 | || (info->hdr->e_shnum * sizeof(Elf_Shdr) > | ||
| 2477 | info->len - info->hdr->e_shoff)) | ||
| 2478 | return -ENOEXEC; | ||
| 2471 | 2479 | ||
| 2472 | if (len < sizeof(*hdr)) | 2480 | return 0; |
| 2481 | } | ||
| 2482 | |||
| 2483 | /* Sets info->hdr and info->len. */ | ||
| 2484 | static int copy_module_from_user(const void __user *umod, unsigned long len, | ||
| 2485 | struct load_info *info) | ||
| 2486 | { | ||
| 2487 | info->len = len; | ||
| 2488 | if (info->len < sizeof(*(info->hdr))) | ||
| 2473 | return -ENOEXEC; | 2489 | return -ENOEXEC; |
| 2474 | 2490 | ||
| 2475 | /* Suck in entire file: we'll want most of it. */ | 2491 | /* Suck in entire file: we'll want most of it. */ |
| 2476 | if ((hdr = vmalloc(len)) == NULL) | 2492 | info->hdr = vmalloc(info->len); |
| 2493 | if (!info->hdr) | ||
| 2477 | return -ENOMEM; | 2494 | return -ENOMEM; |
| 2478 | 2495 | ||
| 2479 | if (copy_from_user(hdr, umod, len) != 0) { | 2496 | if (copy_from_user(info->hdr, umod, info->len) != 0) { |
| 2480 | err = -EFAULT; | 2497 | vfree(info->hdr); |
| 2481 | goto free_hdr; | 2498 | return -EFAULT; |
| 2482 | } | 2499 | } |
| 2483 | 2500 | ||
| 2484 | err = module_sig_check(info, hdr, &len); | 2501 | return 0; |
| 2502 | } | ||
| 2503 | |||
| 2504 | /* Sets info->hdr and info->len. */ | ||
| 2505 | static int copy_module_from_fd(int fd, struct load_info *info) | ||
| 2506 | { | ||
| 2507 | struct file *file; | ||
| 2508 | int err; | ||
| 2509 | struct kstat stat; | ||
| 2510 | loff_t pos; | ||
| 2511 | ssize_t bytes = 0; | ||
| 2512 | |||
| 2513 | file = fget(fd); | ||
| 2514 | if (!file) | ||
| 2515 | return -ENOEXEC; | ||
| 2516 | |||
| 2517 | err = vfs_getattr(file->f_vfsmnt, file->f_dentry, &stat); | ||
| 2485 | if (err) | 2518 | if (err) |
| 2486 | goto free_hdr; | 2519 | goto out; |
| 2487 | 2520 | ||
| 2488 | /* Sanity checks against insmoding binaries or wrong arch, | 2521 | if (stat.size > INT_MAX) { |
| 2489 | weird elf version */ | 2522 | err = -EFBIG; |
| 2490 | if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0 | 2523 | goto out; |
| 2491 | || hdr->e_type != ET_REL | ||
| 2492 | || !elf_check_arch(hdr) | ||
| 2493 | || hdr->e_shentsize != sizeof(Elf_Shdr)) { | ||
| 2494 | err = -ENOEXEC; | ||
| 2495 | goto free_hdr; | ||
| 2496 | } | 2524 | } |
| 2497 | 2525 | info->hdr = vmalloc(stat.size); | |
| 2498 | if (hdr->e_shoff >= len || | 2526 | if (!info->hdr) { |
| 2499 | hdr->e_shnum * sizeof(Elf_Shdr) > len - hdr->e_shoff) { | 2527 | err = -ENOMEM; |
| 2500 | err = -ENOEXEC; | 2528 | goto out; |
| 2501 | goto free_hdr; | ||
| 2502 | } | 2529 | } |
| 2503 | 2530 | ||
| 2504 | info->hdr = hdr; | 2531 | pos = 0; |
| 2505 | info->len = len; | 2532 | while (pos < stat.size) { |
| 2506 | return 0; | 2533 | bytes = kernel_read(file, pos, (char *)(info->hdr) + pos, |
| 2534 | stat.size - pos); | ||
| 2535 | if (bytes < 0) { | ||
