From c1884ac2c30aaa9c3c356f2e6fd0425c74608424 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Tue, 4 Sep 2012 15:28:15 -0700 Subject: [PATCH] CHROMIUM: LSM: add module loading restrictions 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 Reviewed-on: https://gerrit.chromium.org/gerrit/34306 Reviewed-by: Olof Johansson --- security/chromiumos/Kconfig | 9 +- security/chromiumos/Makefile | 2 +- security/chromiumos/lsm.c | 165 +++++++++++++++++++++++++++++++++-- security/chromiumos/utils.c | 154 ++++++++++++++++++++++++++++++++ security/chromiumos/utils.h | 30 +++++++ 5 files changed, 348 insertions(+), 12 deletions(-) create mode 100644 security/chromiumos/utils.c create mode 100644 security/chromiumos/utils.h diff --git a/security/chromiumos/Kconfig b/security/chromiumos/Kconfig index 58bf5082da2c..78f7d1475875 100644 --- a/security/chromiumos/Kconfig +++ b/security/chromiumos/Kconfig @@ -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. diff --git a/security/chromiumos/Makefile b/security/chromiumos/Makefile index 7f441bd5695c..0e92746fcbf0 100644 --- a/security/chromiumos/Makefile +++ b/security/chromiumos/Makefile @@ -1 +1 @@ -obj-$(CONFIG_SECURITY_CHROMIUMOS) += lsm.o +obj-$(CONFIG_SECURITY_CHROMIUMOS) += lsm.o utils.o diff --git a/security/chromiumos/lsm.c b/security/chromiumos/lsm.c index 202d90684627..deefdb4628e9 100644 --- a/security/chromiumos/lsm.c +++ b/security/chromiumos/lsm.c @@ -3,8 +3,9 @@ * * Copyright 2011 Google Inc. All Rights Reserved * - * Author: + * Authors: * Stephan Uphoff + * Kees Cook * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -16,9 +17,18 @@ * GNU General Public License for more details. */ +#define pr_fmt(fmt) "Chromium OS LSM: " fmt + #include #include #include /* current and other task related stuff */ +#include +#include +#include +#include +#include + +#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 = ""; + else { + /* We will allow 11 spaces for ' (deleted)' to be appended */ + alloced = pathname = kmalloc(PATH_MAX+11, GFP_KERNEL); + if (!pathname) + pathname = ""; + else { + pathname = d_path(path, pathname, PATH_MAX+11); + if (IS_ERR(pathname)) + pathname = ""; + 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 index 000000000000..75a22bc88685 --- /dev/null +++ b/security/chromiumos/utils.c @@ -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 + * + * 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 +#include + +#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 index 000000000000..4bb0c9e4dd07 --- /dev/null +++ b/security/chromiumos/utils.h @@ -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 + * + * 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 +#include + +char *printable(char *source); +char *printable_cmdline(struct task_struct *task); + +#endif /* _SECURITY_CHROMIUMOS_UTILS_H */ -- 2.20.1