Commit | Line | Data |
---|---|---|
aeca4e2c MM |
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 | ||
aeca4e2c MM |
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> | |
1cd02a27 | 22 | #include "lsm.h" |
aeca4e2c MM |
23 | |
24 | /* Flag indicating whether initialization completed */ | |
25 | int safesetid_initialized; | |
26 | ||
03638e62 | 27 | struct setuid_ruleset __rcu *safesetid_setuid_rules; |
aeca4e2c | 28 | |
03638e62 JH |
29 | /* Compute a decision for a transition from @src to @dst under @policy. */ |
30 | enum sid_policy_type _setuid_policy_lookup(struct setuid_ruleset *policy, | |
31 | kuid_t src, kuid_t dst) | |
aeca4e2c | 32 | { |
03638e62 | 33 | struct setuid_rule *rule; |
1cd02a27 | 34 | enum sid_policy_type result = SIDPOL_DEFAULT; |
aeca4e2c | 35 | |
03638e62 JH |
36 | hash_for_each_possible(policy->rules, rule, next, __kuid_val(src)) { |
37 | if (!uid_eq(rule->src_uid, src)) | |
1cd02a27 | 38 | continue; |
03638e62 | 39 | if (uid_eq(rule->dst_uid, dst)) |
1cd02a27 | 40 | return SIDPOL_ALLOWED; |
1cd02a27 | 41 | result = SIDPOL_CONSTRAINED; |
aeca4e2c | 42 | } |
03638e62 JH |
43 | return result; |
44 | } | |
45 | ||
46 | /* | |
47 | * Compute a decision for a transition from @src to @dst under the active | |
48 | * policy. | |
49 | */ | |
50 | static enum sid_policy_type setuid_policy_lookup(kuid_t src, kuid_t dst) | |
51 | { | |
52 | enum sid_policy_type result = SIDPOL_DEFAULT; | |
53 | struct setuid_ruleset *pol; | |
54 | ||
55 | rcu_read_lock(); | |
56 | pol = rcu_dereference(safesetid_setuid_rules); | |
57 | if (pol) | |
58 | result = _setuid_policy_lookup(pol, src, dst); | |
aeca4e2c | 59 | rcu_read_unlock(); |
1cd02a27 | 60 | return result; |
aeca4e2c MM |
61 | } |
62 | ||
63 | static int safesetid_security_capable(const struct cred *cred, | |
64 | struct user_namespace *ns, | |
65 | int cap, | |
66 | unsigned int opts) | |
67 | { | |
8068866c JH |
68 | /* We're only interested in CAP_SETUID. */ |
69 | if (cap != CAP_SETUID) | |
70 | return 0; | |
71 | ||
72 | /* | |
73 | * If CAP_SETUID is currently used for a set*uid() syscall, we want to | |
74 | * let it go through here; the real security check happens later, in the | |
75 | * task_fix_setuid hook. | |
76 | */ | |
77 | if ((opts & CAP_OPT_INSETID) != 0) | |
78 | return 0; | |
79 | ||
80 | /* | |
81 | * If no policy applies to this task, allow the use of CAP_SETUID for | |
82 | * other purposes. | |
83 | */ | |
84 | if (setuid_policy_lookup(cred->uid, INVALID_UID) == SIDPOL_DEFAULT) | |
85 | return 0; | |
86 | ||
87 | /* | |
88 | * Reject use of CAP_SETUID for functionality other than calling | |
89 | * set*uid() (e.g. setting up userns uid mappings). | |
90 | */ | |
91 | pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n", | |
92 | __kuid_val(cred->uid)); | |
e10337da | 93 | return -EPERM; |
aeca4e2c MM |
94 | } |
95 | ||
7ef6b306 JH |
96 | /* |
97 | * Check whether a caller with old credentials @old is allowed to switch to | |
98 | * credentials that contain @new_uid. | |
99 | */ | |
100 | static bool uid_permitted_for_cred(const struct cred *old, kuid_t new_uid) | |
aeca4e2c | 101 | { |
7ef6b306 JH |
102 | bool permitted; |
103 | ||
104 | /* If our old creds already had this UID in it, it's fine. */ | |
105 | if (uid_eq(new_uid, old->uid) || uid_eq(new_uid, old->euid) || | |
106 | uid_eq(new_uid, old->suid)) | |
107 | return true; | |
108 | ||
aeca4e2c | 109 | /* |
7ef6b306 JH |
110 | * Transitions to new UIDs require a check against the policy of the old |
111 | * RUID. | |
aeca4e2c | 112 | */ |
1cd02a27 JH |
113 | permitted = |
114 | setuid_policy_lookup(old->uid, new_uid) != SIDPOL_CONSTRAINED; | |
7ef6b306 JH |
115 | if (!permitted) { |
116 | pr_warn("UID transition ((%d,%d,%d) -> %d) blocked\n", | |
117 | __kuid_val(old->uid), __kuid_val(old->euid), | |
118 | __kuid_val(old->suid), __kuid_val(new_uid)); | |
119 | } | |
120 | return permitted; | |
aeca4e2c MM |
121 | } |
122 | ||
123 | /* | |
124 | * Check whether there is either an exception for user under old cred struct to | |
125 | * set*uid to user under new cred struct, or the UID transition is allowed (by | |
126 | * Linux set*uid rules) even without CAP_SETUID. | |
127 | */ | |
128 | static int safesetid_task_fix_setuid(struct cred *new, | |
129 | const struct cred *old, | |
130 | int flags) | |
131 | { | |
132 | ||
7ef6b306 | 133 | /* Do nothing if there are no setuid restrictions for our old RUID. */ |
1cd02a27 | 134 | if (setuid_policy_lookup(old->uid, INVALID_UID) == SIDPOL_DEFAULT) |
aeca4e2c MM |
135 | return 0; |
136 | ||
7ef6b306 JH |
137 | if (uid_permitted_for_cred(old, new->uid) && |
138 | uid_permitted_for_cred(old, new->euid) && | |
139 | uid_permitted_for_cred(old, new->suid) && | |
140 | uid_permitted_for_cred(old, new->fsuid)) | |
141 | return 0; | |
142 | ||
143 | /* | |
144 | * Kill this process to avoid potential security vulnerabilities | |
145 | * that could arise from a missing whitelist entry preventing a | |
146 | * privileged process from dropping to a lesser-privileged one. | |
147 | */ | |
148 | force_sig(SIGKILL); | |
149 | return -EACCES; | |
aeca4e2c MM |
150 | } |
151 | ||
aeca4e2c MM |
152 | static struct security_hook_list safesetid_security_hooks[] = { |
153 | LSM_HOOK_INIT(task_fix_setuid, safesetid_task_fix_setuid), | |
154 | LSM_HOOK_INIT(capable, safesetid_security_capable) | |
155 | }; | |
156 | ||
157 | static int __init safesetid_security_init(void) | |
158 | { | |
159 | security_add_hooks(safesetid_security_hooks, | |
160 | ARRAY_SIZE(safesetid_security_hooks), "safesetid"); | |
161 | ||
162 | /* Report that SafeSetID successfully initialized */ | |
163 | safesetid_initialized = 1; | |
164 | ||
165 | return 0; | |
166 | } | |
167 | ||
168 | DEFINE_LSM(safesetid_security_init) = { | |
169 | .init = safesetid_security_init, | |
f67e20d2 | 170 | .name = "safesetid", |
aeca4e2c | 171 | }; |