Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/security/safesetid/lsm.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/lsm_hooks.h>
18
#include <linux/module.h>
19
#include <linux/ptrace.h>
20
#include <linux/sched/task_stack.h>
21
#include <linux/security.h>
22
#include <uapi/linux/lsm.h>
23
#include "lsm.h"
24
25
/* Flag indicating whether initialization completed */
26
int safesetid_initialized __initdata;
27
28
struct setid_ruleset __rcu *safesetid_setuid_rules;
29
struct setid_ruleset __rcu *safesetid_setgid_rules;
30
31
32
/* Compute a decision for a transition from @src to @dst under @policy. */
33
enum sid_policy_type _setid_policy_lookup(struct setid_ruleset *policy,
34
kid_t src, kid_t dst)
35
{
36
struct setid_rule *rule;
37
enum sid_policy_type result = SIDPOL_DEFAULT;
38
39
if (policy->type == UID) {
40
hash_for_each_possible(policy->rules, rule, next, __kuid_val(src.uid)) {
41
if (!uid_eq(rule->src_id.uid, src.uid))
42
continue;
43
if (uid_eq(rule->dst_id.uid, dst.uid))
44
return SIDPOL_ALLOWED;
45
result = SIDPOL_CONSTRAINED;
46
}
47
} else if (policy->type == GID) {
48
hash_for_each_possible(policy->rules, rule, next, __kgid_val(src.gid)) {
49
if (!gid_eq(rule->src_id.gid, src.gid))
50
continue;
51
if (gid_eq(rule->dst_id.gid, dst.gid)){
52
return SIDPOL_ALLOWED;
53
}
54
result = SIDPOL_CONSTRAINED;
55
}
56
} else {
57
/* Should not reach here, report the ID as contrainsted */
58
result = SIDPOL_CONSTRAINED;
59
}
60
return result;
61
}
62
63
/*
64
* Compute a decision for a transition from @src to @dst under the active
65
* policy.
66
*/
67
static enum sid_policy_type setid_policy_lookup(kid_t src, kid_t dst, enum setid_type new_type)
68
{
69
enum sid_policy_type result = SIDPOL_DEFAULT;
70
struct setid_ruleset *pol;
71
72
rcu_read_lock();
73
if (new_type == UID)
74
pol = rcu_dereference(safesetid_setuid_rules);
75
else if (new_type == GID)
76
pol = rcu_dereference(safesetid_setgid_rules);
77
else { /* Should not reach here */
78
result = SIDPOL_CONSTRAINED;
79
rcu_read_unlock();
80
return result;
81
}
82
83
if (pol) {
84
pol->type = new_type;
85
result = _setid_policy_lookup(pol, src, dst);
86
}
87
rcu_read_unlock();
88
return result;
89
}
90
91
static int safesetid_security_capable(const struct cred *cred,
92
struct user_namespace *ns,
93
int cap,
94
unsigned int opts)
95
{
96
/* We're only interested in CAP_SETUID and CAP_SETGID. */
97
if (cap != CAP_SETUID && cap != CAP_SETGID)
98
return 0;
99
100
/*
101
* If CAP_SET{U/G}ID is currently used for a setid or setgroups syscall, we
102
* want to let it go through here; the real security check happens later, in
103
* the task_fix_set{u/g}id or task_fix_setgroups hooks.
104
*/
105
if ((opts & CAP_OPT_INSETID) != 0)
106
return 0;
107
108
switch (cap) {
109
case CAP_SETUID:
110
/*
111
* If no policy applies to this task, allow the use of CAP_SETUID for
112
* other purposes.
113
*/
114
if (setid_policy_lookup((kid_t){.uid = cred->uid}, INVALID_ID, UID) == SIDPOL_DEFAULT)
115
return 0;
116
/*
117
* Reject use of CAP_SETUID for functionality other than calling
118
* set*uid() (e.g. setting up userns uid mappings).
119
*/
120
pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n",
121
__kuid_val(cred->uid));
122
return -EPERM;
123
case CAP_SETGID:
124
/*
125
* If no policy applies to this task, allow the use of CAP_SETGID for
126
* other purposes.
127
*/
128
if (setid_policy_lookup((kid_t){.gid = cred->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT)
129
return 0;
130
/*
131
* Reject use of CAP_SETUID for functionality other than calling
132
* set*gid() (e.g. setting up userns gid mappings).
133
*/
134
pr_warn("Operation requires CAP_SETGID, which is not available to GID %u for operations besides approved set*gid transitions\n",
135
__kgid_val(cred->gid));
136
return -EPERM;
137
default:
138
/* Error, the only capabilities were checking for is CAP_SETUID/GID */
139
return 0;
140
}
141
return 0;
142
}
143
144
/*
145
* Check whether a caller with old credentials @old is allowed to switch to
146
* credentials that contain @new_id.
147
*/
148
static bool id_permitted_for_cred(const struct cred *old, kid_t new_id, enum setid_type new_type)
149
{
150
bool permitted;
151
152
/* If our old creds already had this ID in it, it's fine. */
153
if (new_type == UID) {
154
if (uid_eq(new_id.uid, old->uid) || uid_eq(new_id.uid, old->euid) ||
155
uid_eq(new_id.uid, old->suid))
156
return true;
157
} else if (new_type == GID){
158
if (gid_eq(new_id.gid, old->gid) || gid_eq(new_id.gid, old->egid) ||
159
gid_eq(new_id.gid, old->sgid))
160
return true;
161
} else /* Error, new_type is an invalid type */
162
return false;
163
164
/*
165
* Transitions to new UIDs require a check against the policy of the old
166
* RUID.
167
*/
168
permitted =
169
setid_policy_lookup((kid_t){.uid = old->uid}, new_id, new_type) != SIDPOL_CONSTRAINED;
170
171
if (!permitted) {
172
if (new_type == UID) {
173
pr_warn("UID transition ((%d,%d,%d) -> %d) blocked\n",
174
__kuid_val(old->uid), __kuid_val(old->euid),
175
__kuid_val(old->suid), __kuid_val(new_id.uid));
176
} else if (new_type == GID) {
177
pr_warn("GID transition ((%d,%d,%d) -> %d) blocked\n",
178
__kgid_val(old->gid), __kgid_val(old->egid),
179
__kgid_val(old->sgid), __kgid_val(new_id.gid));
180
} else /* Error, new_type is an invalid type */
181
return false;
182
}
183
return permitted;
184
}
185
186
/*
187
* Check whether there is either an exception for user under old cred struct to
188
* set*uid to user under new cred struct, or the UID transition is allowed (by
189
* Linux set*uid rules) even without CAP_SETUID.
190
*/
191
static int safesetid_task_fix_setuid(struct cred *new,
192
const struct cred *old,
193
int flags)
194
{
195
196
/* Do nothing if there are no setuid restrictions for our old RUID. */
197
if (setid_policy_lookup((kid_t){.uid = old->uid}, INVALID_ID, UID) == SIDPOL_DEFAULT)
198
return 0;
199
200
if (id_permitted_for_cred(old, (kid_t){.uid = new->uid}, UID) &&
201
id_permitted_for_cred(old, (kid_t){.uid = new->euid}, UID) &&
202
id_permitted_for_cred(old, (kid_t){.uid = new->suid}, UID) &&
203
id_permitted_for_cred(old, (kid_t){.uid = new->fsuid}, UID))
204
return 0;
205
206
/*
207
* Kill this process to avoid potential security vulnerabilities
208
* that could arise from a missing allowlist entry preventing a
209
* privileged process from dropping to a lesser-privileged one.
210
*/
211
force_sig(SIGKILL);
212
return -EACCES;
213
}
214
215
static int safesetid_task_fix_setgid(struct cred *new,
216
const struct cred *old,
217
int flags)
218
{
219
220
/* Do nothing if there are no setgid restrictions for our old RGID. */
221
if (setid_policy_lookup((kid_t){.gid = old->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT)
222
return 0;
223
224
if (id_permitted_for_cred(old, (kid_t){.gid = new->gid}, GID) &&
225
id_permitted_for_cred(old, (kid_t){.gid = new->egid}, GID) &&
226
id_permitted_for_cred(old, (kid_t){.gid = new->sgid}, GID) &&
227
id_permitted_for_cred(old, (kid_t){.gid = new->fsgid}, GID))
228
return 0;
229
230
/*
231
* Kill this process to avoid potential security vulnerabilities
232
* that could arise from a missing allowlist entry preventing a
233
* privileged process from dropping to a lesser-privileged one.
234
*/
235
force_sig(SIGKILL);
236
return -EACCES;
237
}
238
239
static int safesetid_task_fix_setgroups(struct cred *new, const struct cred *old)
240
{
241
int i;
242
243
/* Do nothing if there are no setgid restrictions for our old RGID. */
244
if (setid_policy_lookup((kid_t){.gid = old->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT)
245
return 0;
246
247
get_group_info(new->group_info);
248
for (i = 0; i < new->group_info->ngroups; i++) {
249
if (!id_permitted_for_cred(old, (kid_t){.gid = new->group_info->gid[i]}, GID)) {
250
put_group_info(new->group_info);
251
/*
252
* Kill this process to avoid potential security vulnerabilities
253
* that could arise from a missing allowlist entry preventing a
254
* privileged process from dropping to a lesser-privileged one.
255
*/
256
force_sig(SIGKILL);
257
return -EACCES;
258
}
259
}
260
261
put_group_info(new->group_info);
262
return 0;
263
}
264
265
static const struct lsm_id safesetid_lsmid = {
266
.name = "safesetid",
267
.id = LSM_ID_SAFESETID,
268
};
269
270
static struct security_hook_list safesetid_security_hooks[] = {
271
LSM_HOOK_INIT(task_fix_setuid, safesetid_task_fix_setuid),
272
LSM_HOOK_INIT(task_fix_setgid, safesetid_task_fix_setgid),
273
LSM_HOOK_INIT(task_fix_setgroups, safesetid_task_fix_setgroups),
274
LSM_HOOK_INIT(capable, safesetid_security_capable)
275
};
276
277
static int __init safesetid_security_init(void)
278
{
279
security_add_hooks(safesetid_security_hooks,
280
ARRAY_SIZE(safesetid_security_hooks),
281
&safesetid_lsmid);
282
283
/* Report that SafeSetID successfully initialized */
284
safesetid_initialized = 1;
285
286
return 0;
287
}
288
289
DEFINE_LSM(safesetid_security_init) = {
290
.init = safesetid_security_init,
291
.name = "safesetid",
292
};
293
294