aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2019-07-16 14:49:58 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2019-07-16 14:49:58 -0400
commit1ec4013bab89058dcc594dfe7b5a20f5d46bbc5f (patch)
tree4db305a87f3a36bea2a2296557faaec76cdfb9e6
parent3c69914b4c7b0b72ff0275c14743778057ee8a6e (diff)
parente10337daefecb47209fd2af5f4fab0d1a370737f (diff)
Merge tag 'safesetid-5.3' of git://github.com/micah-morton/linux
Pull safesetid updates from Micah Morton: "These changes from Jann Horn fix a couple issues in the recently added SafeSetID LSM: - There was a simple logic bug in one of the hooks for the LSM where the code was incorrectly returning early in some cases before all security checks had been passed. - There was a more high level issue with how this LSM gets configured that could allow for a program to bypass the security restrictions by switching to an allowed UID and then again to any other UID on the system if the target UID of the first transition is unconstrained on the system. Luckily this is an easy fix that we now enforce at the time the LSM gets configured. There are also some changes from Jann that make policy updates for this LSM atomic. Kees Cook, Jann and myself have reviewed these changes and they look good from our point of view" * tag 'safesetid-5.3' of git://github.com/micah-morton/linux: LSM: SafeSetID: fix use of literal -1 in capable hook LSM: SafeSetID: verify transitive constrainedness LSM: SafeSetID: add read handler LSM: SafeSetID: rewrite userspace API to atomic updates LSM: SafeSetID: fix userns handling in securityfs LSM: SafeSetID: refactor policy parsing LSM: SafeSetID: refactor safesetid_security_capable() LSM: SafeSetID: refactor policy hash table LSM: SafeSetID: fix check for setresuid(new1, new2, new3) LSM: SafeSetID: fix pr_warn() to include newline
-rw-r--r--security/safesetid/lsm.c276
-rw-r--r--security/safesetid/lsm.h34
-rw-r--r--security/safesetid/securityfs.c307
-rw-r--r--tools/testing/selftests/safesetid/safesetid-test.c18
4 files changed, 306 insertions, 329 deletions
diff --git a/security/safesetid/lsm.c b/security/safesetid/lsm.c
index 06d4259f9ab1..7760019ad35d 100644
--- a/security/safesetid/lsm.c
+++ b/security/safesetid/lsm.c
@@ -14,67 +14,50 @@
14 14
15#define pr_fmt(fmt) "SafeSetID: " fmt 15#define pr_fmt(fmt) "SafeSetID: " fmt
16 16
17#include <linux/hashtable.h>
18#include <linux/lsm_hooks.h> 17#include <linux/lsm_hooks.h>
19#include <linux/module.h> 18#include <linux/module.h>
20#include <linux/ptrace.h> 19#include <linux/ptrace.h>
21#include <linux/sched/task_stack.h> 20#include <linux/sched/task_stack.h>
22#include <linux/security.h> 21#include <linux/security.h>
22#include "lsm.h"
23 23
24/* Flag indicating whether initialization completed */ 24/* Flag indicating whether initialization completed */
25int safesetid_initialized; 25int safesetid_initialized;
26 26
27#define NUM_BITS 8 /* 128 buckets in hash table */ 27struct setuid_ruleset __rcu *safesetid_setuid_rules;
28 28
29static DEFINE_HASHTABLE(safesetid_whitelist_hashtable, NUM_BITS); 29/* Compute a decision for a transition from @src to @dst under @policy. */
30 30enum sid_policy_type _setuid_policy_lookup(struct setuid_ruleset *policy,
31/* 31 kuid_t src, kuid_t dst)
32 * Hash table entry to store safesetid policy signifying that 'parent' user
33 * can setid to 'child' user.
34 */
35struct 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
42static DEFINE_SPINLOCK(safesetid_whitelist_hashtable_spinlock);
43
44static bool check_setuid_policy_hashtable_key(kuid_t parent)
45{ 32{
46 struct entry *entry; 33 struct setuid_rule *rule;
47 34 enum sid_policy_type result = SIDPOL_DEFAULT;
48 rcu_read_lock(); 35
49 hash_for_each_possible_rcu(safesetid_whitelist_hashtable, 36 hash_for_each_possible(policy->rules, rule, next, __kuid_val(src)) {
50 entry, next, __kuid_val(parent)) { 37 if (!uid_eq(rule->src_uid, src))
51 if (entry->parent_kuid == __kuid_val(parent)) { 38 continue;
52 rcu_read_unlock(); 39 if (uid_eq(rule->dst_uid, dst))
53 return true; 40 return SIDPOL_ALLOWED;
54 } 41 result = SIDPOL_CONSTRAINED;
55 } 42 }
56 rcu_read_unlock(); 43 return result;
57
58 return false;
59} 44}
60 45
61static bool check_setuid_policy_hashtable_key_value(kuid_t parent, 46/*
62 kuid_t child) 47 * Compute a decision for a transition from @src to @dst under the active
48 * policy.
49 */
50static enum sid_policy_type setuid_policy_lookup(kuid_t src, kuid_t dst)
63{ 51{
64 struct entry *entry; 52 enum sid_policy_type result = SIDPOL_DEFAULT;
53 struct setuid_ruleset *pol;
65 54
66 rcu_read_lock(); 55 rcu_read_lock();
67 hash_for_each_possible_rcu(safesetid_whitelist_hashtable, 56 pol = rcu_dereference(safesetid_setuid_rules);
68 entry, next, __kuid_val(parent)) { 57 if (pol)
69 if (entry->parent_kuid == __kuid_val(parent) && 58 result = _setuid_policy_lookup(pol, src, dst);
70 entry->child_kuid == __kuid_val(child)) {
71 rcu_read_unlock();
72 return true;
73 }
74 }
75 rcu_read_unlock(); 59 rcu_read_unlock();
76 60 return result;
77 return false;
78} 61}
79 62
80static int safesetid_security_capable(const struct cred *cred, 63static int safesetid_security_capable(const struct cred *cred,
@@ -82,37 +65,59 @@ static int safesetid_security_capable(const struct cred *cred,
82 int cap, 65 int cap,
83 unsigned int opts) 66 unsigned int opts)
84{ 67{
85 if (cap == CAP_SETUID && 68 /* We're only interested in CAP_SETUID. */
86 check_setuid_policy_hashtable_key(cred->uid)) { 69 if (cap != CAP_SETUID)
87 if (!(opts & CAP_OPT_INSETID)) { 70 return 0;
88 /* 71
89 * Deny if we're not in a set*uid() syscall to avoid 72 /*
90 * giving powers gated by CAP_SETUID that are related 73 * If CAP_SETUID is currently used for a set*uid() syscall, we want to
91 * to functionality other than calling set*uid() (e.g. 74 * let it go through here; the real security check happens later, in the
92 * allowing user to set up userns uid mappings). 75 * task_fix_setuid hook.
93 */ 76 */
94 pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions", 77 if ((opts & CAP_OPT_INSETID) != 0)
95 __kuid_val(cred->uid)); 78 return 0;
96 return -1; 79
97 } 80 /*
98 } 81 * If no policy applies to this task, allow the use of CAP_SETUID for
99 return 0; 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));
93 return -EPERM;
100} 94}
101 95
102static int check_uid_transition(kuid_t parent, kuid_t child) 96/*
97 * Check whether a caller with old credentials @old is allowed to switch to
98 * credentials that contain @new_uid.
99 */
100static bool uid_permitted_for_cred(const struct cred *old, kuid_t new_uid)
103{ 101{
104 if (check_setuid_policy_hashtable_key_value(parent, child)) 102 bool permitted;
105 return 0; 103
106 pr_warn("UID transition (%d -> %d) blocked", 104 /* If our old creds already had this UID in it, it's fine. */
107 __kuid_val(parent), 105 if (uid_eq(new_uid, old->uid) || uid_eq(new_uid, old->euid) ||
108 __kuid_val(child)); 106 uid_eq(new_uid, old->suid))
107 return true;
108
109 /* 109 /*
110 * Kill this process to avoid potential security vulnerabilities 110 * Transitions to new UIDs require a check against the policy of the old
111 * that could arise from a missing whitelist entry preventing a 111 * RUID.
112 * privileged process from dropping to a lesser-privileged one.
113 */ 112 */
114 force_sig(SIGKILL); 113 permitted =
115 return -EACCES; 114 setuid_policy_lookup(old->uid, new_uid) != SIDPOL_CONSTRAINED;
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;
116} 121}
117 122
118/* 123/*
@@ -125,134 +130,23 @@ static int safesetid_task_fix_setuid(struct cred *new,
125 int flags) 130 int flags)
126{ 131{
127 132
128 /* Do nothing if there are no setuid restrictions for this UID. */ 133 /* Do nothing if there are no setuid restrictions for our old RUID. */
129 if (!check_setuid_policy_hashtable_key(old->uid)) 134 if (setuid_policy_lookup(old->uid, INVALID_UID) == SIDPOL_DEFAULT)
130 return 0; 135 return 0;
131 136
132 switch (flags) { 137 if (uid_permitted_for_cred(old, new->uid) &&
133 case LSM_SETID_RE: 138 uid_permitted_for_cred(old, new->euid) &&
134 /* 139 uid_permitted_for_cred(old, new->suid) &&
135 * Users for which setuid restrictions exist can only set the 140 uid_permitted_for_cred(old, new->fsuid))
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
212int 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; 141 return 0;
219 142
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
233void 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 /* 143 /*
241 * Could probably use hash_for_each_rcu here instead, but this should 144 * Kill this process to avoid potential security vulnerabilities
242 * be fine as well. 145 * that could arise from a missing whitelist entry preventing a
146 * privileged process from dropping to a lesser-privileged one.
243 */ 147 */
244 spin_lock(&safesetid_whitelist_hashtable_spinlock); 148 force_sig(SIGKILL);
245 hash_for_each_safe(safesetid_whitelist_hashtable, bkt_loop_cursor, 149 return -EACCES;
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} 150}
257 151
258static struct security_hook_list safesetid_security_hooks[] = { 152static struct security_hook_list safesetid_security_hooks[] = {
diff --git a/security/safesetid/lsm.h b/security/safesetid/lsm.h
index c1ea3c265fcf..db6d16e6bbc3 100644
--- a/security/safesetid/lsm.h
+++ b/security/safesetid/lsm.h
@@ -15,19 +15,39 @@
15#define _SAFESETID_H 15#define _SAFESETID_H
16 16
17#include <linux/types.h> 17#include <linux/types.h>
18#include <linux/uidgid.h>
19#include <linux/hashtable.h>
18 20
19/* Flag indicating whether initialization completed */ 21/* Flag indicating whether initialization completed */
20extern int safesetid_initialized; 22extern int safesetid_initialized;
21 23
22/* Function type. */ 24enum sid_policy_type {
23enum safesetid_whitelist_file_write_type { 25 SIDPOL_DEFAULT, /* source ID is unaffected by policy */
24 SAFESETID_WHITELIST_ADD, /* Add whitelist policy. */ 26 SIDPOL_CONSTRAINED, /* source ID is affected by policy */
25 SAFESETID_WHITELIST_FLUSH, /* Flush whitelist policies. */ 27 SIDPOL_ALLOWED /* target ID explicitly allowed */
26}; 28};
27 29
28/* Add entry to safesetid whitelist to allow 'parent' to setid to 'child'. */ 30/*
29int add_safesetid_whitelist_entry(kuid_t parent, kuid_t child); 31 * Hash table entry to store safesetid policy signifying that 'src_uid'
32 * can setuid to 'dst_uid'.
33 */
34struct setuid_rule {
35 struct hlist_node next;
36 kuid_t src_uid;
37 kuid_t dst_uid;
38};
39
40#define SETID_HASH_BITS 8 /* 256 buckets in hash table */
41
42struct setuid_ruleset {
43 DECLARE_HASHTABLE(rules, SETID_HASH_BITS);
44 char *policy_str;
45 struct rcu_head rcu;
46};
47
48enum sid_policy_type _setuid_policy_lookup(struct setuid_ruleset *policy,
49 kuid_t src, kuid_t dst);
30 50
31void flush_safesetid_whitelist_entries(void); 51extern struct setuid_ruleset __rcu *safesetid_setuid_rules;
32 52
33#endif /* _SAFESETID_H */ 53#endif /* _SAFESETID_H */
diff --git a/security/safesetid/securityfs.c b/security/safesetid/securityfs.c
index 2c6c829be044..d568e17dd773 100644
--- a/security/safesetid/securityfs.c
+++ b/security/safesetid/securityfs.c
@@ -11,92 +11,184 @@
11 * published by the Free Software Foundation. 11 * published by the Free Software Foundation.
12 * 12 *
13 */ 13 */
14
15#define pr_fmt(fmt) "SafeSetID: " fmt
16
14#include <linux/security.h> 17#include <linux/security.h>
15#include <linux/cred.h> 18#include <linux/cred.h>
16 19
17#include "lsm.h" 20#include "lsm.h"
18 21
19static struct dentry *safesetid_policy_dir; 22static DEFINE_MUTEX(policy_update_lock);
20
21struct safesetid_file_entry {
22 const char *name;
23 enum safesetid_whitelist_file_write_type type;
24 struct dentry *dentry;
25};
26
27static struct safesetid_file_entry safesetid_files[] = {
28 {.name = "add_whitelist_policy",
29 .type = SAFESETID_WHITELIST_ADD},
30 {.name = "flush_whitelist_policies",
31 .type = SAFESETID_WHITELIST_FLUSH},
32};
33 23
34/* 24/*
35 * In the case the input buffer contains one or more invalid UIDs, the kuid_t 25 * In the case the input buffer contains one or more invalid UIDs, the kuid_t
36 * variables pointed to by 'parent' and 'child' will get updated but this 26 * variables pointed to by @parent and @child will get updated but this
37 * function will return an error. 27 * function will return an error.
28 * Contents of @buf may be modified.
38 */ 29 */
39static int parse_safesetid_whitelist_policy(const char __user *buf, 30static int parse_policy_line(struct file *file, char *buf,
40 size_t len, 31 struct setuid_rule *rule)
41 kuid_t *parent,
42 kuid_t *child)
43{ 32{
44 char *kern_buf; 33 char *child_str;
45 char *parent_buf;
46 char *child_buf;
47 const char separator[] = ":";
48 int ret; 34 int ret;
49 size_t first_substring_length; 35 u32 parsed_parent, parsed_child;
50 long parsed_parent;
51 long parsed_child;
52 36
53 /* Duplicate string from user memory and NULL-terminate */ 37 /* Format of |buf| string should be <UID>:<UID>. */
54 kern_buf = memdup_user_nul(buf, len); 38 child_str = strchr(buf, ':');
55 if (IS_ERR(kern_buf)) 39 if (child_str == NULL)
56 return PTR_ERR(kern_buf); 40 return -EINVAL;
41 *child_str = '\0';
42 child_str++;
57 43
58 /* 44 ret = kstrtou32(buf, 0, &parsed_parent);
59 * Format of |buf| string should be <UID>:<UID>. 45 if (ret)
60 * Find location of ":" in kern_buf (copied from |buf|). 46 return ret;
61 */ 47
62 first_substring_length = strcspn(kern_buf, separator); 48 ret = kstrtou32(child_str, 0, &parsed_child);
63 if (first_substring_length == 0 || first_substring_length == len) { 49 if (ret)
64 ret = -EINVAL; 50 return ret;
65 goto free_kern;
66 }
67 51
68 parent_buf = kmemdup_nul(kern_buf, first_substring_length, GFP_KERNEL); 52 rule->src_uid = make_kuid(file->f_cred->user_ns, parsed_parent);
69 if (!parent_buf) { 53 rule->dst_uid = make_kuid(file->f_cred->user_ns, parsed_child);
70 ret = -ENOMEM; 54 if (!uid_valid(rule->src_uid) || !uid_valid(rule->dst_uid))
71 goto free_kern; 55 return -EINVAL;
56
57 return 0;
58}
59
60static void __release_ruleset(struct rcu_head *rcu)
61{
62 struct setuid_ruleset *pol =
63 container_of(rcu, struct setuid_ruleset, rcu);
64 int bucket;
65 struct setuid_rule *rule;
66 struct hlist_node *tmp;
67
68 hash_for_each_safe(pol->rules, bucket, tmp, rule, next)
69 kfree(rule);
70 kfree(pol->policy_str);
71 kfree(pol);
72}
73
74static void release_ruleset(struct setuid_ruleset *pol)
75{
76 call_rcu(&pol->rcu, __release_ruleset);
77}
78
79static void insert_rule(struct setuid_ruleset *pol, struct setuid_rule *rule)
80{
81 hash_add(pol->rules, &rule->next, __kuid_val(rule->src_uid));
82}
83
84static int verify_ruleset(struct setuid_ruleset *pol)
85{
86 int bucket;
87 struct setuid_rule *rule, *nrule;
88 int res = 0;
89
90 hash_for_each(pol->rules, bucket, rule, next) {
91 if (_setuid_policy_lookup(pol, rule->dst_uid, INVALID_UID) ==
92 SIDPOL_DEFAULT) {
93 pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n",
94 __kuid_val(rule->src_uid),
95 __kuid_val(rule->dst_uid));
96 res = -EINVAL;
97
98 /* fix it up */
99 nrule = kmalloc(sizeof(struct setuid_rule), GFP_KERNEL);
100 if (!nrule)
101 return -ENOMEM;
102 nrule->src_uid = rule->dst_uid;
103 nrule->dst_uid = rule->dst_uid;
104 insert_rule(pol, nrule);
105 }
72 } 106 }
107 return res;
108}
73 109
74 ret = kstrtol(parent_buf, 0, &parsed_parent); 110static ssize_t handle_policy_update(struct file *file,
75 if (ret) 111 const char __user *ubuf, size_t len)
76 goto free_both; 112{
113 struct setuid_ruleset *pol;
114 char *buf, *p, *end;
115 int err;
77 116
78 child_buf = kern_buf + first_substring_length + 1; 117 pol = kmalloc(sizeof(struct setuid_ruleset), GFP_KERNEL);
79 ret = kstrtol(child_buf, 0, &parsed_child); 118 if (!pol)
80 if (ret) 119 return -ENOMEM;
81 goto free_both; 120 pol->policy_str = NULL;
121 hash_init(pol->rules);
82 122
83 *parent = make_kuid(current_user_ns(), parsed_parent); 123 p = buf = memdup_user_nul(ubuf, len);
84 if (!uid_valid(*parent)) { 124 if (IS_ERR(buf)) {
85 ret = -EINVAL; 125 err = PTR_ERR(buf);
86 goto free_both; 126 goto out_free_pol;
87 } 127 }
128 pol->policy_str = kstrdup(buf, GFP_KERNEL);
129 if (pol->policy_str == NULL) {
130 err = -ENOMEM;
131 goto out_free_buf;
132 }
133
134 /* policy lines, including the last one, end with \n */
135 while (*p != '\0') {
136 struct setuid_rule *rule;
137
138 end = strchr(p, '\n');
139 if (end == NULL) {
140 err = -EINVAL;
141 goto out_free_buf;
142 }
143 *end = '\0';
144
145 rule = kmalloc(sizeof(struct setuid_rule), GFP_KERNEL);
146 if (!rule) {
147 err = -ENOMEM;
148 goto out_free_buf;
149 }
88 150
89 *child = make_kuid(current_user_ns(), parsed_child); 151 err = parse_policy_line(file, p, rule);
90 if (!uid_valid(*child)) { 152 if (err)
91 ret = -EINVAL; 153 goto out_free_rule;
92 goto free_both; 154
155 if (_setuid_policy_lookup(pol, rule->src_uid, rule->dst_uid) ==
156 SIDPOL_ALLOWED) {
157 pr_warn("bad policy: duplicate entry\n");
158 err = -EEXIST;
159 goto out_free_rule;
160 }
161
162 insert_rule(pol, rule);
163 p = end + 1;
164 continue;
165
166out_free_rule:
167 kfree(rule);
168 goto out_free_buf;
93 } 169 }
94 170
95free_both: 171 err = verify_ruleset(pol);
96 kfree(parent_buf); 172 /* bogus policy falls through after fixing it up */
97free_kern: 173 if (err && err != -EINVAL)
98 kfree(kern_buf); 174 goto out_free_buf;
99 return ret; 175
176 /*
177 * Everything looks good, apply the policy and release the old one.
178 * What we really want here is an xchg() wrapper for RCU, but since that
179 * doesn't currently exist, just use a spinlock for now.
180 */
181 mutex_lock(&policy_update_lock);
182 rcu_swap_protected(safesetid_setuid_rules, pol,
183 lockdep_is_held(&policy_update_lock));
184 mutex_unlock(&policy_update_lock);
185 err = len;
186
187out_free_buf:
188 kfree(buf);
189out_free_pol:
190 release_ruleset(pol);
191 return err;
100} 192}
101 193
102static ssize_t safesetid_file_write(struct file *file, 194static ssize_t safesetid_file_write(struct file *file,
@@ -104,90 +196,65 @@ static ssize_t safesetid_file_write(struct file *file,
104 size_t len, 196 size_t len,
105 loff_t *ppos) 197 loff_t *ppos)
106{ 198{
107 struct safesetid_file_entry *file_entry = 199 if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
108 file->f_inode->i_private;
109 kuid_t parent;
110 kuid_t child;
111 int ret;
112
113 if (!ns_capable(current_user_ns(), CAP_MAC_ADMIN))
114 return -EPERM; 200 return -EPERM;
115 201
116 if (*ppos != 0) 202 if (*ppos != 0)
117 return -EINVAL; 203 return -EINVAL;
118 204
119 switch (file_entry->type) { 205 return handle_policy_update(file, buf, len);
120 case SAFESETID_WHITELIST_FLUSH:
121 flush_safesetid_whitelist_entries();
122 break;
123 case SAFESETID_WHITELIST_ADD:
124 ret = parse_safesetid_whitelist_policy(buf, len, &parent,
125 &child);
126 if (ret)
127 return ret;
128
129 ret = add_safesetid_whitelist_entry(parent, child);
130 if (ret)
131 return ret;
132 break;
133 default:
134 pr_warn("Unknown securityfs file %d\n", file_entry->type);
135 break;
136 }
137
138 /* Return len on success so caller won't keep trying to write */
139 return len;
140} 206}
141 207
142static const struct file_operations safesetid_file_fops = { 208static ssize_t safesetid_file_read(struct file *file, char __user *buf,
143 .write = safesetid_file_write, 209 size_t len, loff_t *ppos)
144};
145
146static void safesetid_shutdown_securityfs(void)
147{ 210{
148 int i; 211 ssize_t res = 0;
212 struct setuid_ruleset *pol;
213 const char *kbuf;
149 214
150 for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) { 215 mutex_lock(&policy_update_lock);
151 struct safesetid_file_entry *entry = 216 pol = rcu_dereference_protected(safesetid_setuid_rules,
152 &safesetid_files[i]; 217 lockdep_is_held(&policy_update_lock));
153 securityfs_remove(entry->dentry); 218 if (pol) {
154 entry->dentry = NULL; 219 kbuf = pol->policy_str;
220 res = simple_read_from_buffer(buf, len, ppos,
221 kbuf, strlen(kbuf));
155 } 222 }
156 223 mutex_unlock(&policy_update_lock);
157 securityfs_remove(safesetid_policy_dir); 224 return res;
158 safesetid_policy_dir = NULL;
159} 225}
160 226
227static const struct file_operations safesetid_file_fops = {
228 .read = safesetid_file_read,
229 .write = safesetid_file_write,
230};
231
161static int __init safesetid_init_securityfs(void) 232static int __init safesetid_init_securityfs(void)
162{ 233{
163 int i;
164 int ret; 234 int ret;
235 struct dentry *policy_dir;
236 struct dentry *policy_file;
165 237
166 if (!safesetid_initialized) 238 if (!safesetid_initialized)
167 return 0; 239 return 0;
168 240
169 safesetid_policy_dir = securityfs_create_dir("safesetid", NULL); 241 policy_dir = securityfs_create_dir("safesetid", NULL);
170 if (IS_ERR(safesetid_policy_dir)) { 242 if (IS_ERR(policy_dir)) {
171 ret = PTR_ERR(safesetid_policy_dir); 243 ret = PTR_ERR(policy_dir);
172 goto error; 244 goto error;
173 } 245 }
174 246
175 for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) { 247 policy_file = securityfs_create_file("whitelist_policy", 0600,
176 struct safesetid_file_entry *entry = 248 policy_dir, NULL, &safesetid_file_fops);
177 &safesetid_files[i]; 249 if (IS_ERR(policy_file)) {
178 entry->dentry = securityfs_create_file( 250 ret = PTR_ERR(policy_file);
179 entry->name, 0200, safesetid_policy_dir, 251 goto error;
180 entry, &safesetid_file_fops);
181 if (IS_ERR(entry->dentry)) {
182 ret = PTR_ERR(entry->dentry);
183 goto error;
184 }
185 } 252 }
186 253
187 return 0; 254 return 0;
188 255
189error: 256error:
190 safesetid_shutdown_securityfs(); 257 securityfs_remove(policy_dir);
191 return ret; 258 return ret;
192} 259}
193fs_initcall(safesetid_init_securityfs); 260fs_initcall(safesetid_init_securityfs);
diff --git a/tools/testing/selftests/safesetid/safesetid-test.c b/tools/testing/selftests/safesetid/safesetid-test.c
index 892c8e8b1b8b..8f40c6ecdad1 100644
--- a/tools/testing/selftests/safesetid/safesetid-test.c
+++ b/tools/testing/selftests/safesetid/safesetid-test.c
@@ -142,23 +142,19 @@ static void ensure_securityfs_mounted(void)
142 142
143static void write_policies(void) 143static void write_policies(void)
144{ 144{
145 static char *policy_str =
146 "1:2\n"
147 "1:3\n"
148 "2:2\n"
149 "3:3\n";
145 ssize_t written; 150 ssize_t written;
146 int fd; 151 int fd;
147 152
148 fd = open(add_whitelist_policy_file, O_WRONLY); 153 fd = open(add_whitelist_policy_file, O_WRONLY);
149 if (fd < 0) 154 if (fd < 0)
150 die("cant open add_whitelist_policy file\n"); 155 die("cant open add_whitelist_policy file\n");
151 written = write(fd, "1:2", strlen("1:2")); 156 written = write(fd, policy_str, strlen(policy_str));
152 if (written != strlen("1:2")) { 157 if (written != strlen(policy_str)) {
153 if (written >= 0) {
154 die("short write to %s\n", add_whitelist_policy_file);
155 } else {
156 die("write to %s failed: %s\n",
157 add_whitelist_policy_file, strerror(errno));
158 }
159 }
160 written = write(fd, "1:3", strlen("1:3"));
161 if (written != strlen("1:3")) {
162 if (written >= 0) { 158 if (written >= 0) {
163 die("short write to %s\n", add_whitelist_policy_file); 159 die("short write to %s\n", add_whitelist_policy_file);
164 } else { 160 } else {