lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20150724005330.GA15227@www.outflux.net>
Date:	Thu, 23 Jul 2015 17:53:30 -0700
From:	Kees Cook <keescook@...omium.org>
To:	linux-security-module@...r.kernel.org
Cc:	James Morris <james.l.morris@...cle.com>,
	Casey Schaufler <casey@...aufler-ca.com>,
	linux-kernel@...r.kernel.org
Subject: [PATCH] LSM: LoadPin for module and firmware loading restrictions

This LSM enforces that kernel-loaded modules and firmware 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@...omium.org>
---
 MAINTAINERS                |   6 +
 include/linux/lsm_hooks.h  |   5 +
 security/Kconfig           |   1 +
 security/Makefile          |   2 +
 security/loadpin/Kconfig   |   9 ++
 security/loadpin/Makefile  |   1 +
 security/loadpin/loadpin.c | 279 +++++++++++++++++++++++++++++++++++++++++++++
 security/security.c        |   2 +
 8 files changed, 305 insertions(+)
 create mode 100644 security/loadpin/Kconfig
 create mode 100644 security/loadpin/Makefile
 create mode 100644 security/loadpin/loadpin.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 2d3d55c8f5be..671e760cbe85 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9101,6 +9101,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@...omium.org>
+T:	git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git lsm/loadpin
+S:	Supported
+F:	security/loadpin/
+
 SENSABLE PHANTOM
 M:	Jiri Slaby <jirislaby@...il.com>
 S:	Maintained
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 9429f054c323..d8ceb8099bc0 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1884,5 +1884,10 @@ extern void __init capability_add_hooks(void);
 #ifdef CONFIG_SECURITY_YAMA_STACKED
 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 bf4ec46474b6..f09b58ef43af 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..8efb8458a9a2
