162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Yama Linux Security Module 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Author: Kees Cook <keescook@chromium.org> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Copyright (C) 2010 Canonical, Ltd. 862306a36Sopenharmony_ci * Copyright (C) 2011 The Chromium OS Authors. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/lsm_hooks.h> 1262306a36Sopenharmony_ci#include <linux/sysctl.h> 1362306a36Sopenharmony_ci#include <linux/ptrace.h> 1462306a36Sopenharmony_ci#include <linux/prctl.h> 1562306a36Sopenharmony_ci#include <linux/ratelimit.h> 1662306a36Sopenharmony_ci#include <linux/workqueue.h> 1762306a36Sopenharmony_ci#include <linux/string_helpers.h> 1862306a36Sopenharmony_ci#include <linux/task_work.h> 1962306a36Sopenharmony_ci#include <linux/sched.h> 2062306a36Sopenharmony_ci#include <linux/spinlock.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define YAMA_SCOPE_DISABLED 0 2362306a36Sopenharmony_ci#define YAMA_SCOPE_RELATIONAL 1 2462306a36Sopenharmony_ci#define YAMA_SCOPE_CAPABILITY 2 2562306a36Sopenharmony_ci#define YAMA_SCOPE_NO_ATTACH 3 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic int ptrace_scope = YAMA_SCOPE_RELATIONAL; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* describe a ptrace relationship for potential exception */ 3062306a36Sopenharmony_cistruct ptrace_relation { 3162306a36Sopenharmony_ci struct task_struct *tracer; 3262306a36Sopenharmony_ci struct task_struct *tracee; 3362306a36Sopenharmony_ci bool invalid; 3462306a36Sopenharmony_ci struct list_head node; 3562306a36Sopenharmony_ci struct rcu_head rcu; 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic LIST_HEAD(ptracer_relations); 3962306a36Sopenharmony_cistatic DEFINE_SPINLOCK(ptracer_relations_lock); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic void yama_relation_cleanup(struct work_struct *work); 4262306a36Sopenharmony_cistatic DECLARE_WORK(yama_relation_work, yama_relation_cleanup); 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistruct access_report_info { 4562306a36Sopenharmony_ci struct callback_head work; 4662306a36Sopenharmony_ci const char *access; 4762306a36Sopenharmony_ci struct task_struct *target; 4862306a36Sopenharmony_ci struct task_struct *agent; 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic void __report_access(struct callback_head *work) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci struct access_report_info *info = 5462306a36Sopenharmony_ci container_of(work, struct access_report_info, work); 5562306a36Sopenharmony_ci char *target_cmd, *agent_cmd; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci target_cmd = kstrdup_quotable_cmdline(info->target, GFP_KERNEL); 5862306a36Sopenharmony_ci agent_cmd = kstrdup_quotable_cmdline(info->agent, GFP_KERNEL); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci pr_notice_ratelimited( 6162306a36Sopenharmony_ci "ptrace %s of \"%s\"[%d] was attempted by \"%s\"[%d]\n", 6262306a36Sopenharmony_ci info->access, target_cmd, info->target->pid, agent_cmd, 6362306a36Sopenharmony_ci info->agent->pid); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci kfree(agent_cmd); 6662306a36Sopenharmony_ci kfree(target_cmd); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci put_task_struct(info->agent); 6962306a36Sopenharmony_ci put_task_struct(info->target); 7062306a36Sopenharmony_ci kfree(info); 7162306a36Sopenharmony_ci} 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci/* defers execution because cmdline access can sleep */ 7462306a36Sopenharmony_cistatic void report_access(const char *access, struct task_struct *target, 7562306a36Sopenharmony_ci struct task_struct *agent) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct access_report_info *info; 7862306a36Sopenharmony_ci char agent_comm[sizeof(agent->comm)]; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci assert_spin_locked(&target->alloc_lock); /* for target->comm */ 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (current->flags & PF_KTHREAD) { 8362306a36Sopenharmony_ci /* I don't think kthreads call task_work_run() before exiting. 8462306a36Sopenharmony_ci * Imagine angry ranting about procfs here. 8562306a36Sopenharmony_ci */ 8662306a36Sopenharmony_ci pr_notice_ratelimited( 8762306a36Sopenharmony_ci "ptrace %s of \"%s\"[%d] was attempted by \"%s\"[%d]\n", 8862306a36Sopenharmony_ci access, target->comm, target->pid, 8962306a36Sopenharmony_ci get_task_comm(agent_comm, agent), agent->pid); 9062306a36Sopenharmony_ci return; 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci info = kmalloc(sizeof(*info), GFP_ATOMIC); 9462306a36Sopenharmony_ci if (!info) 9562306a36Sopenharmony_ci return; 9662306a36Sopenharmony_ci init_task_work(&info->work, __report_access); 9762306a36Sopenharmony_ci get_task_struct(target); 9862306a36Sopenharmony_ci get_task_struct(agent); 9962306a36Sopenharmony_ci info->access = access; 10062306a36Sopenharmony_ci info->target = target; 10162306a36Sopenharmony_ci info->agent = agent; 10262306a36Sopenharmony_ci if (task_work_add(current, &info->work, TWA_RESUME) == 0) 10362306a36Sopenharmony_ci return; /* success */ 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci WARN(1, "report_access called from exiting task"); 10662306a36Sopenharmony_ci put_task_struct(target); 10762306a36Sopenharmony_ci put_task_struct(agent); 10862306a36Sopenharmony_ci kfree(info); 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci/** 11262306a36Sopenharmony_ci * yama_relation_cleanup - remove invalid entries from the relation list 11362306a36Sopenharmony_ci * 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_cistatic void yama_relation_cleanup(struct work_struct *work) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci struct ptrace_relation *relation; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci spin_lock(&ptracer_relations_lock); 12062306a36Sopenharmony_ci rcu_read_lock(); 12162306a36Sopenharmony_ci list_for_each_entry_rcu(relation, &ptracer_relations, node) { 12262306a36Sopenharmony_ci if (relation->invalid) { 12362306a36Sopenharmony_ci list_del_rcu(&relation->node); 12462306a36Sopenharmony_ci kfree_rcu(relation, rcu); 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci rcu_read_unlock(); 12862306a36Sopenharmony_ci spin_unlock(&ptracer_relations_lock); 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci/** 13262306a36Sopenharmony_ci * yama_ptracer_add - add/replace an exception for this tracer/tracee pair 13362306a36Sopenharmony_ci * @tracer: the task_struct of the process doing the ptrace 13462306a36Sopenharmony_ci * @tracee: the task_struct of the process to be ptraced 13562306a36Sopenharmony_ci * 13662306a36Sopenharmony_ci * Each tracee can have, at most, one tracer registered. Each time this 13762306a36Sopenharmony_ci * is called, the prior registered tracer will be replaced for the tracee. 13862306a36Sopenharmony_ci * 13962306a36Sopenharmony_ci * Returns 0 if relationship was added, -ve on error. 14062306a36Sopenharmony_ci */ 14162306a36Sopenharmony_cistatic int yama_ptracer_add(struct task_struct *tracer, 14262306a36Sopenharmony_ci struct task_struct *tracee) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci struct ptrace_relation *relation, *added; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci added = kmalloc(sizeof(*added), GFP_KERNEL); 14762306a36Sopenharmony_ci if (!added) 14862306a36Sopenharmony_ci return -ENOMEM; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci added->tracee = tracee; 15162306a36Sopenharmony_ci added->tracer = tracer; 15262306a36Sopenharmony_ci added->invalid = false; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci spin_lock(&ptracer_relations_lock); 15562306a36Sopenharmony_ci rcu_read_lock(); 15662306a36Sopenharmony_ci list_for_each_entry_rcu(relation, &ptracer_relations, node) { 15762306a36Sopenharmony_ci if (relation->invalid) 15862306a36Sopenharmony_ci continue; 15962306a36Sopenharmony_ci if (relation->tracee == tracee) { 16062306a36Sopenharmony_ci list_replace_rcu(&relation->node, &added->node); 16162306a36Sopenharmony_ci kfree_rcu(relation, rcu); 16262306a36Sopenharmony_ci goto out; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci list_add_rcu(&added->node, &ptracer_relations); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ciout: 16962306a36Sopenharmony_ci rcu_read_unlock(); 17062306a36Sopenharmony_ci spin_unlock(&ptracer_relations_lock); 17162306a36Sopenharmony_ci return 0; 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci/** 17562306a36Sopenharmony_ci * yama_ptracer_del - remove exceptions related to the given tasks 17662306a36Sopenharmony_ci * @tracer: remove any relation where tracer task matches 17762306a36Sopenharmony_ci * @tracee: remove any relation where tracee task matches 17862306a36Sopenharmony_ci */ 17962306a36Sopenharmony_cistatic void yama_ptracer_del(struct task_struct *tracer, 18062306a36Sopenharmony_ci struct task_struct *tracee) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci struct ptrace_relation *relation; 18362306a36Sopenharmony_ci bool marked = false; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci rcu_read_lock(); 18662306a36Sopenharmony_ci list_for_each_entry_rcu(relation, &ptracer_relations, node) { 18762306a36Sopenharmony_ci if (relation->invalid) 18862306a36Sopenharmony_ci continue; 18962306a36Sopenharmony_ci if (relation->tracee == tracee || 19062306a36Sopenharmony_ci (tracer && relation->tracer == tracer)) { 19162306a36Sopenharmony_ci relation->invalid = true; 19262306a36Sopenharmony_ci marked = true; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci rcu_read_unlock(); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (marked) 19862306a36Sopenharmony_ci schedule_work(&yama_relation_work); 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci/** 20262306a36Sopenharmony_ci * yama_task_free - check for task_pid to remove from exception list 20362306a36Sopenharmony_ci * @task: task being removed 20462306a36Sopenharmony_ci */ 20562306a36Sopenharmony_cistatic void yama_task_free(struct task_struct *task) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci yama_ptracer_del(task, task); 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci/** 21162306a36Sopenharmony_ci * yama_task_prctl - check for Yama-specific prctl operations 21262306a36Sopenharmony_ci * @option: operation 21362306a36Sopenharmony_ci * @arg2: argument 21462306a36Sopenharmony_ci * @arg3: argument 21562306a36Sopenharmony_ci * @arg4: argument 21662306a36Sopenharmony_ci * @arg5: argument 21762306a36Sopenharmony_ci * 21862306a36Sopenharmony_ci * Return 0 on success, -ve on error. -ENOSYS is returned when Yama 21962306a36Sopenharmony_ci * does not handle the given option. 22062306a36Sopenharmony_ci */ 22162306a36Sopenharmony_cistatic int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, 22262306a36Sopenharmony_ci unsigned long arg4, unsigned long arg5) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci int rc = -ENOSYS; 22562306a36Sopenharmony_ci struct task_struct *myself = current; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci switch (option) { 22862306a36Sopenharmony_ci case PR_SET_PTRACER: 22962306a36Sopenharmony_ci /* Since a thread can call prctl(), find the group leader 23062306a36Sopenharmony_ci * before calling _add() or _del() on it, since we want 23162306a36Sopenharmony_ci * process-level granularity of control. The tracer group 23262306a36Sopenharmony_ci * leader checking is handled later when walking the ancestry 23362306a36Sopenharmony_ci * at the time of PTRACE_ATTACH check. 23462306a36Sopenharmony_ci */ 23562306a36Sopenharmony_ci rcu_read_lock(); 23662306a36Sopenharmony_ci if (!thread_group_leader(myself)) 23762306a36Sopenharmony_ci myself = rcu_dereference(myself->group_leader); 23862306a36Sopenharmony_ci get_task_struct(myself); 23962306a36Sopenharmony_ci rcu_read_unlock(); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci if (arg2 == 0) { 24262306a36Sopenharmony_ci yama_ptracer_del(NULL, myself); 24362306a36Sopenharmony_ci rc = 0; 24462306a36Sopenharmony_ci } else if (arg2 == PR_SET_PTRACER_ANY || (int)arg2 == -1) { 24562306a36Sopenharmony_ci rc = yama_ptracer_add(NULL, myself); 24662306a36Sopenharmony_ci } else { 24762306a36Sopenharmony_ci struct task_struct *tracer; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci tracer = find_get_task_by_vpid(arg2); 25062306a36Sopenharmony_ci if (!tracer) { 25162306a36Sopenharmony_ci rc = -EINVAL; 25262306a36Sopenharmony_ci } else { 25362306a36Sopenharmony_ci rc = yama_ptracer_add(tracer, myself); 25462306a36Sopenharmony_ci put_task_struct(tracer); 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci } 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci put_task_struct(myself); 25962306a36Sopenharmony_ci break; 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci return rc; 26362306a36Sopenharmony_ci} 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci/** 26662306a36Sopenharmony_ci * task_is_descendant - walk up a process family tree looking for a match 26762306a36Sopenharmony_ci * @parent: the process to compare against while walking up from child 26862306a36Sopenharmony_ci * @child: the process to start from while looking upwards for parent 26962306a36Sopenharmony_ci * 27062306a36Sopenharmony_ci * Returns 1 if child is a descendant of parent, 0 if not. 27162306a36Sopenharmony_ci */ 27262306a36Sopenharmony_cistatic int task_is_descendant(struct task_struct *parent, 27362306a36Sopenharmony_ci struct task_struct *child) 27462306a36Sopenharmony_ci{ 27562306a36Sopenharmony_ci int rc = 0; 27662306a36Sopenharmony_ci struct task_struct *walker = child; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci if (!parent || !child) 27962306a36Sopenharmony_ci return 0; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci rcu_read_lock(); 28262306a36Sopenharmony_ci if (!thread_group_leader(parent)) 28362306a36Sopenharmony_ci parent = rcu_dereference(parent->group_leader); 28462306a36Sopenharmony_ci while (walker->pid > 0) { 28562306a36Sopenharmony_ci if (!thread_group_leader(walker)) 28662306a36Sopenharmony_ci walker = rcu_dereference(walker->group_leader); 28762306a36Sopenharmony_ci if (walker == parent) { 28862306a36Sopenharmony_ci rc = 1; 28962306a36Sopenharmony_ci break; 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci walker = rcu_dereference(walker->real_parent); 29262306a36Sopenharmony_ci } 29362306a36Sopenharmony_ci rcu_read_unlock(); 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci return rc; 29662306a36Sopenharmony_ci} 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci/** 29962306a36Sopenharmony_ci * ptracer_exception_found - tracer registered as exception for this tracee 30062306a36Sopenharmony_ci * @tracer: the task_struct of the process attempting ptrace 30162306a36Sopenharmony_ci * @tracee: the task_struct of the process to be ptraced 30262306a36Sopenharmony_ci * 30362306a36Sopenharmony_ci * Returns 1 if tracer has a ptracer exception ancestor for tracee. 30462306a36Sopenharmony_ci */ 30562306a36Sopenharmony_cistatic int ptracer_exception_found(struct task_struct *tracer, 30662306a36Sopenharmony_ci struct task_struct *tracee) 30762306a36Sopenharmony_ci{ 30862306a36Sopenharmony_ci int rc = 0; 30962306a36Sopenharmony_ci struct ptrace_relation *relation; 31062306a36Sopenharmony_ci struct task_struct *parent = NULL; 31162306a36Sopenharmony_ci bool found = false; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci rcu_read_lock(); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci /* 31662306a36Sopenharmony_ci * If there's already an active tracing relationship, then make an 31762306a36Sopenharmony_ci * exception for the sake of other accesses, like process_vm_rw(). 31862306a36Sopenharmony_ci */ 31962306a36Sopenharmony_ci parent = ptrace_parent(tracee); 32062306a36Sopenharmony_ci if (parent != NULL && same_thread_group(parent, tracer)) { 32162306a36Sopenharmony_ci rc = 1; 32262306a36Sopenharmony_ci goto unlock; 32362306a36Sopenharmony_ci } 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci /* Look for a PR_SET_PTRACER relationship. */ 32662306a36Sopenharmony_ci if (!thread_group_leader(tracee)) 32762306a36Sopenharmony_ci tracee = rcu_dereference(tracee->group_leader); 32862306a36Sopenharmony_ci list_for_each_entry_rcu(relation, &ptracer_relations, node) { 32962306a36Sopenharmony_ci if (relation->invalid) 33062306a36Sopenharmony_ci continue; 33162306a36Sopenharmony_ci if (relation->tracee == tracee) { 33262306a36Sopenharmony_ci parent = relation->tracer; 33362306a36Sopenharmony_ci found = true; 33462306a36Sopenharmony_ci break; 33562306a36Sopenharmony_ci } 33662306a36Sopenharmony_ci } 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci if (found && (parent == NULL || task_is_descendant(parent, tracer))) 33962306a36Sopenharmony_ci rc = 1; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ciunlock: 34262306a36Sopenharmony_ci rcu_read_unlock(); 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci return rc; 34562306a36Sopenharmony_ci} 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci/** 34862306a36Sopenharmony_ci * yama_ptrace_access_check - validate PTRACE_ATTACH calls 34962306a36Sopenharmony_ci * @child: task that current task is attempting to ptrace 35062306a36Sopenharmony_ci * @mode: ptrace attach mode 35162306a36Sopenharmony_ci * 35262306a36Sopenharmony_ci * Returns 0 if following the ptrace is allowed, -ve on error. 35362306a36Sopenharmony_ci */ 35462306a36Sopenharmony_cistatic int yama_ptrace_access_check(struct task_struct *child, 35562306a36Sopenharmony_ci unsigned int mode) 35662306a36Sopenharmony_ci{ 35762306a36Sopenharmony_ci int rc = 0; 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci /* require ptrace target be a child of ptracer on attach */ 36062306a36Sopenharmony_ci if (mode & PTRACE_MODE_ATTACH) { 36162306a36Sopenharmony_ci switch (ptrace_scope) { 36262306a36Sopenharmony_ci case YAMA_SCOPE_DISABLED: 36362306a36Sopenharmony_ci /* No additional restrictions. */ 36462306a36Sopenharmony_ci break; 36562306a36Sopenharmony_ci case YAMA_SCOPE_RELATIONAL: 36662306a36Sopenharmony_ci rcu_read_lock(); 36762306a36Sopenharmony_ci if (!pid_alive(child)) 36862306a36Sopenharmony_ci rc = -EPERM; 36962306a36Sopenharmony_ci if (!rc && !task_is_descendant(current, child) && 37062306a36Sopenharmony_ci !ptracer_exception_found(current, child) && 37162306a36Sopenharmony_ci !ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE)) 37262306a36Sopenharmony_ci rc = -EPERM; 37362306a36Sopenharmony_ci rcu_read_unlock(); 37462306a36Sopenharmony_ci break; 37562306a36Sopenharmony_ci case YAMA_SCOPE_CAPABILITY: 37662306a36Sopenharmony_ci rcu_read_lock(); 37762306a36Sopenharmony_ci if (!ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE)) 37862306a36Sopenharmony_ci rc = -EPERM; 37962306a36Sopenharmony_ci rcu_read_unlock(); 38062306a36Sopenharmony_ci break; 38162306a36Sopenharmony_ci case YAMA_SCOPE_NO_ATTACH: 38262306a36Sopenharmony_ci default: 38362306a36Sopenharmony_ci rc = -EPERM; 38462306a36Sopenharmony_ci break; 38562306a36Sopenharmony_ci } 38662306a36Sopenharmony_ci } 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci if (rc && (mode & PTRACE_MODE_NOAUDIT) == 0) 38962306a36Sopenharmony_ci report_access("attach", child, current); 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci return rc; 39262306a36Sopenharmony_ci} 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci/** 39562306a36Sopenharmony_ci * yama_ptrace_traceme - validate PTRACE_TRACEME calls 39662306a36Sopenharmony_ci * @parent: task that will become the ptracer of the current task 39762306a36Sopenharmony_ci * 39862306a36Sopenharmony_ci * Returns 0 if following the ptrace is allowed, -ve on error. 39962306a36Sopenharmony_ci */ 40062306a36Sopenharmony_cistatic int yama_ptrace_traceme(struct task_struct *parent) 40162306a36Sopenharmony_ci{ 40262306a36Sopenharmony_ci int rc = 0; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci /* Only disallow PTRACE_TRACEME on more aggressive settings. */ 40562306a36Sopenharmony_ci switch (ptrace_scope) { 40662306a36Sopenharmony_ci case YAMA_SCOPE_CAPABILITY: 40762306a36Sopenharmony_ci if (!has_ns_capability(parent, current_user_ns(), CAP_SYS_PTRACE)) 40862306a36Sopenharmony_ci rc = -EPERM; 40962306a36Sopenharmony_ci break; 41062306a36Sopenharmony_ci case YAMA_SCOPE_NO_ATTACH: 41162306a36Sopenharmony_ci rc = -EPERM; 41262306a36Sopenharmony_ci break; 41362306a36Sopenharmony_ci } 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci if (rc) { 41662306a36Sopenharmony_ci task_lock(current); 41762306a36Sopenharmony_ci report_access("traceme", current, parent); 41862306a36Sopenharmony_ci task_unlock(current); 41962306a36Sopenharmony_ci } 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci return rc; 42262306a36Sopenharmony_ci} 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_cistatic struct security_hook_list yama_hooks[] __ro_after_init = { 42562306a36Sopenharmony_ci LSM_HOOK_INIT(ptrace_access_check, yama_ptrace_access_check), 42662306a36Sopenharmony_ci LSM_HOOK_INIT(ptrace_traceme, yama_ptrace_traceme), 42762306a36Sopenharmony_ci LSM_HOOK_INIT(task_prctl, yama_task_prctl), 42862306a36Sopenharmony_ci LSM_HOOK_INIT(task_free, yama_task_free), 42962306a36Sopenharmony_ci}; 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci#ifdef CONFIG_SYSCTL 43262306a36Sopenharmony_cistatic int yama_dointvec_minmax(struct ctl_table *table, int write, 43362306a36Sopenharmony_ci void *buffer, size_t *lenp, loff_t *ppos) 43462306a36Sopenharmony_ci{ 43562306a36Sopenharmony_ci struct ctl_table table_copy; 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci if (write && !capable(CAP_SYS_PTRACE)) 43862306a36Sopenharmony_ci return -EPERM; 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci /* Lock the max value if it ever gets set. */ 44162306a36Sopenharmony_ci table_copy = *table; 44262306a36Sopenharmony_ci if (*(int *)table_copy.data == *(int *)table_copy.extra2) 44362306a36Sopenharmony_ci table_copy.extra1 = table_copy.extra2; 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci return proc_dointvec_minmax(&table_copy, write, buffer, lenp, ppos); 44662306a36Sopenharmony_ci} 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_cistatic int max_scope = YAMA_SCOPE_NO_ATTACH; 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_cistatic struct ctl_table yama_sysctl_table[] = { 45162306a36Sopenharmony_ci { 45262306a36Sopenharmony_ci .procname = "ptrace_scope", 45362306a36Sopenharmony_ci .data = &ptrace_scope, 45462306a36Sopenharmony_ci .maxlen = sizeof(int), 45562306a36Sopenharmony_ci .mode = 0644, 45662306a36Sopenharmony_ci .proc_handler = yama_dointvec_minmax, 45762306a36Sopenharmony_ci .extra1 = SYSCTL_ZERO, 45862306a36Sopenharmony_ci .extra2 = &max_scope, 45962306a36Sopenharmony_ci }, 46062306a36Sopenharmony_ci { } 46162306a36Sopenharmony_ci}; 46262306a36Sopenharmony_cistatic void __init yama_init_sysctl(void) 46362306a36Sopenharmony_ci{ 46462306a36Sopenharmony_ci if (!register_sysctl("kernel/yama", yama_sysctl_table)) 46562306a36Sopenharmony_ci panic("Yama: sysctl registration failed.\n"); 46662306a36Sopenharmony_ci} 46762306a36Sopenharmony_ci#else 46862306a36Sopenharmony_cistatic inline void yama_init_sysctl(void) { } 46962306a36Sopenharmony_ci#endif /* CONFIG_SYSCTL */ 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_cistatic int __init yama_init(void) 47262306a36Sopenharmony_ci{ 47362306a36Sopenharmony_ci pr_info("Yama: becoming mindful.\n"); 47462306a36Sopenharmony_ci security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks), "yama"); 47562306a36Sopenharmony_ci yama_init_sysctl(); 47662306a36Sopenharmony_ci return 0; 47762306a36Sopenharmony_ci} 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ciDEFINE_LSM(yama) = { 48062306a36Sopenharmony_ci .name = "yama", 48162306a36Sopenharmony_ci .init = yama_init, 48262306a36Sopenharmony_ci}; 483