1 // SPDX-License-Identifier: GPL-2.0
3 * SafeSetID Linux Security Module
5 * Author: Micah Morton <mortonm@chromium.org>
7 * Copyright (C) 2018 The Chromium OS Authors.
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.
15 #define pr_fmt(fmt) "SafeSetID: " fmt
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>
24 /* Flag indicating whether initialization completed */
25 int safesetid_initialized;
27 #define NUM_BITS 8 /* 256 buckets in hash table */
29 static DEFINE_HASHTABLE(safesetid_whitelist_hashtable, NUM_BITS);
31 static DEFINE_SPINLOCK(safesetid_whitelist_hashtable_spinlock);
33 static enum sid_policy_type setuid_policy_lookup(kuid_t src, kuid_t dst)
36 enum sid_policy_type result = SIDPOL_DEFAULT;
39 hash_for_each_possible_rcu(safesetid_whitelist_hashtable,
40 entry, next, __kuid_val(src)) {
41 if (!uid_eq(entry->src_uid, src))
43 if (uid_eq(entry->dst_uid, dst)) {
45 return SIDPOL_ALLOWED;
47 result = SIDPOL_CONSTRAINED;
53 static int safesetid_security_capable(const struct cred *cred,
54 struct user_namespace *ns,
58 if (cap == CAP_SETUID &&
59 setuid_policy_lookup(cred->uid, INVALID_UID) != SIDPOL_DEFAULT) {
60 if (!(opts & CAP_OPT_INSETID)) {
62 * Deny if we're not in a set*uid() syscall to avoid
63 * giving powers gated by CAP_SETUID that are related
64 * to functionality other than calling set*uid() (e.g.
65 * allowing user to set up userns uid mappings).
67 pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n",
68 __kuid_val(cred->uid));
76 * Check whether a caller with old credentials @old is allowed to switch to
77 * credentials that contain @new_uid.
79 static bool uid_permitted_for_cred(const struct cred *old, kuid_t new_uid)
83 /* If our old creds already had this UID in it, it's fine. */
84 if (uid_eq(new_uid, old->uid) || uid_eq(new_uid, old->euid) ||
85 uid_eq(new_uid, old->suid))
89 * Transitions to new UIDs require a check against the policy of the old
93 setuid_policy_lookup(old->uid, new_uid) != SIDPOL_CONSTRAINED;
95 pr_warn("UID transition ((%d,%d,%d) -> %d) blocked\n",
96 __kuid_val(old->uid), __kuid_val(old->euid),
97 __kuid_val(old->suid), __kuid_val(new_uid));
103 * Check whether there is either an exception for user under old cred struct to
104 * set*uid to user under new cred struct, or the UID transition is allowed (by
105 * Linux set*uid rules) even without CAP_SETUID.
107 static int safesetid_task_fix_setuid(struct cred *new,
108 const struct cred *old,
112 /* Do nothing if there are no setuid restrictions for our old RUID. */
113 if (setuid_policy_lookup(old->uid, INVALID_UID) == SIDPOL_DEFAULT)
116 if (uid_permitted_for_cred(old, new->uid) &&
117 uid_permitted_for_cred(old, new->euid) &&
118 uid_permitted_for_cred(old, new->suid) &&
119 uid_permitted_for_cred(old, new->fsuid))
123 * Kill this process to avoid potential security vulnerabilities
124 * that could arise from a missing whitelist entry preventing a
125 * privileged process from dropping to a lesser-privileged one.
131 int add_safesetid_whitelist_entry(kuid_t parent, kuid_t child)
135 /* Return if entry already exists */
136 if (setuid_policy_lookup(parent, child) == SIDPOL_ALLOWED)
139 new = kzalloc(sizeof(struct entry), GFP_KERNEL);
142 new->src_uid = parent;
143 new->dst_uid = child;
144 spin_lock(&safesetid_whitelist_hashtable_spinlock);
145 hash_add_rcu(safesetid_whitelist_hashtable,
148 spin_unlock(&safesetid_whitelist_hashtable_spinlock);
152 void flush_safesetid_whitelist_entries(void)
155 struct hlist_node *hlist_node;
156 unsigned int bkt_loop_cursor;
157 HLIST_HEAD(free_list);
160 * Could probably use hash_for_each_rcu here instead, but this should
163 spin_lock(&safesetid_whitelist_hashtable_spinlock);
164 hash_for_each_safe(safesetid_whitelist_hashtable, bkt_loop_cursor,
165 hlist_node, entry, next) {
166 hash_del_rcu(&entry->next);
167 hlist_add_head(&entry->dlist, &free_list);
169 spin_unlock(&safesetid_whitelist_hashtable_spinlock);
171 hlist_for_each_entry_safe(entry, hlist_node, &free_list, dlist) {
172 hlist_del(&entry->dlist);
177 static struct security_hook_list safesetid_security_hooks[] = {
178 LSM_HOOK_INIT(task_fix_setuid, safesetid_task_fix_setuid),
179 LSM_HOOK_INIT(capable, safesetid_security_capable)
182 static int __init safesetid_security_init(void)
184 security_add_hooks(safesetid_security_hooks,
185 ARRAY_SIZE(safesetid_security_hooks), "safesetid");
187 /* Report that SafeSetID successfully initialized */
188 safesetid_initialized = 1;
193 DEFINE_LSM(safesetid_security_init) = {
194 .init = safesetid_security_init,