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