Quoting Kees Cook (keescook@xxxxxxxxxxxx): > 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@xxxxxxxxxxxx> Acked-by: Serge Hallyn <serge.hallyn@xxxxxxxxxxxxx> > --- > Documentation/security/LoadPin.txt | 17 ++++ > MAINTAINERS | 6 ++ > include/linux/lsm_hooks.h | 5 + > security/Kconfig | 1 + > security/Makefile | 2 + > security/loadpin/Kconfig | 10 ++ > security/loadpin/Makefile | 1 + > security/loadpin/loadpin.c | 190 +++++++++++++++++++++++++++++++++++++ > security/security.c | 1 + > 9 files changed, 233 insertions(+) > create mode 100644 Documentation/security/LoadPin.txt > create mode 100644 security/loadpin/Kconfig > create mode 100644 security/loadpin/Makefile > create mode 100644 security/loadpin/loadpin.c > > diff --git a/Documentation/security/LoadPin.txt b/Documentation/security/LoadPin.txt > new file mode 100644 > index 000000000000..e11877f5d3d4 > --- /dev/null > +++ b/Documentation/security/LoadPin.txt > @@ -0,0 +1,17 @@ > +LoadPin is a Linux Security Module that ensures all kernel-loaded files > +(modules, firmware, etc) all originate 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. > + > +The LSM is selectable at build-time with CONFIG_SECURITY_LOADPIN, and > +can be controlled at boot-time with the kernel command line option > +"loadpin.enabled". By default, it is enabled, but can be disabled at > +boot ("loadpin.enabled=0"). > + > +LoadPin starts pinning when it sees the first file loaded. If the > +block device backing the filesystem is not read-only, a sysctl is > +created to toggle pinning: /proc/sys/kernel/loadpin/enabled. (Having > +a mutable filesystem means pinning is mutable too, but having the > +sysctl allows for easy testing on systems with a mutable filesystem.) > diff --git a/MAINTAINERS b/MAINTAINERS > index 40eb1dbe2ae5..de4cf8e9247e 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -9964,6 +9964,12 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/jj/apparmor-dev.git > S: Supported > F: security/apparmor/ > > +LOADPIN SECURITY MODULE > +M: Kees Cook <keescook@xxxxxxxxxxxx> > +T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git lsm/loadpin > +S: Supported > +F: security/loadpin/ > + > YAMA SECURITY MODULE > M: Kees Cook <keescook@xxxxxxxxxxxx> > T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git yama/tip > diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h > index cdee11cbcdf1..f3402aab1927 100644 > --- a/include/linux/lsm_hooks.h > +++ b/include/linux/lsm_hooks.h > @@ -1893,5 +1893,10 @@ extern void __init yama_add_hooks(void); > #else > static inline void __init yama_add_hooks(void) { } > #endif > +#ifdef CONFIG_SECURITY_LOADPIN > +void __init loadpin_add_hooks(void); > +#else > +static inline void loadpin_add_hooks(void) { }; > +#endif > > #endif /* ! __LINUX_LSM_HOOKS_H */ > diff --git a/security/Kconfig b/security/Kconfig > index e45237897b43..176758cdfa57 100644 > --- a/security/Kconfig > +++ b/security/Kconfig > @@ -122,6 +122,7 @@ source security/selinux/Kconfig > source security/smack/Kconfig > source security/tomoyo/Kconfig > source security/apparmor/Kconfig > +source security/loadpin/Kconfig > source security/yama/Kconfig > > source security/integrity/Kconfig > diff --git a/security/Makefile b/security/Makefile > index c9bfbc84ff50..f2d71cdb8e19 100644 > --- a/security/Makefile > +++ b/security/Makefile > @@ -8,6 +8,7 @@ subdir-$(CONFIG_SECURITY_SMACK) += smack > subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo > subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor > subdir-$(CONFIG_SECURITY_YAMA) += yama > +subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin > > # always enable default capabilities > obj-y += commoncap.o > @@ -22,6 +23,7 @@ obj-$(CONFIG_AUDIT) += lsm_audit.o > obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/ > obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/ > obj-$(CONFIG_SECURITY_YAMA) += yama/ > +obj-$(CONFIG_SECURITY_LOADPIN) += loadpin/ > obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o > > # Object integrity file lists > 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@xxxxxxxxxxxx> > + * > + * 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)"); > diff --git a/security/security.c b/security/security.c > index 3644b0344d29..ce02178c892f 100644 > --- a/security/security.c > +++ b/security/security.c > @@ -60,6 +60,7 @@ int __init security_init(void) > */ > capability_add_hooks(); > yama_add_hooks(); > + loadpin_add_hooks(); > > /* > * Load all the remaining security modules. > -- > 2.6.3 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html