aboutsummaryrefslogtreecommitdiffstats
path: root/security/safesetid/securityfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/safesetid/securityfs.c')
-rw-r--r--security/safesetid/securityfs.c307
1 files changed, 187 insertions, 120 deletions
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);