~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/security/safesetid/lsm.c

Version: ~ [ linux-6.11.5 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.58 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.114 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.169 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.228 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.284 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.322 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  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 };
293 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php