diff options
author | Kees Cook <keescook@chromium.org> | 2016-04-20 15:46:28 -0700 |
---|---|---|
committer | James Morris <james.l.morris@oracle.com> | 2016-04-21 10:47:27 +1000 |
commit | 9b091556a073a9f5f93e2ad23d118f45c4796a84 (patch) | |
tree | 075fffff80b5caad9738f633c83333dea9e04efd /security/loadpin | |
parent | 1284ab5b2dcb927d38e4f3fbc2e307f3d1af9262 (diff) |
LSM: LoadPin for kernel file loading restrictions
This LSM enforces that kernel-loaded files (modules, firmware, etc)
must all come from the same filesystem, with the expectation that
such a filesystem is backed by a read-only device such as dm-verity
or CDROM. This allows systems that have a verified and/or unchangeable
filesystem to enforce module and firmware loading restrictions without
needing to sign the files individually.
Signed-off-by: Kees Cook <keescook@chromium.org>
Acked-by: Serge Hallyn <serge.hallyn@canonical.com>
Signed-off-by: James Morris <james.l.morris@oracle.com>
Diffstat (limited to 'security/loadpin')
-rw-r--r-- | security/loadpin/Kconfig | 10 | ||||
-rw-r--r-- | security/loadpin/Makefile | 1 | ||||
-rw-r--r-- | security/loadpin/loadpin.c | 190 |
3 files changed, 201 insertions, 0 deletions
diff --git a/security/loadpin/Kconfig b/security/loadpin/Kconfig new file mode 100644 index 000000000000..c668ac4eda65 --- /dev/null +++ b/security/loadpin/Kconfig @@ -0,0 +1,10 @@ +config SECURITY_LOADPIN + bool "Pin load of kernel files (modules, fw, etc) to one filesystem" + depends on SECURITY && BLOCK + help + Any files read through the kernel file reading interface + (kernel modules, firmware, kexec images, security policy) will + be pinned to the first filesystem used for loading. Any files + that come from other filesystems will be rejected. This is best + used on systems without an initrd that have a root filesystem + backed by a read-only device such as dm-verity or a CDROM. diff --git a/security/loadpin/Makefile b/security/loadpin/Makefile new file mode 100644 index 000000000000..c2d77f83037b --- /dev/null +++ b/security/loadpin/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SECURITY_LOADPIN) += loadpin.o diff --git a/security/loadpin/loadpin.c b/security/loadpin/loadpin.c new file mode 100644 index 000000000000..e4debae3c4d6 --- /dev/null +++ b/security/loadpin/loadpin.c @@ -0,0 +1,190 @@ +/* + * Module and Firmware Pinning Security Module + * + * Copyright 2011-2016 Google Inc. + * + * Author: Kees Cook <keescook@chromium.org> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "LoadPin: " fmt + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/fs_struct.h> +#include <linux/lsm_hooks.h> +#include <linux/mount.h> +#include <linux/path.h> +#include <linux/sched.h> /* current */ +#include <linux/string_helpers.h> + +static void report_load(const char *origin, struct file *file, char *operation) +{ + char *cmdline, *pathname; + + pathname = kstrdup_quotable_file(file, GFP_KERNEL); + cmdline = kstrdup_quotable_cmdline(current, GFP_KERNEL); + + pr_notice("%s %s obj=%s%s%s pid=%d cmdline=%s%s%s\n", + origin, operation, + (pathname && pathname[0] != '<') ? "\"" : "", + pathname, + (pathname && pathname[0] != '<') ? "\"" : "", + task_pid_nr(current), + cmdline ? "\"" : "", cmdline, cmdline ? "\"" : ""); + + kfree(cmdline); + kfree(pathname); +} + +static int enabled = 1; +static struct super_block *pinned_root; +static DEFINE_SPINLOCK(pinned_root_spinlock); + +#ifdef CONFIG_SYSCTL +static int zero; +static int one = 1; + +static struct ctl_path loadpin_sysctl_path[] = { + { .procname = "kernel", }, + { .procname = "loadpin", }, + { } +}; + +static struct ctl_table loadpin_sysctl_table[] = { + { + .procname = "enabled", + .data = &enabled, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &zero, + .extra2 = &one, + }, + { } +}; + +/* + * This must be called after early kernel init, since then the rootdev + * is available. + */ +static void check_pinning_enforcement(struct super_block *mnt_sb) +{ + bool ro = false; + + /* + * If load pinning is not enforced via a read-only block + * device, allow sysctl to change modes for testing. + */ + if (mnt_sb->s_bdev) { + ro = bdev_read_only(mnt_sb->s_bdev); + pr_info("dev(%u,%u): %s\n", + MAJOR(mnt_sb->s_bdev->bd_dev), + MINOR(mnt_sb->s_bdev->bd_dev), + ro ? "read-only" : "writable"); + } else + pr_info("mnt_sb lacks block device, treating as: writable\n"); + + if (!ro) { + if (!register_sysctl_paths(loadpin_sysctl_path, + loadpin_sysctl_table)) + pr_notice("sysctl registration failed!\n"); + else + pr_info("load pinning can be disabled.\n"); + } else + pr_info("load pinning engaged.\n"); +} +#else +static void check_pinning_enforcement(struct super_block *mnt_sb) +{ + pr_info("load pinning engaged.\n"); +} +#endif + +static void loadpin_sb_free_security(struct super_block *mnt_sb) +{ + /* + * When unmounting the filesystem we were using for load + * pinning, we acknowledge the superblock release, but make sure + * no other modules or firmware can be loaded. + */ + if (!IS_ERR_OR_NULL(pinned_root) && mnt_sb == pinned_root) { + pinned_root = ERR_PTR(-EIO); + pr_info("umount pinned fs: refusing further loads\n"); + } +} + +static int loadpin_read_file(struct file *file, enum kernel_read_file_id id) +{ + struct super_block *load_root; + const char *origin = kernel_read_file_id_str(id); + + /* This handles the older init_module API that has a NULL file. */ + if (!file) { + if (!enabled) { + report_load(origin, NULL, "old-api-pinning-ignored"); + return 0; + } + + report_load(origin, NULL, "old-api-denied"); + return -EPERM; + } + + load_root = file->f_path.mnt->mnt_sb; + + /* First loaded module/firmware defines the root for all others. */ + spin_lock(&pinned_root_spinlock); + /* + * pinned_root is only NULL at startup. Otherwise, it is either + * a valid reference, or an ERR_PTR. + */ + if (!pinned_root) { + pinned_root = load_root; + /* + * Unlock now since it's only pinned_root we care about. + * In the worst case, we will (correctly) report pinning + * failures before we have announced that pinning is + * enabled. This would be purely cosmetic. + */ + spin_unlock(&pinned_root_spinlock); + check_pinning_enforcement(pinned_root); + report_load(origin, file, "pinned"); + } else { + spin_unlock(&pinned_root_spinlock); + } + + if (IS_ERR_OR_NULL(pinned_root) || load_root != pinned_root) { + if (unlikely(!enabled)) { + report_load(origin, file, "pinning-ignored"); + return 0; + } + + report_load(origin, file, "denied"); + return -EPERM; + } + + return 0; +} + +static struct security_hook_list loadpin_hooks[] = { + LSM_HOOK_INIT(sb_free_security, loadpin_sb_free_security), + LSM_HOOK_INIT(kernel_read_file, loadpin_read_file), +}; + +void __init loadpin_add_hooks(void) +{ + pr_info("ready to pin (currently %sabled)", enabled ? "en" : "dis"); + security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks)); +} + +/* Should not be mutable after boot, so not listed in sysfs (perm == 0). */ +module_param(enabled, int, 0); +MODULE_PARM_DESC(enabled, "Pin module/firmware loading (default: true)"); |