lkml.org 
[lkml]   [2012]   [May]   [22]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 21/23] MODSIGN: Module signature verification
    Date
    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 <dhowells@redhat.com>
    ---

    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 <linux/kernel.h>
    +#include <linux/module.h>
    +#include <linux/slab.h>
    +#include <linux/sched.h>
    +#include <linux/modsign.h>
    +#include <linux/moduleparam.h>
    +#include <keys/crypto-type.h>
    +#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 <linux/jump_label.h>
    #include <linux/pfn.h>
    #include <linux/bsearch.h>
    +#include "module-verify.h"

    #define CREATE_TRACE_POINTS
    #include <trace/events/module.h>
    @@ -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);


    \
     
     \ /
      Last update: 2012-05-23 01:41    [W:4.076 / U:0.056 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site