1aeca4e2cSMicah Morton // SPDX-License-Identifier: GPL-2.0
2aeca4e2cSMicah Morton /*
3aeca4e2cSMicah Morton * SafeSetID Linux Security Module
4aeca4e2cSMicah Morton *
5aeca4e2cSMicah Morton * Author: Micah Morton <mortonm@chromium.org>
6aeca4e2cSMicah Morton *
7aeca4e2cSMicah Morton * Copyright (C) 2018 The Chromium OS Authors.
8aeca4e2cSMicah Morton *
9aeca4e2cSMicah Morton * This program is free software; you can redistribute it and/or modify
10aeca4e2cSMicah Morton * it under the terms of the GNU General Public License version 2, as
11aeca4e2cSMicah Morton * published by the Free Software Foundation.
12aeca4e2cSMicah Morton *
13aeca4e2cSMicah Morton */
14aeca4e2cSMicah Morton
15aeca4e2cSMicah Morton #define pr_fmt(fmt) "SafeSetID: " fmt
16aeca4e2cSMicah Morton
17aeca4e2cSMicah Morton #include <linux/lsm_hooks.h>
18aeca4e2cSMicah Morton #include <linux/module.h>
19aeca4e2cSMicah Morton #include <linux/ptrace.h>
20aeca4e2cSMicah Morton #include <linux/sched/task_stack.h>
21aeca4e2cSMicah Morton #include <linux/security.h>
221cd02a27SJann Horn #include "lsm.h"
23aeca4e2cSMicah Morton
24aeca4e2cSMicah Morton /* Flag indicating whether initialization completed */
251b8b7192SAustin Kim int safesetid_initialized __initdata;
26aeca4e2cSMicah Morton
275294bac9SThomas Cedeno struct setid_ruleset __rcu *safesetid_setuid_rules;
285294bac9SThomas Cedeno struct setid_ruleset __rcu *safesetid_setgid_rules;
295294bac9SThomas Cedeno
30aeca4e2cSMicah Morton
3103638e62SJann Horn /* Compute a decision for a transition from @src to @dst under @policy. */
_setid_policy_lookup(struct setid_ruleset * policy,kid_t src,kid_t dst)325294bac9SThomas Cedeno enum sid_policy_type _setid_policy_lookup(struct setid_ruleset *policy,
335294bac9SThomas Cedeno kid_t src, kid_t dst)
34aeca4e2cSMicah Morton {
355294bac9SThomas Cedeno struct setid_rule *rule;
361cd02a27SJann Horn enum sid_policy_type result = SIDPOL_DEFAULT;
37aeca4e2cSMicah Morton
385294bac9SThomas Cedeno if (policy->type == UID) {
395294bac9SThomas Cedeno hash_for_each_possible(policy->rules, rule, next, __kuid_val(src.uid)) {
405294bac9SThomas Cedeno if (!uid_eq(rule->src_id.uid, src.uid))
411cd02a27SJann Horn continue;
425294bac9SThomas Cedeno if (uid_eq(rule->dst_id.uid, dst.uid))
431cd02a27SJann Horn return SIDPOL_ALLOWED;
441cd02a27SJann Horn result = SIDPOL_CONSTRAINED;
45aeca4e2cSMicah Morton }
465294bac9SThomas Cedeno } else if (policy->type == GID) {
475294bac9SThomas Cedeno hash_for_each_possible(policy->rules, rule, next, __kgid_val(src.gid)) {
485294bac9SThomas Cedeno if (!gid_eq(rule->src_id.gid, src.gid))
495294bac9SThomas Cedeno continue;
505294bac9SThomas Cedeno if (gid_eq(rule->dst_id.gid, dst.gid)){
515294bac9SThomas Cedeno return SIDPOL_ALLOWED;
525294bac9SThomas Cedeno }
535294bac9SThomas Cedeno result = SIDPOL_CONSTRAINED;
545294bac9SThomas Cedeno }
555294bac9SThomas Cedeno } else {
565294bac9SThomas Cedeno /* Should not reach here, report the ID as contrainsted */
575294bac9SThomas Cedeno result = SIDPOL_CONSTRAINED;
585294bac9SThomas Cedeno }
5903638e62SJann Horn return result;
6003638e62SJann Horn }
6103638e62SJann Horn
6203638e62SJann Horn /*
6303638e62SJann Horn * Compute a decision for a transition from @src to @dst under the active
6403638e62SJann Horn * policy.
6503638e62SJann Horn */
setid_policy_lookup(kid_t src,kid_t dst,enum setid_type new_type)665294bac9SThomas Cedeno static enum sid_policy_type setid_policy_lookup(kid_t src, kid_t dst, enum setid_type new_type)
6703638e62SJann Horn {
6803638e62SJann Horn enum sid_policy_type result = SIDPOL_DEFAULT;
695294bac9SThomas Cedeno struct setid_ruleset *pol;
7003638e62SJann Horn
7103638e62SJann Horn rcu_read_lock();
725294bac9SThomas Cedeno if (new_type == UID)
7303638e62SJann Horn pol = rcu_dereference(safesetid_setuid_rules);
745294bac9SThomas Cedeno else if (new_type == GID)
755294bac9SThomas Cedeno pol = rcu_dereference(safesetid_setgid_rules);
765294bac9SThomas Cedeno else { /* Should not reach here */
775294bac9SThomas Cedeno result = SIDPOL_CONSTRAINED;
785294bac9SThomas Cedeno rcu_read_unlock();
795294bac9SThomas Cedeno return result;
805294bac9SThomas Cedeno }
815294bac9SThomas Cedeno
825294bac9SThomas Cedeno if (pol) {
835294bac9SThomas Cedeno pol->type = new_type;
845294bac9SThomas Cedeno result = _setid_policy_lookup(pol, src, dst);
855294bac9SThomas Cedeno }
86aeca4e2cSMicah Morton rcu_read_unlock();
871cd02a27SJann Horn return result;
88aeca4e2cSMicah Morton }
89aeca4e2cSMicah Morton
safesetid_security_capable(const struct cred * cred,struct user_namespace * ns,int cap,unsigned int opts)90aeca4e2cSMicah Morton static int safesetid_security_capable(const struct cred *cred,
91aeca4e2cSMicah Morton struct user_namespace *ns,
92aeca4e2cSMicah Morton int cap,
93aeca4e2cSMicah Morton unsigned int opts)
94aeca4e2cSMicah Morton {
955294bac9SThomas Cedeno /* We're only interested in CAP_SETUID and CAP_SETGID. */
965294bac9SThomas Cedeno if (cap != CAP_SETUID && cap != CAP_SETGID)
978068866cSJann Horn return 0;
988068866cSJann Horn
99aeca4e2cSMicah Morton /*
1003e3374d3SMicah Morton * If CAP_SET{U/G}ID is currently used for a setid or setgroups syscall, we
1013e3374d3SMicah Morton * want to let it go through here; the real security check happens later, in
1023e3374d3SMicah Morton * the task_fix_set{u/g}id or task_fix_setgroups hooks.
1038068866cSJann Horn */
1048068866cSJann Horn if ((opts & CAP_OPT_INSETID) != 0)
1058068866cSJann Horn return 0;
1068068866cSJann Horn
1075294bac9SThomas Cedeno switch (cap) {
1085294bac9SThomas Cedeno case CAP_SETUID:
1098068866cSJann Horn /*
1108068866cSJann Horn * If no policy applies to this task, allow the use of CAP_SETUID for
1118068866cSJann Horn * other purposes.
1128068866cSJann Horn */
11303ca0ec1SThomas Cedeno if (setid_policy_lookup((kid_t){.uid = cred->uid}, INVALID_ID, UID) == SIDPOL_DEFAULT)
1148068866cSJann Horn return 0;
1158068866cSJann Horn /*
1168068866cSJann Horn * Reject use of CAP_SETUID for functionality other than calling
1178068866cSJann Horn * set*uid() (e.g. setting up userns uid mappings).
118aeca4e2cSMicah Morton */
119c783d525SJann Horn pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n",
120aeca4e2cSMicah Morton __kuid_val(cred->uid));
121e10337daSJann Horn return -EPERM;
1225294bac9SThomas Cedeno case CAP_SETGID:
1235294bac9SThomas Cedeno /*
1245294bac9SThomas Cedeno * If no policy applies to this task, allow the use of CAP_SETGID for
1255294bac9SThomas Cedeno * other purposes.
1265294bac9SThomas Cedeno */
12703ca0ec1SThomas Cedeno if (setid_policy_lookup((kid_t){.gid = cred->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT)
1285294bac9SThomas Cedeno return 0;
1295294bac9SThomas Cedeno /*
1305294bac9SThomas Cedeno * Reject use of CAP_SETUID for functionality other than calling
1315294bac9SThomas Cedeno * set*gid() (e.g. setting up userns gid mappings).
1325294bac9SThomas Cedeno */
1335294bac9SThomas Cedeno pr_warn("Operation requires CAP_SETGID, which is not available to GID %u for operations besides approved set*gid transitions\n",
134*970ebb8aSAlexander Mikhalitsyn __kgid_val(cred->gid));
1355294bac9SThomas Cedeno return -EPERM;
1365294bac9SThomas Cedeno default:
1375294bac9SThomas Cedeno /* Error, the only capabilities were checking for is CAP_SETUID/GID */
1385294bac9SThomas Cedeno return 0;
1395294bac9SThomas Cedeno }
1405294bac9SThomas Cedeno return 0;
141aeca4e2cSMicah Morton }
142aeca4e2cSMicah Morton
143aeca4e2cSMicah Morton /*
1447ef6b306SJann Horn * Check whether a caller with old credentials @old is allowed to switch to
1455294bac9SThomas Cedeno * credentials that contain @new_id.
146aeca4e2cSMicah Morton */
id_permitted_for_cred(const struct cred * old,kid_t new_id,enum setid_type new_type)1475294bac9SThomas Cedeno static bool id_permitted_for_cred(const struct cred *old, kid_t new_id, enum setid_type new_type)
1487ef6b306SJann Horn {
1497ef6b306SJann Horn bool permitted;
1507ef6b306SJann Horn
1515294bac9SThomas Cedeno /* If our old creds already had this ID in it, it's fine. */
1525294bac9SThomas Cedeno if (new_type == UID) {
1535294bac9SThomas Cedeno if (uid_eq(new_id.uid, old->uid) || uid_eq(new_id.uid, old->euid) ||
1545294bac9SThomas Cedeno uid_eq(new_id.uid, old->suid))
1557ef6b306SJann Horn return true;
1565294bac9SThomas Cedeno } else if (new_type == GID){
1575294bac9SThomas Cedeno if (gid_eq(new_id.gid, old->gid) || gid_eq(new_id.gid, old->egid) ||
1585294bac9SThomas Cedeno gid_eq(new_id.gid, old->sgid))
1595294bac9SThomas Cedeno return true;
1605294bac9SThomas Cedeno } else /* Error, new_type is an invalid type */
1615294bac9SThomas Cedeno return false;
1627ef6b306SJann Horn
1637ef6b306SJann Horn /*
1647ef6b306SJann Horn * Transitions to new UIDs require a check against the policy of the old
1657ef6b306SJann Horn * RUID.
1667ef6b306SJann Horn */
1671cd02a27SJann Horn permitted =
16803ca0ec1SThomas Cedeno setid_policy_lookup((kid_t){.uid = old->uid}, new_id, new_type) != SIDPOL_CONSTRAINED;
1695294bac9SThomas Cedeno
1707ef6b306SJann Horn if (!permitted) {
1715294bac9SThomas Cedeno if (new_type == UID) {
1727ef6b306SJann Horn pr_warn("UID transition ((%d,%d,%d) -> %d) blocked\n",
1737ef6b306SJann Horn __kuid_val(old->uid), __kuid_val(old->euid),
1745294bac9SThomas Cedeno __kuid_val(old->suid), __kuid_val(new_id.uid));
1755294bac9SThomas Cedeno } else if (new_type == GID) {
1765294bac9SThomas Cedeno pr_warn("GID transition ((%d,%d,%d) -> %d) blocked\n",
1775294bac9SThomas Cedeno __kgid_val(old->gid), __kgid_val(old->egid),
1785294bac9SThomas Cedeno __kgid_val(old->sgid), __kgid_val(new_id.gid));
1795294bac9SThomas Cedeno } else /* Error, new_type is an invalid type */
1805294bac9SThomas Cedeno return false;
1817ef6b306SJann Horn }
1827ef6b306SJann Horn return permitted;
183aeca4e2cSMicah Morton }
184aeca4e2cSMicah Morton
185aeca4e2cSMicah Morton /*
186aeca4e2cSMicah Morton * Check whether there is either an exception for user under old cred struct to
187aeca4e2cSMicah Morton * set*uid to user under new cred struct, or the UID transition is allowed (by
188aeca4e2cSMicah Morton * Linux set*uid rules) even without CAP_SETUID.
189aeca4e2cSMicah Morton */
safesetid_task_fix_setuid(struct cred * new,const struct cred * old,int flags)190aeca4e2cSMicah Morton static int safesetid_task_fix_setuid(struct cred *new,
191aeca4e2cSMicah Morton const struct cred *old,
192aeca4e2cSMicah Morton int flags)
193aeca4e2cSMicah Morton {
194aeca4e2cSMicah Morton
1957ef6b306SJann Horn /* Do nothing if there are no setuid restrictions for our old RUID. */
19603ca0ec1SThomas Cedeno if (setid_policy_lookup((kid_t){.uid = old->uid}, INVALID_ID, UID) == SIDPOL_DEFAULT)
197aeca4e2cSMicah Morton return 0;
198aeca4e2cSMicah Morton
19903ca0ec1SThomas Cedeno if (id_permitted_for_cred(old, (kid_t){.uid = new->uid}, UID) &&
20003ca0ec1SThomas Cedeno id_permitted_for_cred(old, (kid_t){.uid = new->euid}, UID) &&
20103ca0ec1SThomas Cedeno id_permitted_for_cred(old, (kid_t){.uid = new->suid}, UID) &&
20203ca0ec1SThomas Cedeno id_permitted_for_cred(old, (kid_t){.uid = new->fsuid}, UID))
203aeca4e2cSMicah Morton return 0;
2047ef6b306SJann Horn
2057ef6b306SJann Horn /*
2067ef6b306SJann Horn * Kill this process to avoid potential security vulnerabilities
2075294bac9SThomas Cedeno * that could arise from a missing allowlist entry preventing a
2085294bac9SThomas Cedeno * privileged process from dropping to a lesser-privileged one.
2095294bac9SThomas Cedeno */
2105294bac9SThomas Cedeno force_sig(SIGKILL);
2115294bac9SThomas Cedeno return -EACCES;
2125294bac9SThomas Cedeno }
2135294bac9SThomas Cedeno
safesetid_task_fix_setgid(struct cred * new,const struct cred * old,int flags)2145294bac9SThomas Cedeno static int safesetid_task_fix_setgid(struct cred *new,
2155294bac9SThomas Cedeno const struct cred *old,
2165294bac9SThomas Cedeno int flags)
2175294bac9SThomas Cedeno {
2185294bac9SThomas Cedeno
2195294bac9SThomas Cedeno /* Do nothing if there are no setgid restrictions for our old RGID. */
22003ca0ec1SThomas Cedeno if (setid_policy_lookup((kid_t){.gid = old->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT)
2215294bac9SThomas Cedeno return 0;
2225294bac9SThomas Cedeno
22303ca0ec1SThomas Cedeno if (id_permitted_for_cred(old, (kid_t){.gid = new->gid}, GID) &&
22403ca0ec1SThomas Cedeno id_permitted_for_cred(old, (kid_t){.gid = new->egid}, GID) &&
22503ca0ec1SThomas Cedeno id_permitted_for_cred(old, (kid_t){.gid = new->sgid}, GID) &&
22603ca0ec1SThomas Cedeno id_permitted_for_cred(old, (kid_t){.gid = new->fsgid}, GID))
2275294bac9SThomas Cedeno return 0;
2285294bac9SThomas Cedeno
2295294bac9SThomas Cedeno /*
2305294bac9SThomas Cedeno * Kill this process to avoid potential security vulnerabilities
2315294bac9SThomas Cedeno * that could arise from a missing allowlist entry preventing a
2327ef6b306SJann Horn * privileged process from dropping to a lesser-privileged one.
2337ef6b306SJann Horn */
2347ef6b306SJann Horn force_sig(SIGKILL);
2357ef6b306SJann Horn return -EACCES;
236aeca4e2cSMicah Morton }
237aeca4e2cSMicah Morton
safesetid_task_fix_setgroups(struct cred * new,const struct cred * old)2383e3374d3SMicah Morton static int safesetid_task_fix_setgroups(struct cred *new, const struct cred *old)
2393e3374d3SMicah Morton {
2403e3374d3SMicah Morton int i;
2413e3374d3SMicah Morton
2423e3374d3SMicah Morton /* Do nothing if there are no setgid restrictions for our old RGID. */
2433e3374d3SMicah Morton if (setid_policy_lookup((kid_t){.gid = old->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT)
2443e3374d3SMicah Morton return 0;
2453e3374d3SMicah Morton
2463e3374d3SMicah Morton get_group_info(new->group_info);
2473e3374d3SMicah Morton for (i = 0; i < new->group_info->ngroups; i++) {
2483e3374d3SMicah Morton if (!id_permitted_for_cred(old, (kid_t){.gid = new->group_info->gid[i]}, GID)) {
2493e3374d3SMicah Morton put_group_info(new->group_info);
2503e3374d3SMicah Morton /*
2513e3374d3SMicah Morton * Kill this process to avoid potential security vulnerabilities
2523e3374d3SMicah Morton * that could arise from a missing allowlist entry preventing a
2533e3374d3SMicah Morton * privileged process from dropping to a lesser-privileged one.
2543e3374d3SMicah Morton */
2553e3374d3SMicah Morton force_sig(SIGKILL);
2563e3374d3SMicah Morton return -EACCES;
2573e3374d3SMicah Morton }
2583e3374d3SMicah Morton }
2593e3374d3SMicah Morton
2603e3374d3SMicah Morton put_group_info(new->group_info);
2613e3374d3SMicah Morton return 0;
2623e3374d3SMicah Morton }
2633e3374d3SMicah Morton
264aeca4e2cSMicah Morton static struct security_hook_list safesetid_security_hooks[] = {
265aeca4e2cSMicah Morton LSM_HOOK_INIT(task_fix_setuid, safesetid_task_fix_setuid),
2665294bac9SThomas Cedeno LSM_HOOK_INIT(task_fix_setgid, safesetid_task_fix_setgid),
2673e3374d3SMicah Morton LSM_HOOK_INIT(task_fix_setgroups, safesetid_task_fix_setgroups),
268aeca4e2cSMicah Morton LSM_HOOK_INIT(capable, safesetid_security_capable)
269aeca4e2cSMicah Morton };
270aeca4e2cSMicah Morton
safesetid_security_init(void)271aeca4e2cSMicah Morton static int __init safesetid_security_init(void)
272aeca4e2cSMicah Morton {
273aeca4e2cSMicah Morton security_add_hooks(safesetid_security_hooks,
274aeca4e2cSMicah Morton ARRAY_SIZE(safesetid_security_hooks), "safesetid");
275aeca4e2cSMicah Morton
276aeca4e2cSMicah Morton /* Report that SafeSetID successfully initialized */
277aeca4e2cSMicah Morton safesetid_initialized = 1;
278aeca4e2cSMicah Morton
279aeca4e2cSMicah Morton return 0;
280aeca4e2cSMicah Morton }
281aeca4e2cSMicah Morton
282aeca4e2cSMicah Morton DEFINE_LSM(safesetid_security_init) = {
283aeca4e2cSMicah Morton .init = safesetid_security_init,
284f67e20d2SMicah Morton .name = "safesetid",
285aeca4e2cSMicah Morton };
286