1aeca4e2cSMicah Morton // SPDX-License-Identifier: GPL-2.0 2aeca4e2cSMicah Morton /* 3aeca4e2cSMicah Morton * SafeSetID Linux Security Module 4aeca4e2cSMicah Morton * 5aeca4e2cSMicah Morton * Author: Micah Morton <mortonm@chromium.org> 6aeca4e2cSMicah Morton * 7aeca4e2cSMicah Morton * Copyright (C) 2018 The Chromium OS Authors. 8aeca4e2cSMicah Morton * 9aeca4e2cSMicah Morton * This program is free software; you can redistribute it and/or modify 10aeca4e2cSMicah Morton * it under the terms of the GNU General Public License version 2, as 11aeca4e2cSMicah Morton * published by the Free Software Foundation. 12aeca4e2cSMicah Morton * 13aeca4e2cSMicah Morton */ 14aeca4e2cSMicah Morton #include <linux/security.h> 15aeca4e2cSMicah Morton #include <linux/cred.h> 16aeca4e2cSMicah Morton 17aeca4e2cSMicah Morton #include "lsm.h" 18aeca4e2cSMicah Morton 19aeca4e2cSMicah Morton static struct dentry *safesetid_policy_dir; 20aeca4e2cSMicah Morton 21aeca4e2cSMicah Morton struct safesetid_file_entry { 22aeca4e2cSMicah Morton const char *name; 23aeca4e2cSMicah Morton enum safesetid_whitelist_file_write_type type; 24aeca4e2cSMicah Morton struct dentry *dentry; 25aeca4e2cSMicah Morton }; 26aeca4e2cSMicah Morton 27aeca4e2cSMicah Morton static struct safesetid_file_entry safesetid_files[] = { 28aeca4e2cSMicah Morton {.name = "add_whitelist_policy", 29aeca4e2cSMicah Morton .type = SAFESETID_WHITELIST_ADD}, 30aeca4e2cSMicah Morton {.name = "flush_whitelist_policies", 31aeca4e2cSMicah Morton .type = SAFESETID_WHITELIST_FLUSH}, 32aeca4e2cSMicah Morton }; 33aeca4e2cSMicah Morton 34aeca4e2cSMicah Morton /* 35aeca4e2cSMicah Morton * In the case the input buffer contains one or more invalid UIDs, the kuid_t 3678ae7df9SJann Horn * variables pointed to by @parent and @child will get updated but this 37aeca4e2cSMicah Morton * function will return an error. 3878ae7df9SJann Horn * Contents of @buf may be modified. 39aeca4e2cSMicah Morton */ 4078ae7df9SJann Horn static int parse_policy_line( 4178ae7df9SJann Horn struct file *file, char *buf, kuid_t *parent, kuid_t *child) 42aeca4e2cSMicah Morton { 4378ae7df9SJann Horn char *child_str; 44aeca4e2cSMicah Morton int ret; 4578ae7df9SJann Horn u32 parsed_parent, parsed_child; 46aeca4e2cSMicah Morton 4778ae7df9SJann Horn /* Format of |buf| string should be <UID>:<UID>. */ 4878ae7df9SJann Horn child_str = strchr(buf, ':'); 4978ae7df9SJann Horn if (child_str == NULL) 5078ae7df9SJann Horn return -EINVAL; 5178ae7df9SJann Horn *child_str = '\0'; 5278ae7df9SJann Horn child_str++; 53aeca4e2cSMicah Morton 5478ae7df9SJann Horn ret = kstrtou32(buf, 0, &parsed_parent); 55aeca4e2cSMicah Morton if (ret) 5678ae7df9SJann Horn return ret; 57aeca4e2cSMicah Morton 5878ae7df9SJann Horn ret = kstrtou32(child_str, 0, &parsed_child); 59aeca4e2cSMicah Morton if (ret) 6078ae7df9SJann Horn return ret; 61aeca4e2cSMicah Morton 62aeca4e2cSMicah Morton *parent = make_kuid(current_user_ns(), parsed_parent); 63aeca4e2cSMicah Morton *child = make_kuid(current_user_ns(), parsed_child); 6478ae7df9SJann Horn if (!uid_valid(*parent) || !uid_valid(*child)) 6578ae7df9SJann Horn return -EINVAL; 6678ae7df9SJann Horn 6778ae7df9SJann Horn return 0; 68aeca4e2cSMicah Morton } 69aeca4e2cSMicah Morton 7078ae7df9SJann Horn static int parse_safesetid_whitelist_policy( 7178ae7df9SJann Horn struct file *file, const char __user *buf, size_t len, 7278ae7df9SJann Horn kuid_t *parent, kuid_t *child) 7378ae7df9SJann Horn { 7478ae7df9SJann Horn char *kern_buf = memdup_user_nul(buf, len); 7578ae7df9SJann Horn int ret; 7678ae7df9SJann Horn 7778ae7df9SJann Horn if (IS_ERR(kern_buf)) 7878ae7df9SJann Horn return PTR_ERR(kern_buf); 7978ae7df9SJann Horn ret = parse_policy_line(file, kern_buf, parent, child); 80aeca4e2cSMicah Morton kfree(kern_buf); 81aeca4e2cSMicah Morton return ret; 82aeca4e2cSMicah Morton } 83aeca4e2cSMicah Morton 84aeca4e2cSMicah Morton static ssize_t safesetid_file_write(struct file *file, 85aeca4e2cSMicah Morton const char __user *buf, 86aeca4e2cSMicah Morton size_t len, 87aeca4e2cSMicah Morton loff_t *ppos) 88aeca4e2cSMicah Morton { 89aeca4e2cSMicah Morton struct safesetid_file_entry *file_entry = 90aeca4e2cSMicah Morton file->f_inode->i_private; 91aeca4e2cSMicah Morton kuid_t parent; 92aeca4e2cSMicah Morton kuid_t child; 93aeca4e2cSMicah Morton int ret; 94aeca4e2cSMicah Morton 95aeca4e2cSMicah Morton if (!ns_capable(current_user_ns(), CAP_MAC_ADMIN)) 96aeca4e2cSMicah Morton return -EPERM; 97aeca4e2cSMicah Morton 98aeca4e2cSMicah Morton if (*ppos != 0) 99aeca4e2cSMicah Morton return -EINVAL; 100aeca4e2cSMicah Morton 101aeca4e2cSMicah Morton switch (file_entry->type) { 102aeca4e2cSMicah Morton case SAFESETID_WHITELIST_FLUSH: 103aeca4e2cSMicah Morton flush_safesetid_whitelist_entries(); 104aeca4e2cSMicah Morton break; 105aeca4e2cSMicah Morton case SAFESETID_WHITELIST_ADD: 10678ae7df9SJann Horn ret = parse_safesetid_whitelist_policy(file, buf, len, 10778ae7df9SJann Horn &parent, &child); 108aeca4e2cSMicah Morton if (ret) 109aeca4e2cSMicah Morton return ret; 110aeca4e2cSMicah Morton 111aeca4e2cSMicah Morton ret = add_safesetid_whitelist_entry(parent, child); 112aeca4e2cSMicah Morton if (ret) 113aeca4e2cSMicah Morton return ret; 114aeca4e2cSMicah Morton break; 115aeca4e2cSMicah Morton default: 116aeca4e2cSMicah Morton pr_warn("Unknown securityfs file %d\n", file_entry->type); 117aeca4e2cSMicah Morton break; 118aeca4e2cSMicah Morton } 119aeca4e2cSMicah Morton 120aeca4e2cSMicah Morton /* Return len on success so caller won't keep trying to write */ 121aeca4e2cSMicah Morton return len; 122aeca4e2cSMicah Morton } 123aeca4e2cSMicah Morton 124aeca4e2cSMicah Morton static const struct file_operations safesetid_file_fops = { 125aeca4e2cSMicah Morton .write = safesetid_file_write, 126aeca4e2cSMicah Morton }; 127aeca4e2cSMicah Morton 128aeca4e2cSMicah Morton static void safesetid_shutdown_securityfs(void) 129aeca4e2cSMicah Morton { 130aeca4e2cSMicah Morton int i; 131aeca4e2cSMicah Morton 132aeca4e2cSMicah Morton for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) { 133aeca4e2cSMicah Morton struct safesetid_file_entry *entry = 134aeca4e2cSMicah Morton &safesetid_files[i]; 135aeca4e2cSMicah Morton securityfs_remove(entry->dentry); 136aeca4e2cSMicah Morton entry->dentry = NULL; 137aeca4e2cSMicah Morton } 138aeca4e2cSMicah Morton 139aeca4e2cSMicah Morton securityfs_remove(safesetid_policy_dir); 140aeca4e2cSMicah Morton safesetid_policy_dir = NULL; 141aeca4e2cSMicah Morton } 142aeca4e2cSMicah Morton 143aeca4e2cSMicah Morton static int __init safesetid_init_securityfs(void) 144aeca4e2cSMicah Morton { 145aeca4e2cSMicah Morton int i; 146aeca4e2cSMicah Morton int ret; 147aeca4e2cSMicah Morton 148aeca4e2cSMicah Morton if (!safesetid_initialized) 149aeca4e2cSMicah Morton return 0; 150aeca4e2cSMicah Morton 151aeca4e2cSMicah Morton safesetid_policy_dir = securityfs_create_dir("safesetid", NULL); 152e7a44cfdSWei Yongjun if (IS_ERR(safesetid_policy_dir)) { 153aeca4e2cSMicah Morton ret = PTR_ERR(safesetid_policy_dir); 154aeca4e2cSMicah Morton goto error; 155aeca4e2cSMicah Morton } 156aeca4e2cSMicah Morton 157aeca4e2cSMicah Morton for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) { 158aeca4e2cSMicah Morton struct safesetid_file_entry *entry = 159aeca4e2cSMicah Morton &safesetid_files[i]; 160aeca4e2cSMicah Morton entry->dentry = securityfs_create_file( 161aeca4e2cSMicah Morton entry->name, 0200, safesetid_policy_dir, 162aeca4e2cSMicah Morton entry, &safesetid_file_fops); 163aeca4e2cSMicah Morton if (IS_ERR(entry->dentry)) { 164aeca4e2cSMicah Morton ret = PTR_ERR(entry->dentry); 165aeca4e2cSMicah Morton goto error; 166aeca4e2cSMicah Morton } 167aeca4e2cSMicah Morton } 168aeca4e2cSMicah Morton 169aeca4e2cSMicah Morton return 0; 170aeca4e2cSMicah Morton 171aeca4e2cSMicah Morton error: 172aeca4e2cSMicah Morton safesetid_shutdown_securityfs(); 173aeca4e2cSMicah Morton return ret; 174aeca4e2cSMicah Morton } 175aeca4e2cSMicah Morton fs_initcall(safesetid_init_securityfs); 176