]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blob - security/safesetid/lsm.c
06d4259f9ab1d5f2cbeaedd160ea56be8e8b7988
[mirror_ubuntu-hirsute-kernel.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/hashtable.h>
18 #include <linux/lsm_hooks.h>
19 #include <linux/module.h>
20 #include <linux/ptrace.h>
21 #include <linux/sched/task_stack.h>
22 #include <linux/security.h>
23
24 /* Flag indicating whether initialization completed */
25 int safesetid_initialized;
26
27 #define NUM_BITS 8 /* 128 buckets in hash table */
28
29 static DEFINE_HASHTABLE(safesetid_whitelist_hashtable, NUM_BITS);
30
31 /*
32 * Hash table entry to store safesetid policy signifying that 'parent' user
33 * can setid to 'child' user.
34 */
35 struct entry {
36 struct hlist_node next;
37 struct hlist_node dlist; /* for deletion cleanup */
38 uint64_t parent_kuid;
39 uint64_t child_kuid;
40 };
41
42 static DEFINE_SPINLOCK(safesetid_whitelist_hashtable_spinlock);
43
44 static bool check_setuid_policy_hashtable_key(kuid_t parent)
45 {
46 struct entry *entry;
47
48 rcu_read_lock();
49 hash_for_each_possible_rcu(safesetid_whitelist_hashtable,
50 entry, next, __kuid_val(parent)) {
51 if (entry->parent_kuid == __kuid_val(parent)) {
52 rcu_read_unlock();
53 return true;
54 }
55 }
56 rcu_read_unlock();
57
58 return false;
59 }
60
61 static bool check_setuid_policy_hashtable_key_value(kuid_t parent,
62 kuid_t child)
63 {
64 struct entry *entry;
65
66 rcu_read_lock();
67 hash_for_each_possible_rcu(safesetid_whitelist_hashtable,
68 entry, next, __kuid_val(parent)) {
69 if (entry->parent_kuid == __kuid_val(parent) &&
70 entry->child_kuid == __kuid_val(child)) {
71 rcu_read_unlock();
72 return true;
73 }
74 }
75 rcu_read_unlock();
76
77 return false;
78 }
79
80 static int safesetid_security_capable(const struct cred *cred,
81 struct user_namespace *ns,
82 int cap,
83 unsigned int opts)
84 {
85 if (cap == CAP_SETUID &&
86 check_setuid_policy_hashtable_key(cred->uid)) {
87 if (!(opts & CAP_OPT_INSETID)) {
88 /*
89 * Deny if we're not in a set*uid() syscall to avoid
90 * giving powers gated by CAP_SETUID that are related
91 * to functionality other than calling set*uid() (e.g.
92 * allowing user to set up userns uid mappings).
93 */
94 pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions",
95 __kuid_val(cred->uid));
96 return -1;
97 }
98 }
99 return 0;
100 }
101
102 static int check_uid_transition(kuid_t parent, kuid_t child)
103 {
104 if (check_setuid_policy_hashtable_key_value(parent, child))
105 return 0;
106 pr_warn("UID transition (%d -> %d) blocked",
107 __kuid_val(parent),
108 __kuid_val(child));
109 /*
110 * Kill this process to avoid potential security vulnerabilities
111 * that could arise from a missing whitelist entry preventing a
112 * privileged process from dropping to a lesser-privileged one.
113 */
114 force_sig(SIGKILL);
115 return -EACCES;
116 }
117
118 /*
119 * Check whether there is either an exception for user under old cred struct to
120 * set*uid to user under new cred struct, or the UID transition is allowed (by
121 * Linux set*uid rules) even without CAP_SETUID.
122 */
123 static int safesetid_task_fix_setuid(struct cred *new,
124 const struct cred *old,
125 int flags)
126 {
127
128 /* Do nothing if there are no setuid restrictions for this UID. */
129 if (!check_setuid_policy_hashtable_key(old->uid))
130 return 0;
131
132 switch (flags) {
133 case LSM_SETID_RE:
134 /*
135 * Users for which setuid restrictions exist can only set the
136 * real UID to the real UID or the effective UID, unless an
137 * explicit whitelist policy allows the transition.
138 */
139 if (!uid_eq(old->uid, new->uid) &&
140 !uid_eq(old->euid, new->uid)) {
141 return check_uid_transition(old->uid, new->uid);
142 }
143 /*
144 * Users for which setuid restrictions exist can only set the
145 * effective UID to the real UID, the effective UID, or the
146 * saved set-UID, unless an explicit whitelist policy allows
147 * the transition.
148 */
149 if (!uid_eq(old->uid, new->euid) &&
150 !uid_eq(old->euid, new->euid) &&
151 !uid_eq(old->suid, new->euid)) {
152 return check_uid_transition(old->euid, new->euid);
153 }
154 break;
155 case LSM_SETID_ID:
156 /*
157 * Users for which setuid restrictions exist cannot change the
158 * real UID or saved set-UID unless an explicit whitelist
159 * policy allows the transition.
160 */
161 if (!uid_eq(old->uid, new->uid))
162 return check_uid_transition(old->uid, new->uid);
163 if (!uid_eq(old->suid, new->suid))
164 return check_uid_transition(old->suid, new->suid);
165 break;
166 case LSM_SETID_RES:
167 /*
168 * Users for which setuid restrictions exist cannot change the
169 * real UID, effective UID, or saved set-UID to anything but
170 * one of: the current real UID, the current effective UID or
171 * the current saved set-user-ID unless an explicit whitelist
172 * policy allows the transition.
173 */
174 if (!uid_eq(new->uid, old->uid) &&
175 !uid_eq(new->uid, old->euid) &&
176 !uid_eq(new->uid, old->suid)) {
177 return check_uid_transition(old->uid, new->uid);
178 }
179 if (!uid_eq(new->euid, old->uid) &&
180 !uid_eq(new->euid, old->euid) &&
181 !uid_eq(new->euid, old->suid)) {
182 return check_uid_transition(old->euid, new->euid);
183 }
184 if (!uid_eq(new->suid, old->uid) &&
185 !uid_eq(new->suid, old->euid) &&
186 !uid_eq(new->suid, old->suid)) {
187 return check_uid_transition(old->suid, new->suid);
188 }
189 break;
190 case LSM_SETID_FS:
191 /*
192 * Users for which setuid restrictions exist cannot change the
193 * filesystem UID to anything but one of: the current real UID,
194 * the current effective UID or the current saved set-UID
195 * unless an explicit whitelist policy allows the transition.
196 */
197 if (!uid_eq(new->fsuid, old->uid) &&
198 !uid_eq(new->fsuid, old->euid) &&
199 !uid_eq(new->fsuid, old->suid) &&
200 !uid_eq(new->fsuid, old->fsuid)) {
201 return check_uid_transition(old->fsuid, new->fsuid);
202 }
203 break;
204 default:
205 pr_warn("Unknown setid state %d\n", flags);
206 force_sig(SIGKILL);
207 return -EINVAL;
208 }
209 return 0;
210 }
211
212 int add_safesetid_whitelist_entry(kuid_t parent, kuid_t child)
213 {
214 struct entry *new;
215
216 /* Return if entry already exists */
217 if (check_setuid_policy_hashtable_key_value(parent, child))
218 return 0;
219
220 new = kzalloc(sizeof(struct entry), GFP_KERNEL);
221 if (!new)
222 return -ENOMEM;
223 new->parent_kuid = __kuid_val(parent);
224 new->child_kuid = __kuid_val(child);
225 spin_lock(&safesetid_whitelist_hashtable_spinlock);
226 hash_add_rcu(safesetid_whitelist_hashtable,
227 &new->next,
228 __kuid_val(parent));
229 spin_unlock(&safesetid_whitelist_hashtable_spinlock);
230 return 0;
231 }
232
233 void flush_safesetid_whitelist_entries(void)
234 {
235 struct entry *entry;
236 struct hlist_node *hlist_node;
237 unsigned int bkt_loop_cursor;
238 HLIST_HEAD(free_list);
239
240 /*
241 * Could probably use hash_for_each_rcu here instead, but this should
242 * be fine as well.
243 */
244 spin_lock(&safesetid_whitelist_hashtable_spinlock);
245 hash_for_each_safe(safesetid_whitelist_hashtable, bkt_loop_cursor,
246 hlist_node, entry, next) {
247 hash_del_rcu(&entry->next);
248 hlist_add_head(&entry->dlist, &free_list);
249 }
250 spin_unlock(&safesetid_whitelist_hashtable_spinlock);
251 synchronize_rcu();
252 hlist_for_each_entry_safe(entry, hlist_node, &free_list, dlist) {
253 hlist_del(&entry->dlist);
254 kfree(entry);
255 }
256 }
257
258 static struct security_hook_list safesetid_security_hooks[] = {
259 LSM_HOOK_INIT(task_fix_setuid, safesetid_task_fix_setuid),
260 LSM_HOOK_INIT(capable, safesetid_security_capable)
261 };
262
263 static int __init safesetid_security_init(void)
264 {
265 security_add_hooks(safesetid_security_hooks,
266 ARRAY_SIZE(safesetid_security_hooks), "safesetid");
267
268 /* Report that SafeSetID successfully initialized */
269 safesetid_initialized = 1;
270
271 return 0;
272 }
273
274 DEFINE_LSM(safesetid_security_init) = {
275 .init = safesetid_security_init,
276 .name = "safesetid",
277 };