xref: /linux/security/safesetid/securityfs.c (revision b8d312aa075f33282565467662c4628dae0a2aff)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * SafeSetID Linux Security Module
4  *
5  * Author: Micah Morton <mortonm@chromium.org>
6  *
7  * Copyright (C) 2018 The Chromium OS Authors.
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License version 2, as
11  * published by the Free Software Foundation.
12  *
13  */
14 
15 #define pr_fmt(fmt) "SafeSetID: " fmt
16 
17 #include <linux/security.h>
18 #include <linux/cred.h>
19 
20 #include "lsm.h"
21 
22 static DEFINE_MUTEX(policy_update_lock);
23 
24 /*
25  * In the case the input buffer contains one or more invalid UIDs, the kuid_t
26  * variables pointed to by @parent and @child will get updated but this
27  * function will return an error.
28  * Contents of @buf may be modified.
29  */
30 static int parse_policy_line(struct file *file, char *buf,
31 	struct setuid_rule *rule)
32 {
33 	char *child_str;
34 	int ret;
35 	u32 parsed_parent, parsed_child;
36 
37 	/* Format of |buf| string should be <UID>:<UID>. */
38 	child_str = strchr(buf, ':');
39 	if (child_str == NULL)
40 		return -EINVAL;
41 	*child_str = '\0';
42 	child_str++;
43 
44 	ret = kstrtou32(buf, 0, &parsed_parent);
45 	if (ret)
46 		return ret;
47 
48 	ret = kstrtou32(child_str, 0, &parsed_child);
49 	if (ret)
50 		return ret;
51 
52 	rule->src_uid = make_kuid(file->f_cred->user_ns, parsed_parent);
53 	rule->dst_uid = make_kuid(file->f_cred->user_ns, parsed_child);
54 	if (!uid_valid(rule->src_uid) || !uid_valid(rule->dst_uid))
55 		return -EINVAL;
56 
57 	return 0;
58 }
59 
60 static void __release_ruleset(struct rcu_head *rcu)
61 {
62 	struct setuid_ruleset *pol =
63 		container_of(rcu, struct setuid_ruleset, rcu);
64 	int bucket;
65 	struct setuid_rule *rule;
66 	struct hlist_node *tmp;
67 
68 	hash_for_each_safe(pol->rules, bucket, tmp, rule, next)
69 		kfree(rule);
70 	kfree(pol->policy_str);
71 	kfree(pol);
72 }
73 
74 static void release_ruleset(struct setuid_ruleset *pol)
75 {
76 	call_rcu(&pol->rcu, __release_ruleset);
77 }
78 
79 static void insert_rule(struct setuid_ruleset *pol, struct setuid_rule *rule)
80 {
81 	hash_add(pol->rules, &rule->next, __kuid_val(rule->src_uid));
82 }
83 
84 static int verify_ruleset(struct setuid_ruleset *pol)
85 {
86 	int bucket;
87 	struct setuid_rule *rule, *nrule;
88 	int res = 0;
89 
90 	hash_for_each(pol->rules, bucket, rule, next) {
91 		if (_setuid_policy_lookup(pol, rule->dst_uid, INVALID_UID) ==
92 		    SIDPOL_DEFAULT) {
93 			pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n",
94 				__kuid_val(rule->src_uid),
95 				__kuid_val(rule->dst_uid));
96 			res = -EINVAL;
97 
98 			/* fix it up */
99 			nrule = kmalloc(sizeof(struct setuid_rule), GFP_KERNEL);
100 			if (!nrule)
101 				return -ENOMEM;
102 			nrule->src_uid = rule->dst_uid;
103 			nrule->dst_uid = rule->dst_uid;
104 			insert_rule(pol, nrule);
105 		}
106 	}
107 	return res;
108 }
109 
110 static ssize_t handle_policy_update(struct file *file,
111 				    const char __user *ubuf, size_t len)
112 {
113 	struct setuid_ruleset *pol;
114 	char *buf, *p, *end;
115 	int err;
116 
117 	pol = kmalloc(sizeof(struct setuid_ruleset), GFP_KERNEL);
118 	if (!pol)
119 		return -ENOMEM;
120 	pol->policy_str = NULL;
121 	hash_init(pol->rules);
122 
123 	p = buf = memdup_user_nul(ubuf, len);
124 	if (IS_ERR(buf)) {
125 		err = PTR_ERR(buf);
126 		goto out_free_pol;
127 	}
128 	pol->policy_str = kstrdup(buf, GFP_KERNEL);
129 	if (pol->policy_str == NULL) {
130 		err = -ENOMEM;
131 		goto out_free_buf;
132 	}
133 
134 	/* policy lines, including the last one, end with \n */
135 	while (*p != '\0') {
136 		struct setuid_rule *rule;
137 
138 		end = strchr(p, '\n');
139 		if (end == NULL) {
140 			err = -EINVAL;
141 			goto out_free_buf;
142 		}
143 		*end = '\0';
144 
145 		rule = kmalloc(sizeof(struct setuid_rule), GFP_KERNEL);
146 		if (!rule) {
147 			err = -ENOMEM;
148 			goto out_free_buf;
149 		}
150 
151 		err = parse_policy_line(file, p, rule);
152 		if (err)
153 			goto out_free_rule;
154 
155 		if (_setuid_policy_lookup(pol, rule->src_uid, rule->dst_uid) ==
156 		    SIDPOL_ALLOWED) {
157 			pr_warn("bad policy: duplicate entry\n");
158 			err = -EEXIST;
159 			goto out_free_rule;
160 		}
161 
162 		insert_rule(pol, rule);
163 		p = end + 1;
164 		continue;
165 
166 out_free_rule:
167 		kfree(rule);
168 		goto out_free_buf;
169 	}
170 
171 	err = verify_ruleset(pol);
172 	/* bogus policy falls through after fixing it up */
173 	if (err && err != -EINVAL)
174 		goto out_free_buf;
175 
176 	/*
177 	 * Everything looks good, apply the policy and release the old one.
178 	 * What we really want here is an xchg() wrapper for RCU, but since that
179 	 * doesn't currently exist, just use a spinlock for now.
180 	 */
181 	mutex_lock(&policy_update_lock);
182 	rcu_swap_protected(safesetid_setuid_rules, pol,
183 			   lockdep_is_held(&policy_update_lock));
184 	mutex_unlock(&policy_update_lock);
185 	err = len;
186 
187 out_free_buf:
188 	kfree(buf);
189 out_free_pol:
190 	release_ruleset(pol);
191 	return err;
192 }
193 
194 static ssize_t safesetid_file_write(struct file *file,
195 				    const char __user *buf,
196 				    size_t len,
197 				    loff_t *ppos)
198 {
199 	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
200 		return -EPERM;
201 
202 	if (*ppos != 0)
203 		return -EINVAL;
204 
205 	return handle_policy_update(file, buf, len);
206 }
207 
208 static ssize_t safesetid_file_read(struct file *file, char __user *buf,
209 				   size_t len, loff_t *ppos)
210 {
211 	ssize_t res = 0;
212 	struct setuid_ruleset *pol;
213 	const char *kbuf;
214 
215 	mutex_lock(&policy_update_lock);
216 	pol = rcu_dereference_protected(safesetid_setuid_rules,
217 					lockdep_is_held(&policy_update_lock));
218 	if (pol) {
219 		kbuf = pol->policy_str;
220 		res = simple_read_from_buffer(buf, len, ppos,
221 					      kbuf, strlen(kbuf));
222 	}
223 	mutex_unlock(&policy_update_lock);
224 	return res;
225 }
226 
227 static const struct file_operations safesetid_file_fops = {
228 	.read = safesetid_file_read,
229 	.write = safesetid_file_write,
230 };
231 
232 static int __init safesetid_init_securityfs(void)
233 {
234 	int ret;
235 	struct dentry *policy_dir;
236 	struct dentry *policy_file;
237 
238 	if (!safesetid_initialized)
239 		return 0;
240 
241 	policy_dir = securityfs_create_dir("safesetid", NULL);
242 	if (IS_ERR(policy_dir)) {
243 		ret = PTR_ERR(policy_dir);
244 		goto error;
245 	}
246 
247 	policy_file = securityfs_create_file("whitelist_policy", 0600,
248 			policy_dir, NULL, &safesetid_file_fops);
249 	if (IS_ERR(policy_file)) {
250 		ret = PTR_ERR(policy_file);
251 		goto error;
252 	}
253 
254 	return 0;
255 
256 error:
257 	securityfs_remove(policy_dir);
258 	return ret;
259 }
260 fs_initcall(safesetid_init_securityfs);
261