linux/security/safesetid/lsm.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/lsm_hooks.h>
  18#include <linux/module.h>
  19#include <linux/ptrace.h>
  20#include <linux/sched/task_stack.h>
  21#include <linux/security.h>
  22#include "lsm.h"
  23
  24/* Flag indicating whether initialization completed */
  25int safesetid_initialized;
  26
  27struct setuid_ruleset __rcu *safesetid_setuid_rules;
  28
  29/* Compute a decision for a transition from @src to @dst under @policy. */
  30enum sid_policy_type _setuid_policy_lookup(struct setuid_ruleset *policy,
  31                kuid_t src, kuid_t dst)
  32{
  33        struct setuid_rule *rule;
  34        enum sid_policy_type result = SIDPOL_DEFAULT;
  35
  36        hash_for_each_possible(policy->rules, rule, next, __kuid_val(src)) {
  37                if (!uid_eq(rule->src_uid, src))
  38                        continue;
  39                if (uid_eq(rule->dst_uid, dst))
  40                        return SIDPOL_ALLOWED;
  41                result = SIDPOL_CONSTRAINED;
  42        }
  43        return result;
  44}
  45
  46/*
  47 * Compute a decision for a transition from @src to @dst under the active
  48 * policy.
  49 */
  50static enum sid_policy_type setuid_policy_lookup(kuid_t src, kuid_t dst)
  51{
  52        enum sid_policy_type result = SIDPOL_DEFAULT;
  53        struct setuid_ruleset *pol;
  54
  55        rcu_read_lock();
  56        pol = rcu_dereference(safesetid_setuid_rules);
  57        if (pol)
  58                result = _setuid_policy_lookup(pol, src, dst);
  59        rcu_read_unlock();
  60        return result;
  61}
  62
  63static int safesetid_security_capable(const struct cred *cred,
  64                                      struct user_namespace *ns,
  65                                      int cap,
  66                                      unsigned int opts)
  67{
  68        /* We're only interested in CAP_SETUID. */
  69        if (cap != CAP_SETUID)
  70                return 0;
  71
  72        /*
  73         * If CAP_SETUID is currently used for a set*uid() syscall, we want to
  74         * let it go through here; the real security check happens later, in the
  75         * task_fix_setuid hook.
  76         */
  77        if ((opts & CAP_OPT_INSETID) != 0)
  78                return 0;
  79
  80        /*
  81         * If no policy applies to this task, allow the use of CAP_SETUID for
  82         * other purposes.
  83         */
  84        if (setuid_policy_lookup(cred->uid, INVALID_UID) == SIDPOL_DEFAULT)
  85                return 0;
  86
  87        /*
  88         * Reject use of CAP_SETUID for functionality other than calling
  89         * set*uid() (e.g. setting up userns uid mappings).
  90         */
  91        pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n",
  92                __kuid_val(cred->uid));
  93        return -EPERM;
  94}
  95
  96/*
  97 * Check whether a caller with old credentials @old is allowed to switch to
  98 * credentials that contain @new_uid.
  99 */
 100static bool uid_permitted_for_cred(const struct cred *old, kuid_t new_uid)
 101{
 102        bool permitted;
 103
 104        /* If our old creds already had this UID in it, it's fine. */
 105        if (uid_eq(new_uid, old->uid) || uid_eq(new_uid, old->euid) ||
 106            uid_eq(new_uid, old->suid))
 107                return true;
 108
 109        /*
 110         * Transitions to new UIDs require a check against the policy of the old
 111         * RUID.
 112         */
 113        permitted =
 114            setuid_policy_lookup(old->uid, new_uid) != SIDPOL_CONSTRAINED;
 115        if (!permitted) {
 116                pr_warn("UID transition ((%d,%d,%d) -> %d) blocked\n",
 117                        __kuid_val(old->uid), __kuid_val(old->euid),
 118                        __kuid_val(old->suid), __kuid_val(new_uid));
 119        }
 120        return permitted;
 121}
 122
 123/*
 124 * Check whether there is either an exception for user under old cred struct to
 125 * set*uid to user under new cred struct, or the UID transition is allowed (by
 126 * Linux set*uid rules) even without CAP_SETUID.
 127 */
 128static int safesetid_task_fix_setuid(struct cred *new,
 129                                     const struct cred *old,
 130                                     int flags)
 131{
 132
 133        /* Do nothing if there are no setuid restrictions for our old RUID. */
 134        if (setuid_policy_lookup(old->uid, INVALID_UID) == SIDPOL_DEFAULT)
 135                return 0;
 136
 137        if (uid_permitted_for_cred(old, new->uid) &&
 138            uid_permitted_for_cred(old, new->euid) &&
 139            uid_permitted_for_cred(old, new->suid) &&
 140            uid_permitted_for_cred(old, new->fsuid))
 141                return 0;
 142
 143        /*
 144         * Kill this process to avoid potential security vulnerabilities
 145         * that could arise from a missing whitelist entry preventing a
 146         * privileged process from dropping to a lesser-privileged one.
 147         */
 148        force_sig(SIGKILL);
 149        return -EACCES;
 150}
 151
 152static struct security_hook_list safesetid_security_hooks[] = {
 153        LSM_HOOK_INIT(task_fix_setuid, safesetid_task_fix_setuid),
 154        LSM_HOOK_INIT(capable, safesetid_security_capable)
 155};
 156
 157static int __init safesetid_security_init(void)
 158{
 159        security_add_hooks(safesetid_security_hooks,
 160                           ARRAY_SIZE(safesetid_security_hooks), "safesetid");
 161
 162        /* Report that SafeSetID successfully initialized */
 163        safesetid_initialized = 1;
 164
 165        return 0;
 166}
 167
 168DEFINE_LSM(safesetid_security_init) = {
 169        .init = safesetid_security_init,
 170        .name = "safesetid",
 171};
 172