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 36aeca4e2cSMicah Morton * variables pointed to by 'parent' and 'child' will get updated but this 37aeca4e2cSMicah Morton * function will return an error. 38aeca4e2cSMicah Morton */ 39aeca4e2cSMicah Morton static int parse_safesetid_whitelist_policy(const char __user *buf, 40aeca4e2cSMicah Morton size_t len, 41aeca4e2cSMicah Morton kuid_t *parent, 42aeca4e2cSMicah Morton kuid_t *child) 43aeca4e2cSMicah Morton { 44aeca4e2cSMicah Morton char *kern_buf; 45aeca4e2cSMicah Morton char *parent_buf; 46aeca4e2cSMicah Morton char *child_buf; 47aeca4e2cSMicah Morton const char separator[] = ":"; 48aeca4e2cSMicah Morton int ret; 49aeca4e2cSMicah Morton size_t first_substring_length; 50aeca4e2cSMicah Morton long parsed_parent; 51aeca4e2cSMicah Morton long parsed_child; 52aeca4e2cSMicah Morton 53aeca4e2cSMicah Morton /* Duplicate string from user memory and NULL-terminate */ 54aeca4e2cSMicah Morton kern_buf = memdup_user_nul(buf, len); 55aeca4e2cSMicah Morton if (IS_ERR(kern_buf)) 56aeca4e2cSMicah Morton return PTR_ERR(kern_buf); 57aeca4e2cSMicah Morton 58aeca4e2cSMicah Morton /* 59aeca4e2cSMicah Morton * Format of |buf| string should be <UID>:<UID>. 60aeca4e2cSMicah Morton * Find location of ":" in kern_buf (copied from |buf|). 61aeca4e2cSMicah Morton */ 62aeca4e2cSMicah Morton first_substring_length = strcspn(kern_buf, separator); 63aeca4e2cSMicah Morton if (first_substring_length == 0 || first_substring_length == len) { 64aeca4e2cSMicah Morton ret = -EINVAL; 65aeca4e2cSMicah Morton goto free_kern; 66aeca4e2cSMicah Morton } 67aeca4e2cSMicah Morton 68aeca4e2cSMicah Morton parent_buf = kmemdup_nul(kern_buf, first_substring_length, GFP_KERNEL); 69aeca4e2cSMicah Morton if (!parent_buf) { 70aeca4e2cSMicah Morton ret = -ENOMEM; 71aeca4e2cSMicah Morton goto free_kern; 72aeca4e2cSMicah Morton } 73aeca4e2cSMicah Morton 74aeca4e2cSMicah Morton ret = kstrtol(parent_buf, 0, &parsed_parent); 75aeca4e2cSMicah Morton if (ret) 76aeca4e2cSMicah Morton goto free_both; 77aeca4e2cSMicah Morton 78aeca4e2cSMicah Morton child_buf = kern_buf + first_substring_length + 1; 79aeca4e2cSMicah Morton ret = kstrtol(child_buf, 0, &parsed_child); 80aeca4e2cSMicah Morton if (ret) 81aeca4e2cSMicah Morton goto free_both; 82aeca4e2cSMicah Morton 83aeca4e2cSMicah Morton *parent = make_kuid(current_user_ns(), parsed_parent); 84aeca4e2cSMicah Morton if (!uid_valid(*parent)) { 85aeca4e2cSMicah Morton ret = -EINVAL; 86aeca4e2cSMicah Morton goto free_both; 87aeca4e2cSMicah Morton } 88aeca4e2cSMicah Morton 89aeca4e2cSMicah Morton *child = make_kuid(current_user_ns(), parsed_child); 90aeca4e2cSMicah Morton if (!uid_valid(*child)) { 91aeca4e2cSMicah Morton ret = -EINVAL; 92aeca4e2cSMicah Morton goto free_both; 93aeca4e2cSMicah Morton } 94aeca4e2cSMicah Morton 95aeca4e2cSMicah Morton free_both: 96aeca4e2cSMicah Morton kfree(parent_buf); 97aeca4e2cSMicah Morton free_kern: 98aeca4e2cSMicah Morton kfree(kern_buf); 99aeca4e2cSMicah Morton return ret; 100aeca4e2cSMicah Morton } 101aeca4e2cSMicah Morton 102aeca4e2cSMicah Morton static ssize_t safesetid_file_write(struct file *file, 103aeca4e2cSMicah Morton const char __user *buf, 104aeca4e2cSMicah Morton size_t len, 105aeca4e2cSMicah Morton loff_t *ppos) 106aeca4e2cSMicah Morton { 107aeca4e2cSMicah Morton struct safesetid_file_entry *file_entry = 108aeca4e2cSMicah Morton file->f_inode->i_private; 109aeca4e2cSMicah Morton kuid_t parent; 110aeca4e2cSMicah Morton kuid_t child; 111aeca4e2cSMicah Morton int ret; 112aeca4e2cSMicah Morton 113aeca4e2cSMicah Morton if (!ns_capable(current_user_ns(), CAP_MAC_ADMIN)) 114aeca4e2cSMicah Morton return -EPERM; 115aeca4e2cSMicah Morton 116aeca4e2cSMicah Morton if (*ppos != 0) 117aeca4e2cSMicah Morton return -EINVAL; 118aeca4e2cSMicah Morton 119aeca4e2cSMicah Morton switch (file_entry->type) { 120aeca4e2cSMicah Morton case SAFESETID_WHITELIST_FLUSH: 121aeca4e2cSMicah Morton flush_safesetid_whitelist_entries(); 122aeca4e2cSMicah Morton break; 123aeca4e2cSMicah Morton case SAFESETID_WHITELIST_ADD: 124aeca4e2cSMicah Morton ret = parse_safesetid_whitelist_policy(buf, len, &parent, 125aeca4e2cSMicah Morton &child); 126aeca4e2cSMicah Morton if (ret) 127aeca4e2cSMicah Morton return ret; 128aeca4e2cSMicah Morton 129aeca4e2cSMicah Morton ret = add_safesetid_whitelist_entry(parent, child); 130aeca4e2cSMicah Morton if (ret) 131aeca4e2cSMicah Morton return ret; 132aeca4e2cSMicah Morton break; 133aeca4e2cSMicah Morton default: 134aeca4e2cSMicah Morton pr_warn("Unknown securityfs file %d\n", file_entry->type); 135aeca4e2cSMicah Morton break; 136aeca4e2cSMicah Morton } 137aeca4e2cSMicah Morton 138aeca4e2cSMicah Morton /* Return len on success so caller won't keep trying to write */ 139aeca4e2cSMicah Morton return len; 140aeca4e2cSMicah Morton } 141aeca4e2cSMicah Morton 142aeca4e2cSMicah Morton static const struct file_operations safesetid_file_fops = { 143aeca4e2cSMicah Morton .write = safesetid_file_write, 144aeca4e2cSMicah Morton }; 145aeca4e2cSMicah Morton 146aeca4e2cSMicah Morton static void safesetid_shutdown_securityfs(void) 147aeca4e2cSMicah Morton { 148aeca4e2cSMicah Morton int i; 149aeca4e2cSMicah Morton 150aeca4e2cSMicah Morton for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) { 151aeca4e2cSMicah Morton struct safesetid_file_entry *entry = 152aeca4e2cSMicah Morton &safesetid_files[i]; 153aeca4e2cSMicah Morton securityfs_remove(entry->dentry); 154aeca4e2cSMicah Morton entry->dentry = NULL; 155aeca4e2cSMicah Morton } 156aeca4e2cSMicah Morton 157aeca4e2cSMicah Morton securityfs_remove(safesetid_policy_dir); 158aeca4e2cSMicah Morton safesetid_policy_dir = NULL; 159aeca4e2cSMicah Morton } 160aeca4e2cSMicah Morton 161aeca4e2cSMicah Morton static int __init safesetid_init_securityfs(void) 162aeca4e2cSMicah Morton { 163aeca4e2cSMicah Morton int i; 164aeca4e2cSMicah Morton int ret; 165aeca4e2cSMicah Morton 166aeca4e2cSMicah Morton if (!safesetid_initialized) 167aeca4e2cSMicah Morton return 0; 168aeca4e2cSMicah Morton 169aeca4e2cSMicah Morton safesetid_policy_dir = securityfs_create_dir("safesetid", NULL); 170e7a44cfdSWei Yongjun if (IS_ERR(safesetid_policy_dir)) { 171aeca4e2cSMicah Morton ret = PTR_ERR(safesetid_policy_dir); 172aeca4e2cSMicah Morton goto error; 173aeca4e2cSMicah Morton } 174aeca4e2cSMicah Morton 175aeca4e2cSMicah Morton for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) { 176aeca4e2cSMicah Morton struct safesetid_file_entry *entry = 177aeca4e2cSMicah Morton &safesetid_files[i]; 178aeca4e2cSMicah Morton entry->dentry = securityfs_create_file( 179aeca4e2cSMicah Morton entry->name, 0200, safesetid_policy_dir, 180aeca4e2cSMicah Morton entry, &safesetid_file_fops); 181aeca4e2cSMicah Morton if (IS_ERR(entry->dentry)) { 182aeca4e2cSMicah Morton ret = PTR_ERR(entry->dentry); 183aeca4e2cSMicah Morton goto error; 184aeca4e2cSMicah Morton } 185aeca4e2cSMicah Morton } 186aeca4e2cSMicah Morton 187aeca4e2cSMicah Morton return 0; 188aeca4e2cSMicah Morton 189aeca4e2cSMicah Morton error: 190aeca4e2cSMicah Morton safesetid_shutdown_securityfs(); 191aeca4e2cSMicah Morton return ret; 192aeca4e2cSMicah Morton } 193aeca4e2cSMicah Morton fs_initcall(safesetid_init_securityfs); 194