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