Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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
 22static DEFINE_MUTEX(uid_policy_update_lock);
 23static 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 */
 31static 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
 70static 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
 84static void release_ruleset(struct setid_ruleset *pol){
 85	call_rcu(&pol->rcu, __release_ruleset);
 86}
 87
 88static 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
 98static 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
139static 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	if (len >= KMALLOC_MAX_SIZE)
147		return -EINVAL;
148
149	pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL);
150	if (!pol)
151		return -ENOMEM;
152	pol->policy_str = NULL;
153	pol->type = policy_type;
154	hash_init(pol->rules);
155
156	p = buf = memdup_user_nul(ubuf, len);
157	if (IS_ERR(buf)) {
158		err = PTR_ERR(buf);
159		goto out_free_pol;
160	}
161	pol->policy_str = kstrdup(buf, GFP_KERNEL);
162	if (pol->policy_str == NULL) {
163		err = -ENOMEM;
164		goto out_free_buf;
165	}
166
167	/* policy lines, including the last one, end with \n */
168	while (*p != '\0') {
169		struct setid_rule *rule;
170
171		end = strchr(p, '\n');
172		if (end == NULL) {
173			err = -EINVAL;
174			goto out_free_buf;
175		}
176		*end = '\0';
177
178		rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
179		if (!rule) {
180			err = -ENOMEM;
181			goto out_free_buf;
182		}
183
184		rule->type = policy_type;
185		err = parse_policy_line(file, p, rule);
186		if (err)
187			goto out_free_rule;
188
189		if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == SIDPOL_ALLOWED) {
190			pr_warn("bad policy: duplicate entry\n");
191			err = -EEXIST;
192			goto out_free_rule;
193		}
194
195		insert_rule(pol, rule);
196		p = end + 1;
197		continue;
198
199out_free_rule:
200		kfree(rule);
201		goto out_free_buf;
202	}
203
204	err = verify_ruleset(pol);
205	/* bogus policy falls through after fixing it up */
206	if (err && err != -EINVAL)
207		goto out_free_buf;
208
209	/*
210	 * Everything looks good, apply the policy and release the old one.
211	 * What we really want here is an xchg() wrapper for RCU, but since that
212	 * doesn't currently exist, just use a spinlock for now.
213	 */
214	if (policy_type == UID) {
215		mutex_lock(&uid_policy_update_lock);
216		pol = rcu_replace_pointer(safesetid_setuid_rules, pol,
217					  lockdep_is_held(&uid_policy_update_lock));
218		mutex_unlock(&uid_policy_update_lock);
219	} else if (policy_type == GID) {
220		mutex_lock(&gid_policy_update_lock);
221		pol = rcu_replace_pointer(safesetid_setgid_rules, pol,
222					  lockdep_is_held(&gid_policy_update_lock));
223		mutex_unlock(&gid_policy_update_lock);
224	} else {
225		/* Error, policy type is neither UID or GID */
226		pr_warn("error: bad policy type");
227	}
228	err = len;
229
230out_free_buf:
231	kfree(buf);
232out_free_pol:
233	if (pol)
234		release_ruleset(pol);
235	return err;
236}
237
238static ssize_t safesetid_uid_file_write(struct file *file,
239				    const char __user *buf,
240				    size_t len,
241				    loff_t *ppos)
242{
243	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
244		return -EPERM;
245
246	if (*ppos != 0)
247		return -EINVAL;
248
249	return handle_policy_update(file, buf, len, UID);
250}
251
252static ssize_t safesetid_gid_file_write(struct file *file,
253				    const char __user *buf,
254				    size_t len,
255				    loff_t *ppos)
256{
257	if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
258		return -EPERM;
259
260	if (*ppos != 0)
261		return -EINVAL;
262
263	return handle_policy_update(file, buf, len, GID);
264}
265
266static ssize_t safesetid_file_read(struct file *file, char __user *buf,
267				   size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset)
268{
269	ssize_t res = 0;
270	struct setid_ruleset *pol;
271	const char *kbuf;
272
273	mutex_lock(policy_update_lock);
274	pol = rcu_dereference_protected(ruleset, lockdep_is_held(policy_update_lock));
275	if (pol) {
276		kbuf = pol->policy_str;
277		res = simple_read_from_buffer(buf, len, ppos,
278					      kbuf, strlen(kbuf));
279	}
280	mutex_unlock(policy_update_lock);
281
282	return res;
283}
284
285static ssize_t safesetid_uid_file_read(struct file *file, char __user *buf,
286				   size_t len, loff_t *ppos)
287{
288	return safesetid_file_read(file, buf, len, ppos,
289				   &uid_policy_update_lock, safesetid_setuid_rules);
290}
291
292static ssize_t safesetid_gid_file_read(struct file *file, char __user *buf,
293				   size_t len, loff_t *ppos)
294{
295	return safesetid_file_read(file, buf, len, ppos,
296				   &gid_policy_update_lock, safesetid_setgid_rules);
297}
298
299
300
301static const struct file_operations safesetid_uid_file_fops = {
302	.read = safesetid_uid_file_read,
303	.write = safesetid_uid_file_write,
304};
305
306static const struct file_operations safesetid_gid_file_fops = {
307	.read = safesetid_gid_file_read,
308	.write = safesetid_gid_file_write,
309};
310
311static int __init safesetid_init_securityfs(void)
312{
313	int ret;
314	struct dentry *policy_dir;
315	struct dentry *uid_policy_file;
316	struct dentry *gid_policy_file;
317
318	if (!safesetid_initialized)
319		return 0;
320
321	policy_dir = securityfs_create_dir("safesetid", NULL);
322	if (IS_ERR(policy_dir)) {
323		ret = PTR_ERR(policy_dir);
324		goto error;
325	}
326
327	uid_policy_file = securityfs_create_file("uid_allowlist_policy", 0600,
328			policy_dir, NULL, &safesetid_uid_file_fops);
329	if (IS_ERR(uid_policy_file)) {
330		ret = PTR_ERR(uid_policy_file);
331		goto error;
332	}
333
334	gid_policy_file = securityfs_create_file("gid_allowlist_policy", 0600,
335			policy_dir, NULL, &safesetid_gid_file_fops);
336	if (IS_ERR(gid_policy_file)) {
337		ret = PTR_ERR(gid_policy_file);
338		goto error;
339	}
340
341
342	return 0;
343
344error:
345	securityfs_remove(policy_dir);
346	return ret;
347}
348fs_initcall(safesetid_init_securityfs);