CHROMIUM: LSM: add module loading restrictions
authorKees Cook <keescook@chromium.org>
Tue, 4 Sep 2012 22:28:15 +0000 (15:28 -0700)
committerGerrit <chrome-bot@google.com>
Wed, 10 Oct 2012 18:08:58 +0000 (11:08 -0700)
Only allow modules to be loaded from the same initial file system.

At run-time, if root device is writable (dm-verity disabled), the restriction
can be disabled via the sysctl "/proc/sys/kernel/chromiumos/module_locking".

BUG=chromium-os:34134
TEST=parrot build & boot, manual testing

Change-Id: I2ee7b15fb70b342ba8ffbdb74a0377215187924a
Signed-off-by: Kees Cook <keescook@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/34306
Reviewed-by: Olof Johansson <olofj@chromium.org>
security/chromiumos/Kconfig
security/chromiumos/Makefile
security/chromiumos/lsm.c
security/chromiumos/utils.c [new file with mode: 0644]
security/chromiumos/utils.h [new file with mode: 0644]

index 58bf508..78f7d14 100644 (file)
@@ -3,8 +3,7 @@ config SECURITY_CHROMIUMOS
        depends on SECURITY
        help
          The purpose of the Chromium OS security module is to reduce attacking
-         surface by preventing access to general purpose access modes not required
-         by Chromium OS.
-         Currently only the mount operation is restricted by requiring a mount point
-         path without symbolic links.
-
+         surface by preventing access to general purpose access modes not
+         required by Chromium OS. Currently: the mount operation is
+         restricted by requiring a mount point path without symbolic links,
+         and loading modules is limited to only the root filesystem.
index 7f441bd..0e92746 100644 (file)
@@ -1 +1 @@
-obj-$(CONFIG_SECURITY_CHROMIUMOS) += lsm.o
+obj-$(CONFIG_SECURITY_CHROMIUMOS) += lsm.o utils.o
index 202d906..deefdb4 100644 (file)
@@ -3,8 +3,9 @@
  *
  * Copyright 2011 Google Inc. All Rights Reserved
  *
- * Author:
+ * Authors:
  *      Stephan Uphoff  <ups@google.com>
+ *      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
  * GNU General Public License for more details.
  */
 
+#define pr_fmt(fmt) "Chromium OS LSM: " fmt
+
 #include <linux/module.h>
 #include <linux/security.h>
 #include <linux/sched.h>       /* current and other task related stuff */
+#include <linux/fs.h>
+#include <linux/fs_struct.h>
+#include <linux/mount.h>
+#include <linux/path.h>
+#include <linux/root_dev.h>
+
+#include "utils.h"
 
 static int chromiumos_security_sb_mount(char *dev_name, struct path *path,
                         char *type, unsigned long flags, void *data)
@@ -26,18 +36,158 @@ static int chromiumos_security_sb_mount(char *dev_name, struct path *path,
        int error = current->total_link_count ? -ELOOP : 0;
 
        if (error) {
-               char name[sizeof(current->comm)];
-               printk(KERN_NOTICE "Chromium OS LSM: Mount path with symlinks"
-                       " prohibited - Task %s (pid = %d)\n",
-                       get_task_comm(name, current), task_pid_nr(current));
+               char *cmdline;
+
+               cmdline = printable_cmdline(current);
+               pr_notice("Mount path with symlinks prohibited - "
+                       "pid=%d cmdline=%s\n",
+                       task_pid_nr(current), cmdline);
+               kfree(cmdline);
        }
 
        return error;
 }
 