--- /dev/null
+++ b/security/loadpin/Kconfig
@@ -0,0 +1,9 @@
+config SECURITY_LOADPIN
+	bool "Pin loading of kernel modules and firmware to one filesystem"
+	depends on SECURITY && BLOCK
+	help
+	  Kernel module and firmware loading 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..60efa69c9dfb
--- /dev/null
+++ b/security/loadpin/loadpin.c
@@ -0,0 +1,279 @@
+/*
+ * Module and Firmware Pinning Security Module
+ *
+ * Copyright 2011-2015 Google Inc.
+ *
+ * Author: Kees Cook <keescook@...omium.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/mm.h>		/* get_cmdline() */
+#include <linux/mount.h>
+#include <linux/path.h>
+#include <linux/sched.h>	/* current */
+#include <linux/string_helpers.h>
+
+/*
+ * Return an allocated string that has been escaped of special characters
+ * and double quotes, making it safe to log in quotes.
+ */
+static char *kstrdup_quotable(char *src)
+{
+	size_t slen, dlen;
+	char *dst;
+	const int flags = ESCAPE_HEX;
+	const char esc[] = "\f\n\r\t\v\a\e\\\"";
+
+	if (!src)
+		return NULL;
+	slen = strlen(src);
+
+	dlen = string_escape_mem(src, slen, NULL, 0, flags, esc);
+	dst = kmalloc(dlen + 1, GFP_KERNEL);
+	if (!dst)
+		return NULL;
+
+	BUG_ON(string_escape_mem(src, slen, dst, dlen, flags, esc) != dlen);
+	dst[dlen] = '\0';
+
+	return dst;
+}
+
+/*
+ * Returns allocated NULL-terminated string containing process
+ * command line, with inter-argument NULLs replaced with spaces,
+ * and other special characters escaped.
+ */
+static char *kstrdup_quotable_cmdline(struct task_struct *task)
+{
+	char *buffer, *quoted;
+	int i, res;
+
+	buffer = kmalloc(PAGE_SIZE, GFP_TEMPORARY);
+	if (!buffer)
+		return NULL;
+
+	res = get_cmdline(task, buffer, PAGE_SIZE - 1);
+	buffer[res] = '\0';
+
+	/* Collapse trailing NULLs. */
+	for (; res > 0; res--)
+		if (buffer[res-1] != '\0')
+			break;
+
+	/* Replace inter-argument NULLs. */
+	for (i = 0; i < res; i++)
+		if (buffer[i] == '\0')
+			buffer[i] = ' ';
+
+	/* Make sure result is printable. */
+	quoted = kstrdup_quotable(buffer);
+	kfree(buffer);
+	return quoted;
+}
+
+static void report_load(const char *origin, struct file *file, char *operation)
+{
+	char *alloced = NULL, *cmdline;
+	char *pathname; /* Pointer to either static string or "alloced". */
+
+	if (!file)
+		pathname = "<unknown>";
+	else {
+		/* We will allow 11 spaces for ' (deleted)' to be appended */
+		alloced = pathname = kmalloc(PATH_MAX + 11, GFP_TEMPORARY);
+		if (!pathname)
+			pathname = "<no_memory>";
+		else {
+			pathname = file_path(file, pathname, PATH_MAX + 11);
+			if (IS_ERR(pathname))
+				pathname = "<too_long>";
+			else {
+				pathname = kstrdup_quotable(pathname);
+				kfree(alloced);
+				alloced = pathname;
+			}
+		}
+	}
+
+	cmdline = kstrdup_quotable_cmdline(current);
+
+	pr_notice("%s %s obj=%s%s%s pid=%d cmdline=%s%s%s\n",
+		  origin, operation,
+		  pathname ? "\"" : "", pathname, pathname ? "\"" : "",
+		  task_pid_nr(current),
+		  cmdline ? "\"" : "", cmdline, cmdline ? "\"" : "");
+
+	kfree(cmdline);
+	kfree(alloced);
+}
+
+static int load_pinning = 1;
+static struct vfsmount *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", },
+	{ }
+};
+
+static struct ctl_table loadpin_sysctl_table[] = {
+	{
+		.procname       = "load_pinning",
+		.data           = &load_pinning,
+		.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 vfsmount *mnt)
+{
+	bool ro = false;
+
+	/*
+	 * If load pinning is not enforced via a read-only block
+	 * device, allow sysctl to change modes for testing.
+	 */
+	if (mnt->mnt_sb->s_bdev) {
+		ro = bdev_read_only(mnt->mnt_sb->s_bdev);
+		pr_info("dev(%u,%u): %s\n",
+			MAJOR(mnt->mnt_sb->s_bdev->bd_dev),
+			MINOR(mnt->mnt_sb->s_bdev->bd_dev),
+			ro ? "read-only" : "writable");
+	} else
+		pr_info("vfsmount 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 vfsmount *mnt)
+{
+	pr_info("load pinning engaged.\n");
+}
+#endif
+
+int loadpin_sb_umount(struct vfsmount *mnt, int flags)
+{
+	/*
+	 * When unmounting the filesystem we were using for load
+	 * pinning, we must release our reservation, but make sure
+	 * no other modules or firmware can be loaded.
+	 */
+	if (!IS_ERR_OR_NULL(pinned_root) && mnt == pinned_root) {
+		mntput(pinned_root);
+		pinned_root = ERR_PTR(-EIO);
+		pr_info("umount pinned fs: refusing further loads\n");
+	}
+
+	return 0;
+}
+
+static int check_pinning(const char *origin, struct file *file)
+{
+	struct vfsmount *load_root;
+
+	/* This handles the older init_module API that has a NULL file. */
+	if (!file) {
+		if (!load_pinning) {
+			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;
+
+	/* 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 = mntget(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(!load_pinning)) {
+			report_load(origin, file, "pinning-ignored");
+			return 0;
+		}
+
+		report_load(origin, file, "denied");
+		return -EPERM;
+	}
+
+	return 0;
+}
+
+int loadpin_load_module(struct file *file)
+{
+	return check_pinning("init_module", file);
+}
+
+int loadpin_load_firmware(struct file *file, char *buf, size_t size)
+{
+	return check_pinning("request_firmware", file);
+}
+
+static struct security_hook_list loadpin_hooks[] = {
+	LSM_HOOK_INIT(sb_umount, loadpin_sb_umount),
+	LSM_HOOK_INIT(kernel_module_from_file, loadpin_load_module),
+	LSM_HOOK_INIT(kernel_fw_from_file, loadpin_load_firmware),
+};
+
+void __init loadpin_add_hooks(void)
+{
+	pr_info("preparing to pin");
+	security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks));
+}
+
+/* Should not be mutable after boot, so not listed in sysfs (perm == 0). */
+module_param(load_pinning, int, 0);
+MODULE_PARM_DESC(load_pinning, "Pin module/firmware loading (default: true)");
diff --git a/security/security.c b/security/security.c
index 595fffab48b0..45557e200ac5 100644
--- a/security/security.c
+++ b/security/security.c
@@ -65,6 +65,8 @@ int __init security_init(void)
 	 */
 	yama_add_hooks();
 #endif
+	loadpin_add_hooks();
+
 	/*
 	 * Load the chosen module if there is one.
 	 * This will also find yama if it is stacking
-- 
1.9.1


-- 
Kees Cook
Chrome OS Security
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