1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * SafeSetID Linux Security Module 4 * 5 * Author: Micah Morton <mortonm@chromium.org> 6 * 7 * Copyright (C) 2018 The Chromium OS Authors. 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License version 2, as 11 * published by the Free Software Foundation. 12 * 13 */ 14 15 #define pr_fmt(fmt) "SafeSetID: " fmt 16 17 #include <linux/security.h> 18 #include <linux/cred.h> 19 20 #include "lsm.h" 21 22 static DEFINE_MUTEX(policy_update_lock); 23 24 /* 25 * In the case the input buffer contains one or more invalid UIDs, the kuid_t 26 * variables pointed to by @parent and @child will get updated but this 27 * function will return an error. 28 * Contents of @buf may be modified. 29 */ 30 static int parse_policy_line(struct file *file, char *buf, 31 struct setuid_rule *rule) 32 { 33 char *child_str; 34 int ret; 35 u32 parsed_parent, parsed_child; 36 37 /* Format of |buf| string should be <UID>:<UID>. */ 38 child_str = strchr(buf, ':'); 39 if (child_str == NULL) 40 return -EINVAL; 41 *child_str = '\0'; 42 child_str++; 43 44 ret = kstrtou32(buf, 0, &parsed_parent); 45 if (ret) 46 return ret; 47 48 ret = kstrtou32(child_str, 0, &parsed_child); 49 if (ret) 50 return ret; 51 52 rule->src_uid = make_kuid(file->f_cred->user_ns, parsed_parent); 53 rule->dst_uid = make_kuid(file->f_cred->user_ns, parsed_child); 54 if (!uid_valid(rule->src_uid) || !uid_valid(rule->dst_uid)) 55 return -EINVAL; 56 57 return 0; 58 } 59 60 static void __release_ruleset(struct rcu_head *rcu) 61 { 62 struct setuid_ruleset *pol = 63 container_of(rcu, struct setuid_ruleset, rcu); 64 int bucket; 65 struct setuid_rule *rule; 66 struct hlist_node *tmp; 67 68 hash_for_each_safe(pol->rules, bucket, tmp, rule, next) 69 kfree(rule); 70 kfree(pol->policy_str); 71 kfree(pol); 72 } 73 74 static void release_ruleset(struct setuid_ruleset *pol) 75 { 76 call_rcu(&pol->rcu, __release_ruleset); 77 } 78 79 static void insert_rule(struct setuid_ruleset *pol, struct setuid_rule *rule) 80 { 81 hash_add(pol->rules, &rule->next, __kuid_val(rule->src_uid)); 82 } 83 84 static int verify_ruleset(struct setuid_ruleset *pol) 85 { 86 int bucket; 87 struct setuid_rule *rule, *nrule; 88 int res = 0; 89 90 hash_for_each(pol->rules, bucket, rule, next) { 91 if (_setuid_policy_lookup(pol, rule->dst_uid, INVALID_UID) == 92 SIDPOL_DEFAULT) { 93 pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n", 94 __kuid_val(rule->src_uid), 95 __kuid_val(rule->dst_uid)); 96 res = -EINVAL; 97 98 /* fix it up */ 99 nrule = kmalloc(sizeof(struct setuid_rule), GFP_KERNEL); 100 if (!nrule) 101 return -ENOMEM; 102 nrule->src_uid = rule->dst_uid; 103 nrule->dst_uid = rule->dst_uid; 104 insert_rule(pol, nrule); 105 } 106 } 107 return res; 108 } 109 110 static ssize_t handle_policy_update(struct file *file, 111 const char __user *ubuf, size_t len) 112 { 113 struct setuid_ruleset *pol; 114 char *buf, *p, *end; 115 int err; 116 117 pol = kmalloc(sizeof(struct setuid_ruleset), GFP_KERNEL); 118 if (!pol) 119 return -ENOMEM; 120 pol->policy_str = NULL; 121 hash_init(pol->rules); 122 123 p = buf = memdup_user_nul(ubuf, len); 124 if (IS_ERR(buf)) { 125 err = PTR_ERR(buf); 126 goto out_free_pol; 127 } 128 pol->policy_str = kstrdup(buf, GFP_KERNEL); 129 if (pol->policy_str == NULL) { 130 err = -ENOMEM; 131 goto out_free_buf; 132 } 133 134 /* policy lines, including the last one, end with \n */ 135 while (*p != '\0') { 136 struct setuid_rule *rule; 137 138 end = strchr(p, '\n'); 139 if (end == NULL) { 140 err = -EINVAL; 141 goto out_free_buf; 142 } 143 *end = '\0'; 144 145 rule = kmalloc(sizeof(struct setuid_rule), GFP_KERNEL); 146 if (!rule) { 147 err = -ENOMEM; 148 goto out_free_buf; 149 } 150 151 err = parse_policy_line(file, p, rule); 152 if (err) 153 goto out_free_rule; 154 155 if (_setuid_policy_lookup(pol, rule->src_uid, rule->dst_uid) == 156 SIDPOL_ALLOWED) { 157 pr_warn("bad policy: duplicate entry\n"); 158 err = -EEXIST; 159 goto out_free_rule; 160 } 161 162 insert_rule(pol, rule); 163 p = end + 1; 164 continue; 165 166 out_free_rule: 167 kfree(rule); 168 goto out_free_buf; 169 } 170 171 err = verify_ruleset(pol); 172 /* bogus policy falls through after fixing it up */ 173 if (err && err != -EINVAL) 174 goto out_free_buf; 175 176 /* 177 * Everything looks good, apply the policy and release the old one. 178 * What we really want here is an xchg() wrapper for RCU, but since that 179 * doesn't currently exist, just use a spinlock for now. 180 */ 181 mutex_lock(&policy_update_lock); 182 rcu_swap_protected(safesetid_setuid_rules, pol, 183 lockdep_is_held(&policy_update_lock)); 184 mutex_unlock(&policy_update_lock); 185 err = len; 186 187 out_free_buf: 188 kfree(buf); 189 out_free_pol: 190 release_ruleset(pol); 191 return err; 192 } 193 194 static ssize_t safesetid_file_write(struct file *file, 195 const char __user *buf, 196 size_t len, 197 loff_t *ppos) 198 { 199 if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN)) 200 return -EPERM; 201 202 if (*ppos != 0) 203 return -EINVAL; 204 205 return handle_policy_update(file, buf, len); 206 } 207 208 static ssize_t safesetid_file_read(struct file *file, char __user *buf, 209 size_t len, loff_t *ppos) 210 { 211 ssize_t res = 0; 212 struct setuid_ruleset *pol; 213 const char *kbuf; 214 215 mutex_lock(&policy_update_lock); 216 pol = rcu_dereference_protected(safesetid_setuid_rules, 217 lockdep_is_held(&policy_update_lock)); 218 if (pol) { 219 kbuf = pol->policy_str; 220 res = simple_read_from_buffer(buf, len, ppos, 221 kbuf, strlen(kbuf)); 222 } 223 mutex_unlock(&policy_update_lock); 224 return res; 225 } 226 227 static const struct file_operations safesetid_file_fops = { 228 .read = safesetid_file_read, 229 .write = safesetid_file_write, 230 }; 231 232 static int __init safesetid_init_securityfs(void) 233 { 234 int ret; 235 struct dentry *policy_dir; 236 struct dentry *policy_file; 237 238 if (!safesetid_initialized) 239 return 0; 240 241 policy_dir = securityfs_create_dir("safesetid", NULL); 242 if (IS_ERR(policy_dir)) { 243 ret = PTR_ERR(policy_dir); 244 goto error; 245 } 246 247 policy_file = securityfs_create_file("whitelist_policy", 0600, 248 policy_dir, NULL, &safesetid_file_fops); 249 if (IS_ERR(policy_file)) { 250 ret = PTR_ERR(policy_file); 251 goto error; 252 } 253 254 return 0; 255 256 error: 257 securityfs_remove(policy_dir); 258 return ret; 259 } 260 fs_initcall(safesetid_init_securityfs); 261