blob: f8bc574cea9c2236b626fe19610f696db87b6207 [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
Jann Hornfbd9acb2019-04-11 13:11:54 -070022static DEFINE_MUTEX(policy_update_lock);
Micah Mortonaeca4e22019-01-16 07:46:06 -080023
24/*
25 * In the case the input buffer contains one or more invalid UIDs, the kuid_t
Jann Horn78ae7df2019-04-10 09:55:48 -070026 * variables pointed to by @parent and @child will get updated but this
Micah Mortonaeca4e22019-01-16 07:46:06 -080027 * function will return an error.
Jann Horn78ae7df2019-04-10 09:55:48 -070028 * Contents of @buf may be modified.
Micah Mortonaeca4e22019-01-16 07:46:06 -080029 */
Jann Horn03638e62019-04-10 09:56:05 -070030static int parse_policy_line(struct file *file, char *buf,
31 struct setuid_rule *rule)
Micah Mortonaeca4e22019-01-16 07:46:06 -080032{
Jann Horn78ae7df2019-04-10 09:55:48 -070033 char *child_str;
Micah Mortonaeca4e22019-01-16 07:46:06 -080034 int ret;
Jann Horn78ae7df2019-04-10 09:55:48 -070035 u32 parsed_parent, parsed_child;
Micah Mortonaeca4e22019-01-16 07:46:06 -080036
Jann Horn78ae7df2019-04-10 09:55:48 -070037 /* 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++;
Micah Mortonaeca4e22019-01-16 07:46:06 -080043
Jann Horn78ae7df2019-04-10 09:55:48 -070044 ret = kstrtou32(buf, 0, &parsed_parent);
Micah Mortonaeca4e22019-01-16 07:46:06 -080045 if (ret)
Jann Horn78ae7df2019-04-10 09:55:48 -070046 return ret;
Micah Mortonaeca4e22019-01-16 07:46:06 -080047
Jann Horn78ae7df2019-04-10 09:55:48 -070048 ret = kstrtou32(child_str, 0, &parsed_child);
Micah Mortonaeca4e22019-01-16 07:46:06 -080049 if (ret)
Jann Horn78ae7df2019-04-10 09:55:48 -070050 return ret;
Micah Mortonaeca4e22019-01-16 07:46:06 -080051
Jann Horn03638e62019-04-10 09:56:05 -070052 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))
Jann Horn78ae7df2019-04-10 09:55:48 -070055 return -EINVAL;
Micah Mortonaeca4e22019-01-16 07:46:06 -080056
Jann Horn78ae7df2019-04-10 09:55:48 -070057 return 0;
58}
59
Jann Horn03638e62019-04-10 09:56:05 -070060static void __release_ruleset(struct rcu_head *rcu)
Jann Horn78ae7df2019-04-10 09:55:48 -070061{
Jann Horn03638e62019-04-10 09:56:05 -070062 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;
Jann Horn78ae7df2019-04-10 09:55:48 -070067
Jann Horn03638e62019-04-10 09:56:05 -070068 hash_for_each_safe(pol->rules, bucket, tmp, rule, next)
69 kfree(rule);
Jann Hornfbd9acb2019-04-11 13:11:54 -070070 kfree(pol->policy_str);
Jann Horn03638e62019-04-10 09:56:05 -070071 kfree(pol);
72}
73
74static void release_ruleset(struct setuid_ruleset *pol)
75{
76 call_rcu(&pol->rcu, __release_ruleset);
77}
78
Jann Horn4f721232019-04-11 13:12:43 -070079static 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
84static 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
Jann Horn03638e62019-04-10 09:56:05 -0700110static 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;
Jann Hornfbd9acb2019-04-11 13:11:54 -0700120 pol->policy_str = NULL;
Jann Horn03638e62019-04-10 09:56:05 -0700121 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 }
Jann Hornfbd9acb2019-04-11 13:11:54 -0700128 pol->policy_str = kstrdup(buf, GFP_KERNEL);
129 if (pol->policy_str == NULL) {
130 err = -ENOMEM;
131 goto out_free_buf;
132 }
Jann Horn03638e62019-04-10 09:56:05 -0700133
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
Jann Horn4f721232019-04-11 13:12:43 -0700162 insert_rule(pol, rule);
Jann Horn03638e62019-04-10 09:56:05 -0700163 p = end + 1;
164 continue;
165
166out_free_rule:
167 kfree(rule);
168 goto out_free_buf;
169 }
170
Jann Horn4f721232019-04-11 13:12:43 -0700171 err = verify_ruleset(pol);
172 /* bogus policy falls through after fixing it up */
173 if (err && err != -EINVAL)
174 goto out_free_buf;
175
Jann Horn03638e62019-04-10 09:56:05 -0700176 /*
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 */
Jann Hornfbd9acb2019-04-11 13:11:54 -0700181 mutex_lock(&policy_update_lock);
Paul E. McKenneya60a5742019-10-04 15:07:09 -0700182 pol = rcu_replace_pointer(safesetid_setuid_rules, pol,
183 lockdep_is_held(&policy_update_lock));
Jann Hornfbd9acb2019-04-11 13:11:54 -0700184 mutex_unlock(&policy_update_lock);
Jann Horn03638e62019-04-10 09:56:05 -0700185 err = len;
186
187out_free_buf:
188 kfree(buf);
189out_free_pol:
Micah Morton21ab8582019-09-17 11:27:05 -0700190 if (pol)
191 release_ruleset(pol);
Jann Horn03638e62019-04-10 09:56:05 -0700192 return err;
Micah Mortonaeca4e22019-01-16 07:46:06 -0800193}
194
195static ssize_t safesetid_file_write(struct file *file,
196 const char __user *buf,
197 size_t len,
198 loff_t *ppos)
199{
Jann Horn71a98972019-04-10 09:55:58 -0700200 if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
Micah Mortonaeca4e22019-01-16 07:46:06 -0800201 return -EPERM;
202
203 if (*ppos != 0)
204 return -EINVAL;
205
Jann Horn03638e62019-04-10 09:56:05 -0700206 return handle_policy_update(file, buf, len);
Micah Mortonaeca4e22019-01-16 07:46:06 -0800207}
208
Jann Hornfbd9acb2019-04-11 13:11:54 -0700209static ssize_t safesetid_file_read(struct file *file, char __user *buf,
210 size_t len, loff_t *ppos)
211{
212 ssize_t res = 0;
213 struct setuid_ruleset *pol;
214 const char *kbuf;
215
216 mutex_lock(&policy_update_lock);
217 pol = rcu_dereference_protected(safesetid_setuid_rules,
218 lockdep_is_held(&policy_update_lock));
219 if (pol) {
220 kbuf = pol->policy_str;
221 res = simple_read_from_buffer(buf, len, ppos,
222 kbuf, strlen(kbuf));
223 }
224 mutex_unlock(&policy_update_lock);
225 return res;
226}
227
Micah Mortonaeca4e22019-01-16 07:46:06 -0800228static const struct file_operations safesetid_file_fops = {
Jann Hornfbd9acb2019-04-11 13:11:54 -0700229 .read = safesetid_file_read,
Micah Mortonaeca4e22019-01-16 07:46:06 -0800230 .write = safesetid_file_write,
231};
232
Micah Mortonaeca4e22019-01-16 07:46:06 -0800233static int __init safesetid_init_securityfs(void)
234{
Micah Mortonaeca4e22019-01-16 07:46:06 -0800235 int ret;
Jann Horn03638e62019-04-10 09:56:05 -0700236 struct dentry *policy_dir;
237 struct dentry *policy_file;
Micah Mortonaeca4e22019-01-16 07:46:06 -0800238
239 if (!safesetid_initialized)
240 return 0;
241
Jann Horn03638e62019-04-10 09:56:05 -0700242 policy_dir = securityfs_create_dir("safesetid", NULL);
243 if (IS_ERR(policy_dir)) {
244 ret = PTR_ERR(policy_dir);
Micah Mortonaeca4e22019-01-16 07:46:06 -0800245 goto error;
246 }
247
Jann Hornfbd9acb2019-04-11 13:11:54 -0700248 policy_file = securityfs_create_file("whitelist_policy", 0600,
Jann Horn03638e62019-04-10 09:56:05 -0700249 policy_dir, NULL, &safesetid_file_fops);
250 if (IS_ERR(policy_file)) {
251 ret = PTR_ERR(policy_file);
252 goto error;
Micah Mortonaeca4e22019-01-16 07:46:06 -0800253 }
254
255 return 0;
256
257error:
Jann Horn03638e62019-04-10 09:56:05 -0700258 securityfs_remove(policy_dir);
Micah Mortonaeca4e22019-01-16 07:46:06 -0800259 return ret;
260}
261fs_initcall(safesetid_init_securityfs);