Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933123Ab2EVXGU (ORCPT ); Tue, 22 May 2012 19:06:20 -0400 Received: from mx1.redhat.com ([209.132.183.28]:2753 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S933052Ab2EVXFM (ORCPT ); Tue, 22 May 2012 19:05:12 -0400 From: David Howells Subject: [PATCH 21/23] MODSIGN: Module signature verification To: rusty@rustcorp.com.au, kyle@mcmartin.ca Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, keyrings@linux-nfs.org, David Howells Date: Wed, 23 May 2012 00:05:02 +0100 Message-ID: <20120522230502.24007.37175.stgit@warthog.procyon.org.uk> In-Reply-To: <20120522230218.24007.3556.stgit@warthog.procyon.org.uk> References: <20120522230218.24007.3556.stgit@warthog.procyon.org.uk> User-Agent: StGIT/0.14.3 MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 12855 Lines: 387 Apply signature checking to modules on module load, checking the signature against the ring of public keys compiled into the kernel (if enabled by CONFIG_MODULE_SIG). There are several reasons why these patches are useful, amongst which are: (1) to prevent accidentally corrupted modules from causing damage; (2) to prevent maliciously modified modules from causing damage; (3) to allow a sysadmin (or more likely an IT department) to enforce a policy that only known and approved modules shall be loaded onto machines which they're expected to support; (4) to allow other support providers to do likewise, or at least to _detect_ the fact that unsupported modules are loaded; (5) to allow the detection of modules replaced by a second-order distro or a preloaded Linux purveyor. These patches have two main appeals: (a) preventing malicious modules from being loaded, and (b) reducing support workload by pointing out modules on a crashing box that aren't what they're expected to be. Note that this is not a complete solution by any means: the core kernel is not protected, and nor are /dev/mem or /dev/kmem, but it denies (or at least controls) one relatively simple attack vector. To protect the kernel image would be the responsibility of the boot loader or the system BIOS. This facility is optional: the builder of a kernel is by no means under any requirement to actually enable it, let alone force the set of loadable modules to be restricted to just those that the builder provides (there are degrees of restriction available). If CONFIG_MODULE_SIG_FORCE is enabled or "enforcemodulesig=1" is supplied on the kernel command line, the kernel will _only_ load validly signed modules for which it has a public key. Otherwise, it will also load modules that are unsigned. Any module for which the kernel has a key, but which proves to have a signature mismatch will not be permitted to load. This table indicates the behaviours in the various situations: MODULE STATE PERMISSIVE MODE ENFORCING MODE ======================================= =============== =============== Unsigned Ok EKEYREJECTED Signed, no public key ENOKEY ENOKEY Validly signed, public key Ok Ok Invalidly signed, public key EKEYREJECTED EKEYREJECTED Validly signed, expired key EKEYEXPIRED EKEYEXPIRED Signed, hash algorithm unavailable ENOPKG ENOPKG Signed, pubkey algorithm unavailable ENOPKG ENOPKG Signature without sig packet ENOMSG ENOMSG Corrupt signature EBADMSG EBADMSG Corrupt file ELIBBAD ELIBBAD ======================= !!!IMPORTANT WARNING!!! ======================= Signed modules generated by this kernel very likely CANNOT be used with existing packaging and installation infrastructure. For example, in Fedora's environment, the module is potentially stripped at least twice: (1) by rpmbuild when the debuginfo is detached from the module, and (2) by the initrd image composer to reduce the module size. Both of these will potentially result in the module signature being discarded or rendered unverifiable, resulting in the module load just going ahead if the signature magic is not found and enforcemodulesig=1 not being supplied or -EKEYREJECTED being given or a panic being forced if FIPS mode is engaged. To aid with (2), the module is completely stripped prior to signing as it cannot be stripped after signing. Both "strip -x -g" and "eu-strip" are applied as the use of both of these results in a smaller binary. That, however, means there is no debug information directly available for the module. The original unstripped binary for the foo.ko module can be found as foo.ko.unsigned in the build tree. It may be possible to use this as the debug info source. Signed-off-by: David Howells --- include/linux/module.h | 3 + kernel/Makefile | 2 - kernel/module-verify.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ kernel/module-verify.h | 6 ++ kernel/module.c | 26 +++++++- 5 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 kernel/module-verify.c diff --git a/include/linux/module.h b/include/linux/module.h index fbcafe2..7391833 100644 --- a/include/linux/module.h +++ b/include/linux/module.h @@ -227,6 +227,9 @@ struct module /* Unique handle for this module */ char name[MODULE_NAME_LEN]; + /* Is this module GPG signed */ + bool gpgsig_ok; + /* Sysfs stuff. */ struct module_kobject mkobj; struct module_attribute *modinfo_attrs; diff --git a/kernel/Makefile b/kernel/Makefile index bde66b6..28f0ec4 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -51,7 +51,7 @@ obj-$(CONFIG_DEBUG_SPINLOCK) += spinlock.o obj-$(CONFIG_PROVE_LOCKING) += spinlock.o obj-$(CONFIG_UID16) += uid16.o obj-$(CONFIG_MODULES) += module.o -obj-$(CONFIG_MODULE_SIG) += modsign-pubkey.o +obj-$(CONFIG_MODULE_SIG) += module-verify.o modsign-pubkey.o obj-$(CONFIG_KALLSYMS) += kallsyms.o obj-$(CONFIG_BSD_PROCESS_ACCT) += acct.o obj-$(CONFIG_KEXEC) += kexec.o diff --git a/kernel/module-verify.c b/kernel/module-verify.c new file mode 100644 index 0000000..f989fee --- /dev/null +++ b/kernel/module-verify.c @@ -0,0 +1,148 @@ +/* Module signature verification + * + * The code in this file examines a signed kernel module and attempts to + * determine if the PGP signature attached to the end of the module matches the + * entire content of the module without the signature attached. + * + * Copyright (C) 2004, 2011, 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * - Method specified by Rusty Russell. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include "module-verify.h" + +#ifdef CONFIG_MODULE_SIG_FORCE +#define modsign_signedonly true +#else +static bool modsign_signedonly; +#endif + +static const char modsign_magic[] = "This Is A Crypto Signed Module"; + +/* + * Verify a module's signature, if it has one + * + * Returns 0 if module is validly signed, 1 if there's no signature and a + * negative error code otherwise. + */ +static int module_verify_signature(const void *data, size_t size) +{ + struct crypto_key_verify_context *mod_sig; + const char *cp, *sig; + char *end; + size_t magic_size, sig_size, mod_size; + int ret; + + magic_size = sizeof(modsign_magic) - 1; + if (size <= 11 + 11 + magic_size) + return 1; + + if (memcmp(data + size - magic_size, modsign_magic, magic_size) != 0) + return 1; + size -= 11 + 11 + magic_size; + + cp = data + size; + if (cp[ 0] != '@' && cp[ 9] != '@' && cp[10] != '\n' && + cp[11] != '@' && cp[20] != '@' && cp[21] != '\n') + return -ELIBBAD; + mod_size = simple_strtoul(cp + 1, &end, 10); + if (mod_size > size || (*end != ' ' && *end != '@')) + return -ELIBBAD; + sig_size = simple_strtoul(cp + 12, &end, 10); + if (sig_size > size || (*end != ' ' && *end != '@')) + return -ELIBBAD; + + pr_devel("sig at %zu, size %zu (to %zu)\n", mod_size, sig_size, size); + if (size - mod_size != sig_size) + return -ELIBBAD; + + sig = data + mod_size; + pr_devel("sig dump: %02x%02x%02x%02x%02x%02x%02x%02x\n", + sig[0], sig[1], sig[2], sig[3], + sig[4], sig[5], sig[6], sig[7]); + + /* Find the crypto key for the module signature + * - !!! if this tries to load the required hash algorithm module, + * we will deadlock!!! + */ + mod_sig = verify_sig_begin(modsign_keyring, sig, sig_size); + if (IS_ERR(mod_sig)) { + pr_err("Couldn't initiate module signature verification: %ld\n", + PTR_ERR(mod_sig)); + return PTR_ERR(mod_sig); + } + + /* Load the module contents into the digest */ + ret = verify_sig_add_data(mod_sig, data, mod_size); + if (ret < 0) { + verify_sig_cancel(mod_sig); + return ret; + } + + /* Do the actual signature verification */ + ret = verify_sig_end(mod_sig, sig, sig_size); + pr_devel("verify-sig : %d\n", ret); + return ret; +} + +/* + * Verify a module's integrity + */ +int module_verify(const void *data, size_t size, bool *_gpgsig_ok) +{ + int ret; + + pr_devel("-->module_verify(,%zu,)\n", size); + + ret = module_verify_signature(data, size); + + pr_devel("module_verify_signature() = %d\n", ret); + + switch (ret) { + case 0: /* Good signature */ + *_gpgsig_ok = true; + break; + case 1: /* Unsigned module */ + if (modsign_signedonly) { + pr_err("An attempt to load unsigned module was rejected\n"); + return -EKEYREJECTED; + } + ret = 0; + break; + case -ELIBBAD: + pr_err("Module format error encountered\n"); + break; + case -EBADMSG: + pr_err("Module signature error encountered\n"); + break; + case -EKEYREJECTED: /* Signature mismatch or number format error */ + pr_err("Module signature verification failed\n"); + break; + case -ENOKEY: /* Signed, but we don't have the public key */ + pr_err("Module signed with unknown public key\n"); + break; + default: /* Other error (probably ENOMEM) */ + break; + } + return ret; +} + +static int __init sign_setup(char *str) +{ +#ifndef CONFIG_MODULE_SIG_FORCE + modsign_signedonly = true; +#endif + return 0; +} +__setup("enforcemodulesig", sign_setup); diff --git a/kernel/module-verify.h b/kernel/module-verify.h index 2f6cc16..d59e7c9 100644 --- a/kernel/module-verify.h +++ b/kernel/module-verify.h @@ -11,4 +11,10 @@ #ifdef CONFIG_MODULE_SIG extern struct key *modsign_keyring; +extern int module_verify(const void *data, size_t size, bool *_gpgsig_ok); +#else +static inline int module_verify(const void *data, size_t size, bool *_gpgsig_ok) +{ + return 0; +} #endif diff --git a/kernel/module.c b/kernel/module.c index 377cb06..c3797f7 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -58,6 +58,7 @@ #include #include #include +#include "module-verify.h" #define CREATE_TRACE_POINTS #include @@ -2402,7 +2403,8 @@ static inline void kmemleak_load_module(const struct module *mod, /* Sets info->hdr and info->len. */ static int copy_and_check(struct load_info *info, const void __user *umod, unsigned long len, - const char __user *uargs) + const char __user *uargs, + bool *_gpgsig_ok) { int err; Elf_Ehdr *hdr; @@ -2435,6 +2437,12 @@ static int copy_and_check(struct load_info *info, goto free_hdr; } + /* Verify the module's contents */ + *_gpgsig_ok = false; + err = module_verify(hdr, len, _gpgsig_ok); + if (err < 0) + goto free_hdr; + info->hdr = hdr; info->len = len; return 0; @@ -2777,7 +2785,8 @@ int __weak module_frob_arch_sections(Elf_Ehdr *hdr, return 0; } -static struct module *layout_and_allocate(struct load_info *info) +static struct module *layout_and_allocate(struct load_info *info, + bool gpgsig_ok) { /* Module within temporary copy. */ struct module *mod; @@ -2787,6 +2796,7 @@ static struct module *layout_and_allocate(struct load_info *info) mod = setup_load_info(info); if (IS_ERR(mod)) return mod; + mod->gpgsig_ok = gpgsig_ok; err = check_modinfo(mod, info); if (err) @@ -2870,17 +2880,18 @@ static struct module *load_module(void __user *umod, struct load_info info = { NULL, }; struct module *mod; long err; + bool gpgsig_ok; pr_debug("load_module: umod=%p, len=%lu, uargs=%p\n", umod, len, uargs); /* Copy in the blobs from userspace, check they are vaguely sane. */ - err = copy_and_check(&info, umod, len, uargs); + err = copy_and_check(&info, umod, len, uargs, &gpgsig_ok); if (err) return ERR_PTR(err); /* Figure out module layout, and allocate all the memory. */ - mod = layout_and_allocate(&info); + mod = layout_and_allocate(&info, gpgsig_ok); if (IS_ERR(mod)) { err = PTR_ERR(mod); goto free_copy; @@ -3517,8 +3528,13 @@ void print_modules(void) printk(KERN_DEFAULT "Modules linked in:"); /* Most callers should already have preempt disabled, but make sure */ preempt_disable(); - list_for_each_entry_rcu(mod, &modules, list) + list_for_each_entry_rcu(mod, &modules, list) { printk(" %s%s", mod->name, module_flags(mod, buf)); +#ifdef CONFIG_MODULE_SIG + if (!mod->gpgsig_ok) + printk("(U)"); +#endif + } preempt_enable(); if (last_unloaded_module[0]) printk(" [last unloaded: %s]", last_unloaded_module); -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/