Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/security/safesetid/securityfs.c
26285 views
1
// SPDX-License-Identifier: GPL-2.0
2
/*
3
* SafeSetID Linux Security Module
4
*
5
* Author: Micah Morton <[email protected]>
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
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
199
out_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
230
out_free_buf:
231
kfree(buf);
232
out_free_pol:
233
if (pol)
234
release_ruleset(pol);
235
return err;
236
}
237
238
static 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
252
static 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
266
static 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
285
static 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
292
static 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
301
static const struct file_operations safesetid_uid_file_fops = {
302
.read = safesetid_uid_file_read,
303
.write = safesetid_uid_file_write,
304
};
305
306
static const struct file_operations safesetid_gid_file_fops = {
307
.read = safesetid_gid_file_read,
308
.write = safesetid_gid_file_write,
309
};
310
311
static 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
344
error:
345
securityfs_remove(policy_dir);
346
return ret;
347
}
348
fs_initcall(safesetid_init_securityfs);
349
350