mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-29 09:16:33 +00:00
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>
This commit is contained in:
parent
1284ab5b2d
commit
9b091556a0
17
Documentation/security/LoadPin.txt
Normal file
17
Documentation/security/LoadPin.txt
Normal file
@ -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.)
|
@ -9962,6 +9962,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@chromium.org>
|
||||
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@chromium.org>
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git yama/tip
|
||||
|
@ -1892,5 +1892,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 */
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
10
security/loadpin/Kconfig
Normal file
10
security/loadpin/Kconfig
Normal file
@ -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.
|
1
security/loadpin/Makefile
Normal file
1
security/loadpin/Makefile
Normal file
@ -0,0 +1 @@
|
||||
obj-$(CONFIG_SECURITY_LOADPIN) += loadpin.o
|
190
security/loadpin/loadpin.c
Normal file
190
security/loadpin/loadpin.c
Normal file
@ -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)");
|
@ -60,6 +60,7 @@ int __init security_init(void)
|
||||
*/
|
||||
capability_add_hooks();
|
||||
yama_add_hooks();
|
||||
loadpin_add_hooks();
|
||||
|
||||
/*
|
||||
* Load all the remaining security modules.
|
||||
|
Loading…
Reference in New Issue
Block a user