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