162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * SafeSetID Linux Security Module
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Author: Micah Morton <mortonm@chromium.org>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (C) 2018 The Chromium OS Authors.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or modify
1062306a36Sopenharmony_ci * it under the terms of the GNU General Public License version 2, as
1162306a36Sopenharmony_ci * published by the Free Software Foundation.
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define pr_fmt(fmt) "SafeSetID: " fmt
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/security.h>
1862306a36Sopenharmony_ci#include <linux/cred.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "lsm.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic DEFINE_MUTEX(uid_policy_update_lock);
2362306a36Sopenharmony_cistatic DEFINE_MUTEX(gid_policy_update_lock);
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci/*
2662306a36Sopenharmony_ci * In the case the input buffer contains one or more invalid IDs, the kid_t
2762306a36Sopenharmony_ci * variables pointed to by @parent and @child will get updated but this
2862306a36Sopenharmony_ci * function will return an error.
2962306a36Sopenharmony_ci * Contents of @buf may be modified.
3062306a36Sopenharmony_ci */
3162306a36Sopenharmony_cistatic int parse_policy_line(struct file *file, char *buf,
3262306a36Sopenharmony_ci	struct setid_rule *rule)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	char *child_str;
3562306a36Sopenharmony_ci	int ret;
3662306a36Sopenharmony_ci	u32 parsed_parent, parsed_child;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	/* Format of |buf| string should be <UID>:<UID> or <GID>:<GID> */
3962306a36Sopenharmony_ci	child_str = strchr(buf, ':');
4062306a36Sopenharmony_ci	if (child_str == NULL)
4162306a36Sopenharmony_ci		return -EINVAL;
4262306a36Sopenharmony_ci	*child_str = '\0';
4362306a36Sopenharmony_ci	child_str++;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	ret = kstrtou32(buf, 0, &parsed_parent);
4662306a36Sopenharmony_ci	if (ret)
4762306a36Sopenharmony_ci		return ret;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	ret = kstrtou32(child_str, 0, &parsed_child);
5062306a36Sopenharmony_ci	if (ret)
5162306a36Sopenharmony_ci		return ret;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (rule->type == UID){
5462306a36Sopenharmony_ci		rule->src_id.uid = make_kuid(file->f_cred->user_ns, parsed_parent);
5562306a36Sopenharmony_ci		rule->dst_id.uid = make_kuid(file->f_cred->user_ns, parsed_child);
5662306a36Sopenharmony_ci		if (!uid_valid(rule->src_id.uid) || !uid_valid(rule->dst_id.uid))
5762306a36Sopenharmony_ci			return -EINVAL;
5862306a36Sopenharmony_ci	} else if (rule->type == GID){
5962306a36Sopenharmony_ci		rule->src_id.gid = make_kgid(file->f_cred->user_ns, parsed_parent);
6062306a36Sopenharmony_ci		rule->dst_id.gid = make_kgid(file->f_cred->user_ns, parsed_child);
6162306a36Sopenharmony_ci		if (!gid_valid(rule->src_id.gid) || !gid_valid(rule->dst_id.gid))
6262306a36Sopenharmony_ci			return -EINVAL;
6362306a36Sopenharmony_ci	} else {
6462306a36Sopenharmony_ci		/* Error, rule->type is an invalid type */
6562306a36Sopenharmony_ci		return -EINVAL;
6662306a36Sopenharmony_ci	}
6762306a36Sopenharmony_ci	return 0;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic void __release_ruleset(struct rcu_head *rcu)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct setid_ruleset *pol =
7362306a36Sopenharmony_ci		container_of(rcu, struct setid_ruleset, rcu);
7462306a36Sopenharmony_ci	int bucket;
7562306a36Sopenharmony_ci	struct setid_rule *rule;
7662306a36Sopenharmony_ci	struct hlist_node *tmp;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	hash_for_each_safe(pol->rules, bucket, tmp, rule, next)
7962306a36Sopenharmony_ci		kfree(rule);
8062306a36Sopenharmony_ci	kfree(pol->policy_str);
8162306a36Sopenharmony_ci	kfree(pol);
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic void release_ruleset(struct setid_ruleset *pol){
8562306a36Sopenharmony_ci	call_rcu(&pol->rcu, __release_ruleset);
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic void insert_rule(struct setid_ruleset *pol, struct setid_rule *rule)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	if (pol->type == UID)
9162306a36Sopenharmony_ci		hash_add(pol->rules, &rule->next, __kuid_val(rule->src_id.uid));
9262306a36Sopenharmony_ci	else if (pol->type == GID)
9362306a36Sopenharmony_ci		hash_add(pol->rules, &rule->next, __kgid_val(rule->src_id.gid));
9462306a36Sopenharmony_ci	else /* Error, pol->type is neither UID or GID */
9562306a36Sopenharmony_ci		return;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic int verify_ruleset(struct setid_ruleset *pol)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	int bucket;
10162306a36Sopenharmony_ci	struct setid_rule *rule, *nrule;
10262306a36Sopenharmony_ci	int res = 0;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	hash_for_each(pol->rules, bucket, rule, next) {
10562306a36Sopenharmony_ci		if (_setid_policy_lookup(pol, rule->dst_id, INVALID_ID) == SIDPOL_DEFAULT) {
10662306a36Sopenharmony_ci			if (pol->type == UID) {
10762306a36Sopenharmony_ci				pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n",
10862306a36Sopenharmony_ci					__kuid_val(rule->src_id.uid),
10962306a36Sopenharmony_ci					__kuid_val(rule->dst_id.uid));
11062306a36Sopenharmony_ci			} else if (pol->type == GID) {
11162306a36Sopenharmony_ci				pr_warn("insecure policy detected: gid %d is constrained but transitively unconstrained through gid %d\n",
11262306a36Sopenharmony_ci					__kgid_val(rule->src_id.gid),
11362306a36Sopenharmony_ci					__kgid_val(rule->dst_id.gid));
11462306a36Sopenharmony_ci			} else { /* pol->type is an invalid type */
11562306a36Sopenharmony_ci				res = -EINVAL;
11662306a36Sopenharmony_ci				return res;
11762306a36Sopenharmony_ci			}
11862306a36Sopenharmony_ci			res = -EINVAL;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci			/* fix it up */
12162306a36Sopenharmony_ci			nrule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
12262306a36Sopenharmony_ci			if (!nrule)
12362306a36Sopenharmony_ci				return -ENOMEM;
12462306a36Sopenharmony_ci			if (pol->type == UID){
12562306a36Sopenharmony_ci				nrule->src_id.uid = rule->dst_id.uid;
12662306a36Sopenharmony_ci				nrule->dst_id.uid = rule->dst_id.uid;
12762306a36Sopenharmony_ci				nrule->type = UID;
12862306a36Sopenharmony_ci			} else { /* pol->type must be GID if we've made it to here */
12962306a36Sopenharmony_ci				nrule->src_id.gid = rule->dst_id.gid;
13062306a36Sopenharmony_ci				nrule->dst_id.gid = rule->dst_id.gid;
13162306a36Sopenharmony_ci				nrule->type = GID;
13262306a36Sopenharmony_ci			}
13362306a36Sopenharmony_ci			insert_rule(pol, nrule);
13462306a36Sopenharmony_ci		}
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci	return res;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic ssize_t handle_policy_update(struct file *file,
14062306a36Sopenharmony_ci				    const char __user *ubuf, size_t len, enum setid_type policy_type)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	struct setid_ruleset *pol;
14362306a36Sopenharmony_ci	char *buf, *p, *end;
14462306a36Sopenharmony_ci	int err;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL);
14762306a36Sopenharmony_ci	if (!pol)
14862306a36Sopenharmony_ci		return -ENOMEM;
14962306a36Sopenharmony_ci	pol->policy_str = NULL;
15062306a36Sopenharmony_ci	pol->type = policy_type;
15162306a36Sopenharmony_ci	hash_init(pol->rules);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	p = buf = memdup_user_nul(ubuf, len);
15462306a36Sopenharmony_ci	if (IS_ERR(buf)) {
15562306a36Sopenharmony_ci		err = PTR_ERR(buf);
15662306a36Sopenharmony_ci		goto out_free_pol;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci	pol->policy_str = kstrdup(buf, GFP_KERNEL);
15962306a36Sopenharmony_ci	if (pol->policy_str == NULL) {
16062306a36Sopenharmony_ci		err = -ENOMEM;
16162306a36Sopenharmony_ci		goto out_free_buf;
16262306a36Sopenharmony_ci	}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/* policy lines, including the last one, end with \n */
16562306a36Sopenharmony_ci	while (*p != '\0') {
16662306a36Sopenharmony_ci		struct setid_rule *rule;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci		end = strchr(p, '\n');
16962306a36Sopenharmony_ci		if (end == NULL) {
17062306a36Sopenharmony_ci			err = -EINVAL;
17162306a36Sopenharmony_ci			goto out_free_buf;
17262306a36Sopenharmony_ci		}
17362306a36Sopenharmony_ci		*end = '\0';
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci		rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
17662306a36Sopenharmony_ci		if (!rule) {
17762306a36Sopenharmony_ci			err = -ENOMEM;
17862306a36Sopenharmony_ci			goto out_free_buf;
17962306a36Sopenharmony_ci		}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci		rule->type = policy_type;
18262306a36Sopenharmony_ci		err = parse_policy_line(file, p, rule);
18362306a36Sopenharmony_ci		if (err)
18462306a36Sopenharmony_ci			goto out_free_rule;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci		if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == SIDPOL_ALLOWED) {
18762306a36Sopenharmony_ci			pr_warn("bad policy: duplicate entry\n");
18862306a36Sopenharmony_ci			err = -EEXIST;
18962306a36Sopenharmony_ci			goto out_free_rule;
19062306a36Sopenharmony_ci		}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci		insert_rule(pol, rule);
19362306a36Sopenharmony_ci		p = end + 1;
19462306a36Sopenharmony_ci		continue;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ciout_free_rule:
19762306a36Sopenharmony_ci		kfree(rule);
19862306a36Sopenharmony_ci		goto out_free_buf;
19962306a36Sopenharmony_ci	}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	err = verify_ruleset(pol);
20262306a36Sopenharmony_ci	/* bogus policy falls through after fixing it up */
20362306a36Sopenharmony_ci	if (err && err != -EINVAL)
20462306a36Sopenharmony_ci		goto out_free_buf;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	/*
20762306a36Sopenharmony_ci	 * Everything looks good, apply the policy and release the old one.
20862306a36Sopenharmony_ci	 * What we really want here is an xchg() wrapper for RCU, but since that
20962306a36Sopenharmony_ci	 * doesn't currently exist, just use a spinlock for now.
21062306a36Sopenharmony_ci	 */
21162306a36Sopenharmony_ci	if (policy_type == UID) {
21262306a36Sopenharmony_ci		mutex_lock(&uid_policy_update_lock);
21362306a36Sopenharmony_ci		pol = rcu_replace_pointer(safesetid_setuid_rules, pol,
21462306a36Sopenharmony_ci					  lockdep_is_held(&uid_policy_update_lock));
21562306a36Sopenharmony_ci		mutex_unlock(&uid_policy_update_lock);
21662306a36Sopenharmony_ci	} else if (policy_type == GID) {
21762306a36Sopenharmony_ci		mutex_lock(&gid_policy_update_lock);
21862306a36Sopenharmony_ci		pol = rcu_replace_pointer(safesetid_setgid_rules, pol,
21962306a36Sopenharmony_ci					  lockdep_is_held(&gid_policy_update_lock));
22062306a36Sopenharmony_ci		mutex_unlock(&gid_policy_update_lock);
22162306a36Sopenharmony_ci	} else {
22262306a36Sopenharmony_ci		/* Error, policy type is neither UID or GID */
22362306a36Sopenharmony_ci		pr_warn("error: bad policy type");
22462306a36Sopenharmony_ci	}
22562306a36Sopenharmony_ci	err = len;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ciout_free_buf:
22862306a36Sopenharmony_ci	kfree(buf);
22962306a36Sopenharmony_ciout_free_pol:
23062306a36Sopenharmony_ci	if (pol)
23162306a36Sopenharmony_ci		release_ruleset(pol);
23262306a36Sopenharmony_ci	return err;
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic ssize_t safesetid_uid_file_write(struct file *file,
23662306a36Sopenharmony_ci				    const char __user *buf,
23762306a36Sopenharmony_ci				    size_t len,
23862306a36Sopenharmony_ci				    loff_t *ppos)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
24162306a36Sopenharmony_ci		return -EPERM;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	if (*ppos != 0)
24462306a36Sopenharmony_ci		return -EINVAL;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	return handle_policy_update(file, buf, len, UID);
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic ssize_t safesetid_gid_file_write(struct file *file,
25062306a36Sopenharmony_ci				    const char __user *buf,
25162306a36Sopenharmony_ci				    size_t len,
25262306a36Sopenharmony_ci				    loff_t *ppos)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
25562306a36Sopenharmony_ci		return -EPERM;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	if (*ppos != 0)
25862306a36Sopenharmony_ci		return -EINVAL;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	return handle_policy_update(file, buf, len, GID);
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_cistatic ssize_t safesetid_file_read(struct file *file, char __user *buf,
26462306a36Sopenharmony_ci				   size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	ssize_t res = 0;
26762306a36Sopenharmony_ci	struct setid_ruleset *pol;
26862306a36Sopenharmony_ci	const char *kbuf;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	mutex_lock(policy_update_lock);
27162306a36Sopenharmony_ci	pol = rcu_dereference_protected(ruleset, lockdep_is_held(policy_update_lock));
27262306a36Sopenharmony_ci	if (pol) {
27362306a36Sopenharmony_ci		kbuf = pol->policy_str;
27462306a36Sopenharmony_ci		res = simple_read_from_buffer(buf, len, ppos,
27562306a36Sopenharmony_ci					      kbuf, strlen(kbuf));
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci	mutex_unlock(policy_update_lock);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	return res;
28062306a36Sopenharmony_ci}
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_cistatic ssize_t safesetid_uid_file_read(struct file *file, char __user *buf,
28362306a36Sopenharmony_ci				   size_t len, loff_t *ppos)
28462306a36Sopenharmony_ci{
28562306a36Sopenharmony_ci	return safesetid_file_read(file, buf, len, ppos,
28662306a36Sopenharmony_ci				   &uid_policy_update_lock, safesetid_setuid_rules);
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cistatic ssize_t safesetid_gid_file_read(struct file *file, char __user *buf,
29062306a36Sopenharmony_ci				   size_t len, loff_t *ppos)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	return safesetid_file_read(file, buf, len, ppos,
29362306a36Sopenharmony_ci				   &gid_policy_update_lock, safesetid_setgid_rules);
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cistatic const struct file_operations safesetid_uid_file_fops = {
29962306a36Sopenharmony_ci	.read = safesetid_uid_file_read,
30062306a36Sopenharmony_ci	.write = safesetid_uid_file_write,
30162306a36Sopenharmony_ci};
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic const struct file_operations safesetid_gid_file_fops = {
30462306a36Sopenharmony_ci	.read = safesetid_gid_file_read,
30562306a36Sopenharmony_ci	.write = safesetid_gid_file_write,
30662306a36Sopenharmony_ci};
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic int __init safesetid_init_securityfs(void)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	int ret;
31162306a36Sopenharmony_ci	struct dentry *policy_dir;
31262306a36Sopenharmony_ci	struct dentry *uid_policy_file;
31362306a36Sopenharmony_ci	struct dentry *gid_policy_file;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	if (!safesetid_initialized)
31662306a36Sopenharmony_ci		return 0;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	policy_dir = securityfs_create_dir("safesetid", NULL);
31962306a36Sopenharmony_ci	if (IS_ERR(policy_dir)) {
32062306a36Sopenharmony_ci		ret = PTR_ERR(policy_dir);
32162306a36Sopenharmony_ci		goto error;
32262306a36Sopenharmony_ci	}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	uid_policy_file = securityfs_create_file("uid_allowlist_policy", 0600,
32562306a36Sopenharmony_ci			policy_dir, NULL, &safesetid_uid_file_fops);
32662306a36Sopenharmony_ci	if (IS_ERR(uid_policy_file)) {
32762306a36Sopenharmony_ci		ret = PTR_ERR(uid_policy_file);
32862306a36Sopenharmony_ci		goto error;
32962306a36Sopenharmony_ci	}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	gid_policy_file = securityfs_create_file("gid_allowlist_policy", 0600,
33262306a36Sopenharmony_ci			policy_dir, NULL, &safesetid_gid_file_fops);
33362306a36Sopenharmony_ci	if (IS_ERR(gid_policy_file)) {
33462306a36Sopenharmony_ci		ret = PTR_ERR(gid_policy_file);
33562306a36Sopenharmony_ci		goto error;
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	return 0;
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cierror:
34262306a36Sopenharmony_ci	securityfs_remove(policy_dir);
34362306a36Sopenharmony_ci	return ret;
34462306a36Sopenharmony_ci}
34562306a36Sopenharmony_cifs_initcall(safesetid_init_securityfs);
346