// SPDX-License-Identifier: GPL-2.0 /* * SafeSetID Linux Security Module * * Author: Micah Morton <mortonm@chromium.org> * * Copyright (C) 2018 The Chromium OS Authors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, as * published by the Free Software Foundation. *
*/
/* Compute a decision for a transition from @src to @dst under @policy. */ enum sid_policy_type _setid_policy_lookup(struct setid_ruleset *policy,
kid_t src, kid_t dst)
{ struct setid_rule *rule; enum sid_policy_type result = SIDPOL_DEFAULT;
if (policy->type == UID) {
hash_for_each_possible(policy->rules, rule, next, __kuid_val(src.uid)) { if (!uid_eq(rule->src_id.uid, src.uid)) continue; if (uid_eq(rule->dst_id.uid, dst.uid)) return SIDPOL_ALLOWED;
result = SIDPOL_CONSTRAINED;
}
} elseif (policy->type == GID) {
hash_for_each_possible(policy->rules, rule, next, __kgid_val(src.gid)) { if (!gid_eq(rule->src_id.gid, src.gid)) continue; if (gid_eq(rule->dst_id.gid, dst.gid)){ return SIDPOL_ALLOWED;
}
result = SIDPOL_CONSTRAINED;
}
} else { /* Should not reach here, report the ID as contrainsted */
result = SIDPOL_CONSTRAINED;
} return result;
}
/* * Compute a decision for a transition from @src to @dst under the active * policy.
*/ staticenum sid_policy_type setid_policy_lookup(kid_t src, kid_t dst, enum setid_type new_type)
{ enum sid_policy_type result = SIDPOL_DEFAULT; struct setid_ruleset *pol;
rcu_read_lock(); if (new_type == UID)
pol = rcu_dereference(safesetid_setuid_rules); elseif (new_type == GID)
pol = rcu_dereference(safesetid_setgid_rules); else { /* Should not reach here */
result = SIDPOL_CONSTRAINED;
rcu_read_unlock(); return result;
}
if (pol) {
pol->type = new_type;
result = _setid_policy_lookup(pol, src, dst);
}
rcu_read_unlock(); return result;
}
staticint safesetid_security_capable(conststruct cred *cred, struct user_namespace *ns, int cap, unsignedint opts)
{ /* We're only interested in CAP_SETUID and CAP_SETGID. */ if (cap != CAP_SETUID && cap != CAP_SETGID) return 0;
/* * If CAP_SET{U/G}ID is currently used for a setid or setgroups syscall, we * want to let it go through here; the real security check happens later, in * the task_fix_set{u/g}id or task_fix_setgroups hooks.
*/ if ((opts & CAP_OPT_INSETID) != 0) return 0;
switch (cap) { case CAP_SETUID: /* * If no policy applies to this task, allow the use of CAP_SETUID for * other purposes.
*/ if (setid_policy_lookup((kid_t){.uid = cred->uid}, INVALID_ID, UID) == SIDPOL_DEFAULT) return 0; /* * Reject use of CAP_SETUID for functionality other than calling * set*uid() (e.g. setting up userns uid mappings).
*/
pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n",
__kuid_val(cred->uid)); return -EPERM; case CAP_SETGID: /* * If no policy applies to this task, allow the use of CAP_SETGID for * other purposes.
*/ if (setid_policy_lookup((kid_t){.gid = cred->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT) return 0; /* * Reject use of CAP_SETUID for functionality other than calling * set*gid() (e.g. setting up userns gid mappings).
*/
pr_warn("Operation requires CAP_SETGID, which is not available to GID %u for operations besides approved set*gid transitions\n",
__kgid_val(cred->gid)); return -EPERM; default: /* Error, the only capabilities were checking for is CAP_SETUID/GID */ return 0;
} return 0;
}
/* * Check whether a caller with old credentials @old is allowed to switch to * credentials that contain @new_id.
*/ staticbool id_permitted_for_cred(conststruct cred *old, kid_t new_id, enum setid_type new_type)
{ bool permitted;
/* If our old creds already had this ID in it, it's fine. */ if (new_type == UID) { if (uid_eq(new_id.uid, old->uid) || uid_eq(new_id.uid, old->euid) ||
uid_eq(new_id.uid, old->suid)) returntrue;
} elseif (new_type == GID){ if (gid_eq(new_id.gid, old->gid) || gid_eq(new_id.gid, old->egid) ||
gid_eq(new_id.gid, old->sgid)) returntrue;
} else/* Error, new_type is an invalid type */ returnfalse;
/* * Transitions to new UIDs require a check against the policy of the old * RUID.
*/
permitted =
setid_policy_lookup((kid_t){.uid = old->uid}, new_id, new_type) != SIDPOL_CONSTRAINED;
if (!permitted) { if (new_type == UID) {
pr_warn("UID transition ((%d,%d,%d) -> %d) blocked\n",
__kuid_val(old->uid), __kuid_val(old->euid),
__kuid_val(old->suid), __kuid_val(new_id.uid));
} elseif (new_type == GID) {
pr_warn("GID transition ((%d,%d,%d) -> %d) blocked\n",
__kgid_val(old->gid), __kgid_val(old->egid),
__kgid_val(old->sgid), __kgid_val(new_id.gid));
} else/* Error, new_type is an invalid type */ returnfalse;
} return permitted;
}
/* * Check whether there is either an exception for user under old cred struct to * set*uid to user under new cred struct, or the UID transition is allowed (by * Linux set*uid rules) even without CAP_SETUID.
*/ staticint safesetid_task_fix_setuid(struct cred *new, conststruct cred *old, int flags)
{
/* Do nothing if there are no setuid restrictions for our old RUID. */ if (setid_policy_lookup((kid_t){.uid = old->uid}, INVALID_ID, UID) == SIDPOL_DEFAULT) return 0;
/* * Kill this process to avoid potential security vulnerabilities * that could arise from a missing allowlist entry preventing a * privileged process from dropping to a lesser-privileged one.
*/
force_sig(SIGKILL); return -EACCES;
}
staticint safesetid_task_fix_setgid(struct cred *new, conststruct cred *old, int flags)
{
/* Do nothing if there are no setgid restrictions for our old RGID. */ if (setid_policy_lookup((kid_t){.gid = old->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT) return 0;
/* * Kill this process to avoid potential security vulnerabilities * that could arise from a missing allowlist entry preventing a * privileged process from dropping to a lesser-privileged one.
*/
force_sig(SIGKILL); return -EACCES;
}
staticint safesetid_task_fix_setgroups(struct cred *new, conststruct cred *old)
{ int i;
/* Do nothing if there are no setgid restrictions for our old RGID. */ if (setid_policy_lookup((kid_t){.gid = old->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT) return 0;
get_group_info(new->group_info); for (i = 0; i < new->group_info->ngroups; i++) { if (!id_permitted_for_cred(old, (kid_t){.gid = new->group_info->gid[i]}, GID)) {
put_group_info(new->group_info); /* * Kill this process to avoid potential security vulnerabilities * that could arise from a missing allowlist entry preventing a * privileged process from dropping to a lesser-privileged one.
*/
force_sig(SIGKILL); return -EACCES;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.