~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/security/safesetid/securityfs.c

Version: ~ [ linux-6.11-rc3 ] ~ [ linux-6.10.4 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.45 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.104 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.164 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.223 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.281 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.319 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  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/security.h>
 18 #include <linux/cred.h>
 19 
 20 #include "lsm.h"
 21 
 22 static DEFINE_MUTEX(uid_policy_update_lock);
 23 static DEFINE_MUTEX(gid_policy_update_lock);
 24 
 25 /*
 26  * In the case the input buffer contains one or more invalid IDs, the kid_t
 27  * variables pointed to by @parent and @child will get updated but this
 28  * function will return an error.
 29  * Contents of @buf may be modified.
 30  */
 31 static int parse_policy_line(struct file *file, char *buf,
 32         struct setid_rule *rule)
 33 {
 34         char *child_str;
 35         int ret;
 36         u32 parsed_parent, parsed_child;
 37 
 38         /* Format of |buf| string should be <UID>:<UID> or <GID>:<GID> */
 39         child_str = strchr(buf, ':');
 40         if (child_str == NULL)
 41                 return -EINVAL;
 42         *child_str = '\0';
 43         child_str++;
 44 
 45         ret = kstrtou32(buf, 0, &parsed_parent);
 46         if (ret)
 47                 return ret;
 48 
 49         ret = kstrtou32(child_str, 0, &parsed_child);
 50         if (ret)
 51                 return ret;
 52 
 53         if (rule->type == UID){
 54                 rule->src_id.uid = make_kuid(file->f_cred->user_ns, parsed_parent);
 55                 rule->dst_id.uid = make_kuid(file->f_cred->user_ns, parsed_child);
 56                 if (!uid_valid(rule->src_id.uid) || !uid_valid(rule->dst_id.uid))
 57                         return -EINVAL;
 58         } else if (rule->type == GID){
 59                 rule->src_id.gid = make_kgid(file->f_cred->user_ns, parsed_parent);
 60                 rule->dst_id.gid = make_kgid(file->f_cred->user_ns, parsed_child);
 61                 if (!gid_valid(rule->src_id.gid) || !gid_valid(rule->dst_id.gid))
 62                         return -EINVAL;
 63         } else {
 64                 /* Error, rule->type is an invalid type */
 65                 return -EINVAL;
 66         }
 67         return 0;
 68 }
 69 
 70 static void __release_ruleset(struct rcu_head *rcu)
 71 {
 72         struct setid_ruleset *pol =
 73                 container_of(rcu, struct setid_ruleset, rcu);
 74         int bucket;
 75         struct setid_rule *rule;
 76         struct hlist_node *tmp;
 77 
 78         hash_for_each_safe(pol->rules, bucket, tmp, rule, next)
 79                 kfree(rule);
 80         kfree(pol->policy_str);
 81         kfree(pol);
 82 }
 83 
 84 static void release_ruleset(struct setid_ruleset *pol){
 85         call_rcu(&pol->rcu, __release_ruleset);
 86 }
 87 
 88 static void insert_rule(struct setid_ruleset *pol, struct setid_rule *rule)
 89 {
 90         if (pol->type == UID)
 91                 hash_add(pol->rules, &rule->next, __kuid_val(rule->src_id.uid));
 92         else if (pol->type == GID)
 93                 hash_add(pol->rules, &rule->next, __kgid_val(rule->src_id.gid));
 94         else /* Error, pol->type is neither UID or GID */
 95                 return;
 96 }
 97 
 98 static int verify_ruleset(struct setid_ruleset *pol)
 99 {
100         int bucket;
101         struct setid_rule *rule, *nrule;
102         int res = 0;
103 
104         hash_for_each(pol->rules, bucket, rule, next) {
105                 if (_setid_policy_lookup(pol, rule->dst_id, INVALID_ID) == SIDPOL_DEFAULT) {
106                         if (pol->type == UID) {
107                                 pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n",
108                                         __kuid_val(rule->src_id.uid),
109                                         __kuid_val(rule->dst_id.uid));
110                         } else if (pol->type == GID) {
111                                 pr_warn("insecure policy detected: gid %d is constrained but transitively unconstrained through gid %d\n",
112                                         __kgid_val(rule->src_id.gid),
113                                         __kgid_val(rule->dst_id.gid));
114                         } else { /* pol->type is an invalid type */
115                                 res = -EINVAL;
116                                 return res;
117                         }
118                         res = -EINVAL;
119 
120                         /* fix it up */
121                         nrule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
122                         if (!nrule)
123                                 return -ENOMEM;
124                         if (pol->type == UID){
125                                 nrule->src_id.uid = rule->dst_id.uid;
126                                 nrule->dst_id.uid = rule->dst_id.uid;
127                                 nrule->type = UID;
128                         } else { /* pol->type must be GID if we've made it to here */
129                                 nrule->src_id.gid = rule->dst_id.gid;
130                                 nrule->dst_id.gid = rule->dst_id.gid;
131                                 nrule->type = GID;
132                         }
133                         insert_rule(pol, nrule);
134                 }
135         }
136         return res;
137 }
138 
139 static ssize_t handle_policy_update(struct file *file,
140                                     const char __user *ubuf, size_t len, enum setid_type policy_type)
141 {
142         struct setid_ruleset *pol;
143         char *buf, *p, *end;
144         int err;
145 
146         pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL);
147         if (!pol)
148                 return -ENOMEM;
149         pol->policy_str = NULL;
150         pol->type = policy_type;
151         hash_init(pol->rules);
152 
153         p = buf = memdup_user_nul(ubuf, len);
154         if (IS_ERR(buf)) {
155                 err = PTR_ERR(buf);
156                 goto out_free_pol;
157         }
158         pol->policy_str = kstrdup(buf, GFP_KERNEL);
159         if (pol->policy_str == NULL) {
160                 err = -ENOMEM;
161                 goto out_free_buf;
162         }
163 
164         /* policy lines, including the last one, end with \n */
165         while (*p != '\0') {
166                 struct setid_rule *rule;
167 
168                 end = strchr(p, '\n');
169                 if (end == NULL) {
170                         err = -EINVAL;
171                         goto out_free_buf;
172                 }
173                 *end = '\0';
174 
175                 rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL);
176                 if (!rule) {
177                         err = -ENOMEM;
178                         goto out_free_buf;
179                 }
180 
181                 rule->type = policy_type;
182                 err = parse_policy_line(file, p, rule);
183                 if (err)
184                         goto out_free_rule;
185 
186                 if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == SIDPOL_ALLOWED) {
187                         pr_warn("bad policy: duplicate entry\n");
188                         err = -EEXIST;
189                         goto out_free_rule;
190                 }
191 
192                 insert_rule(pol, rule);
193                 p = end + 1;
194                 continue;
195 
196 out_free_rule:
197                 kfree(rule);
198                 goto out_free_buf;
199         }
200 
201         err = verify_ruleset(pol);
202         /* bogus policy falls through after fixing it up */
203         if (err && err != -EINVAL)
204                 goto out_free_buf;
205 
206         /*
207          * Everything looks good, apply the policy and release the old one.
208          * What we really want here is an xchg() wrapper for RCU, but since that
209          * doesn't currently exist, just use a spinlock for now.
210          */
211         if (policy_type == UID) {
212                 mutex_lock(&uid_policy_update_lock);
213                 pol = rcu_replace_pointer(safesetid_setuid_rules, pol,
214                                           lockdep_is_held(&uid_policy_update_lock));
215                 mutex_unlock(&uid_policy_update_lock);
216         } else if (policy_type == GID) {
217                 mutex_lock(&gid_policy_update_lock);
218                 pol = rcu_replace_pointer(safesetid_setgid_rules, pol,
219                                           lockdep_is_held(&gid_policy_update_lock));
220                 mutex_unlock(&gid_policy_update_lock);
221         } else {
222                 /* Error, policy type is neither UID or GID */
223                 pr_warn("error: bad policy type");
224         }
225         err = len;
226 
227 out_free_buf:
228         kfree(buf);
229 out_free_pol:
230         if (pol)
231                 release_ruleset(pol);
232         return err;
233 }
234 
235 static ssize_t safesetid_uid_file_write(struct file *file,
236                                     const char __user *buf,
237                                     size_t len,
238                                     loff_t *ppos)
239 {
240         if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
241                 return -EPERM;
242 
243         if (*ppos != 0)
244                 return -EINVAL;
245 
246         return handle_policy_update(file, buf, len, UID);
247 }
248 
249 static ssize_t safesetid_gid_file_write(struct file *file,
250                                     const char __user *buf,
251                                     size_t len,
252                                     loff_t *ppos)
253 {
254         if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
255                 return -EPERM;
256 
257         if (*ppos != 0)
258                 return -EINVAL;
259 
260         return handle_policy_update(file, buf, len, GID);
261 }
262 
263 static ssize_t safesetid_file_read(struct file *file, char __user *buf,
264                                    size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset)
265 {
266         ssize_t res = 0;
267         struct setid_ruleset *pol;
268         const char *kbuf;
269 
270         mutex_lock(policy_update_lock);
271         pol = rcu_dereference_protected(ruleset, lockdep_is_held(policy_update_lock));
272         if (pol) {
273                 kbuf = pol->policy_str;
274                 res = simple_read_from_buffer(buf, len, ppos,
275                                               kbuf, strlen(kbuf));
276         }
277         mutex_unlock(policy_update_lock);
278 
279         return res;
280 }
281 
282 static ssize_t safesetid_uid_file_read(struct file *file, char __user *buf,
283                                    size_t len, loff_t *ppos)
284 {
285         return safesetid_file_read(file, buf, len, ppos,
286                                    &uid_policy_update_lock, safesetid_setuid_rules);
287 }
288 
289 static ssize_t safesetid_gid_file_read(struct file *file, char __user *buf,
290                                    size_t len, loff_t *ppos)
291 {
292         return safesetid_file_read(file, buf, len, ppos,
293                                    &gid_policy_update_lock, safesetid_setgid_rules);
294 }
295 
296 
297 
298 static const struct file_operations safesetid_uid_file_fops = {
299         .read = safesetid_uid_file_read,
300         .write = safesetid_uid_file_write,
301 };
302 
303 static const struct file_operations safesetid_gid_file_fops = {
304         .read = safesetid_gid_file_read,
305         .write = safesetid_gid_file_write,
306 };
307 
308 static int __init safesetid_init_securityfs(void)
309 {
310         int ret;
311         struct dentry *policy_dir;
312         struct dentry *uid_policy_file;
313         struct dentry *gid_policy_file;
314 
315         if (!safesetid_initialized)
316                 return 0;
317 
318         policy_dir = securityfs_create_dir("safesetid", NULL);
319         if (IS_ERR(policy_dir)) {
320                 ret = PTR_ERR(policy_dir);
321                 goto error;
322         }
323 
324         uid_policy_file = securityfs_create_file("uid_allowlist_policy", 0600,
325                         policy_dir, NULL, &safesetid_uid_file_fops);
326         if (IS_ERR(uid_policy_file)) {
327                 ret = PTR_ERR(uid_policy_file);
328                 goto error;
329         }
330 
331         gid_policy_file = securityfs_create_file("gid_allowlist_policy", 0600,
332                         policy_dir, NULL, &safesetid_gid_file_fops);
333         if (IS_ERR(gid_policy_file)) {
334                 ret = PTR_ERR(gid_policy_file);
335                 goto error;
336         }
337 
338 
339         return 0;
340 
341 error:
342         securityfs_remove(policy_dir);
343         return ret;
344 }
345 fs_initcall(safesetid_init_securityfs);
346 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php