Skip to content
This repository has been archived by the owner on Sep 24, 2020. It is now read-only.

Commit

Permalink
LSM: LoadPin for kernel file loading restrictions
Browse files Browse the repository at this point in the history
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 <[email protected]>
Acked-by: Serge Hallyn <[email protected]>
Signed-off-by: James Morris <[email protected]>
  • Loading branch information
kees authored and James Morris committed Apr 21, 2016
1 parent 1284ab5 commit 9b09155
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 0 deletions.
17 changes: 17 additions & 0 deletions Documentation/security/LoadPin.txt
Original file line number Diff line number Diff line change
@@ -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.)
6 changes: 6 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>
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 <[email protected]>
T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git yama/tip
Expand Down
5 changes: 5 additions & 0 deletions include/linux/lsm_hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
1 change: 1 addition & 0 deletions security/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions security/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions security/loadpin/Kconfig
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions security/loadpin/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
obj-$(CONFIG_SECURITY_LOADPIN) += loadpin.o
190 changes: 190 additions & 0 deletions security/loadpin/loadpin.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Module and Firmware Pinning Security Module
*
* Copyright 2011-2016 Google Inc.
*
* Author: Kees Cook <[email protected]>
*
* 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)");
1 change: 1 addition & 0 deletions security/security.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ int __init security_init(void)
*/
capability_add_hooks();
yama_add_hooks();
loadpin_add_hooks();

/*
* Load all the remaining security modules.
Expand Down

0 comments on commit 9b09155

Please sign in to comment.