xref: /openbmc/linux/security/safesetid/securityfs.c (revision 360823a09426347ea8f232b0b0b5156d0aed0302)
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 
146*96fae5bdSLeo Stone 	if (len >= KMALLOC_MAX_SIZE)
147*96fae5bdSLeo Stone 		return -EINVAL;
148*96fae5bdSLeo Stone 
1495294bac9SThomas Cedeno 	pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL);
15003638e62SJann Horn 	if (!pol)
15103638e62SJann Horn 		return -ENOMEM;
152fbd9acb2SJann Horn 	pol->policy_str = NULL;
1535294bac9SThomas Cedeno 	pol->type = policy_type;
15403638e62SJann Horn 	hash_init(pol->rules);
15503638e62SJann Horn 
15603638e62SJann Horn 	p = buf = memdup_user_nul(ubuf, len);
15703638e62SJann Horn 	if (IS_ERR(buf)) {
15803638e62SJann Horn 		err = PTR_ERR(buf);
15903638e62SJann Horn 		goto out_free_pol;
16003638e62SJann Horn 	}
161fbd9acb2SJann Horn 	pol->policy_str = kstrdup(buf, GFP_KERNEL);
162fbd9acb2SJann Horn 	if (pol->policy_str == NULL) {
163fbd9acb2SJann Horn 		err = -ENOMEM;
164fbd9acb2SJann Horn 		goto out_free_buf;
165fbd9acb2SJann Horn 	}
16603638e62SJann Horn 
16703638e62SJann Horn 	/* policy lines, including the last one, end with \n */
16803638e62SJann Horn 	while (*p != '\0') {
1695294bac9SThomas Cedeno 		struct setid_rule *rule;
17003638e62SJann Horn 
17103638e62SJann Horn 		end = strchr(p, '\n');
17203638e62SJann Horn 		if (end == NULL) {
17303638e62SJann Horn 			err = -EINVAL;
17403638e62SJann Horn 			goto out_free_buf;
17503638e62SJann Horn 		}
17603638e62SJann Horn 		*end = '\0';
17703638e62SJann Horn 
1785294bac9SThomas Cedeno 		rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
17903638e62SJann Horn 		if (!rule) {
18003638e62SJann Horn 			err = -ENOMEM;
18103638e62SJann Horn 			goto out_free_buf;
18203638e62SJann Horn 		}
18303638e62SJann Horn 
1845294bac9SThomas Cedeno 		rule->type = policy_type;
18503638e62SJann Horn 		err = parse_policy_line(file, p, rule);
18603638e62SJann Horn 		if (err)
18703638e62SJann Horn 			goto out_free_rule;
18803638e62SJann Horn 
1895294bac9SThomas Cedeno 		if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == SIDPOL_ALLOWED) {
19003638e62SJann Horn 			pr_warn("bad policy: duplicate entry\n");
19103638e62SJann Horn 			err = -EEXIST;
19203638e62SJann Horn 			goto out_free_rule;
19303638e62SJann Horn 		}
19403638e62SJann Horn 
1954f72123dSJann Horn 		insert_rule(pol, rule);
19603638e62SJann Horn 		p = end + 1;
19703638e62SJann Horn 		continue;
19803638e62SJann Horn 
19903638e62SJann Horn out_free_rule:
20003638e62SJann Horn 		kfree(rule);
20103638e62SJann Horn 		goto out_free_buf;
20203638e62SJann Horn 	}
20303638e62SJann Horn 
2044f72123dSJann Horn 	err = verify_ruleset(pol);
2054f72123dSJann Horn 	/* bogus policy falls through after fixing it up */
2064f72123dSJann Horn 	if (err && err != -EINVAL)
2074f72123dSJann Horn 		goto out_free_buf;
2084f72123dSJann Horn 
20903638e62SJann Horn 	/*
21003638e62SJann Horn 	 * Everything looks good, apply the policy and release the old one.
21103638e62SJann Horn 	 * What we really want here is an xchg() wrapper for RCU, but since that
21203638e62SJann Horn 	 * doesn't currently exist, just use a spinlock for now.
21303638e62SJann Horn 	 */
2145294bac9SThomas Cedeno 	if (policy_type == UID) {
2155294bac9SThomas Cedeno 		mutex_lock(&uid_policy_update_lock);
216a60a5746SPaul E. McKenney 		pol = rcu_replace_pointer(safesetid_setuid_rules, pol,
2175294bac9SThomas Cedeno 					  lockdep_is_held(&uid_policy_update_lock));
2185294bac9SThomas Cedeno 		mutex_unlock(&uid_policy_update_lock);
2195294bac9SThomas Cedeno 	} else if (policy_type == GID) {
2205294bac9SThomas Cedeno 		mutex_lock(&gid_policy_update_lock);
2215294bac9SThomas Cedeno 		pol = rcu_replace_pointer(safesetid_setgid_rules, pol,
2225294bac9SThomas Cedeno 					  lockdep_is_held(&gid_policy_update_lock));
2235294bac9SThomas Cedeno 		mutex_unlock(&gid_policy_update_lock);
2245294bac9SThomas Cedeno 	} else {
2255294bac9SThomas Cedeno 		/* Error, policy type is neither UID or GID */
2265294bac9SThomas Cedeno 		pr_warn("error: bad policy type");
2275294bac9SThomas Cedeno 	}
22803638e62SJann Horn 	err = len;
22903638e62SJann Horn 
23003638e62SJann Horn out_free_buf:
23103638e62SJann Horn 	kfree(buf);
23203638e62SJann Horn out_free_pol:
23321ab8580SMicah Morton 	if (pol)
23403638e62SJann Horn 		release_ruleset(pol);
23503638e62SJann Horn 	return err;
236aeca4e2cSMicah Morton }
237aeca4e2cSMicah Morton 
safesetid_uid_file_write(struct file * file,const char __user * buf,size_t len,loff_t * ppos)2385294bac9SThomas Cedeno static ssize_t safesetid_uid_file_write(struct file *file,
239aeca4e2cSMicah Morton 				    const char __user *buf,
240aeca4e2cSMicah Morton 				    size_t len,
241aeca4e2cSMicah Morton 				    loff_t *ppos)
242aeca4e2cSMicah Morton {
24371a98971SJann Horn 	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
244aeca4e2cSMicah Morton 		return -EPERM;
245aeca4e2cSMicah Morton 
246aeca4e2cSMicah Morton 	if (*ppos != 0)
247aeca4e2cSMicah Morton 		return -EINVAL;
248aeca4e2cSMicah Morton 
2495294bac9SThomas Cedeno 	return handle_policy_update(file, buf, len, UID);
2505294bac9SThomas Cedeno }
2515294bac9SThomas Cedeno 
safesetid_gid_file_write(struct file * file,const char __user * buf,size_t len,loff_t * ppos)2525294bac9SThomas Cedeno static ssize_t safesetid_gid_file_write(struct file *file,
2535294bac9SThomas Cedeno 				    const char __user *buf,
2545294bac9SThomas Cedeno 				    size_t len,
2555294bac9SThomas Cedeno 				    loff_t *ppos)
2565294bac9SThomas Cedeno {
2575294bac9SThomas Cedeno 	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
2585294bac9SThomas Cedeno 		return -EPERM;
2595294bac9SThomas Cedeno 
2605294bac9SThomas Cedeno 	if (*ppos != 0)
2615294bac9SThomas Cedeno 		return -EINVAL;
2625294bac9SThomas Cedeno 
2635294bac9SThomas Cedeno 	return handle_policy_update(file, buf, len, GID);
264aeca4e2cSMicah Morton }
265aeca4e2cSMicah 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)266fbd9acb2SJann Horn static ssize_t safesetid_file_read(struct file *file, char __user *buf,
26703ca0ec1SThomas Cedeno 				   size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset)
268fbd9acb2SJann Horn {
269fbd9acb2SJann Horn 	ssize_t res = 0;
2705294bac9SThomas Cedeno 	struct setid_ruleset *pol;
271fbd9acb2SJann Horn 	const char *kbuf;
272fbd9acb2SJann Horn 
2735294bac9SThomas Cedeno 	mutex_lock(policy_update_lock);
2745294bac9SThomas Cedeno 	pol = rcu_dereference_protected(ruleset, lockdep_is_held(policy_update_lock));
275fbd9acb2SJann Horn 	if (pol) {
276fbd9acb2SJann Horn 		kbuf = pol->policy_str;
277fbd9acb2SJann Horn 		res = simple_read_from_buffer(buf, len, ppos,
278fbd9acb2SJann Horn 					      kbuf, strlen(kbuf));
279fbd9acb2SJann Horn 	}
2805294bac9SThomas Cedeno 	mutex_unlock(policy_update_lock);
2815294bac9SThomas Cedeno 
282fbd9acb2SJann Horn 	return res;
283fbd9acb2SJann Horn }
284fbd9acb2SJann Horn 
safesetid_uid_file_read(struct file * file,char __user * buf,size_t len,loff_t * ppos)2855294bac9SThomas Cedeno static ssize_t safesetid_uid_file_read(struct file *file, char __user *buf,
2865294bac9SThomas Cedeno 				   size_t len, loff_t *ppos)
2875294bac9SThomas Cedeno {
2885294bac9SThomas Cedeno 	return safesetid_file_read(file, buf, len, ppos,
2895294bac9SThomas Cedeno 				   &uid_policy_update_lock, safesetid_setuid_rules);
2905294bac9SThomas Cedeno }
2915294bac9SThomas Cedeno 
safesetid_gid_file_read(struct file * file,char __user * buf,size_t len,loff_t * ppos)2925294bac9SThomas Cedeno static ssize_t safesetid_gid_file_read(struct file *file, char __user *buf,
2935294bac9SThomas Cedeno 				   size_t len, loff_t *ppos)
2945294bac9SThomas Cedeno {
2955294bac9SThomas Cedeno 	return safesetid_file_read(file, buf, len, ppos,
2965294bac9SThomas Cedeno 				   &gid_policy_update_lock, safesetid_setgid_rules);
2975294bac9SThomas Cedeno }
2985294bac9SThomas Cedeno 
2995294bac9SThomas Cedeno 
3005294bac9SThomas Cedeno 
3015294bac9SThomas Cedeno static const struct file_operations safesetid_uid_file_fops = {
3025294bac9SThomas Cedeno 	.read = safesetid_uid_file_read,
3035294bac9SThomas Cedeno 	.write = safesetid_uid_file_write,
3045294bac9SThomas Cedeno };
3055294bac9SThomas Cedeno 
3065294bac9SThomas Cedeno static const struct file_operations safesetid_gid_file_fops = {
3075294bac9SThomas Cedeno 	.read = safesetid_gid_file_read,
3085294bac9SThomas Cedeno 	.write = safesetid_gid_file_write,
309aeca4e2cSMicah Morton };
310aeca4e2cSMicah Morton 
safesetid_init_securityfs(void)311aeca4e2cSMicah Morton static int __init safesetid_init_securityfs(void)
312aeca4e2cSMicah Morton {
313aeca4e2cSMicah Morton 	int ret;
31403638e62SJann Horn 	struct dentry *policy_dir;
3155294bac9SThomas Cedeno 	struct dentry *uid_policy_file;
3165294bac9SThomas Cedeno 	struct dentry *gid_policy_file;
317aeca4e2cSMicah Morton 
318aeca4e2cSMicah Morton 	if (!safesetid_initialized)
319aeca4e2cSMicah Morton 		return 0;
320aeca4e2cSMicah Morton 
32103638e62SJann Horn 	policy_dir = securityfs_create_dir("safesetid", NULL);
32203638e62SJann Horn 	if (IS_ERR(policy_dir)) {
32303638e62SJann Horn 		ret = PTR_ERR(policy_dir);
324aeca4e2cSMicah Morton 		goto error;
325aeca4e2cSMicah Morton 	}
326aeca4e2cSMicah Morton 
3275294bac9SThomas Cedeno 	uid_policy_file = securityfs_create_file("uid_allowlist_policy", 0600,
3285294bac9SThomas Cedeno 			policy_dir, NULL, &safesetid_uid_file_fops);
3295294bac9SThomas Cedeno 	if (IS_ERR(uid_policy_file)) {
3305294bac9SThomas Cedeno 		ret = PTR_ERR(uid_policy_file);
331aeca4e2cSMicah Morton 		goto error;
332aeca4e2cSMicah Morton 	}
333aeca4e2cSMicah Morton 
3345294bac9SThomas Cedeno 	gid_policy_file = securityfs_create_file("gid_allowlist_policy", 0600,
3355294bac9SThomas Cedeno 			policy_dir, NULL, &safesetid_gid_file_fops);
3365294bac9SThomas Cedeno 	if (IS_ERR(gid_policy_file)) {
3375294bac9SThomas Cedeno 		ret = PTR_ERR(gid_policy_file);
3385294bac9SThomas Cedeno 		goto error;
3395294bac9SThomas Cedeno 	}
3405294bac9SThomas Cedeno 
3415294bac9SThomas Cedeno 
342aeca4e2cSMicah Morton 	return 0;
343aeca4e2cSMicah Morton 
344aeca4e2cSMicah Morton error:
34503638e62SJann Horn 	securityfs_remove(policy_dir);
346aeca4e2cSMicah Morton 	return ret;
347aeca4e2cSMicah Morton }
348aeca4e2cSMicah Morton fs_initcall(safesetid_init_securityfs);
349