xref: /linux/security/safesetid/securityfs.c (revision c532de5a67a70f8533d495f8f2aaa9a0491c3ad0)
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(uid_policy_update_lock);
23 static DEFINE_MUTEX(gid_policy_update_lock);
24 
25 /*
26  * In the case the input buffer contains one or more invalid IDs, the kid_t
27  * variables pointed to by @parent and @child will get updated but this
28  * function will return an error.
29  * Contents of @buf may be modified.
30  */
31 static int parse_policy_line(struct file *file, char *buf,
32 	struct setid_rule *rule)
33 {
34 	char *child_str;
35 	int ret;
36 	u32 parsed_parent, parsed_child;
37 
38 	/* Format of |buf| string should be <UID>:<UID> or <GID>:<GID> */
39 	child_str = strchr(buf, ':');
40 	if (child_str == NULL)
41 		return -EINVAL;
42 	*child_str = '\0';
43 	child_str++;
44 
45 	ret = kstrtou32(buf, 0, &parsed_parent);
46 	if (ret)
47 		return ret;
48 
49 	ret = kstrtou32(child_str, 0, &parsed_child);
50 	if (ret)
51 		return ret;
52 
53 	if (rule->type == UID){
54 		rule->src_id.uid = make_kuid(file->f_cred->user_ns, parsed_parent);
55 		rule->dst_id.uid = make_kuid(file->f_cred->user_ns, parsed_child);
56 		if (!uid_valid(rule->src_id.uid) || !uid_valid(rule->dst_id.uid))
57 			return -EINVAL;
58 	} else if (rule->type == GID){
59 		rule->src_id.gid = make_kgid(file->f_cred->user_ns, parsed_parent);
60 		rule->dst_id.gid = make_kgid(file->f_cred->user_ns, parsed_child);
61 		if (!gid_valid(rule->src_id.gid) || !gid_valid(rule->dst_id.gid))
62 			return -EINVAL;
63 	} else {
64 		/* Error, rule->type is an invalid type */
65 		return -EINVAL;
66 	}
67 	return 0;
68 }
69 
70 static void __release_ruleset(struct rcu_head *rcu)
71 {
72 	struct setid_ruleset *pol =
73 		container_of(rcu, struct setid_ruleset, rcu);
74 	int bucket;
75 	struct setid_rule *rule;
76 	struct hlist_node *tmp;
77 
78 	hash_for_each_safe(pol->rules, bucket, tmp, rule, next)
79 		kfree(rule);
80 	kfree(pol->policy_str);
81 	kfree(pol);
82 }
83 
84 static void release_ruleset(struct setid_ruleset *pol){
85 	call_rcu(&pol->rcu, __release_ruleset);
86 }
87 
88 static void insert_rule(struct setid_ruleset *pol, struct setid_rule *rule)
89 {
90 	if (pol->type == UID)
91 		hash_add(pol->rules, &rule->next, __kuid_val(rule->src_id.uid));
92 	else if (pol->type == GID)
93 		hash_add(pol->rules, &rule->next, __kgid_val(rule->src_id.gid));
94 	else /* Error, pol->type is neither UID or GID */
95 		return;
96 }
97 
98 static int verify_ruleset(struct setid_ruleset *pol)
99 {
100 	int bucket;
101 	struct setid_rule *rule, *nrule;
102 	int res = 0;
103 
104 	hash_for_each(pol->rules, bucket, rule, next) {
105 		if (_setid_policy_lookup(pol, rule->dst_id, INVALID_ID) == SIDPOL_DEFAULT) {
106 			if (pol->type == UID) {
107 				pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n",
108 					__kuid_val(rule->src_id.uid),
109 					__kuid_val(rule->dst_id.uid));
110 			} else if (pol->type == GID) {
111 				pr_warn("insecure policy detected: gid %d is constrained but transitively unconstrained through gid %d\n",
112 					__kgid_val(rule->src_id.gid),
113 					__kgid_val(rule->dst_id.gid));
114 			} else { /* pol->type is an invalid type */
115 				res = -EINVAL;
116 				return res;
117 			}
118 			res = -EINVAL;
119 
120 			/* fix it up */
121 			nrule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
122 			if (!nrule)
123 				return -ENOMEM;
124 			if (pol->type == UID){
125 				nrule->src_id.uid = rule->dst_id.uid;
126 				nrule->dst_id.uid = rule->dst_id.uid;
127 				nrule->type = UID;
128 			} else { /* pol->type must be GID if we've made it to here */
129 				nrule->src_id.gid = rule->dst_id.gid;
130 				nrule->dst_id.gid = rule->dst_id.gid;
131 				nrule->type = GID;
132 			}
133 			insert_rule(pol, nrule);
134 		}
135 	}
136 	return res;
137 }
138 
139 static ssize_t handle_policy_update(struct file *file,
140 				    const char __user *ubuf, size_t len, enum setid_type policy_type)
141 {
142 	struct setid_ruleset *pol;
143 	char *buf, *p, *end;
144 	int err;
145 
146 	pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL);
147 	if (!pol)
148 		return -ENOMEM;
149 	pol->policy_str = NULL;
150 	pol->type = policy_type;
151 	hash_init(pol->rules);
152 
153 	p = buf = memdup_user_nul(ubuf, len);
154 	if (IS_ERR(buf)) {
155 		err = PTR_ERR(buf);
156 		goto out_free_pol;
157 	}
158 	pol->policy_str = kstrdup(buf, GFP_KERNEL);
159 	if (pol->policy_str == NULL) {
160 		err = -ENOMEM;
161 		goto out_free_buf;
162 	}
163 
164 	/* policy lines, including the last one, end with \n */
165 	while (*p != '\0') {
166 		struct setid_rule *rule;
167 
168 		end = strchr(p, '\n');
169 		if (end == NULL) {
170 			err = -EINVAL;
171 			goto out_free_buf;
172 		}
173 		*end = '\0';
174 
175 		rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
176 		if (!rule) {
177 			err = -ENOMEM;
178 			goto out_free_buf;
179 		}
180 
181 		rule->type = policy_type;
182 		err = parse_policy_line(file, p, rule);
183 		if (err)
184 			goto out_free_rule;
185 
186 		if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == SIDPOL_ALLOWED) {
187 			pr_warn("bad policy: duplicate entry\n");
188 			err = -EEXIST;
189 			goto out_free_rule;
190 		}
191 
192 		insert_rule(pol, rule);
193 		p = end + 1;
194 		continue;
195 
196 out_free_rule:
197 		kfree(rule);
198 		goto out_free_buf;
199 	}
200 
201 	err = verify_ruleset(pol);
202 	/* bogus policy falls through after fixing it up */
203 	if (err && err != -EINVAL)
204 		goto out_free_buf;
205 
206 	/*
207 	 * Everything looks good, apply the policy and release the old one.
208 	 * What we really want here is an xchg() wrapper for RCU, but since that
209 	 * doesn't currently exist, just use a spinlock for now.
210 	 */
211 	if (policy_type == UID) {
212 		mutex_lock(&uid_policy_update_lock);
213 		pol = rcu_replace_pointer(safesetid_setuid_rules, pol,
214 					  lockdep_is_held(&uid_policy_update_lock));
215 		mutex_unlock(&uid_policy_update_lock);
216 	} else if (policy_type == GID) {
217 		mutex_lock(&gid_policy_update_lock);
218 		pol = rcu_replace_pointer(safesetid_setgid_rules, pol,
219 					  lockdep_is_held(&gid_policy_update_lock));
220 		mutex_unlock(&gid_policy_update_lock);
221 	} else {
222 		/* Error, policy type is neither UID or GID */
223 		pr_warn("error: bad policy type");
224 	}
225 	err = len;
226 
227 out_free_buf:
228 	kfree(buf);
229 out_free_pol:
230 	if (pol)
231 		release_ruleset(pol);
232 	return err;
233 }
234 
235 static ssize_t safesetid_uid_file_write(struct file *file,
236 				    const char __user *buf,
237 				    size_t len,
238 				    loff_t *ppos)
239 {
240 	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
241 		return -EPERM;
242 
243 	if (*ppos != 0)
244 		return -EINVAL;
245 
246 	return handle_policy_update(file, buf, len, UID);
247 }
248 
249 static ssize_t safesetid_gid_file_write(struct file *file,
250 				    const char __user *buf,
251 				    size_t len,
252 				    loff_t *ppos)
253 {
254 	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
255 		return -EPERM;
256 
257 	if (*ppos != 0)
258 		return -EINVAL;
259 
260 	return handle_policy_update(file, buf, len, GID);
261 }
262 
263 static ssize_t safesetid_file_read(struct file *file, char __user *buf,
264 				   size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset)
265 {
266 	ssize_t res = 0;
267 	struct setid_ruleset *pol;
268 	const char *kbuf;
269 
270 	mutex_lock(policy_update_lock);
271 	pol = rcu_dereference_protected(ruleset, lockdep_is_held(policy_update_lock));
272 	if (pol) {
273 		kbuf = pol->policy_str;
274 		res = simple_read_from_buffer(buf, len, ppos,
275 					      kbuf, strlen(kbuf));
276 	}
277 	mutex_unlock(policy_update_lock);
278 
279 	return res;
280 }
281 
282 static ssize_t safesetid_uid_file_read(struct file *file, char __user *buf,
283 				   size_t len, loff_t *ppos)
284 {
285 	return safesetid_file_read(file, buf, len, ppos,
286 				   &uid_policy_update_lock, safesetid_setuid_rules);
287 }
288 
289 static ssize_t safesetid_gid_file_read(struct file *file, char __user *buf,
290 				   size_t len, loff_t *ppos)
291 {
292 	return safesetid_file_read(file, buf, len, ppos,
293 				   &gid_policy_update_lock, safesetid_setgid_rules);
294 }
295 
296 
297 
298 static const struct file_operations safesetid_uid_file_fops = {
299 	.read = safesetid_uid_file_read,
300 	.write = safesetid_uid_file_write,
301 };
302 
303 static const struct file_operations safesetid_gid_file_fops = {
304 	.read = safesetid_gid_file_read,
305 	.write = safesetid_gid_file_write,
306 };
307 
308 static int __init safesetid_init_securityfs(void)
309 {
310 	int ret;
311 	struct dentry *policy_dir;
312 	struct dentry *uid_policy_file;
313 	struct dentry *gid_policy_file;
314 
315 	if (!safesetid_initialized)
316 		return 0;
317 
318 	policy_dir = securityfs_create_dir("safesetid", NULL);
319 	if (IS_ERR(policy_dir)) {
320 		ret = PTR_ERR(policy_dir);
321 		goto error;
322 	}
323 
324 	uid_policy_file = securityfs_create_file("uid_allowlist_policy", 0600,
325 			policy_dir, NULL, &safesetid_uid_file_fops);
326 	if (IS_ERR(uid_policy_file)) {
327 		ret = PTR_ERR(uid_policy_file);
328 		goto error;
329 	}
330 
331 	gid_policy_file = securityfs_create_file("gid_allowlist_policy", 0600,
332 			policy_dir, NULL, &safesetid_gid_file_fops);
333 	if (IS_ERR(gid_policy_file)) {
334 		ret = PTR_ERR(gid_policy_file);
335 		goto error;
336 	}
337 
338 
339 	return 0;
340 
341 error:
342 	securityfs_remove(policy_dir);
343 	return ret;
344 }
345 fs_initcall(safesetid_init_securityfs);
346