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#include <linux/security.h>
  15#include <linux/cred.h>
  16
  17#include "lsm.h"
  18
  19static struct dentry *safesetid_policy_dir;
  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
  34/*
  35 * 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
  37 * function will return an error.
  38 */
  39static int parse_safesetid_whitelist_policy(const char __user *buf,
  40                                            size_t len,
  41                                            kuid_t *parent,
  42                                            kuid_t *child)
  43{
  44        char *kern_buf;
  45        char *parent_buf;
  46        char *child_buf;
  47        const char separator[] = ":";
  48        int ret;
  49        size_t first_substring_length;
  50        long parsed_parent;
  51        long parsed_child;
  52
  53        /* Duplicate string from user memory and NULL-terminate */
  54        kern_buf = memdup_user_nul(buf, len);
  55        if (IS_ERR(kern_buf))
  56                return PTR_ERR(kern_buf);
  57
  58        /*
  59         * Format of |buf| string should be <UID>:<UID>.
  60         * Find location of ":" in kern_buf (copied from |buf|).
  61         */
  62        first_substring_length = strcspn(kern_buf, separator);
  63        if (first_substring_length == 0 || first_substring_length == len) {
  64                ret = -EINVAL;
  65                goto free_kern;
  66        }
  67
  68        parent_buf = kmemdup_nul(kern_buf, first_substring_length, GFP_KERNEL);
  69        if (!parent_buf) {
  70                ret = -ENOMEM;
  71                goto free_kern;
  72        }
  73
  74        ret = kstrtol(parent_buf, 0, &parsed_parent);
  75        if (ret)
  76                goto free_both;
  77
  78        child_buf = kern_buf + first_substring_length + 1;
  79        ret = kstrtol(child_buf, 0, &parsed_child);
  80        if (ret)
  81                goto free_both;
  82
  83        *parent = make_kuid(current_user_ns(), parsed_parent);
  84        if (!uid_valid(*parent)) {
  85                ret = -EINVAL;
  86                goto free_both;
  87        }
  88
  89        *child = make_kuid(current_user_ns(), parsed_child);
  90        if (!uid_valid(*child)) {
  91                ret = -EINVAL;
  92                goto free_both;
  93        }
  94
  95free_both:
  96        kfree(parent_buf);
  97free_kern:
  98        kfree(kern_buf);
  99        return ret;
 100}
 101
 102static ssize_t safesetid_file_write(struct file *file,
 103                                    const char __user *buf,
 104                                    size_t len,
 105                                    loff_t *ppos)
 106{
 107        struct safesetid_file_entry *file_entry =
 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;
 115
 116        if (*ppos != 0)
 117                return -EINVAL;
 118
 119        switch (file_entry->type) {
 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}
 141
 142static const struct file_operations safesetid_file_fops = {
 143        .write = safesetid_file_write,
 144};
 145
 146static void safesetid_shutdown_securityfs(void)
 147{
 148        int i;
 149
 150        for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
 151                struct safesetid_file_entry *entry =
 152                        &safesetid_files[i];
 153                securityfs_remove(entry->dentry);
 154                entry->dentry = NULL;
 155        }
 156
 157        securityfs_remove(safesetid_policy_dir);
 158        safesetid_policy_dir = NULL;
 159}
 160
 161static int __init safesetid_init_securityfs(void)
 162{
 163        int i;
 164        int ret;
 165
 166        if (!safesetid_initialized)
 167                return 0;
 168
 169        safesetid_policy_dir = securityfs_create_dir("safesetid", NULL);
 170        if (IS_ERR(safesetid_policy_dir)) {
 171                ret = PTR_ERR(safesetid_policy_dir);
 172                goto error;
 173        }
 174
 175        for (i = 0; i < ARRAY_SIZE(safesetid_files); ++i) {
 176                struct safesetid_file_entry *entry =
 177                        &safesetid_files[i];
 178                entry->dentry = securityfs_create_file(
 179                        entry->name, 0200, safesetid_policy_dir,
 180                        entry, &safesetid_file_fops);
 181                if (IS_ERR(entry->dentry)) {
 182                        ret = PTR_ERR(entry->dentry);
 183                        goto error;
 184                }
 185        }
 186
 187        return 0;
 188
 189error:
 190        safesetid_shutdown_securityfs();
 191        return ret;
 192}
 193fs_initcall(safesetid_init_securityfs);
 194