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