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, current); 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, current); 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 }; 278