/* * Linux Security Module for Chromium OS * * Copyright 2016 Google Inc. All Rights Reserved * * Authors: * Mattias Nissler * * 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 #include #include #include #include #include #include "inode_mark.h" #include "process_management.h" static struct dentry *chromiumos_dir; static struct dentry *chromiumos_inode_policy_dir; static struct dentry *chromiumos_process_management_policy_dir; struct chromiumos_inode_policy_file_entry { const char *name; int (*handle_write)(struct chromiumos_inode_policy_file_entry *, struct dentry *); enum chromiumos_inode_security_policy_type type; enum chromiumos_inode_security_policy policy; struct dentry *dentry; }; struct chromiumos_process_management_file_entry { const char *name; enum chromiumos_process_management_file_write_type type; struct dentry *dentry; }; static int chromiumos_inode_policy_file_write( struct chromiumos_inode_policy_file_entry *file_entry, struct dentry *dentry) { return chromiumos_update_inode_security_policy(dentry->d_inode, file_entry->type, file_entry->policy); } /* * Causes all marks to be removed from inodes thus removing all inode security * policies. */ static int chromiumos_inode_policy_file_flush_write( struct chromiumos_inode_policy_file_entry *file_entry, struct dentry *dentry) { return chromiumos_flush_inode_security_policies(dentry->d_sb); } static struct chromiumos_inode_policy_file_entry chromiumos_inode_policy_files[] = { {.name = "block_symlink", .handle_write = chromiumos_inode_policy_file_write, .type = CHROMIUMOS_SYMLINK_TRAVERSAL, .policy = CHROMIUMOS_INODE_POLICY_BLOCK}, {.name = "allow_symlink", .handle_write = chromiumos_inode_policy_file_write, .type = CHROMIUMOS_SYMLINK_TRAVERSAL, .policy = CHROMIUMOS_INODE_POLICY_ALLOW}, {.name = "reset_symlink", .handle_write = chromiumos_inode_policy_file_write, .type = CHROMIUMOS_SYMLINK_TRAVERSAL, .policy = CHROMIUMOS_INODE_POLICY_INHERIT}, {.name = "block_fifo", .handle_write = chromiumos_inode_policy_file_write, .type = CHROMIUMOS_FIFO_ACCESS, .policy = CHROMIUMOS_INODE_POLICY_BLOCK}, {.name = "allow_fifo", .handle_write = chromiumos_inode_policy_file_write, .type = CHROMIUMOS_FIFO_ACCESS, .policy = CHROMIUMOS_INODE_POLICY_ALLOW}, {.name = "reset_fifo", .handle_write = chromiumos_inode_policy_file_write, .type = CHROMIUMOS_FIFO_ACCESS, .policy = CHROMIUMOS_INODE_POLICY_INHERIT}, {.name = "flush_policies", .handle_write = &chromiumos_inode_policy_file_flush_write}, }; static struct chromiumos_process_management_file_entry chromiumos_process_management_files[] = { {.name = "add_whitelist_policy", .type = CHROMIUMOS_PROCESS_MANAGEMENT_ADD}, {.name = "flush_whitelist_policies", .type = CHROMIUMOS_PROCESS_MANAGEMENT_FLUSH}, }; static int chromiumos_resolve_path(const char __user *buf, size_t len, struct path *path) { char *filename = NULL; char *canonical_buf = NULL; char *canonical; int ret; if (len + 1 > PATH_MAX) return -EINVAL; /* * Copy the path to a kernel buffer. We can't use user_path_at() * since it expects a zero-terminated path, which we generally don't * have here. */ filename = kzalloc(len + 1, GFP_KERNEL); if (!filename) return -ENOMEM; if (copy_from_user(filename, buf, len)) { ret = -EFAULT; goto out; } ret = kern_path(filename, 0, path); if (ret) goto out; /* * Make sure the path is canonical, i.e. it didn't contain symlinks. To * check this we convert |path| back to an absolute path (within the * global root) and compare the resulting path name with the passed-in * |filename|. This is stricter than needed (i.e. consecutive slashes * don't get ignored), but that's fine for our purposes. */ canonical_buf = kzalloc(len + 1, GFP_KERNEL); if (!canonical_buf) { ret = -ENOMEM; goto out; } canonical = d_absolute_path(path, canonical_buf, len + 1); if (IS_ERR(canonical)) { ret = PTR_ERR(canonical); /* Buffer too short implies |filename| wasn't canonical. */ if (ret == -ENAMETOOLONG) ret = -EMLINK; goto out; } ret = strcmp(filename, canonical) ? -EMLINK : 0; out: kfree(canonical_buf); if (ret < 0) path_put(path); kfree(filename); return ret; } static ssize_t chromiumos_inode_file_write( struct file *file, const char __user *buf, size_t len, loff_t *ppos) { struct chromiumos_inode_policy_file_entry *file_entry = file->f_inode->i_private; struct path path = {}; int ret; if (!ns_capable(current_cred()->user_ns, CAP_SYS_ADMIN)) return -EPERM; if (*ppos != 0) return -EINVAL; ret = chromiumos_resolve_path(buf, len, &path); if (ret) return ret; ret = file_entry->handle_write(file_entry, path.dentry); path_put(&path); return ret < 0 ? ret : len; } /* * In the case the input buffer contains one or more invalid UIDS, the kuid_t * variables pointed to by 'parent' and 'child' will get updated but this * function will return an error. */ static int chromiumos_parse_process_management_policy(const char __user *buf, size_t len, kuid_t *parent, kuid_t *child) { char *kern_buf; char *parent_buf; char *child_buf; const char separator[] = ":"; int ret; size_t first_substring_length; long parsed_parent; long parsed_child; /* Duplicate string from user memory and NULL-terminate */ kern_buf = memdup_user_nul(buf, len); if (IS_ERR(kern_buf)) return PTR_ERR(kern_buf); /* * Format of |buf| string should be :. * Find location of ":" in kern_buf (copied from |buf|). */ first_substring_length = strcspn(kern_buf, separator); if (first_substring_length == 0 || first_substring_length == len) { ret = -EINVAL; goto free_kern; } parent_buf = kmemdup_nul(kern_buf, first_substring_length, GFP_KERNEL); if (!parent_buf) { ret = -ENOMEM; goto free_kern; } ret = kstrtol(parent_buf, 0, &parsed_parent); if (ret) goto free_both; child_buf = kern_buf + first_substring_length + 1; ret = kstrtol(child_buf, 0, &parsed_child); if (ret) goto free_both; *parent = make_kuid(current_user_ns(), parsed_parent); if (!uid_valid(*parent)) { ret = -EINVAL; goto free_both; } *child = make_kuid(current_user_ns(), parsed_child); if (!uid_valid(*child)) { ret = -EINVAL; goto free_both; } free_both: kfree(parent_buf); free_kern: kfree(kern_buf); return ret; } static ssize_t chromiumos_process_management_file_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos) { struct chromiumos_process_management_file_entry *file_entry = file->f_inode->i_private; kuid_t parent; kuid_t child; int ret; if (!ns_capable(current_user_ns(), CAP_SYS_ADMIN)) return -EPERM; if (*ppos != 0) return -EINVAL; if (file_entry->type == CHROMIUMOS_PROCESS_MANAGEMENT_FLUSH) { chromiumos_flush_process_management_entries(); return len; } /* file_entry->type must equal CHROMIUMOS_PROCESS_MANAGEMENT_ADD */ ret = chromiumos_parse_process_management_policy(buf, len, &parent, &child); if (ret) return ret; ret = chromiumos_add_process_management_entry(parent, child); if (ret) return ret; /* Return len on success so caller won't keep trying to write */ return len; } static const struct file_operations chromiumos_inode_policy_file_fops = { .write = chromiumos_inode_file_write, }; static const struct file_operations chromiumos_process_management_file_fops = { .write = chromiumos_process_management_file_write, }; static void chromiumos_shutdown_securityfs(void) { int i; for (i = 0; i < ARRAY_SIZE(chromiumos_inode_policy_files); ++i) { struct chromiumos_inode_policy_file_entry *entry = &chromiumos_inode_policy_files[i]; securityfs_remove(entry->dentry); entry->dentry = NULL; } for (i = 0; i < ARRAY_SIZE(chromiumos_process_management_files); ++i) { struct chromiumos_process_management_file_entry *entry = &chromiumos_process_management_files[i]; securityfs_remove(entry->dentry); entry->dentry = NULL; } securityfs_remove(chromiumos_inode_policy_dir); chromiumos_inode_policy_dir = NULL; securityfs_remove(chromiumos_process_management_policy_dir); chromiumos_process_management_policy_dir = NULL; securityfs_remove(chromiumos_dir); chromiumos_dir = NULL; } static int chromiumos_init_securityfs(void) { int i; int ret; chromiumos_dir = securityfs_create_dir("chromiumos", NULL); if (!chromiumos_dir) { ret = PTR_ERR(chromiumos_dir); goto error; } chromiumos_inode_policy_dir = securityfs_create_dir( "inode_security_policies", chromiumos_dir); if (!chromiumos_inode_policy_dir) { ret = PTR_ERR(chromiumos_inode_policy_dir); goto error; } for (i = 0; i < ARRAY_SIZE(chromiumos_inode_policy_files); ++i) { struct chromiumos_inode_policy_file_entry *entry = &chromiumos_inode_policy_files[i]; entry->dentry = securityfs_create_file( entry->name, 0200, chromiumos_inode_policy_dir, entry, &chromiumos_inode_policy_file_fops); if (IS_ERR(entry->dentry)) { ret = PTR_ERR(entry->dentry); goto error; } } chromiumos_process_management_policy_dir = securityfs_create_dir( "process_management_policies", chromiumos_dir); if (!chromiumos_process_management_policy_dir) { ret = PTR_ERR(chromiumos_process_management_policy_dir); goto error; } for (i = 0; i < ARRAY_SIZE(chromiumos_process_management_files); ++i) { struct chromiumos_process_management_file_entry *entry = &chromiumos_process_management_files[i]; entry->dentry = securityfs_create_file( entry->name, 0200, chromiumos_process_management_policy_dir, entry, &chromiumos_process_management_file_fops); if (IS_ERR(entry->dentry)) { ret = PTR_ERR(entry->dentry); goto error; } } return 0; error: chromiumos_shutdown_securityfs(); return ret; } fs_initcall(chromiumos_init_securityfs);