GNU Linux-libre 6.9-gnu
[releases.git] / security / safesetid / lsm.c
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 <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 };