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  */
1403638e62SJann Horn 
1503638e62SJann Horn #define pr_fmt(fmt) "SafeSetID: " fmt
1603638e62SJann Horn 
17aeca4e2cSMicah Morton #include <linux/security.h>
18aeca4e2cSMicah Morton #include <linux/cred.h>
19aeca4e2cSMicah Morton 
20aeca4e2cSMicah Morton #include "lsm.h"
21aeca4e2cSMicah Morton 
225294bac9SThomas Cedeno static DEFINE_MUTEX(uid_policy_update_lock);
235294bac9SThomas Cedeno static DEFINE_MUTEX(gid_policy_update_lock);
24aeca4e2cSMicah Morton 
25aeca4e2cSMicah Morton /*
265294bac9SThomas Cedeno  * In the case the input buffer contains one or more invalid IDs, the kid_t
2778ae7df9SJann Horn  * variables pointed to by @parent and @child will get updated but this
28aeca4e2cSMicah Morton  * function will return an error.
2978ae7df9SJann Horn  * Contents of @buf may be modified.
30aeca4e2cSMicah Morton  */
parse_policy_line(struct file * file,char * buf,struct setid_rule * rule)3103638e62SJann Horn static int parse_policy_line(struct file *file, char *buf,
325294bac9SThomas Cedeno 	struct setid_rule *rule)
33aeca4e2cSMicah Morton {
3478ae7df9SJann Horn 	char *child_str;
35aeca4e2cSMicah Morton 	int ret;
3678ae7df9SJann Horn 	u32 parsed_parent, parsed_child;
37aeca4e2cSMicah Morton 
385294bac9SThomas Cedeno 	/* Format of |buf| string should be <UID>:<UID> or <GID>:<GID> */
3978ae7df9SJann Horn 	child_str = strchr(buf, ':');
4078ae7df9SJann Horn 	if (child_str == NULL)
4178ae7df9SJann Horn 		return -EINVAL;
4278ae7df9SJann Horn 	*child_str = '\0';
4378ae7df9SJann Horn 	child_str++;
44aeca4e2cSMicah Morton 
4578ae7df9SJann Horn 	ret = kstrtou32(buf, 0, &parsed_parent);
46aeca4e2cSMicah Morton 	if (ret)
4778ae7df9SJann Horn 		return ret;
48aeca4e2cSMicah Morton 
4978ae7df9SJann Horn 	ret = kstrtou32(child_str, 0, &parsed_child);
50aeca4e2cSMicah Morton 	if (ret)
5178ae7df9SJann Horn 		return ret;
52aeca4e2cSMicah Morton 
535294bac9SThomas Cedeno 	if (rule->type == UID){
545294bac9SThomas Cedeno 		rule->src_id.uid = make_kuid(file->f_cred->user_ns, parsed_parent);
555294bac9SThomas Cedeno 		rule->dst_id.uid = make_kuid(file->f_cred->user_ns, parsed_child);
565294bac9SThomas Cedeno 		if (!uid_valid(rule->src_id.uid) || !uid_valid(rule->dst_id.uid))
5778ae7df9SJann Horn 			return -EINVAL;
585294bac9SThomas Cedeno 	} else if (rule->type == GID){
595294bac9SThomas Cedeno 		rule->src_id.gid = make_kgid(file->f_cred->user_ns, parsed_parent);
605294bac9SThomas Cedeno 		rule->dst_id.gid = make_kgid(file->f_cred->user_ns, parsed_child);
615294bac9SThomas Cedeno 		if (!gid_valid(rule->src_id.gid) || !gid_valid(rule->dst_id.gid))
625294bac9SThomas Cedeno 			return -EINVAL;
635294bac9SThomas Cedeno 	} else {
645294bac9SThomas Cedeno 		/* Error, rule->type is an invalid type */
655294bac9SThomas Cedeno 		return -EINVAL;
665294bac9SThomas Cedeno 	}
6778ae7df9SJann Horn 	return 0;
68aeca4e2cSMicah Morton }
69aeca4e2cSMicah Morton 
__release_ruleset(struct rcu_head * rcu)7003638e62SJann Horn static void __release_ruleset(struct rcu_head *rcu)
7178ae7df9SJann Horn {
725294bac9SThomas Cedeno 	struct setid_ruleset *pol =
735294bac9SThomas Cedeno 		container_of(rcu, struct setid_ruleset, rcu);
7403638e62SJann Horn 	int bucket;
755294bac9SThomas Cedeno 	struct setid_rule *rule;
7603638e62SJann Horn 	struct hlist_node *tmp;
7778ae7df9SJann Horn 
7803638e62SJann Horn 	hash_for_each_safe(pol->rules, bucket, tmp, rule, next)
7903638e62SJann Horn 		kfree(rule);
80fbd9acb2SJann Horn 	kfree(pol->policy_str);
8103638e62SJann Horn 	kfree(pol);
8203638e62SJann Horn }
8303638e62SJann Horn 
release_ruleset(struct setid_ruleset * pol)845294bac9SThomas Cedeno static void release_ruleset(struct setid_ruleset *pol){
8503638e62SJann Horn 	call_rcu(&pol->rcu, __release_ruleset);
8603638e62SJann Horn }
8703638e62SJann Horn 
insert_rule(struct setid_ruleset * pol,struct setid_rule * rule)885294bac9SThomas Cedeno static void insert_rule(struct setid_ruleset *pol, struct setid_rule *rule)
894f72123dSJann Horn {
905294bac9SThomas Cedeno 	if (pol->type == UID)
915294bac9SThomas Cedeno 		hash_add(pol->rules, &rule->next, __kuid_val(rule->src_id.uid));
925294bac9SThomas Cedeno 	else if (pol->type == GID)
935294bac9SThomas Cedeno 		hash_add(pol->rules, &rule->next, __kgid_val(rule->src_id.gid));
945294bac9SThomas Cedeno 	else /* Error, pol->type is neither UID or GID */
955294bac9SThomas Cedeno 		return;
964f72123dSJann Horn }
974f72123dSJann Horn 
verify_ruleset(struct setid_ruleset * pol)985294bac9SThomas Cedeno static int verify_ruleset(struct setid_ruleset *pol)
994f72123dSJann Horn {
1004f72123dSJann Horn 	int bucket;
1015294bac9SThomas Cedeno 	struct setid_rule *rule, *nrule;
1024f72123dSJann Horn 	int res = 0;
1034f72123dSJann Horn 
1044f72123dSJann Horn 	hash_for_each(pol->rules, bucket, rule, next) {
1055294bac9SThomas Cedeno 		if (_setid_policy_lookup(pol, rule->dst_id, INVALID_ID) == SIDPOL_DEFAULT) {
1065294bac9SThomas Cedeno 			if (pol->type == UID) {
1074f72123dSJann Horn 				pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n",
1085294bac9SThomas Cedeno 					__kuid_val(rule->src_id.uid),
1095294bac9SThomas Cedeno 					__kuid_val(rule->dst_id.uid));
1105294bac9SThomas Cedeno 			} else if (pol->type == GID) {
1115294bac9SThomas Cedeno 				pr_warn("insecure policy detected: gid %d is constrained but transitively unconstrained through gid %d\n",
1125294bac9SThomas Cedeno 					__kgid_val(rule->src_id.gid),
1135294bac9SThomas Cedeno 					__kgid_val(rule->dst_id.gid));
1145294bac9SThomas Cedeno 			} else { /* pol->type is an invalid type */
1155294bac9SThomas Cedeno 				res = -EINVAL;
1165294bac9SThomas Cedeno 				return res;
1175294bac9SThomas Cedeno 			}
1184f72123dSJann Horn 			res = -EINVAL;
1194f72123dSJann Horn 
1204f72123dSJann Horn 			/* fix it up */
1215294bac9SThomas Cedeno 			nrule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
1224f72123dSJann Horn 			if (!nrule)
1234f72123dSJann Horn 				return -ENOMEM;
1245294bac9SThomas Cedeno 			if (pol->type == UID){
1255294bac9SThomas Cedeno 				nrule->src_id.uid = rule->dst_id.uid;
1265294bac9SThomas Cedeno 				nrule->dst_id.uid = rule->dst_id.uid;
1275294bac9SThomas Cedeno 				nrule->type = UID;
1285294bac9SThomas Cedeno 			} else { /* pol->type must be GID if we've made it to here */
1295294bac9SThomas Cedeno 				nrule->src_id.gid = rule->dst_id.gid;
1305294bac9SThomas Cedeno 				nrule->dst_id.gid = rule->dst_id.gid;
1315294bac9SThomas Cedeno 				nrule->type = GID;
1325294bac9SThomas Cedeno 			}
1334f72123dSJann Horn 			insert_rule(pol, nrule);
1344f72123dSJann Horn 		}
1354f72123dSJann Horn 	}
1364f72123dSJann Horn 	return res;
1374f72123dSJann Horn }
1384f72123dSJann Horn 
handle_policy_update(struct file * file,const char __user * ubuf,size_t len,enum setid_type policy_type)13903638e62SJann Horn static ssize_t handle_policy_update(struct file *file,
1405294bac9SThomas Cedeno 				    const char __user *ubuf, size_t len, enum setid_type policy_type)
14103638e62SJann Horn {
1425294bac9SThomas Cedeno 	struct setid_ruleset *pol;
14303638e62SJann Horn 	char *buf, *p, *end;
14403638e62SJann Horn 	int err;
14503638e62SJann Horn 
1465294bac9SThomas Cedeno 	pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL);
14703638e62SJann Horn 	if (!pol)
14803638e62SJann Horn 		return -ENOMEM;
149fbd9acb2SJann Horn 	pol->policy_str = NULL;
1505294bac9SThomas Cedeno 	pol->type = policy_type;
15103638e62SJann Horn 	hash_init(pol->rules);
15203638e62SJann Horn 
15303638e62SJann Horn 	p = buf = memdup_user_nul(ubuf, len);
15403638e62SJann Horn 	if (IS_ERR(buf)) {
15503638e62SJann Horn 		err = PTR_ERR(buf);
15603638e62SJann Horn 		goto out_free_pol;
15703638e62SJann Horn 	}
158fbd9acb2SJann Horn 	pol->policy_str = kstrdup(buf, GFP_KERNEL);
159fbd9acb2SJann Horn 	if (pol->policy_str == NULL) {
160fbd9acb2SJann Horn 		err = -ENOMEM;
161fbd9acb2SJann Horn 		goto out_free_buf;
162fbd9acb2SJann Horn 	}
16303638e62SJann Horn 
16403638e62SJann Horn 	/* policy lines, including the last one, end with \n */
16503638e62SJann Horn 	while (*p != '\0') {
1665294bac9SThomas Cedeno 		struct setid_rule *rule;
16703638e62SJann Horn 
16803638e62SJann Horn 		end = strchr(p, '\n');
16903638e62SJann Horn 		if (end == NULL) {
17003638e62SJann Horn 			err = -EINVAL;
17103638e62SJann Horn 			goto out_free_buf;
17203638e62SJann Horn 		}
17303638e62SJann Horn 		*end = '\0';
17403638e62SJann Horn 
1755294bac9SThomas Cedeno 		rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
17603638e62SJann Horn 		if (!rule) {
17703638e62SJann Horn 			err = -ENOMEM;
17803638e62SJann Horn 			goto out_free_buf;
17903638e62SJann Horn 		}
18003638e62SJann Horn 
1815294bac9SThomas Cedeno 		rule->type = policy_type;
18203638e62SJann Horn 		err = parse_policy_line(file, p, rule);
18303638e62SJann Horn 		if (err)
18403638e62SJann Horn 			goto out_free_rule;
18503638e62SJann Horn 
1865294bac9SThomas Cedeno 		if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == SIDPOL_ALLOWED) {
18703638e62SJann Horn 			pr_warn("bad policy: duplicate entry\n");
18803638e62SJann Horn 			err = -EEXIST;
18903638e62SJann Horn 			goto out_free_rule;
19003638e62SJann Horn 		}
19103638e62SJann Horn 
1924f72123dSJann Horn 		insert_rule(pol, rule);
19303638e62SJann Horn 		p = end + 1;
19403638e62SJann Horn 		continue;
19503638e62SJann Horn 
19603638e62SJann Horn out_free_rule:
19703638e62SJann Horn 		kfree(rule);
19803638e62SJann Horn 		goto out_free_buf;
19903638e62SJann Horn 	}
20003638e62SJann Horn 
2014f72123dSJann Horn 	err = verify_ruleset(pol);
2024f72123dSJann Horn 	/* bogus policy falls through after fixing it up */
2034f72123dSJann Horn 	if (err && err != -EINVAL)
2044f72123dSJann Horn 		goto out_free_buf;
2054f72123dSJann Horn 
20603638e62SJann Horn 	/*
20703638e62SJann Horn 	 * Everything looks good, apply the policy and release the old one.
20803638e62SJann Horn 	 * What we really want here is an xchg() wrapper for RCU, but since that
20903638e62SJann Horn 	 * doesn't currently exist, just use a spinlock for now.
21003638e62SJann Horn 	 */
2115294bac9SThomas Cedeno 	if (policy_type == UID) {
2125294bac9SThomas Cedeno 		mutex_lock(&uid_policy_update_lock);
213a60a5746SPaul E. McKenney 		pol = rcu_replace_pointer(safesetid_setuid_rules, pol,
2145294bac9SThomas Cedeno 					  lockdep_is_held(&uid_policy_update_lock));
2155294bac9SThomas Cedeno 		mutex_unlock(&uid_policy_update_lock);
2165294bac9SThomas Cedeno 	} else if (policy_type == GID) {
2175294bac9SThomas Cedeno 		mutex_lock(&gid_policy_update_lock);
2185294bac9SThomas Cedeno 		pol = rcu_replace_pointer(safesetid_setgid_rules, pol,
2195294bac9SThomas Cedeno 					  lockdep_is_held(&gid_policy_update_lock));
2205294bac9SThomas Cedeno 		mutex_unlock(&gid_policy_update_lock);
2215294bac9SThomas Cedeno 	} else {
2225294bac9SThomas Cedeno 		/* Error, policy type is neither UID or GID */
2235294bac9SThomas Cedeno 		pr_warn("error: bad policy type");
2245294bac9SThomas Cedeno 	}
22503638e62SJann Horn 	err = len;
22603638e62SJann Horn 
22703638e62SJann Horn out_free_buf:
22803638e62SJann Horn 	kfree(buf);
22903638e62SJann Horn out_free_pol:
23021ab8580SMicah Morton 	if (pol)
23103638e62SJann Horn 		release_ruleset(pol);
23203638e62SJann Horn 	return err;
233aeca4e2cSMicah Morton }
234aeca4e2cSMicah Morton 
safesetid_uid_file_write(struct file * file,const char __user * buf,size_t len,loff_t * ppos)2355294bac9SThomas Cedeno static ssize_t safesetid_uid_file_write(struct file *file,
236aeca4e2cSMicah Morton 				    const char __user *buf,
237aeca4e2cSMicah Morton 				    size_t len,
238aeca4e2cSMicah Morton 				    loff_t *ppos)
239aeca4e2cSMicah Morton {
24071a98971SJann Horn 	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
241aeca4e2cSMicah Morton 		return -EPERM;
242aeca4e2cSMicah Morton 
243aeca4e2cSMicah Morton 	if (*ppos != 0)
244aeca4e2cSMicah Morton 		return -EINVAL;
245aeca4e2cSMicah Morton 
2465294bac9SThomas Cedeno 	return handle_policy_update(file, buf, len, UID);
2475294bac9SThomas Cedeno }
2485294bac9SThomas Cedeno 
safesetid_gid_file_write(struct file * file,const char __user * buf,size_t len,loff_t * ppos)2495294bac9SThomas Cedeno static ssize_t safesetid_gid_file_write(struct file *file,
2505294bac9SThomas Cedeno 				    const char __user *buf,
2515294bac9SThomas Cedeno 				    size_t len,
2525294bac9SThomas Cedeno 				    loff_t *ppos)
2535294bac9SThomas Cedeno {
2545294bac9SThomas Cedeno 	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
2555294bac9SThomas Cedeno 		return -EPERM;
2565294bac9SThomas Cedeno 
2575294bac9SThomas Cedeno 	if (*ppos != 0)
2585294bac9SThomas Cedeno 		return -EINVAL;
2595294bac9SThomas Cedeno 
2605294bac9SThomas Cedeno 	return handle_policy_update(file, buf, len, GID);
261aeca4e2cSMicah Morton }
262aeca4e2cSMicah Morton 
safesetid_file_read(struct file * file,char __user * buf,size_t len,loff_t * ppos,struct mutex * policy_update_lock,struct __rcu setid_ruleset * ruleset)263fbd9acb2SJann Horn static ssize_t safesetid_file_read(struct file *file, char __user *buf,
26403ca0ec1SThomas Cedeno 				   size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset)
265fbd9acb2SJann Horn {
266fbd9acb2SJann Horn 	ssize_t res = 0;
2675294bac9SThomas Cedeno 	struct setid_ruleset *pol;
268fbd9acb2SJann Horn 	const char *kbuf;
269fbd9acb2SJann Horn 
2705294bac9SThomas Cedeno 	mutex_lock(policy_update_lock);
2715294bac9SThomas Cedeno 	pol = rcu_dereference_protected(ruleset, lockdep_is_held(policy_update_lock));
272fbd9acb2SJann Horn 	if (pol) {
273fbd9acb2SJann Horn 		kbuf = pol->policy_str;
274fbd9acb2SJann Horn 		res = simple_read_from_buffer(buf, len, ppos,
275fbd9acb2SJann Horn 					      kbuf, strlen(kbuf));
276fbd9acb2SJann Horn 	}
2775294bac9SThomas Cedeno 	mutex_unlock(policy_update_lock);
2785294bac9SThomas Cedeno 
279fbd9acb2SJann Horn 	return res;
280fbd9acb2SJann Horn }
281fbd9acb2SJann Horn 
safesetid_uid_file_read(struct file * file,char __user * buf,size_t len,loff_t * ppos)2825294bac9SThomas Cedeno static ssize_t safesetid_uid_file_read(struct file *file, char __user *buf,
2835294bac9SThomas Cedeno 				   size_t len, loff_t *ppos)
2845294bac9SThomas Cedeno {
2855294bac9SThomas Cedeno 	return safesetid_file_read(file, buf, len, ppos,
2865294bac9SThomas Cedeno 				   &uid_policy_update_lock, safesetid_setuid_rules);
2875294bac9SThomas Cedeno }
2885294bac9SThomas Cedeno 
safesetid_gid_file_read(struct file * file,char __user * buf,size_t len,loff_t * ppos)2895294bac9SThomas Cedeno static ssize_t safesetid_gid_file_read(struct file *file, char __user *buf,
2905294bac9SThomas Cedeno 				   size_t len, loff_t *ppos)
2915294bac9SThomas Cedeno {
2925294bac9SThomas Cedeno 	return safesetid_file_read(file, buf, len, ppos,
2935294bac9SThomas Cedeno 				   &gid_policy_update_lock, safesetid_setgid_rules);
2945294bac9SThomas Cedeno }
2955294bac9SThomas Cedeno 
2965294bac9SThomas Cedeno 
2975294bac9SThomas Cedeno 
2985294bac9SThomas Cedeno static const struct file_operations safesetid_uid_file_fops = {
2995294bac9SThomas Cedeno 	.read = safesetid_uid_file_read,
3005294bac9SThomas Cedeno 	.write = safesetid_uid_file_write,
3015294bac9SThomas Cedeno };
3025294bac9SThomas Cedeno 
3035294bac9SThomas Cedeno static const struct file_operations safesetid_gid_file_fops = {
3045294bac9SThomas Cedeno 	.read = safesetid_gid_file_read,
3055294bac9SThomas Cedeno 	.write = safesetid_gid_file_write,
306aeca4e2cSMicah Morton };
307aeca4e2cSMicah Morton 
safesetid_init_securityfs(void)308aeca4e2cSMicah Morton static int __init safesetid_init_securityfs(void)
309aeca4e2cSMicah Morton {
310aeca4e2cSMicah Morton 	int ret;
31103638e62SJann Horn 	struct dentry *policy_dir;
3125294bac9SThomas Cedeno 	struct dentry *uid_policy_file;
3135294bac9SThomas Cedeno 	struct dentry *gid_policy_file;
314aeca4e2cSMicah Morton 
315aeca4e2cSMicah Morton 	if (!safesetid_initialized)
316aeca4e2cSMicah Morton 		return 0;
317aeca4e2cSMicah Morton 
31803638e62SJann Horn 	policy_dir = securityfs_create_dir("safesetid", NULL);
31903638e62SJann Horn 	if (IS_ERR(policy_dir)) {
32003638e62SJann Horn 		ret = PTR_ERR(policy_dir);
321aeca4e2cSMicah Morton 		goto error;
322aeca4e2cSMicah Morton 	}
323aeca4e2cSMicah Morton 
3245294bac9SThomas Cedeno 	uid_policy_file = securityfs_create_file("uid_allowlist_policy", 0600,
3255294bac9SThomas Cedeno 			policy_dir, NULL, &safesetid_uid_file_fops);
3265294bac9SThomas Cedeno 	if (IS_ERR(uid_policy_file)) {
3275294bac9SThomas Cedeno 		ret = PTR_ERR(uid_policy_file);
328aeca4e2cSMicah Morton 		goto error;
329aeca4e2cSMicah Morton 	}
330aeca4e2cSMicah Morton 
3315294bac9SThomas Cedeno 	gid_policy_file = securityfs_create_file("gid_allowlist_policy", 0600,
3325294bac9SThomas Cedeno 			policy_dir, NULL, &safesetid_gid_file_fops);
3335294bac9SThomas Cedeno 	if (IS_ERR(gid_policy_file)) {
3345294bac9SThomas Cedeno 		ret = PTR_ERR(gid_policy_file);
3355294bac9SThomas Cedeno 		goto error;
3365294bac9SThomas Cedeno 	}
3375294bac9SThomas Cedeno 
3385294bac9SThomas Cedeno 
339aeca4e2cSMicah Morton 	return 0;
340aeca4e2cSMicah Morton 
341aeca4e2cSMicah Morton error:
34203638e62SJann Horn 	securityfs_remove(policy_dir);
343aeca4e2cSMicah Morton 	return ret;
344aeca4e2cSMicah Morton }
345aeca4e2cSMicah Morton fs_initcall(safesetid_init_securityfs);
346