+static void report_load_module(struct path *path, char *operation)
+{
+       char *alloced = NULL, *cmdline;
+       char *pathname; /* Pointer to either static string or "alloced". */
+
+       if (!path)
+               pathname = "<unknown>";
+       else {
+               /* We will allow 11 spaces for ' (deleted)' to be appended */
+               alloced = pathname = kmalloc(PATH_MAX+11, GFP_KERNEL);
+               if (!pathname)
+                       pathname = "<no_memory>";
+               else {
+                       pathname = d_path(path, pathname, PATH_MAX+11);
+                       if (IS_ERR(pathname))
+                               pathname = "<too_long>";
+                       else {
+                               pathname = printable(pathname);
+                               kfree(alloced);
+                               alloced = pathname;
+                       }
+               }
+       }
+
+       cmdline = printable_cmdline(current);
+
+       pr_notice("init_module %s module=%s pid=%d cmdline=%s\n",
+                 operation, pathname, task_pid_nr(current), cmdline);
+
+       kfree(cmdline);
+       kfree(alloced);
+}
+
+static int module_locking = 1;
+static struct dentry *locked_root;
+
+#ifdef CONFIG_SYSCTL
+static int zero;
+static int one = 1;
+
+static struct ctl_path chromiumos_sysctl_path[] = {
+       { .procname = "kernel", },
+       { .procname = "chromiumos", },
+       { }
+};
+
+static struct ctl_table chromiumos_sysctl_table[] = {
+       {
+               .procname       = "module_locking",
+               .data           = &module_locking,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_dointvec_minmax,
+               .extra1         = &zero,
+               .extra2         = &one,
+       },
+       { }
+};
+
+/* Check if the root device is read-only (e.g. dm-verity is enabled).
+ * This must be called after early kernel init, since then the rootdev
+ * is available.
+ */
+static bool rootdev_readonly(void)
+{
+       bool rc;
+       struct block_device *bdev;
+       const fmode_t mode = FMODE_WRITE;
+
+       bdev = blkdev_get_by_dev(ROOT_DEV, mode, NULL);
+       if (IS_ERR(bdev)) {
+               /* In this weird case, assume it is read-only. */
+               pr_info("dev(%u,%u): FMODE_WRITE disallowed?!\n",
+                       MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
+               return true;
+       }
+
+       rc = bdev_read_only(bdev);
+       blkdev_put(bdev, mode);
+
+       pr_info("dev(%u,%u): %s\n", MAJOR(ROOT_DEV), MINOR(ROOT_DEV),
+               rc ? "read-only" : "writable");
+
+       return rc;
+}
+
+static void check_locking_enforcement(void)
+{
+       /* If module locking is not being enforced, allow sysctl to change
+        * modes for testing.
+        */
+       if (!rootdev_readonly()) {
+               if (!register_sysctl_paths(chromiumos_sysctl_path,
+                                          chromiumos_sysctl_table))
+                       pr_notice("sysctl registration failed!\n");
+               else
+                       pr_info("module locking can be disabled.\n");
+       } else
+               pr_info("module locking engaged.\n");
+}
+#else
+static void check_locking_enforcement(void) { }
+#endif
+
+
+static int chromiumos_security_load_module(struct file *file)
+{
+       struct dentry *module_root;
+
+       if (!file) {
+               report_load_module(NULL, "old-api-denied");
+               return -EPERM;
+       }
+
+       module_root = file->f_vfsmnt->mnt_root;
+
+       /* First loaded module defines the root for all others. */
+       if (!locked_root) {
+               locked_root = dget(module_root);
+               report_load_module(&file->f_path, "locked");
+               check_locking_enforcement();
+       }
+
+       if (module_root != locked_root) {
+               if (unlikely(!module_locking)) {
+                       report_load_module(&file->f_path, "locking-ignored");
+                       return 0;
+               }
+
+               report_load_module(&file->f_path, "denied");
+               return -EPERM;
+       }
+
+       return 0;
+}
+
 static struct security_operations chromiumos_security_ops = {
        .name   = "chromiumos",
        .sb_mount = chromiumos_security_sb_mount,
+       .kernel_module_from_file = chromiumos_security_load_module,
 };
 
 
@@ -48,8 +198,11 @@ static int __init chromiumos_security_init(void)
        error = register_security(&chromiumos_security_ops);
 
        if (error)
-               panic("Could not register chromiumos security module");
+               panic("Could not register Chromium OS security module");
 
        return error;
 }
 security_initcall(chromiumos_security_init);
