Quelle yama_lsm.c
Sprache: C
// SPDX-License-Identifier: GPL-2.0-only
/*
* Yama Linux Security Module
*
* Author : Kees Cook < keescook @ chromium . org >
*
* Copyright ( C ) 2010 Canonical , Ltd .
* Copyright ( C ) 2011 The Chromium OS Authors .
*/
#include <linux/lsm_hooks.h>
#include <linux/sysctl.h>
#include <linux/ptrace.h>
#include <linux/prctl.h>
#include <linux/ratelimit.h>
#include <linux/workqueue.h>
#include <linux/string_helpers.h>
#include <linux/task_work.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <uapi/linux/lsm.h>
#define YAMA_SCOPE_DISABLED 0
#define YAMA_SCOPE_RELATIONAL 1
#define YAMA_SCOPE_CAPABILITY 2
#define YAMA_SCOPE_NO_ATTACH 3
static int ptrace_scope = YAMA_SCOPE_RELATIONAL;
/* describe a ptrace relationship for potential exception */
struct ptrace_relation {
struct task_struct *tracer;
struct task_struct *tracee;
bool invalid;
struct list_head node;
struct rcu_head rcu;
};
static LIST_HEAD(ptracer_relations);
static DEFINE_SPINLOCK(ptracer_relations_lock);
static void yama_relation_cleanup(struct work_struct *work);
static DECLARE_WORK(yama_relation_work, yama_relation_cleanup);
struct access_report_info {
struct callback_head work;
const char *access;
struct task_struct *target;
struct task_struct *agent;
};
static void __report_access(struct callback_head *work)
{
struct access_report_info *info =
container_of(work, struct access_report_info, work);
char *target_cmd, *agent_cmd;
target_cmd = kstrdup_quotable_cmdline(info->target, GFP_KERNEL);
agent_cmd = kstrdup_quotable_cmdline(info->agent, GFP_KERNEL);
pr_notice_ratelimited(
"ptrace %s of \" %s\"[%d] was attempted by \" %s\"[%d]\n" ,
info->access, target_cmd, info->target->pid, agent_cmd,
info->agent->pid);
kfree(agent_cmd);
kfree(target_cmd);
put_task_struct(info->agent);
put_task_struct(info->target);
kfree(info);
}
/* defers execution because cmdline access can sleep */
static void report_access(const char *access, struct task_struct *target,
struct task_struct *agent)
{
struct access_report_info *info;
assert_spin_locked(&target->alloc_lock); /* for target->comm */
if (current->flags & PF_KTHREAD) {
/* I don't think kthreads call task_work_run() before exiting.
* Imagine angry ranting about procfs here .
*/
pr_notice_ratelimited(
"ptrace %s of \" %s\"[%d] was attempted by \" %s\"[%d]\n" ,
access, target->comm, target->pid, agent->comm, agent->pid);
return ;
}
info = kmalloc(sizeof (*info), GFP_ATOMIC);
if (!info)
return ;
init_task_work(&info->work, __report_access);
get_task_struct(target);
get_task_struct(agent);
info->access = access;
info->target = target;
info->agent = agent;
if (task_work_add(current, &info->work, TWA_RESUME) == 0 )
return ; /* success */
WARN(1 , "report_access called from exiting task" );
put_task_struct(target);
put_task_struct(agent);
kfree(info);
}
/**
* yama_relation_cleanup - remove invalid entries from the relation list
* @ work : unused
*
*/
static void yama_relation_cleanup(struct work_struct *work)
{
struct ptrace_relation *relation;
spin_lock(&ptracer_relations_lock);
rcu_read_lock();
list_for_each_entry_rcu(relation, &ptracer_relations, node) {
if (relation->invalid) {
list_del_rcu(&relation->node);
kfree_rcu(relation, rcu);
}
}
rcu_read_unlock();
spin_unlock(&ptracer_relations_lock);
}
/**
* yama_ptracer_add - add / replace an exception for this tracer / tracee pair
* @ tracer : the task_struct of the process doing the ptrace
* @ tracee : the task_struct of the process to be ptraced
*
* Each tracee can have , at most , one tracer registered . Each time this
* is called , the prior registered tracer will be replaced for the tracee .
*
* Returns 0 if relationship was added , - ve on error .
*/
static int yama_ptracer_add(struct task_struct *tracer,
struct task_struct *tracee)
{
struct ptrace_relation *relation, *added;
added = kmalloc(sizeof (*added), GFP_KERNEL);
if (!added)
return -ENOMEM;
added->tracee = tracee;
added->tracer = tracer;
added->invalid = false ;
spin_lock(&ptracer_relations_lock);
rcu_read_lock();
list_for_each_entry_rcu(relation, &ptracer_relations, node) {
if (relation->invalid)
continue ;
if (relation->tracee == tracee) {
list_replace_rcu(&relation->node, &added->node);
kfree_rcu(relation, rcu);
goto out;
}
}
list_add_rcu(&added->node, &ptracer_relations);
out:
rcu_read_unlock();
spin_unlock(&ptracer_relations_lock);
return 0 ;
}
/**
* yama_ptracer_del - remove exceptions related to the given tasks
* @ tracer : remove any relation where tracer task matches
* @ tracee : remove any relation where tracee task matches
*/
static void yama_ptracer_del(struct task_struct *tracer,
struct task_struct *tracee)
{
struct ptrace_relation *relation;
bool marked = false ;
rcu_read_lock();
list_for_each_entry_rcu(relation, &ptracer_relations, node) {
if (relation->invalid)
continue ;
if (relation->tracee == tracee ||
(tracer && relation->tracer == tracer)) {
relation->invalid = true ;
marked = true ;
}
}
rcu_read_unlock();
if (marked)
schedule_work(&yama_relation_work);
}
/**
* yama_task_free - check for task_pid to remove from exception list
* @ task : task being removed
*/
static void yama_task_free(struct task_struct *task)
{
yama_ptracer_del(task, task);
}
/**
* yama_task_prctl - check for Yama - specific prctl operations
* @ option : operation
* @ arg2 : argument
* @ arg3 : argument
* @ arg4 : argument
* @ arg5 : argument
*
* Return 0 on success , - ve on error . - ENOSYS is returned when Yama
* does not handle the given option .
*/
static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
int rc = -ENOSYS;
struct task_struct *myself;
switch (option) {
case PR_SET_PTRACER:
/* Since a thread can call prctl(), find the group leader
* before calling _ add ( ) or _ del ( ) on it , since we want
* process - level granularity of control . The tracer group
* leader checking is handled later when walking the ancestry
* at the time of PTRACE_ATTACH check .
*/
myself = current->group_leader;
if (arg2 == 0 ) {
yama_ptracer_del(NULL, myself);
rc = 0 ;
} else if (arg2 == PR_SET_PTRACER_ANY || (int )arg2 == -1 ) {
rc = yama_ptracer_add(NULL, myself);
} else {
struct task_struct *tracer;
tracer = find_get_task_by_vpid(arg2);
if (!tracer) {
rc = -EINVAL;
} else {
rc = yama_ptracer_add(tracer, myself);
put_task_struct(tracer);
}
}
break ;
}
return rc;
}
/**
* task_is_descendant - walk up a process family tree looking for a match
* @ parent : the process to compare against while walking up from child
* @ child : the process to start from while looking upwards for parent
*
* Returns 1 if child is a descendant of parent , 0 if not .
*/
static int task_is_descendant(struct task_struct *parent,
struct task_struct *child)
{
int rc = 0 ;
struct task_struct *walker = child;
if (!parent || !child)
return 0 ;
rcu_read_lock();
if (!thread_group_leader(parent))
parent = rcu_dereference(parent->group_leader);
while (walker->pid > 0 ) {
if (!thread_group_leader(walker))
walker = rcu_dereference(walker->group_leader);
if (walker == parent) {
rc = 1 ;
break ;
}
walker = rcu_dereference(walker->real_parent);
}
rcu_read_unlock();
return rc;
}
/**
* ptracer_exception_found - tracer registered as exception for this tracee
* @ tracer : the task_struct of the process attempting ptrace
* @ tracee : the task_struct of the process to be ptraced
*
* Returns 1 if tracer has a ptracer exception ancestor for tracee .
*/
static int ptracer_exception_found(struct task_struct *tracer,
struct task_struct *tracee)
{
int rc = 0 ;
struct ptrace_relation *relation;
struct task_struct *parent = NULL;
bool found = false ;
rcu_read_lock();
/*
* If there ' s already an active tracing relationship , then make an
* exception for the sake of other accesses , like process_vm_rw ( ) .
*/
parent = ptrace_parent(tracee);
if (parent != NULL && same_thread_group(parent, tracer)) {
rc = 1 ;
goto unlock;
}
/* Look for a PR_SET_PTRACER relationship. */
if (!thread_group_leader(tracee))
tracee = rcu_dereference(tracee->group_leader);
list_for_each_entry_rcu(relation, &ptracer_relations, node) {
if (relation->invalid)
continue ;
if (relation->tracee == tracee) {
parent = relation->tracer;
found = true ;
break ;
}
}
if (found && (parent == NULL || task_is_descendant(parent, tracer)))
rc = 1 ;
unlock:
rcu_read_unlock();
return rc;
}
/**
* yama_ptrace_access_check - validate PTRACE_ATTACH calls
* @ child : task that current task is attempting to ptrace
* @ mode : ptrace attach mode
*
* Returns 0 if following the ptrace is allowed , - ve on error .
*/
static int yama_ptrace_access_check(struct task_struct *child,
unsigned int mode)
{
int rc = 0 ;
/* require ptrace target be a child of ptracer on attach */
if (mode & PTRACE_MODE_ATTACH) {
switch (ptrace_scope) {
case YAMA_SCOPE_DISABLED:
/* No additional restrictions. */
break ;
case YAMA_SCOPE_RELATIONAL:
rcu_read_lock();
if (!pid_alive(child))
rc = -EPERM;
if (!rc && !task_is_descendant(current, child) &&
!ptracer_exception_found(current, child) &&
!ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE))
rc = -EPERM;
rcu_read_unlock();
break ;
case YAMA_SCOPE_CAPABILITY:
rcu_read_lock();
if (!ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE))
rc = -EPERM;
rcu_read_unlock();
break ;
case YAMA_SCOPE_NO_ATTACH:
default :
rc = -EPERM;
break ;
}
}
if (rc && (mode & PTRACE_MODE_NOAUDIT) == 0 )
report_access("attach" , child, current);
return rc;
}
/**
* yama_ptrace_traceme - validate PTRACE_TRACEME calls
* @ parent : task that will become the ptracer of the current task
*
* Returns 0 if following the ptrace is allowed , - ve on error .
*/
static int yama_ptrace_traceme(struct task_struct *parent)
{
int rc = 0 ;
/* Only disallow PTRACE_TRACEME on more aggressive settings. */
switch (ptrace_scope) {
case YAMA_SCOPE_CAPABILITY:
if (!has_ns_capability(parent, current_user_ns(), CAP_SYS_PTRACE))
rc = -EPERM;
break ;
case YAMA_SCOPE_NO_ATTACH:
rc = -EPERM;
break ;
}
if (rc) {
task_lock(current);
report_access("traceme" , current, parent);
task_unlock(current);
}
return rc;
}
static const struct lsm_id yama_lsmid = {
.name = "yama" ,
.id = LSM_ID_YAMA,
};
static struct security_hook_list yama_hooks[] __ro_after_init = {
LSM_HOOK_INIT(ptrace_access_check, yama_ptrace_access_check),
LSM_HOOK_INIT(ptrace_traceme, yama_ptrace_traceme),
LSM_HOOK_INIT(task_prctl, yama_task_prctl),
LSM_HOOK_INIT(task_free, yama_task_free),
};
#ifdef CONFIG_SYSCTL
static int yama_dointvec_minmax(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
struct ctl_table table_copy;
if (write && !capable(CAP_SYS_PTRACE))
return -EPERM;
/* Lock the max value if it ever gets set. */
table_copy = *table;
if (*(int *)table_copy.data == *(int *)table_copy.extra2)
table_copy.extra1 = table_copy.extra2;
return proc_dointvec_minmax(&table_copy, write, buffer, lenp, ppos);
}
static int max_scope = YAMA_SCOPE_NO_ATTACH;
static const struct ctl_table yama_sysctl_table[] = {
{
.procname = "ptrace_scope" ,
.data = &ptrace_scope,
.maxlen = sizeof (int ),
.mode = 0644 ,
.proc_handler = yama_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &max_scope,
},
};
static void __init yama_init_sysctl(void )
{
if (!register_sysctl("kernel/yama" , yama_sysctl_table))
panic("Yama: sysctl registration failed.\n" );
}
#else
static inline void yama_init_sysctl(void ) { }
#endif /* CONFIG_SYSCTL */
static int __init yama_init(void )
{
pr_info("Yama: becoming mindful.\n" );
security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks), &yama_lsmid);
yama_init_sysctl();
return 0 ;
}
DEFINE_LSM(yama) = {
.name = "yama" ,
.init = yama_init,
};
Messung V0.5 in Prozent C=96 H=98 G=96
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126864
#Domains=1897691