blob: 25310468bcddfff80bb4d6445b084c8654e52e75 [file] [log] [blame]
Micah Mortonaeca4e22019-01-16 07:46:06 -08001// 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 */
Jann Horn03638e62019-04-10 09:56:05 -070014
15#define pr_fmt(fmt) "SafeSetID: " fmt
16
Micah Mortonaeca4e22019-01-16 07:46:06 -080017#include <linux/security.h>
18#include <linux/cred.h>
19
20#include "lsm.h"
21
Thomas Cedeno5294bac2020-07-16 19:52:01 +000022static DEFINE_MUTEX(uid_policy_update_lock);
23static DEFINE_MUTEX(gid_policy_update_lock);
Micah Mortonaeca4e22019-01-16 07:46:06 -080024
25/*
Thomas Cedeno5294bac2020-07-16 19:52:01 +000026 * In the case the input buffer contains one or more invalid IDs, the kid_t
Jann Horn78ae7df2019-04-10 09:55:48 -070027 * variables pointed to by @parent and @child will get updated but this
Micah Mortonaeca4e22019-01-16 07:46:06 -080028 * function will return an error.
Jann Horn78ae7df2019-04-10 09:55:48 -070029 * Contents of @buf may be modified.
Micah Mortonaeca4e22019-01-16 07:46:06 -080030 */
Jann Horn03638e62019-04-10 09:56:05 -070031static int parse_policy_line(struct file *file, char *buf,
Thomas Cedeno5294bac2020-07-16 19:52:01 +000032 struct setid_rule *rule)
Micah Mortonaeca4e22019-01-16 07:46:06 -080033{
Jann Horn78ae7df2019-04-10 09:55:48 -070034 char *child_str;
Micah Mortonaeca4e22019-01-16 07:46:06 -080035 int ret;
Jann Horn78ae7df2019-04-10 09:55:48 -070036 u32 parsed_parent, parsed_child;
Micah Mortonaeca4e22019-01-16 07:46:06 -080037
Thomas Cedeno5294bac2020-07-16 19:52:01 +000038 /* Format of |buf| string should be <UID>:<UID> or <GID>:<GID> */
Jann Horn78ae7df2019-04-10 09:55:48 -070039 child_str = strchr(buf, ':');
40 if (child_str == NULL)
41 return -EINVAL;
42 *child_str = '\0';
43 child_str++;
Micah Mortonaeca4e22019-01-16 07:46:06 -080044
Jann Horn78ae7df2019-04-10 09:55:48 -070045 ret = kstrtou32(buf, 0, &parsed_parent);
Micah Mortonaeca4e22019-01-16 07:46:06 -080046 if (ret)
Jann Horn78ae7df2019-04-10 09:55:48 -070047 return ret;
Micah Mortonaeca4e22019-01-16 07:46:06 -080048
Jann Horn78ae7df2019-04-10 09:55:48 -070049 ret = kstrtou32(child_str, 0, &parsed_child);
Micah Mortonaeca4e22019-01-16 07:46:06 -080050 if (ret)
Jann Horn78ae7df2019-04-10 09:55:48 -070051 return ret;
Micah Mortonaeca4e22019-01-16 07:46:06 -080052
Thomas Cedeno5294bac2020-07-16 19:52:01 +000053 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 */
Jann Horn78ae7df2019-04-10 09:55:48 -070065 return -EINVAL;
Thomas Cedeno5294bac2020-07-16 19:52:01 +000066 }
Jann Horn78ae7df2019-04-10 09:55:48 -070067 return 0;
68}
69
Jann Horn03638e62019-04-10 09:56:05 -070070static void __release_ruleset(struct rcu_head *rcu)
Jann Horn78ae7df2019-04-10 09:55:48 -070071{
Thomas Cedeno5294bac2020-07-16 19:52:01 +000072 struct setid_ruleset *pol =
73 container_of(rcu, struct setid_ruleset, rcu);
Jann Horn03638e62019-04-10 09:56:05 -070074 int bucket;
Thomas Cedeno5294bac2020-07-16 19:52:01 +000075 struct setid_rule *rule;
Jann Horn03638e62019-04-10 09:56:05 -070076 struct hlist_node *tmp;
Jann Horn78ae7df2019-04-10 09:55:48 -070077
Jann Horn03638e62019-04-10 09:56:05 -070078 hash_for_each_safe(pol->rules, bucket, tmp, rule, next)
79 kfree(rule);
Jann Hornfbd9acb2019-04-11 13:11:54 -070080 kfree(pol->policy_str);
Jann Horn03638e62019-04-10 09:56:05 -070081 kfree(pol);
82}
83
Thomas Cedeno5294bac2020-07-16 19:52:01 +000084static void release_ruleset(struct setid_ruleset *pol){
Jann Horn03638e62019-04-10 09:56:05 -070085 call_rcu(&pol->rcu, __release_ruleset);
86}
87
Thomas Cedeno5294bac2020-07-16 19:52:01 +000088static void insert_rule(struct setid_ruleset *pol, struct setid_rule *rule)
Jann Horn4f721232019-04-11 13:12:43 -070089{
Thomas Cedeno5294bac2020-07-16 19:52:01 +000090 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;
Jann Horn4f721232019-04-11 13:12:43 -070096}
97
Thomas Cedeno5294bac2020-07-16 19:52:01 +000098static int verify_ruleset(struct setid_ruleset *pol)
Jann Horn4f721232019-04-11 13:12:43 -070099{
100 int bucket;
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000101 struct setid_rule *rule, *nrule;
Jann Horn4f721232019-04-11 13:12:43 -0700102 int res = 0;
103
104 hash_for_each(pol->rules, bucket, rule, next) {
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000105 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 }
Jann Horn4f721232019-04-11 13:12:43 -0700118 res = -EINVAL;
119
120 /* fix it up */
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000121 nrule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
Jann Horn4f721232019-04-11 13:12:43 -0700122 if (!nrule)
123 return -ENOMEM;
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000124 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 }
Jann Horn4f721232019-04-11 13:12:43 -0700133 insert_rule(pol, nrule);
134 }
135 }
136 return res;
137}
138
Jann Horn03638e62019-04-10 09:56:05 -0700139static ssize_t handle_policy_update(struct file *file,
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000140 const char __user *ubuf, size_t len, enum setid_type policy_type)
Jann Horn03638e62019-04-10 09:56:05 -0700141{
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000142 struct setid_ruleset *pol;
Jann Horn03638e62019-04-10 09:56:05 -0700143 char *buf, *p, *end;
144 int err;
145
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000146 pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL);
Jann Horn03638e62019-04-10 09:56:05 -0700147 if (!pol)
148 return -ENOMEM;
Jann Hornfbd9acb2019-04-11 13:11:54 -0700149 pol->policy_str = NULL;
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000150 pol->type = policy_type;
Jann Horn03638e62019-04-10 09:56:05 -0700151 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 }
Jann Hornfbd9acb2019-04-11 13:11:54 -0700158 pol->policy_str = kstrdup(buf, GFP_KERNEL);
159 if (pol->policy_str == NULL) {
160 err = -ENOMEM;
161 goto out_free_buf;
162 }
Jann Horn03638e62019-04-10 09:56:05 -0700163
164 /* policy lines, including the last one, end with \n */
165 while (*p != '\0') {
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000166 struct setid_rule *rule;
Jann Horn03638e62019-04-10 09:56:05 -0700167
168 end = strchr(p, '\n');
169 if (end == NULL) {
170 err = -EINVAL;
171 goto out_free_buf;
172 }
173 *end = '\0';
174
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000175 rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
Jann Horn03638e62019-04-10 09:56:05 -0700176 if (!rule) {
177 err = -ENOMEM;
178 goto out_free_buf;
179 }
180
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000181 rule->type = policy_type;
Jann Horn03638e62019-04-10 09:56:05 -0700182 err = parse_policy_line(file, p, rule);
183 if (err)
184 goto out_free_rule;
185
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000186 if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == SIDPOL_ALLOWED) {
Jann Horn03638e62019-04-10 09:56:05 -0700187 pr_warn("bad policy: duplicate entry\n");
188 err = -EEXIST;
189 goto out_free_rule;
190 }
191
Jann Horn4f721232019-04-11 13:12:43 -0700192 insert_rule(pol, rule);
Jann Horn03638e62019-04-10 09:56:05 -0700193 p = end + 1;
194 continue;
195
196out_free_rule:
197 kfree(rule);
198 goto out_free_buf;
199 }
200
Jann Horn4f721232019-04-11 13:12:43 -0700201 err = verify_ruleset(pol);
202 /* bogus policy falls through after fixing it up */
203 if (err && err != -EINVAL)
204 goto out_free_buf;
205
Jann Horn03638e62019-04-10 09:56:05 -0700206 /*
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 */
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000211 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 }
Jann Horn03638e62019-04-10 09:56:05 -0700225 err = len;
226
227out_free_buf:
228 kfree(buf);
229out_free_pol:
Micah Morton21ab8582019-09-17 11:27:05 -0700230 if (pol)
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000231 release_ruleset(pol);
Jann Horn03638e62019-04-10 09:56:05 -0700232 return err;
Micah Mortonaeca4e22019-01-16 07:46:06 -0800233}
234
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000235static ssize_t safesetid_uid_file_write(struct file *file,
Micah Mortonaeca4e22019-01-16 07:46:06 -0800236 const char __user *buf,
237 size_t len,
238 loff_t *ppos)
239{
Jann Horn71a98972019-04-10 09:55:58 -0700240 if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
Micah Mortonaeca4e22019-01-16 07:46:06 -0800241 return -EPERM;
242
243 if (*ppos != 0)
244 return -EINVAL;
245
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000246 return handle_policy_update(file, buf, len, UID);
247}
248
249static 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);
Micah Mortonaeca4e22019-01-16 07:46:06 -0800261}
262
Jann Hornfbd9acb2019-04-11 13:11:54 -0700263static ssize_t safesetid_file_read(struct file *file, char __user *buf,
Thomas Cedeno03ca0ec2020-08-11 15:39:51 +0000264 size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset)
Jann Hornfbd9acb2019-04-11 13:11:54 -0700265{
266 ssize_t res = 0;
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000267 struct setid_ruleset *pol;
Jann Hornfbd9acb2019-04-11 13:11:54 -0700268 const char *kbuf;
269
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000270 mutex_lock(policy_update_lock);
271 pol = rcu_dereference_protected(ruleset, lockdep_is_held(policy_update_lock));
Jann Hornfbd9acb2019-04-11 13:11:54 -0700272 if (pol) {
273 kbuf = pol->policy_str;
274 res = simple_read_from_buffer(buf, len, ppos,
275 kbuf, strlen(kbuf));
276 }
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000277 mutex_unlock(policy_update_lock);
278
Jann Hornfbd9acb2019-04-11 13:11:54 -0700279 return res;
280}
281
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000282static 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
289static 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
298static const struct file_operations safesetid_uid_file_fops = {
299 .read = safesetid_uid_file_read,
300 .write = safesetid_uid_file_write,
301};
302
303static const struct file_operations safesetid_gid_file_fops = {
304 .read = safesetid_gid_file_read,
305 .write = safesetid_gid_file_write,
Micah Mortonaeca4e22019-01-16 07:46:06 -0800306};
307
Micah Mortonaeca4e22019-01-16 07:46:06 -0800308static int __init safesetid_init_securityfs(void)
309{
Micah Mortonaeca4e22019-01-16 07:46:06 -0800310 int ret;
Jann Horn03638e62019-04-10 09:56:05 -0700311 struct dentry *policy_dir;
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000312 struct dentry *uid_policy_file;
313 struct dentry *gid_policy_file;
Micah Mortonaeca4e22019-01-16 07:46:06 -0800314
315 if (!safesetid_initialized)
316 return 0;
317
Jann Horn03638e62019-04-10 09:56:05 -0700318 policy_dir = securityfs_create_dir("safesetid", NULL);
319 if (IS_ERR(policy_dir)) {
320 ret = PTR_ERR(policy_dir);
Micah Mortonaeca4e22019-01-16 07:46:06 -0800321 goto error;
322 }
323
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000324 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);
Jann Horn03638e62019-04-10 09:56:05 -0700328 goto error;
Micah Mortonaeca4e22019-01-16 07:46:06 -0800329 }
330
Thomas Cedeno5294bac2020-07-16 19:52:01 +0000331 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
Micah Mortonaeca4e22019-01-16 07:46:06 -0800339 return 0;
340
341error:
Jann Horn03638e62019-04-10 09:56:05 -0700342 securityfs_remove(policy_dir);
Micah Mortonaeca4e22019-01-16 07:46:06 -0800343 return ret;
344}
345fs_initcall(safesetid_init_securityfs);