linux/security/safesetid/securityfs.c
<<
>>
Prefs
   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
  22static 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 */
  30static 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
  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                }
 106        }
 107        return res;
 108}
 109
 110static 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
 166out_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        pol = rcu_replace_pointer(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        if (pol)
 191                release_ruleset(pol);
 192        return err;
 193}
 194
 195static 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
 209static 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
 228static const struct file_operations safesetid_file_fops = {
 229        .read = safesetid_file_read,
 230        .write = safesetid_file_write,
 231};
 232
 233static 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
 257error:
 258        securityfs_remove(policy_dir);
 259        return ret;
 260}
 261fs_initcall(safesetid_init_securityfs);
 262