Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
config SECURITY_SAFESETID
bool "Gate setid transitions to limit CAP_SET{U/G}ID capabilities"
depends on SECURITY
select SECURITYFS
default n
help
SafeSetID is an LSM module that gates the setid family of syscalls to
restrict UID/GID transitions from a given UID/GID to only those
approved by a system-wide whitelist. These restrictions also prohibit
the given UIDs/GIDs from obtaining auxiliary privileges associated
with CAP_SET{U/G}ID, such as allowing a user to set up user namespace
UID mappings.
If you are unsure how to answer this question, answer N.
+7
View File
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the safesetid LSM.
#
obj-$(CONFIG_SECURITY_SAFESETID) := safesetid.o
safesetid-y := lsm.o securityfs.o
+293
View File
@@ -0,0 +1,293 @@
// 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.
*
*/
#define pr_fmt(fmt) "SafeSetID: " fmt
#include <linux/lsm_hooks.h>
#include <linux/module.h>
#include <linux/ptrace.h>
#include <linux/sched/task_stack.h>
#include <linux/security.h>
#include <uapi/linux/lsm.h>
#include "lsm.h"
/* Flag indicating whether initialization completed */
int safesetid_initialized __initdata;
struct setid_ruleset __rcu *safesetid_setuid_rules;
struct setid_ruleset __rcu *safesetid_setgid_rules;
/* 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;
}
} else if (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.
*/
static enum 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);
else if (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;
}
static int safesetid_security_capable(const struct cred *cred,
struct user_namespace *ns,
int cap,
unsigned int 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.
*/
static bool id_permitted_for_cred(const struct 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))
return true;
} else if (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))
return true;
} else /* Error, new_type is an invalid type */
return false;
/*
* 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));
} else if (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 */
return false;
}
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.
*/
static int safesetid_task_fix_setuid(struct cred *new,
const struct 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;
if (id_permitted_for_cred(old, (kid_t){.uid = new->uid}, UID) &&
id_permitted_for_cred(old, (kid_t){.uid = new->euid}, UID) &&
id_permitted_for_cred(old, (kid_t){.uid = new->suid}, UID) &&
id_permitted_for_cred(old, (kid_t){.uid = new->fsuid}, UID))
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;
}
static int safesetid_task_fix_setgid(struct cred *new,
const struct 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;
if (id_permitted_for_cred(old, (kid_t){.gid = new->gid}, GID) &&
id_permitted_for_cred(old, (kid_t){.gid = new->egid}, GID) &&
id_permitted_for_cred(old, (kid_t){.gid = new->sgid}, GID) &&
id_permitted_for_cred(old, (kid_t){.gid = new->fsgid}, GID))
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;
}
static int safesetid_task_fix_setgroups(struct cred *new, const struct 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;
}
}
put_group_info(new->group_info);
return 0;
}
static const struct lsm_id safesetid_lsmid = {
.name = "safesetid",
.id = LSM_ID_SAFESETID,
};
static struct security_hook_list safesetid_security_hooks[] = {
LSM_HOOK_INIT(task_fix_setuid, safesetid_task_fix_setuid),
LSM_HOOK_INIT(task_fix_setgid, safesetid_task_fix_setgid),
LSM_HOOK_INIT(task_fix_setgroups, safesetid_task_fix_setgroups),
LSM_HOOK_INIT(capable, safesetid_security_capable)
};
static int __init safesetid_security_init(void)
{
security_add_hooks(safesetid_security_hooks,
ARRAY_SIZE(safesetid_security_hooks),
&safesetid_lsmid);
/* Report that SafeSetID successfully initialized */
safesetid_initialized = 1;
return 0;
}
DEFINE_LSM(safesetid_security_init) = {
.id = &safesetid_lsmid,
.init = safesetid_security_init,
.initcall_fs = safesetid_init_securityfs,
};
+75
View File
@@ -0,0 +1,75 @@
/* 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.
*
*/
#ifndef _SAFESETID_H
#define _SAFESETID_H
#include <linux/types.h>
#include <linux/uidgid.h>
#include <linux/hashtable.h>
/* Flag indicating whether initialization completed */
extern int safesetid_initialized __initdata;
enum sid_policy_type {
SIDPOL_DEFAULT, /* source ID is unaffected by policy */
SIDPOL_CONSTRAINED, /* source ID is affected by policy */
SIDPOL_ALLOWED /* target ID explicitly allowed */
};
typedef union {
kuid_t uid;
kgid_t gid;
} kid_t;
enum setid_type {
UID,
GID
};
/*
* Hash table entry to store safesetid policy signifying that 'src_id'
* can set*id to 'dst_id'.
*/
struct setid_rule {
struct hlist_node next;
kid_t src_id;
kid_t dst_id;
/* Flag to signal if rule is for UID's or GID's */
enum setid_type type;
};
#define SETID_HASH_BITS 8 /* 256 buckets in hash table */
/* Extension of INVALID_UID/INVALID_GID for kid_t type */
#define INVALID_ID (kid_t){.uid = INVALID_UID}
struct setid_ruleset {
DECLARE_HASHTABLE(rules, SETID_HASH_BITS);
char *policy_str;
struct rcu_head rcu;
//Flag to signal if ruleset is for UID's or GID's
enum setid_type type;
};
enum sid_policy_type _setid_policy_lookup(struct setid_ruleset *policy,
kid_t src, kid_t dst);
extern struct setid_ruleset __rcu *safesetid_setuid_rules;
extern struct setid_ruleset __rcu *safesetid_setgid_rules;
int safesetid_init_securityfs(void);
#endif /* _SAFESETID_H */
+347
View File
@@ -0,0 +1,347 @@
// 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.
*
*/
#define pr_fmt(fmt) "SafeSetID: " fmt
#include <linux/security.h>
#include <linux/cred.h>
#include "lsm.h"
static DEFINE_MUTEX(uid_policy_update_lock);
static DEFINE_MUTEX(gid_policy_update_lock);
/*
* In the case the input buffer contains one or more invalid IDs, the kid_t
* variables pointed to by @parent and @child will get updated but this
* function will return an error.
* Contents of @buf may be modified.
*/
static int parse_policy_line(struct file *file, char *buf,
struct setid_rule *rule)
{
char *child_str;
int ret;
u32 parsed_parent, parsed_child;
/* Format of |buf| string should be <UID>:<UID> or <GID>:<GID> */
child_str = strchr(buf, ':');
if (child_str == NULL)
return -EINVAL;
*child_str = '\0';
child_str++;
ret = kstrtou32(buf, 0, &parsed_parent);
if (ret)
return ret;
ret = kstrtou32(child_str, 0, &parsed_child);
if (ret)
return ret;
if (rule->type == UID){
rule->src_id.uid = make_kuid(file->f_cred->user_ns, parsed_parent);
rule->dst_id.uid = make_kuid(file->f_cred->user_ns, parsed_child);
if (!uid_valid(rule->src_id.uid) || !uid_valid(rule->dst_id.uid))
return -EINVAL;
} else if (rule->type == GID){
rule->src_id.gid = make_kgid(file->f_cred->user_ns, parsed_parent);
rule->dst_id.gid = make_kgid(file->f_cred->user_ns, parsed_child);
if (!gid_valid(rule->src_id.gid) || !gid_valid(rule->dst_id.gid))
return -EINVAL;
} else {
/* Error, rule->type is an invalid type */
return -EINVAL;
}
return 0;
}
static void __release_ruleset(struct rcu_head *rcu)
{
struct setid_ruleset *pol =
container_of(rcu, struct setid_ruleset, rcu);
int bucket;
struct setid_rule *rule;
struct hlist_node *tmp;
hash_for_each_safe(pol->rules, bucket, tmp, rule, next)
kfree(rule);
kfree(pol->policy_str);
kfree(pol);
}
static void release_ruleset(struct setid_ruleset *pol){
call_rcu(&pol->rcu, __release_ruleset);
}
static void insert_rule(struct setid_ruleset *pol, struct setid_rule *rule)
{
if (pol->type == UID)
hash_add(pol->rules, &rule->next, __kuid_val(rule->src_id.uid));
else if (pol->type == GID)
hash_add(pol->rules, &rule->next, __kgid_val(rule->src_id.gid));
else /* Error, pol->type is neither UID or GID */
return;
}
static int verify_ruleset(struct setid_ruleset *pol)
{
int bucket;
struct setid_rule *rule, *nrule;
int res = 0;
hash_for_each(pol->rules, bucket, rule, next) {
if (_setid_policy_lookup(pol, rule->dst_id, INVALID_ID) == SIDPOL_DEFAULT) {
if (pol->type == UID) {
pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n",
__kuid_val(rule->src_id.uid),
__kuid_val(rule->dst_id.uid));
} else if (pol->type == GID) {
pr_warn("insecure policy detected: gid %d is constrained but transitively unconstrained through gid %d\n",
__kgid_val(rule->src_id.gid),
__kgid_val(rule->dst_id.gid));
} else { /* pol->type is an invalid type */
res = -EINVAL;
return res;
}
res = -EINVAL;
/* fix it up */
nrule = kmalloc_obj(struct setid_rule);
if (!nrule)
return -ENOMEM;
if (pol->type == UID){
nrule->src_id.uid = rule->dst_id.uid;
nrule->dst_id.uid = rule->dst_id.uid;
nrule->type = UID;
} else { /* pol->type must be GID if we've made it to here */
nrule->src_id.gid = rule->dst_id.gid;
nrule->dst_id.gid = rule->dst_id.gid;
nrule->type = GID;
}
insert_rule(pol, nrule);
}
}
return res;
}
static ssize_t handle_policy_update(struct file *file,
const char __user *ubuf, size_t len, enum setid_type policy_type)
{
struct setid_ruleset *pol;
char *buf, *p, *end;
int err;
if (len >= KMALLOC_MAX_SIZE)
return -EINVAL;
pol = kmalloc_obj(struct setid_ruleset);
if (!pol)
return -ENOMEM;
pol->policy_str = NULL;
pol->type = policy_type;
hash_init(pol->rules);
p = buf = memdup_user_nul(ubuf, len);
if (IS_ERR(buf)) {
err = PTR_ERR(buf);
goto out_free_pol;
}
pol->policy_str = kstrdup(buf, GFP_KERNEL);
if (pol->policy_str == NULL) {
err = -ENOMEM;
goto out_free_buf;
}
/* policy lines, including the last one, end with \n */
while (*p != '\0') {
struct setid_rule *rule;
end = strchr(p, '\n');
if (end == NULL) {
err = -EINVAL;
goto out_free_buf;
}
*end = '\0';
rule = kmalloc_obj(struct setid_rule);
if (!rule) {
err = -ENOMEM;
goto out_free_buf;
}
rule->type = policy_type;
err = parse_policy_line(file, p, rule);
if (err)
goto out_free_rule;
if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == SIDPOL_ALLOWED) {
pr_warn("bad policy: duplicate entry\n");
err = -EEXIST;
goto out_free_rule;
}
insert_rule(pol, rule);
p = end + 1;
continue;
out_free_rule:
kfree(rule);
goto out_free_buf;
}
err = verify_ruleset(pol);
/* bogus policy falls through after fixing it up */
if (err && err != -EINVAL)
goto out_free_buf;
/*
* Everything looks good, apply the policy and release the old one.
* What we really want here is an xchg() wrapper for RCU, but since that
* doesn't currently exist, just use a spinlock for now.
*/
if (policy_type == UID) {
mutex_lock(&uid_policy_update_lock);
pol = rcu_replace_pointer(safesetid_setuid_rules, pol,
lockdep_is_held(&uid_policy_update_lock));
mutex_unlock(&uid_policy_update_lock);
} else if (policy_type == GID) {
mutex_lock(&gid_policy_update_lock);
pol = rcu_replace_pointer(safesetid_setgid_rules, pol,
lockdep_is_held(&gid_policy_update_lock));
mutex_unlock(&gid_policy_update_lock);
} else {
/* Error, policy type is neither UID or GID */
pr_warn("error: bad policy type");
}
err = len;
out_free_buf:
kfree(buf);
out_free_pol:
if (pol)
release_ruleset(pol);
return err;
}
static ssize_t safesetid_uid_file_write(struct file *file,
const char __user *buf,
size_t len,
loff_t *ppos)
{
if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
return -EPERM;
if (*ppos != 0)
return -EINVAL;
return handle_policy_update(file, buf, len, UID);
}
static ssize_t safesetid_gid_file_write(struct file *file,
const char __user *buf,
size_t len,
loff_t *ppos)
{
if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN))
return -EPERM;
if (*ppos != 0)
return -EINVAL;
return handle_policy_update(file, buf, len, GID);
}
static ssize_t safesetid_file_read(struct file *file, char __user *buf,
size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset)
{
ssize_t res = 0;
struct setid_ruleset *pol;
const char *kbuf;
mutex_lock(policy_update_lock);
pol = rcu_dereference_protected(ruleset, lockdep_is_held(policy_update_lock));
if (pol) {
kbuf = pol->policy_str;
res = simple_read_from_buffer(buf, len, ppos,
kbuf, strlen(kbuf));
}
mutex_unlock(policy_update_lock);
return res;
}
static ssize_t safesetid_uid_file_read(struct file *file, char __user *buf,
size_t len, loff_t *ppos)
{
return safesetid_file_read(file, buf, len, ppos,
&uid_policy_update_lock, safesetid_setuid_rules);
}
static ssize_t safesetid_gid_file_read(struct file *file, char __user *buf,
size_t len, loff_t *ppos)
{
return safesetid_file_read(file, buf, len, ppos,
&gid_policy_update_lock, safesetid_setgid_rules);
}
static const struct file_operations safesetid_uid_file_fops = {
.read = safesetid_uid_file_read,
.write = safesetid_uid_file_write,
};
static const struct file_operations safesetid_gid_file_fops = {
.read = safesetid_gid_file_read,
.write = safesetid_gid_file_write,
};
int __init safesetid_init_securityfs(void)
{
int ret;
struct dentry *policy_dir;
struct dentry *uid_policy_file;
struct dentry *gid_policy_file;
if (!safesetid_initialized)
return 0;
policy_dir = securityfs_create_dir("safesetid", NULL);
if (IS_ERR(policy_dir)) {
ret = PTR_ERR(policy_dir);
goto error;
}
uid_policy_file = securityfs_create_file("uid_allowlist_policy", 0600,
policy_dir, NULL, &safesetid_uid_file_fops);
if (IS_ERR(uid_policy_file)) {
ret = PTR_ERR(uid_policy_file);
goto error;
}
gid_policy_file = securityfs_create_file("gid_allowlist_policy", 0600,
policy_dir, NULL, &safesetid_gid_file_fops);
if (IS_ERR(gid_policy_file)) {
ret = PTR_ERR(gid_policy_file);
goto error;
}
return 0;
error:
securityfs_remove(policy_dir);
return ret;
}