Christian Brauner (Amutable)exec_state: relocate dumpable information6b1c66c5 months agoHistory
1587 lines · 41.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * linux/kernel/ptrace.c * * (C) Copyright 1999 Linus Torvalds * * Common interfaces for "ptrace()" which we do not want * to continually duplicate across every architecture. */ #include <linux/capability.h>#include <linux/export.h>#include <linux/sched.h>#include <linux/sched/mm.h>#include <linux/sched/coredump.h>#include <linux/sched/exec_state.h>#include <linux/sched/task.h>#include <linux/errno.h>#include <linux/mm.h>#include <linux/highmem.h>#include <linux/pagemap.h>#include <linux/ptrace.h>#include <linux/security.h>#include <linux/signal.h>#include <linux/uio.h>#include <linux/audit.h>#include <linux/pid_namespace.h>#include <linux/syscalls.h>#include <linux/uaccess.h>#include <linux/regset.h>#include <linux/hw_breakpoint.h>#include <linux/cn_proc.h>#include <linux/compat.h>#include <linux/sched/signal.h>#include <linux/minmax.h>#include <linux/syscall_user_dispatch.h> #include <asm/syscall.h>	/* for syscall_get_* */ /** * ptracer_access_allowed - may current peek/poke @tsk's address space? * @tsk: tracee * * Per-access check used by ptrace_access_vm() and architecture-specific * tag/register accessors.  Returns true iff current is the registered * ptracer of @tsk and either @tsk is owner-dumpable or current holds * CAP_SYS_PTRACE in @tsk's exec namespace.  Lighter than * __ptrace_may_access(): it re-validates only dumpability and * capability on every access, without re-running LSM hooks or * cred_cap_issubset() checks performed at attach time. */bool ptracer_access_allowed(struct task_struct *tsk){	const struct task_exec_state *es; 	guard(rcu)();	if (ptrace_parent(tsk) != current)		return false;	es = task_exec_state_rcu(tsk);	return READ_ONCE(es->dumpable) == TASK_DUMPABLE_OWNER ||	       ptracer_capable(tsk, es->user_ns);} /* * Access another process' address space via ptrace. * Source/target buffer must be kernel space, * Do not walk the page table directly, use get_user_pages */int ptrace_access_vm(struct task_struct *tsk, unsigned long addr,		     void *buf, int len, unsigned int gup_flags){	struct mm_struct *mm;	int ret = 0; 	mm = get_task_mm(tsk);	if (!mm)		return 0; 	if (ptracer_access_allowed(tsk))		ret = access_remote_vm(mm, addr, buf, len, gup_flags);	mmput(mm); 	return ret;}  void __ptrace_link(struct task_struct *child, struct task_struct *new_parent,		   const struct cred *ptracer_cred){	BUG_ON(!list_empty(&child->ptrace_entry));	list_add(&child->ptrace_entry, &new_parent->ptraced);	child->parent = new_parent;	child->ptracer_cred = get_cred(ptracer_cred);} /* * ptrace a task: make the debugger its new parent and * move it to the ptrace list. * * Must be called with the tasklist lock write-held. */static void ptrace_link(struct task_struct *child, struct task_struct *new_parent){	__ptrace_link(child, new_parent, current_cred());} /** * __ptrace_unlink - unlink ptracee and restore its execution state * @child: ptracee to be unlinked * * Remove @child from the ptrace list, move it back to the original parent, * and restore the execution state so that it conforms to the group stop * state. * * Unlinking can happen via two paths - explicit PTRACE_DETACH or ptracer * exiting.  For PTRACE_DETACH, unless the ptracee has been killed between * ptrace_check_attach() and here, it's guaranteed to be in TASK_TRACED. * If the ptracer is exiting, the ptracee can be in any state. * * After detach, the ptracee should be in a state which conforms to the * group stop.  If the group is stopped or in the process of stopping, the * ptracee should be put into TASK_STOPPED; otherwise, it should be woken * up from TASK_TRACED. * * If the ptracee is in TASK_TRACED and needs to be moved to TASK_STOPPED, * it goes through TRACED -> RUNNING -> STOPPED transition which is similar * to but in the opposite direction of what happens while attaching to a * stopped task.  However, in this direction, the intermediate RUNNING * state is not hidden even from the current ptracer and if it immediately * re-attaches and performs a WNOHANG wait(2), it may fail. * * CONTEXT: * write_lock_irq(tasklist_lock) */void __ptrace_unlink(struct task_struct *child){	const struct cred *old_cred;	BUG_ON(!child->ptrace); 	clear_task_syscall_work(child, SYSCALL_TRACE);#if defined(CONFIG_GENERIC_ENTRY) || defined(TIF_SYSCALL_EMU)	clear_task_syscall_work(child, SYSCALL_EMU);#endif 	child->parent = child->real_parent;	list_del_init(&child->ptrace_entry);	old_cred = child->ptracer_cred;	child->ptracer_cred = NULL;	put_cred(old_cred); 	spin_lock(&child->sighand->siglock);	child->ptrace = 0;	/*	 * Clear all pending traps and TRAPPING.  TRAPPING should be	 * cleared regardless of JOBCTL_STOP_PENDING.  Do it explicitly.	 */	task_clear_jobctl_pending(child, JOBCTL_TRAP_MASK);	task_clear_jobctl_trapping(child); 	/*	 * Reinstate JOBCTL_STOP_PENDING if group stop is in effect and	 * @child isn't dead.	 */	if (!(child->flags & PF_EXITING) &&	    (child->signal->flags & SIGNAL_STOP_STOPPED ||	     child->signal->group_stop_count))		child->jobctl |= JOBCTL_STOP_PENDING; 	/*	 * If transition to TASK_STOPPED is pending or in TASK_TRACED, kick	 * @child in the butt.  Note that @resume should be used iff @child	 * is in TASK_TRACED; otherwise, we might unduly disrupt	 * TASK_KILLABLE sleeps.	 */	if (child->jobctl & JOBCTL_STOP_PENDING || task_is_traced(child))		ptrace_signal_wake_up(child, true); 	spin_unlock(&child->sighand->siglock);} static bool looks_like_a_spurious_pid(struct task_struct *task){	if (task->exit_code != ((PTRACE_EVENT_EXEC << 8) | SIGTRAP))		return false; 	if (task_pid_vnr(task) == task->ptrace_message)		return false;	/*	 * The tracee changed its pid but the PTRACE_EVENT_EXEC event	 * was not wait()'ed, most probably debugger targets the old	 * leader which was destroyed in de_thread().	 */	return true;} /* * Ensure that nothing can wake it up, even SIGKILL * * A task is switched to this state while a ptrace operation is in progress; * such that the ptrace operation is uninterruptible. */static bool ptrace_freeze_traced(struct task_struct *task){	bool ret = false; 	/* Lockless, nobody but us can set this flag */	if (task->jobctl & JOBCTL_LISTENING)		return ret; 	spin_lock_irq(&task->sighand->siglock);	if (task_is_traced(task) && !looks_like_a_spurious_pid(task) &&	    !__fatal_signal_pending(task)) {		task->jobctl |= JOBCTL_PTRACE_FROZEN;		ret = true;	}	spin_unlock_irq(&task->sighand->siglock); 	return ret;} static void ptrace_unfreeze_traced(struct task_struct *task){	unsigned long flags; 	/*	 * The child may be awake and may have cleared	 * JOBCTL_PTRACE_FROZEN (see ptrace_resume).  The child will	 * not set JOBCTL_PTRACE_FROZEN or enter __TASK_TRACED anew.	 */	if (lock_task_sighand(task, &flags)) {		task->jobctl &= ~JOBCTL_PTRACE_FROZEN;		if (__fatal_signal_pending(task)) {			task->jobctl &= ~JOBCTL_TRACED;			wake_up_state(task, __TASK_TRACED);		}		unlock_task_sighand(task, &flags);	}} /** * ptrace_check_attach - check whether ptracee is ready for ptrace operation * @child: ptracee to check for * @ignore_state: don't check whether @child is currently %TASK_TRACED * * Check whether @child is being ptraced by %current and ready for further * ptrace operations.  If @ignore_state is %false, @child also should be in * %TASK_TRACED state and on return the child is guaranteed to be traced * and not executing.  If @ignore_state is %true, @child can be in any * state. * * CONTEXT: * Grabs and releases tasklist_lock and @child->sighand->siglock. * * RETURNS: * 0 on success, -ESRCH if %child is not ready. */static int ptrace_check_attach(struct task_struct *child, bool ignore_state){	int ret = -ESRCH; 	/*	 * We take the read lock around doing both checks to close a	 * possible race where someone else was tracing our child and	 * detached between these two checks.  After this locked check,	 * we are sure that this is our traced child and that can only	 * be changed by us so it's not changing right after this.	 */	read_lock(&tasklist_lock);	if (child->ptrace && child->parent == current) {		/*		 * child->sighand can't be NULL, release_task()		 * does ptrace_unlink() before __exit_signal().		 */		if (ignore_state || ptrace_freeze_traced(child))			ret = 0;	}	read_unlock(&tasklist_lock); 	if (!ret && !ignore_state &&	    WARN_ON_ONCE(!wait_task_inactive(child, __TASK_TRACED|TASK_FROZEN)))		ret = -ESRCH; 	return ret;} static bool ptrace_has_cap(struct user_namespace *ns, unsigned int mode){	if (mode & PTRACE_MODE_NOAUDIT)		return ns_capable_noaudit(ns, CAP_SYS_PTRACE);	return ns_capable(ns, CAP_SYS_PTRACE);} static bool task_still_dumpable(struct task_struct *task, unsigned int mode){	const struct task_exec_state *exec_state; 	guard(rcu)();	exec_state = task_exec_state_rcu(task);	if (READ_ONCE(exec_state->dumpable) == TASK_DUMPABLE_OWNER)		return true;	return ptrace_has_cap(exec_state->user_ns, mode);} /* Returns 0 on success, -errno on denial. */static int __ptrace_may_access(struct task_struct *task, unsigned int mode){	const struct cred *cred = current_cred(), *tcred;	kuid_t caller_uid;	kgid_t caller_gid; 	if (!(mode & PTRACE_MODE_FSCREDS) == !(mode & PTRACE_MODE_REALCREDS)) {		WARN(1, "denying ptrace access check without PTRACE_MODE_*CREDS\n");		return -EPERM;	} 	/* May we inspect the given task?	 * This check is used both for attaching with ptrace	 * and for allowing access to sensitive information in /proc.	 *	 * ptrace_attach denies several cases that /proc allows	 * because setting up the necessary parent/child relationship	 * or halting the specified task is impossible.	 */ 	/* Don't let security modules deny introspection */	if (same_thread_group(task, current))		return 0;	rcu_read_lock();	if (mode & PTRACE_MODE_FSCREDS) {		caller_uid = cred->fsuid;		caller_gid = cred->fsgid;	} else {		/*		 * Using the euid would make more sense here, but something		 * in userland might rely on the old behavior, and this		 * shouldn't be a security problem since		 * PTRACE_MODE_REALCREDS implies that the caller explicitly		 * used a syscall that requests access to another process		 * (and not a filesystem syscall to procfs).		 */		caller_uid = cred->uid;		caller_gid = cred->gid;	}	tcred = __task_cred(task);	if (uid_eq(caller_uid, tcred->euid) &&	    uid_eq(caller_uid, tcred->suid) &&	    uid_eq(caller_uid, tcred->uid)  &&	    gid_eq(caller_gid, tcred->egid) &&	    gid_eq(caller_gid, tcred->sgid) &&	    gid_eq(caller_gid, tcred->gid))		goto ok;	if (ptrace_has_cap(tcred->user_ns, mode))		goto ok;	rcu_read_unlock();	return -EPERM;ok:	rcu_read_unlock();	/*	 * If a task drops privileges and becomes nondumpable (through a syscall	 * like setresuid()) while we are trying to access it, we must ensure	 * that the dumpability is read after the credentials; otherwise,	 * we may be able to attach to a task that we shouldn't be able to	 * attach to (as if the task had dropped privileges without becoming	 * nondumpable).	 * Pairs with a write barrier in commit_creds().	 */	smp_rmb();	if (!task_still_dumpable(task, mode))		return -EPERM; 	return security_ptrace_access_check(task, mode);} bool ptrace_may_access(struct task_struct *task, unsigned int mode){	int err;	task_lock(task);	err = __ptrace_may_access(task, mode);	task_unlock(task);	return !err;} static int check_ptrace_options(unsigned long data){	if (data & ~(unsigned long)PTRACE_O_MASK)		return -EINVAL; 	if (unlikely(data & PTRACE_O_SUSPEND_SECCOMP)) {		if (!IS_ENABLED(CONFIG_CHECKPOINT_RESTORE) ||		    !IS_ENABLED(CONFIG_SECCOMP))			return -EINVAL; 		if (!capable(CAP_SYS_ADMIN))			return -EPERM; 		if (seccomp_mode(&current->seccomp) != SECCOMP_MODE_DISABLED ||		    current->ptrace & PT_SUSPEND_SECCOMP)			return -EPERM;	}	return 0;} static inline void ptrace_set_stopped(struct task_struct *task, bool seize){	guard(spinlock)(&task->sighand->siglock); 	/* SEIZE doesn't trap tracee on attach */	if (!seize)		send_signal_locked(SIGSTOP, SEND_SIG_PRIV, task, PIDTYPE_PID);	/*	 * If the task is already STOPPED, set JOBCTL_TRAP_STOP and	 * TRAPPING, and kick it so that it transits to TRACED.  TRAPPING	 * will be cleared if the child completes the transition or any	 * event which clears the group stop states happens.  We'll wait	 * for the transition to complete before returning from this	 * function.	 *	 * This hides STOPPED -> RUNNING -> TRACED transition from the	 * attaching thread but a different thread in the same group can	 * still observe the transient RUNNING state.  IOW, if another	 * thread's WNOHANG wait(2) on the stopped tracee races against	 * ATTACH, the wait(2) may fail due to the transient RUNNING.	 *	 * The following task_is_stopped() test is safe as both transitions	 * in and out of STOPPED are protected by siglock.	 */	if (task_is_stopped(task) &&	    task_set_jobctl_pending(task, JOBCTL_TRAP_STOP | JOBCTL_TRAPPING)) {		task->jobctl &= ~JOBCTL_STOPPED;		signal_wake_up_state(task, __TASK_STOPPED);	}} static int ptrace_attach(struct task_struct *task, long request,			 unsigned long addr,			 unsigned long flags){	bool seize = (request == PTRACE_SEIZE);	int retval; 	if (seize) {		if (addr != 0)			return -EIO;		/*		 * This duplicates the check in check_ptrace_options() because		 * ptrace_attach() and ptrace_setoptions() have historically		 * used different error codes for unknown ptrace options.		 */		if (flags & ~(unsigned long)PTRACE_O_MASK)			return -EIO; 		retval = check_ptrace_options(flags);		if (retval)			return retval;		flags = PT_PTRACED | PT_SEIZED | (flags << PT_OPT_FLAG_SHIFT);	} else {		flags = PT_PTRACED;	} 	audit_ptrace(task); 	if (unlikely(task->flags & PF_KTHREAD))		return -EPERM;	if (same_thread_group(task, current))		return -EPERM; 	/*	 * Protect exec's credential calculations against our interference;	 * SUID, SGID and LSM creds get determined differently	 * under ptrace.	 */	scoped_cond_guard (mutex_intr, return -ERESTARTNOINTR,			   &task->signal->cred_guard_mutex) { 		scoped_guard (task_lock, task) {			retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH_REALCREDS);			if (retval)				return retval;		} 		scoped_guard (write_lock_irq, &tasklist_lock) {			if (unlikely(task->exit_state))				return -EPERM;			if (task->ptrace)				return -EPERM; 			task->ptrace = flags;			ptrace_link(task, current);			ptrace_set_stopped(task, seize);		}	} 	/*	 * We do not bother to change retval or clear JOBCTL_TRAPPING	 * if wait_on_bit() was interrupted by SIGKILL. The tracer will	 * not return to user-mode, it will exit and clear this bit in	 * __ptrace_unlink() if it wasn't already cleared by the tracee;	 * and until then nobody can ptrace this task.	 */	wait_on_bit(&task->jobctl, JOBCTL_TRAPPING_BIT, TASK_KILLABLE);

Lines 1–500 of 1587.