+
+module_param(module_locking, int, S_IRUSR);
+MODULE_PARM_DESC(module_locking, "Module loading restrictions (default: true)");
diff --git a/security/chromiumos/utils.c b/security/chromiumos/utils.c
new file mode 100644 (file)
index 0000000..75a22bc
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Utilities for the Linux Security Module for Chromium OS
+ * (Since CONFIG_AUDIT is disabled for Chrome OS, we must repurpose
+ * a bunch of the audit string handling logic here instead.)
+ *
+ * Copyright 2012 Google Inc. All Rights Reserved
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/security.h>
+
+#include "utils.h"
+
+/* Disallow double-quote and control characters other than space. */
+static int contains_unprintable(const char *source, size_t len)
+{
+       const unsigned char *p;
+       for (p = source; p < (const unsigned char *)source + len; p++) {
+               if (*p == '"' || *p < 0x20 || *p > 0x7e)
+                       return 1;
+       }
+       return 0;
+}
+
+static char *hex_printable(const char *source, size_t len)
+{
+       size_t i;
+       char *dest, *ptr;
+       const char *hex = "0123456789ABCDEF";
+
+       /* Need to double the length of the string, plus a NULL. */
+       if (len > (INT_MAX - 1) / 2)
+               return NULL;
+       dest = kmalloc((len * 2) + 1, GFP_KERNEL);
+       if (!dest)
+               return NULL;
+
+       for (ptr = dest, i = 0; i < len; i++) {
+               *ptr++ = hex[(source[i] & 0xF0) >> 4];
+               *ptr++ = hex[source[i] & 0x0F];
+       }
+       *ptr = '\0';
+
+       return dest;
+}
+
+static char *quoted_printable(const char *source, size_t len)
+{
+       char *dest;
+
+       /* Need to add 2 double quotes and a NULL. */
+       if (len > INT_MAX - 3)
+               return NULL;
+       dest = kmalloc(len + 3, GFP_KERNEL);
+       if (!dest)
+               return NULL;
+
+       dest[0] = '"';
+       strcpy(dest + 1, source);
+       dest[len + 1] = '"';
+       dest[len + 2] = '\0';
+       return dest;
+}
+
+/* Return a string that has been sanitized and is safe to log. It is either
+ * in double-quotes, or is a series of hex digits.
+ */
+char *printable(char *source)
+{
+       size_t len;
+
+       if (!source)
+               return NULL;
+
+       len = strlen(source);
+       if (contains_unprintable(source, len))
+               return hex_printable(source, len);
+       else
+               return quoted_printable(source, len);
+}
+
+/* Repurposed from fs/proc/base.c, with NULL-replacement for saner printing.
+ * Allocates the buffer itself.
+ */
+char *printable_cmdline(struct task_struct *task)
+{
+       char *buffer = NULL, *sanitized;
+       int res, i;
+       unsigned int len;
+       struct mm_struct *mm;
+
+       mm = get_task_mm(task);
+       if (!mm)
+               goto out;
+
+       if (!mm->arg_end)
+               goto out_mm;    /* Shh! No looking before we're done */
+
+       buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
+       if (!buffer)
+               goto out_mm;
+
+       len = mm->arg_end - mm->arg_start;
+
+       if (len > PAGE_SIZE)
+               len = PAGE_SIZE;
+
+       res = access_process_vm(task, mm->arg_start, buffer, len, 0);
+
+       /* Space-fill NULLs. */
+       if (res > 1)
+               for (i = 0; i < res - 2; ++i)
+                       if (buffer[i] == '\0')
+                               buffer[i] = ' ';
+
+       /* If the NULL at the end of args has been overwritten, then
+        * assume application is using setproctitle(3).
+        */
+       if (res > 0 && buffer[res-1] != '\0' && len < PAGE_SIZE) {
+               len = strnlen(buffer, res);
+               if (len < res) {
+                       res = len;
+               } else {
+                       len = mm->env_end - mm->env_start;
+                       if (len > PAGE_SIZE - res)
+                               len = PAGE_SIZE - res;
+                       res += access_process_vm(task, mm->env_start,
+                                                buffer+res, len, 0);
+                       res = strnlen(buffer, res);
+               }
+       }
+
+       /* Make sure result is printable. */
+       sanitized = printable(buffer);
+       kfree(buffer);
+       buffer = sanitized;
+
+out_mm:
+       mmput(mm);
+out:
+       return buffer;
+}
diff --git a/security/chromiumos/utils.h b/security/chromiumos/utils.h
new file mode 100644 (file)
index 0000000..4bb0c9e
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Utilities for the Linux Security Module for Chromium OS
+ * (Since CONFIG_AUDIT is disabled for Chrome OS, we must repurpose
+ * a bunch of the audit string handling logic here instead.)
+ *
+ * Copyright 2012 Google Inc. All Rights Reserved
+ *
+ * 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.
+ */
+
+#ifndef _SECURITY_CHROMIUMOS_UTILS_H
+#define _SECURITY_CHROMIUMOS_UTILS_H
+
+#include <linux/sched.h>
+#include <linux/mm.h>
+
+char *printable(char *source);
+char *printable_cmdline(struct task_struct *task);
+
+#endif /* _SECURITY_CHROMIUMOS_UTILS_H */