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(uid_policy_update_lock); 23 static DEFINE_MUTEX(gid_policy_update_lock); 24 25 /* 26 * In the case the input buffer contains one or more invalid IDs, the kid_t 27 * variables pointed to by @parent and @child will get updated but this 28 * function will return an error. 29 * Contents of @buf may be modified. 30 */ 31 static int parse_policy_line(struct file *file, char *buf, 32 struct setid_rule *rule) 33 { 34 char *child_str; 35 int ret; 36 u32 parsed_parent, parsed_child; 37 38 /* Format of |buf| string should be <UID>:<UID> or <GID>:<GID> */ 39 child_str = strchr(buf, ':'); 40 if (child_str == NULL) 41 return -EINVAL; 42 *child_str = '\0'; 43 child_str++; 44 45 ret = kstrtou32(buf, 0, &parsed_parent); 46 if (ret) 47 return ret; 48 49 ret = kstrtou32(child_str, 0, &parsed_child); 50 if (ret) 51 return ret; 52 53 if (rule->type == UID){ 54 rule->src_id.uid = make_kuid(file->f_cred->user_ns, parsed_parent); 55 rule->dst_id.uid = make_kuid(file->f_cred->user_ns, parsed_child); 56 if (!uid_valid(rule->src_id.uid) || !uid_valid(rule->dst_id.uid)) 57 return -EINVAL; 58 } else if (rule->type == GID){ 59 rule->src_id.gid = make_kgid(file->f_cred->user_ns, parsed_parent); 60 rule->dst_id.gid = make_kgid(file->f_cred->user_ns, parsed_child); 61 if (!gid_valid(rule->src_id.gid) || !gid_valid(rule->dst_id.gid)) 62 return -EINVAL; 63 } else { 64 /* Error, rule->type is an invalid type */ 65 return -EINVAL; 66 } 67 return 0; 68 } 69 70 static void __release_ruleset(struct rcu_head *rcu) 71 { 72 struct setid_ruleset *pol = 73 container_of(rcu, struct setid_ruleset, rcu); 74 int bucket; 75 struct setid_rule *rule; 76 struct hlist_node *tmp; 77 78 hash_for_each_safe(pol->rules, bucket, tmp, rule, next) 79 kfree(rule); 80 kfree(pol->policy_str); 81 kfree(pol); 82 } 83 84 static void release_ruleset(struct setid_ruleset *pol){ 85 call_rcu(&pol->rcu, __release_ruleset); 86 } 87 88 static void insert_rule(struct setid_ruleset *pol, struct setid_rule *rule) 89 { 90 if (pol->type == UID) 91 hash_add(pol->rules, &rule->next, __kuid_val(rule->src_id.uid)); 92 else if (pol->type == GID) 93 hash_add(pol->rules, &rule->next, __kgid_val(rule->src_id.gid)); 94 else /* Error, pol->type is neither UID or GID */ 95 return; 96 } 97 98 static int verify_ruleset(struct setid_ruleset *pol) 99 { 100 int bucket; 101 struct setid_rule *rule, *nrule; 102 int res = 0; 103 104 hash_for_each(pol->rules, bucket, rule, next) { 105 if (_setid_policy_lookup(pol, rule->dst_id, INVALID_ID) == SIDPOL_DEFAULT) { 106 if (pol->type == UID) { 107 pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n", 108 __kuid_val(rule->src_id.uid), 109 __kuid_val(rule->dst_id.uid)); 110 } else if (pol->type == GID) { 111 pr_warn("insecure policy detected: gid %d is constrained but transitively unconstrained through gid %d\n", 112 __kgid_val(rule->src_id.gid), 113 __kgid_val(rule->dst_id.gid)); 114 } else { /* pol->type is an invalid type */ 115 res = -EINVAL; 116 return res; 117 } 118 res = -EINVAL; 119 120 /* fix it up */ 121 nrule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL); 122 if (!nrule) 123 return -ENOMEM; 124 if (pol->type == UID){ 125 nrule->src_id.uid = rule->dst_id.uid; 126 nrule->dst_id.uid = rule->dst_id.uid; 127 nrule->type = UID; 128 } else { /* pol->type must be GID if we've made it to here */ 129 nrule->src_id.gid = rule->dst_id.gid; 130 nrule->dst_id.gid = rule->dst_id.gid; 131 nrule->type = GID; 132 } 133 insert_rule(pol, nrule); 134 } 135 } 136 return res; 137 } 138 139 static ssize_t handle_policy_update(struct file *file, 140 const char __user *ubuf, size_t len, enum setid_type policy_type) 141 { 142 struct setid_ruleset *pol; 143 char *buf, *p, *end; 144 int err; 145 146 pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL); 147 if (!pol) 148 return -ENOMEM; 149 pol->policy_str = NULL; 150 pol->type = policy_type; 151 hash_init(pol->rules); 152 153 p = buf = memdup_user_nul(ubuf, len); 154 if (IS_ERR(buf)) { 155 err = PTR_ERR(buf); 156 goto out_free_pol; 157 } 158 pol->policy_str = kstrdup(buf, GFP_KERNEL); 159 if (pol->policy_str == NULL) { 160 err = -ENOMEM; 161 goto out_free_buf; 162 } 163 164 /* policy lines, including the last one, end with \n */ 165 while (*p != '\0') { 166 struct setid_rule *rule; 167 168 end = strchr(p, '\n'); 169 if (end == NULL) { 170 err = -EINVAL; 171 goto out_free_buf; 172 } 173 *end = '\0'; 174 175 rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL); 176 if (!rule) { 177 err = -ENOMEM; 178 goto out_free_buf; 179 } 180 181 rule->type = policy_type; 182 err = parse_policy_line(file, p, rule); 183 if (err) 184 goto out_free_rule; 185 186 if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == SIDPOL_ALLOWED) { 187 pr_warn("bad policy: duplicate entry\n"); 188 err = -EEXIST; 189 goto out_free_rule; 190 } 191 192 insert_rule(pol, rule); 193 p = end + 1; 194 continue; 195 196 out_free_rule: 197 kfree(rule); 198 goto out_free_buf; 199 } 200 201 err = verify_ruleset(pol); 202 /* bogus policy falls through after fixing it up */ 203 if (err && err != -EINVAL) 204 goto out_free_buf; 205 206 /* 207 * Everything looks good, apply the policy and release the old one. 208 * What we really want here is an xchg() wrapper for RCU, but since that 209 * doesn't currently exist, just use a spinlock for now. 210 */ 211 if (policy_type == UID) { 212 mutex_lock(&uid_policy_update_lock); 213 pol = rcu_replace_pointer(safesetid_setuid_rules, pol, 214 lockdep_is_held(&uid_policy_update_lock)); 215 mutex_unlock(&uid_policy_update_lock); 216 } else if (policy_type == GID) { 217 mutex_lock(&gid_policy_update_lock); 218 pol = rcu_replace_pointer(safesetid_setgid_rules, pol, 219 lockdep_is_held(&gid_policy_update_lock)); 220 mutex_unlock(&gid_policy_update_lock); 221 } else { 222 /* Error, policy type is neither UID or GID */ 223 pr_warn("error: bad policy type"); 224 } 225 err = len; 226 227 out_free_buf: 228 kfree(buf); 229 out_free_pol: 230 if (pol) 231 release_ruleset(pol); 232 return err; 233 } 234 235 static ssize_t safesetid_uid_file_write(struct file *file, 236 const char __user *buf, 237 size_t len, 238 loff_t *ppos) 239 { 240 if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN)) 241 return -EPERM; 242 243 if (*ppos != 0) 244 return -EINVAL; 245 246 return handle_policy_update(file, buf, len, UID); 247 } 248 249 static ssize_t safesetid_gid_file_write(struct file *file, 250 const char __user *buf, 251 size_t len, 252 loff_t *ppos) 253 { 254 if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN)) 255 return -EPERM; 256 257 if (*ppos != 0) 258 return -EINVAL; 259 260 return handle_policy_update(file, buf, len, GID); 261 } 262 263 static ssize_t safesetid_file_read(struct file *file, char __user *buf, 264 size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset) 265 { 266 ssize_t res = 0; 267 struct setid_ruleset *pol; 268 const char *kbuf; 269 270 mutex_lock(policy_update_lock); 271 pol = rcu_dereference_protected(ruleset, lockdep_is_held(policy_update_lock)); 272 if (pol) { 273 kbuf = pol->policy_str; 274 res = simple_read_from_buffer(buf, len, ppos, 275 kbuf, strlen(kbuf)); 276 } 277 mutex_unlock(policy_update_lock); 278 279 return res; 280 } 281 282 static ssize_t safesetid_uid_file_read(struct file *file, char __user *buf, 283 size_t len, loff_t *ppos) 284 { 285 return safesetid_file_read(file, buf, len, ppos, 286 &uid_policy_update_lock, safesetid_setuid_rules); 287 } 288 289 static ssize_t safesetid_gid_file_read(struct file *file, char __user *buf, 290 size_t len, loff_t *ppos) 291 { 292 return safesetid_file_read(file, buf, len, ppos, 293 &gid_policy_update_lock, safesetid_setgid_rules); 294 } 295 296 297 298 static const struct file_operations safesetid_uid_file_fops = { 299 .read = safesetid_uid_file_read, 300 .write = safesetid_uid_file_write, 301 }; 302 303 static const struct file_operations safesetid_gid_file_fops = { 304 .read = safesetid_gid_file_read, 305 .write = safesetid_gid_file_write, 306 }; 307 308 static int __init safesetid_init_securityfs(void) 309 { 310 int ret; 311 struct dentry *policy_dir; 312 struct dentry *uid_policy_file; 313 struct dentry *gid_policy_file; 314 315 if (!safesetid_initialized) 316 return 0; 317 318 policy_dir = securityfs_create_dir("safesetid", NULL); 319 if (IS_ERR(policy_dir)) { 320 ret = PTR_ERR(policy_dir); 321 goto error; 322 } 323 324 uid_policy_file = securityfs_create_file("uid_allowlist_policy", 0600, 325 policy_dir, NULL, &safesetid_uid_file_fops); 326 if (IS_ERR(uid_policy_file)) { 327 ret = PTR_ERR(uid_policy_file); 328 goto error; 329 } 330 331 gid_policy_file = securityfs_create_file("gid_allowlist_policy", 0600, 332 policy_dir, NULL, &safesetid_gid_file_fops); 333 if (IS_ERR(gid_policy_file)) { 334 ret = PTR_ERR(gid_policy_file); 335 goto error; 336 } 337 338 339 return 0; 340 341 error: 342 securityfs_remove(policy_dir); 343 return ret; 344 } 345 fs_initcall(safesetid_init_securityfs); 346
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.