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(uid_policy_update_lock);
  23static 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 */
  31static 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
  70static 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
  84static void release_ruleset(struct setid_ruleset *pol){
  85        call_rcu(&pol->rcu, __release_ruleset);
  86}
  87
  88static 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
  98static 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
 139static 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
 196out_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
 227out_free_buf:
 228        kfree(buf);
 229out_free_pol:
 230        if (pol)
 231                release_ruleset(pol);
 232        return err;
 233}
 234
 235static 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
 249static 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
 263static 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
 282static 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
 289static 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
 298static const struct file_operations safesetid_uid_file_fops = {
 299        .read = safesetid_uid_file_read,
 300        .write = safesetid_uid_file_write,
 301};
 302
 303static const struct file_operations safesetid_gid_file_fops = {
 304        .read = safesetid_gid_file_read,
 305        .write = safesetid_gid_file_write,
 306};
 307
 308static 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
 341error:
 342        securityfs_remove(policy_dir);
 343        return ret;
 344}
 345fs_initcall(safesetid_init_securityfs);
 346