root/security/safesetid/securityfs.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. parse_policy_line
  2. __release_ruleset
  3. release_ruleset
  4. insert_rule
  5. verify_ruleset
  6. handle_policy_update
  7. safesetid_file_write
  8. safesetid_file_read
  9. safesetid_init_securityfs

   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(policy_update_lock);
  23 
  24 /*
  25  * In the case the input buffer contains one or more invalid UIDs, the kuid_t
  26  * variables pointed to by @parent and @child will get updated but this
  27  * function will return an error.
  28  * Contents of @buf may be modified.
  29  */
  30 static int parse_policy_line(struct file *file, char *buf,
  31         struct setuid_rule *rule)
  32 {
  33         char *child_str;
  34         int ret;
  35         u32 parsed_parent, parsed_child;
  36 
  37         /* Format of |buf| string should be <UID>:<UID>. */
  38         child_str = strchr(buf, ':');
  39         if (child_str == NULL)
  40                 return -EINVAL;
  41         *child_str = '\0';
  42         child_str++;
  43 
  44         ret = kstrtou32(buf, 0, &parsed_parent);
  45         if (ret)
  46                 return ret;
  47 
  48         ret = kstrtou32(child_str, 0, &parsed_child);
  49         if (ret)
  50                 return ret;
  51 
  52         rule->src_uid = make_kuid(file->f_cred->user_ns, parsed_parent);
  53         rule->dst_uid = make_kuid(file->f_cred->user_ns, parsed_child);
  54         if (!uid_valid(rule->src_uid) || !uid_valid(rule->dst_uid))
  55                 return -EINVAL;
  56 
  57         return 0;
  58 }
  59 
  60 static 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 
  74 static void release_ruleset(struct setuid_ruleset *pol)
  75 {
  76         call_rcu(&pol->rcu, __release_ruleset);
  77 }
  78 
  79 static 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 
  84 static 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                 }
 106         }
 107         return res;
 108 }
 109 
 110 static ssize_t handle_policy_update(struct file *file,
 111                                     const char __user *ubuf, size_t len)
 112 {
 113         struct setuid_ruleset *pol;
 114         char *buf, *p, *end;
 115         int err;
 116 
 117         pol = kmalloc(sizeof(struct setuid_ruleset), GFP_KERNEL);
 118         if (!pol)
 119                 return -ENOMEM;
 120         pol->policy_str = NULL;
 121         hash_init(pol->rules);
 122 
 123         p = buf = memdup_user_nul(ubuf, len);
 124         if (IS_ERR(buf)) {
 125                 err = PTR_ERR(buf);
 126                 goto out_free_pol;
 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                 }
 150 
 151                 err = parse_policy_line(file, p, rule);
 152                 if (err)
 153                         goto out_free_rule;
 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 
 166 out_free_rule:
 167                 kfree(rule);
 168                 goto out_free_buf;
 169         }
 170 
 171         err = verify_ruleset(pol);
 172         /* bogus policy falls through after fixing it up */
 173         if (err && err != -EINVAL)
 174                 goto out_free_buf;
 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 
 187 out_free_buf:
 188         kfree(buf);
 189 out_free_pol:
 190         if (pol)
 191                 release_ruleset(pol);
 192         return err;
 193 }
 194 
 195 static ssize_t safesetid_file_write(struct file *file,
 196                                     const char __user *buf,
 197                                     size_t len,
 198                                     loff_t *ppos)
 199 {
 200         if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
 201                 return -EPERM;
 202 
 203         if (*ppos != 0)
 204                 return -EINVAL;
 205 
 206         return handle_policy_update(file, buf, len);
 207 }
 208 
 209 static ssize_t safesetid_file_read(struct file *file, char __user *buf,
 210                                    size_t len, loff_t *ppos)
 211 {
 212         ssize_t res = 0;
 213         struct setuid_ruleset *pol;
 214         const char *kbuf;
 215 
 216         mutex_lock(&policy_update_lock);
 217         pol = rcu_dereference_protected(safesetid_setuid_rules,
 218                                         lockdep_is_held(&policy_update_lock));
 219         if (pol) {
 220                 kbuf = pol->policy_str;
 221                 res = simple_read_from_buffer(buf, len, ppos,
 222                                               kbuf, strlen(kbuf));
 223         }
 224         mutex_unlock(&policy_update_lock);
 225         return res;
 226 }
 227 
 228 static const struct file_operations safesetid_file_fops = {
 229         .read = safesetid_file_read,
 230         .write = safesetid_file_write,
 231 };
 232 
 233 static int __init safesetid_init_securityfs(void)
 234 {
 235         int ret;
 236         struct dentry *policy_dir;
 237         struct dentry *policy_file;
 238 
 239         if (!safesetid_initialized)
 240                 return 0;
 241 
 242         policy_dir = securityfs_create_dir("safesetid", NULL);
 243         if (IS_ERR(policy_dir)) {
 244                 ret = PTR_ERR(policy_dir);
 245                 goto error;
 246         }
 247 
 248         policy_file = securityfs_create_file("whitelist_policy", 0600,
 249                         policy_dir, NULL, &safesetid_file_fops);
 250         if (IS_ERR(policy_file)) {
 251                 ret = PTR_ERR(policy_file);
 252                 goto error;
 253         }
 254 
 255         return 0;
 256 
 257 error:
 258         securityfs_remove(policy_dir);
 259         return ret;
 260 }
 261 fs_initcall(safesetid_init_securityfs);

/* [<][>][^][v][top][bottom][index][help] */