*
* 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)
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,
};
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)");
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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 */