xref: /openbmc/linux/kernel/ptrace.c (revision 481bed45)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  * linux/kernel/ptrace.c
31da177e4SLinus Torvalds  *
41da177e4SLinus Torvalds  * (C) Copyright 1999 Linus Torvalds
51da177e4SLinus Torvalds  *
61da177e4SLinus Torvalds  * Common interfaces for "ptrace()" which we do not want
71da177e4SLinus Torvalds  * to continually duplicate across every architecture.
81da177e4SLinus Torvalds  */
91da177e4SLinus Torvalds 
101da177e4SLinus Torvalds #include <linux/module.h>
111da177e4SLinus Torvalds #include <linux/sched.h>
121da177e4SLinus Torvalds #include <linux/errno.h>
131da177e4SLinus Torvalds #include <linux/mm.h>
141da177e4SLinus Torvalds #include <linux/highmem.h>
151da177e4SLinus Torvalds #include <linux/pagemap.h>
161da177e4SLinus Torvalds #include <linux/smp_lock.h>
171da177e4SLinus Torvalds #include <linux/ptrace.h>
181da177e4SLinus Torvalds #include <linux/security.h>
197ed20e1aSJesper Juhl #include <linux/signal.h>
201da177e4SLinus Torvalds 
211da177e4SLinus Torvalds #include <asm/pgtable.h>
221da177e4SLinus Torvalds #include <asm/uaccess.h>
231da177e4SLinus Torvalds 
241da177e4SLinus Torvalds /*
251da177e4SLinus Torvalds  * ptrace a task: make the debugger its new parent and
261da177e4SLinus Torvalds  * move it to the ptrace list.
271da177e4SLinus Torvalds  *
281da177e4SLinus Torvalds  * Must be called with the tasklist lock write-held.
291da177e4SLinus Torvalds  */
301da177e4SLinus Torvalds void __ptrace_link(task_t *child, task_t *new_parent)
311da177e4SLinus Torvalds {
321da177e4SLinus Torvalds 	if (!list_empty(&child->ptrace_list))
331da177e4SLinus Torvalds 		BUG();
341da177e4SLinus Torvalds 	if (child->parent == new_parent)
351da177e4SLinus Torvalds 		return;
361da177e4SLinus Torvalds 	list_add(&child->ptrace_list, &child->parent->ptrace_children);
371da177e4SLinus Torvalds 	REMOVE_LINKS(child);
381da177e4SLinus Torvalds 	child->parent = new_parent;
391da177e4SLinus Torvalds 	SET_LINKS(child);
401da177e4SLinus Torvalds }
411da177e4SLinus Torvalds 
421da177e4SLinus Torvalds /*
431da177e4SLinus Torvalds  * Turn a tracing stop into a normal stop now, since with no tracer there
441da177e4SLinus Torvalds  * would be no way to wake it up with SIGCONT or SIGKILL.  If there was a
451da177e4SLinus Torvalds  * signal sent that would resume the child, but didn't because it was in
461da177e4SLinus Torvalds  * TASK_TRACED, resume it now.
471da177e4SLinus Torvalds  * Requires that irqs be disabled.
481da177e4SLinus Torvalds  */
491da177e4SLinus Torvalds void ptrace_untrace(task_t *child)
501da177e4SLinus Torvalds {
511da177e4SLinus Torvalds 	spin_lock(&child->sighand->siglock);
521da177e4SLinus Torvalds 	if (child->state == TASK_TRACED) {
531da177e4SLinus Torvalds 		if (child->signal->flags & SIGNAL_STOP_STOPPED) {
541da177e4SLinus Torvalds 			child->state = TASK_STOPPED;
551da177e4SLinus Torvalds 		} else {
561da177e4SLinus Torvalds 			signal_wake_up(child, 1);
571da177e4SLinus Torvalds 		}
581da177e4SLinus Torvalds 	}
5930e0fca6SAndrea Arcangeli 	if (child->signal->flags & SIGNAL_GROUP_EXIT) {
6030e0fca6SAndrea Arcangeli 		sigaddset(&child->pending.signal, SIGKILL);
6130e0fca6SAndrea Arcangeli 		signal_wake_up(child, 1);
6230e0fca6SAndrea Arcangeli 	}
631da177e4SLinus Torvalds 	spin_unlock(&child->sighand->siglock);
641da177e4SLinus Torvalds }
651da177e4SLinus Torvalds 
661da177e4SLinus Torvalds /*
671da177e4SLinus Torvalds  * unptrace a task: move it back to its original parent and
681da177e4SLinus Torvalds  * remove it from the ptrace list.
691da177e4SLinus Torvalds  *
701da177e4SLinus Torvalds  * Must be called with the tasklist lock write-held.
711da177e4SLinus Torvalds  */
721da177e4SLinus Torvalds void __ptrace_unlink(task_t *child)
731da177e4SLinus Torvalds {
741da177e4SLinus Torvalds 	if (!child->ptrace)
751da177e4SLinus Torvalds 		BUG();
761da177e4SLinus Torvalds 	child->ptrace = 0;
771da177e4SLinus Torvalds 	if (!list_empty(&child->ptrace_list)) {
781da177e4SLinus Torvalds 		list_del_init(&child->ptrace_list);
791da177e4SLinus Torvalds 		REMOVE_LINKS(child);
801da177e4SLinus Torvalds 		child->parent = child->real_parent;
811da177e4SLinus Torvalds 		SET_LINKS(child);
821da177e4SLinus Torvalds 	}
831da177e4SLinus Torvalds 
841da177e4SLinus Torvalds 	ptrace_untrace(child);
851da177e4SLinus Torvalds }
861da177e4SLinus Torvalds 
871da177e4SLinus Torvalds /*
881da177e4SLinus Torvalds  * Check that we have indeed attached to the thing..
891da177e4SLinus Torvalds  */
901da177e4SLinus Torvalds int ptrace_check_attach(struct task_struct *child, int kill)
911da177e4SLinus Torvalds {
921da177e4SLinus Torvalds 	int ret = -ESRCH;
931da177e4SLinus Torvalds 
941da177e4SLinus Torvalds 	/*
951da177e4SLinus Torvalds 	 * We take the read lock around doing both checks to close a
961da177e4SLinus Torvalds 	 * possible race where someone else was tracing our child and
971da177e4SLinus Torvalds 	 * detached between these two checks.  After this locked check,
981da177e4SLinus Torvalds 	 * we are sure that this is our traced child and that can only
991da177e4SLinus Torvalds 	 * be changed by us so it's not changing right after this.
1001da177e4SLinus Torvalds 	 */
1011da177e4SLinus Torvalds 	read_lock(&tasklist_lock);
1021da177e4SLinus Torvalds 	if ((child->ptrace & PT_PTRACED) && child->parent == current &&
1031da177e4SLinus Torvalds 	    (!(child->ptrace & PT_ATTACHED) || child->real_parent != current)
1041da177e4SLinus Torvalds 	    && child->signal != NULL) {
1051da177e4SLinus Torvalds 		ret = 0;
1061da177e4SLinus Torvalds 		spin_lock_irq(&child->sighand->siglock);
1071da177e4SLinus Torvalds 		if (child->state == TASK_STOPPED) {
1081da177e4SLinus Torvalds 			child->state = TASK_TRACED;
1091da177e4SLinus Torvalds 		} else if (child->state != TASK_TRACED && !kill) {
1101da177e4SLinus Torvalds 			ret = -ESRCH;
1111da177e4SLinus Torvalds 		}
1121da177e4SLinus Torvalds 		spin_unlock_irq(&child->sighand->siglock);
1131da177e4SLinus Torvalds 	}
1141da177e4SLinus Torvalds 	read_unlock(&tasklist_lock);
1151da177e4SLinus Torvalds 
1161da177e4SLinus Torvalds 	if (!ret && !kill) {
1171da177e4SLinus Torvalds 		wait_task_inactive(child);
1181da177e4SLinus Torvalds 	}
1191da177e4SLinus Torvalds 
1201da177e4SLinus Torvalds 	/* All systems go.. */
1211da177e4SLinus Torvalds 	return ret;
1221da177e4SLinus Torvalds }
1231da177e4SLinus Torvalds 
124ab8d11beSMiklos Szeredi static int may_attach(struct task_struct *task)
125ab8d11beSMiklos Szeredi {
126ab8d11beSMiklos Szeredi 	if (!task->mm)
127ab8d11beSMiklos Szeredi 		return -EPERM;
128ab8d11beSMiklos Szeredi 	if (((current->uid != task->euid) ||
129ab8d11beSMiklos Szeredi 	     (current->uid != task->suid) ||
130ab8d11beSMiklos Szeredi 	     (current->uid != task->uid) ||
131ab8d11beSMiklos Szeredi 	     (current->gid != task->egid) ||
132ab8d11beSMiklos Szeredi 	     (current->gid != task->sgid) ||
133ab8d11beSMiklos Szeredi 	     (current->gid != task->gid)) && !capable(CAP_SYS_PTRACE))
134ab8d11beSMiklos Szeredi 		return -EPERM;
135ab8d11beSMiklos Szeredi 	smp_rmb();
136ab8d11beSMiklos Szeredi 	if (!task->mm->dumpable && !capable(CAP_SYS_PTRACE))
137ab8d11beSMiklos Szeredi 		return -EPERM;
138ab8d11beSMiklos Szeredi 
139ab8d11beSMiklos Szeredi 	return security_ptrace(current, task);
140ab8d11beSMiklos Szeredi }
141ab8d11beSMiklos Szeredi 
142ab8d11beSMiklos Szeredi int ptrace_may_attach(struct task_struct *task)
143ab8d11beSMiklos Szeredi {
144ab8d11beSMiklos Szeredi 	int err;
145ab8d11beSMiklos Szeredi 	task_lock(task);
146ab8d11beSMiklos Szeredi 	err = may_attach(task);
147ab8d11beSMiklos Szeredi 	task_unlock(task);
148ab8d11beSMiklos Szeredi 	return !err;
149ab8d11beSMiklos Szeredi }
150ab8d11beSMiklos Szeredi 
1511da177e4SLinus Torvalds int ptrace_attach(struct task_struct *task)
1521da177e4SLinus Torvalds {
1531da177e4SLinus Torvalds 	int retval;
1541da177e4SLinus Torvalds 	task_lock(task);
1551da177e4SLinus Torvalds 	retval = -EPERM;
1561da177e4SLinus Torvalds 	if (task->pid <= 1)
1571da177e4SLinus Torvalds 		goto bad;
1581da177e4SLinus Torvalds 	if (task == current)
1591da177e4SLinus Torvalds 		goto bad;
1601da177e4SLinus Torvalds 	/* the same process cannot be attached many times */
1611da177e4SLinus Torvalds 	if (task->ptrace & PT_PTRACED)
1621da177e4SLinus Torvalds 		goto bad;
163ab8d11beSMiklos Szeredi 	retval = may_attach(task);
1641da177e4SLinus Torvalds 	if (retval)
1651da177e4SLinus Torvalds 		goto bad;
1661da177e4SLinus Torvalds 
1671da177e4SLinus Torvalds 	/* Go */
1681da177e4SLinus Torvalds 	task->ptrace |= PT_PTRACED | ((task->real_parent != current)
1691da177e4SLinus Torvalds 				      ? PT_ATTACHED : 0);
1701da177e4SLinus Torvalds 	if (capable(CAP_SYS_PTRACE))
1711da177e4SLinus Torvalds 		task->ptrace |= PT_PTRACE_CAP;
1721da177e4SLinus Torvalds 	task_unlock(task);
1731da177e4SLinus Torvalds 
1741da177e4SLinus Torvalds 	write_lock_irq(&tasklist_lock);
1751da177e4SLinus Torvalds 	__ptrace_link(task, current);
1761da177e4SLinus Torvalds 	write_unlock_irq(&tasklist_lock);
1771da177e4SLinus Torvalds 
1781da177e4SLinus Torvalds 	force_sig_specific(SIGSTOP, task);
1791da177e4SLinus Torvalds 	return 0;
1801da177e4SLinus Torvalds 
1811da177e4SLinus Torvalds bad:
1821da177e4SLinus Torvalds 	task_unlock(task);
1831da177e4SLinus Torvalds 	return retval;
1841da177e4SLinus Torvalds }
1851da177e4SLinus Torvalds 
1861da177e4SLinus Torvalds int ptrace_detach(struct task_struct *child, unsigned int data)
1871da177e4SLinus Torvalds {
1887ed20e1aSJesper Juhl 	if (!valid_signal(data))
1891da177e4SLinus Torvalds 		return	-EIO;
1901da177e4SLinus Torvalds 
1911da177e4SLinus Torvalds 	/* Architecture-specific hardware disable .. */
1921da177e4SLinus Torvalds 	ptrace_disable(child);
1931da177e4SLinus Torvalds 
1941da177e4SLinus Torvalds 	/* .. re-parent .. */
1951da177e4SLinus Torvalds 	child->exit_code = data;
1961da177e4SLinus Torvalds 
1971da177e4SLinus Torvalds 	write_lock_irq(&tasklist_lock);
1981da177e4SLinus Torvalds 	__ptrace_unlink(child);
1991da177e4SLinus Torvalds 	/* .. and wake it up. */
2001da177e4SLinus Torvalds 	if (child->exit_state != EXIT_ZOMBIE)
2011da177e4SLinus Torvalds 		wake_up_process(child);
2021da177e4SLinus Torvalds 	write_unlock_irq(&tasklist_lock);
2031da177e4SLinus Torvalds 
2041da177e4SLinus Torvalds 	return 0;
2051da177e4SLinus Torvalds }
2061da177e4SLinus Torvalds 
2071da177e4SLinus Torvalds /*
2081da177e4SLinus Torvalds  * Access another process' address space.
2091da177e4SLinus Torvalds  * Source/target buffer must be kernel space,
2101da177e4SLinus Torvalds  * Do not walk the page table directly, use get_user_pages
2111da177e4SLinus Torvalds  */
2121da177e4SLinus Torvalds 
2131da177e4SLinus Torvalds int access_process_vm(struct task_struct *tsk, unsigned long addr, void *buf, int len, int write)
2141da177e4SLinus Torvalds {
2151da177e4SLinus Torvalds 	struct mm_struct *mm;
2161da177e4SLinus Torvalds 	struct vm_area_struct *vma;
2171da177e4SLinus Torvalds 	struct page *page;
2181da177e4SLinus Torvalds 	void *old_buf = buf;
2191da177e4SLinus Torvalds 
2201da177e4SLinus Torvalds 	mm = get_task_mm(tsk);
2211da177e4SLinus Torvalds 	if (!mm)
2221da177e4SLinus Torvalds 		return 0;
2231da177e4SLinus Torvalds 
2241da177e4SLinus Torvalds 	down_read(&mm->mmap_sem);
2251da177e4SLinus Torvalds 	/* ignore errors, just check how much was sucessfully transfered */
2261da177e4SLinus Torvalds 	while (len) {
2271da177e4SLinus Torvalds 		int bytes, ret, offset;
2281da177e4SLinus Torvalds 		void *maddr;
2291da177e4SLinus Torvalds 
2301da177e4SLinus Torvalds 		ret = get_user_pages(tsk, mm, addr, 1,
2311da177e4SLinus Torvalds 				write, 1, &page, &vma);
2321da177e4SLinus Torvalds 		if (ret <= 0)
2331da177e4SLinus Torvalds 			break;
2341da177e4SLinus Torvalds 
2351da177e4SLinus Torvalds 		bytes = len;
2361da177e4SLinus Torvalds 		offset = addr & (PAGE_SIZE-1);
2371da177e4SLinus Torvalds 		if (bytes > PAGE_SIZE-offset)
2381da177e4SLinus Torvalds 			bytes = PAGE_SIZE-offset;
2391da177e4SLinus Torvalds 
2401da177e4SLinus Torvalds 		maddr = kmap(page);
2411da177e4SLinus Torvalds 		if (write) {
2421da177e4SLinus Torvalds 			copy_to_user_page(vma, page, addr,
2431da177e4SLinus Torvalds 					  maddr + offset, buf, bytes);
2441da177e4SLinus Torvalds 			set_page_dirty_lock(page);
2451da177e4SLinus Torvalds 		} else {
2461da177e4SLinus Torvalds 			copy_from_user_page(vma, page, addr,
2471da177e4SLinus Torvalds 					    buf, maddr + offset, bytes);
2481da177e4SLinus Torvalds 		}
2491da177e4SLinus Torvalds 		kunmap(page);
2501da177e4SLinus Torvalds 		page_cache_release(page);
2511da177e4SLinus Torvalds 		len -= bytes;
2521da177e4SLinus Torvalds 		buf += bytes;
2531da177e4SLinus Torvalds 		addr += bytes;
2541da177e4SLinus Torvalds 	}
2551da177e4SLinus Torvalds 	up_read(&mm->mmap_sem);
2561da177e4SLinus Torvalds 	mmput(mm);
2571da177e4SLinus Torvalds 
2581da177e4SLinus Torvalds 	return buf - old_buf;
2591da177e4SLinus Torvalds }
2601da177e4SLinus Torvalds 
2611da177e4SLinus Torvalds int ptrace_readdata(struct task_struct *tsk, unsigned long src, char __user *dst, int len)
2621da177e4SLinus Torvalds {
2631da177e4SLinus Torvalds 	int copied = 0;
2641da177e4SLinus Torvalds 
2651da177e4SLinus Torvalds 	while (len > 0) {
2661da177e4SLinus Torvalds 		char buf[128];
2671da177e4SLinus Torvalds 		int this_len, retval;
2681da177e4SLinus Torvalds 
2691da177e4SLinus Torvalds 		this_len = (len > sizeof(buf)) ? sizeof(buf) : len;
2701da177e4SLinus Torvalds 		retval = access_process_vm(tsk, src, buf, this_len, 0);
2711da177e4SLinus Torvalds 		if (!retval) {
2721da177e4SLinus Torvalds 			if (copied)
2731da177e4SLinus Torvalds 				break;
2741da177e4SLinus Torvalds 			return -EIO;
2751da177e4SLinus Torvalds 		}
2761da177e4SLinus Torvalds 		if (copy_to_user(dst, buf, retval))
2771da177e4SLinus Torvalds 			return -EFAULT;
2781da177e4SLinus Torvalds 		copied += retval;
2791da177e4SLinus Torvalds 		src += retval;
2801da177e4SLinus Torvalds 		dst += retval;
2811da177e4SLinus Torvalds 		len -= retval;
2821da177e4SLinus Torvalds 	}
2831da177e4SLinus Torvalds 	return copied;
2841da177e4SLinus Torvalds }
2851da177e4SLinus Torvalds 
2861da177e4SLinus Torvalds int ptrace_writedata(struct task_struct *tsk, char __user *src, unsigned long dst, int len)
2871da177e4SLinus Torvalds {
2881da177e4SLinus Torvalds 	int copied = 0;
2891da177e4SLinus Torvalds 
2901da177e4SLinus Torvalds 	while (len > 0) {
2911da177e4SLinus Torvalds 		char buf[128];
2921da177e4SLinus Torvalds 		int this_len, retval;
2931da177e4SLinus Torvalds 
2941da177e4SLinus Torvalds 		this_len = (len > sizeof(buf)) ? sizeof(buf) : len;
2951da177e4SLinus Torvalds 		if (copy_from_user(buf, src, this_len))
2961da177e4SLinus Torvalds 			return -EFAULT;
2971da177e4SLinus Torvalds 		retval = access_process_vm(tsk, dst, buf, this_len, 1);
2981da177e4SLinus Torvalds 		if (!retval) {
2991da177e4SLinus Torvalds 			if (copied)
3001da177e4SLinus Torvalds 				break;
3011da177e4SLinus Torvalds 			return -EIO;
3021da177e4SLinus Torvalds 		}
3031da177e4SLinus Torvalds 		copied += retval;
3041da177e4SLinus Torvalds 		src += retval;
3051da177e4SLinus Torvalds 		dst += retval;
3061da177e4SLinus Torvalds 		len -= retval;
3071da177e4SLinus Torvalds 	}
3081da177e4SLinus Torvalds 	return copied;
3091da177e4SLinus Torvalds }
3101da177e4SLinus Torvalds 
3111da177e4SLinus Torvalds static int ptrace_setoptions(struct task_struct *child, long data)
3121da177e4SLinus Torvalds {
3131da177e4SLinus Torvalds 	child->ptrace &= ~PT_TRACE_MASK;
3141da177e4SLinus Torvalds 
3151da177e4SLinus Torvalds 	if (data & PTRACE_O_TRACESYSGOOD)
3161da177e4SLinus Torvalds 		child->ptrace |= PT_TRACESYSGOOD;
3171da177e4SLinus Torvalds 
3181da177e4SLinus Torvalds 	if (data & PTRACE_O_TRACEFORK)
3191da177e4SLinus Torvalds 		child->ptrace |= PT_TRACE_FORK;
3201da177e4SLinus Torvalds 
3211da177e4SLinus Torvalds 	if (data & PTRACE_O_TRACEVFORK)
3221da177e4SLinus Torvalds 		child->ptrace |= PT_TRACE_VFORK;
3231da177e4SLinus Torvalds 
3241da177e4SLinus Torvalds 	if (data & PTRACE_O_TRACECLONE)
3251da177e4SLinus Torvalds 		child->ptrace |= PT_TRACE_CLONE;
3261da177e4SLinus Torvalds 
3271da177e4SLinus Torvalds 	if (data & PTRACE_O_TRACEEXEC)
3281da177e4SLinus Torvalds 		child->ptrace |= PT_TRACE_EXEC;
3291da177e4SLinus Torvalds 
3301da177e4SLinus Torvalds 	if (data & PTRACE_O_TRACEVFORKDONE)
3311da177e4SLinus Torvalds 		child->ptrace |= PT_TRACE_VFORK_DONE;
3321da177e4SLinus Torvalds 
3331da177e4SLinus Torvalds 	if (data & PTRACE_O_TRACEEXIT)
3341da177e4SLinus Torvalds 		child->ptrace |= PT_TRACE_EXIT;
3351da177e4SLinus Torvalds 
3361da177e4SLinus Torvalds 	return (data & ~PTRACE_O_MASK) ? -EINVAL : 0;
3371da177e4SLinus Torvalds }
3381da177e4SLinus Torvalds 
3391da177e4SLinus Torvalds static int ptrace_getsiginfo(struct task_struct *child, siginfo_t __user * data)
3401da177e4SLinus Torvalds {
3411da177e4SLinus Torvalds 	siginfo_t lastinfo;
3421da177e4SLinus Torvalds 	int error = -ESRCH;
3431da177e4SLinus Torvalds 
3441da177e4SLinus Torvalds 	read_lock(&tasklist_lock);
3451da177e4SLinus Torvalds 	if (likely(child->sighand != NULL)) {
3461da177e4SLinus Torvalds 		error = -EINVAL;
3471da177e4SLinus Torvalds 		spin_lock_irq(&child->sighand->siglock);
3481da177e4SLinus Torvalds 		if (likely(child->last_siginfo != NULL)) {
3491da177e4SLinus Torvalds 			lastinfo = *child->last_siginfo;
3501da177e4SLinus Torvalds 			error = 0;
3511da177e4SLinus Torvalds 		}
3521da177e4SLinus Torvalds 		spin_unlock_irq(&child->sighand->siglock);
3531da177e4SLinus Torvalds 	}
3541da177e4SLinus Torvalds 	read_unlock(&tasklist_lock);
3551da177e4SLinus Torvalds 	if (!error)
3561da177e4SLinus Torvalds 		return copy_siginfo_to_user(data, &lastinfo);
3571da177e4SLinus Torvalds 	return error;
3581da177e4SLinus Torvalds }
3591da177e4SLinus Torvalds 
3601da177e4SLinus Torvalds static int ptrace_setsiginfo(struct task_struct *child, siginfo_t __user * data)
3611da177e4SLinus Torvalds {
3621da177e4SLinus Torvalds 	siginfo_t newinfo;
3631da177e4SLinus Torvalds 	int error = -ESRCH;
3641da177e4SLinus Torvalds 
3651da177e4SLinus Torvalds 	if (copy_from_user(&newinfo, data, sizeof (siginfo_t)))
3661da177e4SLinus Torvalds 		return -EFAULT;
3671da177e4SLinus Torvalds 
3681da177e4SLinus Torvalds 	read_lock(&tasklist_lock);
3691da177e4SLinus Torvalds 	if (likely(child->sighand != NULL)) {
3701da177e4SLinus Torvalds 		error = -EINVAL;
3711da177e4SLinus Torvalds 		spin_lock_irq(&child->sighand->siglock);
3721da177e4SLinus Torvalds 		if (likely(child->last_siginfo != NULL)) {
3731da177e4SLinus Torvalds 			*child->last_siginfo = newinfo;
3741da177e4SLinus Torvalds 			error = 0;
3751da177e4SLinus Torvalds 		}
3761da177e4SLinus Torvalds 		spin_unlock_irq(&child->sighand->siglock);
3771da177e4SLinus Torvalds 	}
3781da177e4SLinus Torvalds 	read_unlock(&tasklist_lock);
3791da177e4SLinus Torvalds 	return error;
3801da177e4SLinus Torvalds }
3811da177e4SLinus Torvalds 
3821da177e4SLinus Torvalds int ptrace_request(struct task_struct *child, long request,
3831da177e4SLinus Torvalds 		   long addr, long data)
3841da177e4SLinus Torvalds {
3851da177e4SLinus Torvalds 	int ret = -EIO;
3861da177e4SLinus Torvalds 
3871da177e4SLinus Torvalds 	switch (request) {
3881da177e4SLinus Torvalds #ifdef PTRACE_OLDSETOPTIONS
3891da177e4SLinus Torvalds 	case PTRACE_OLDSETOPTIONS:
3901da177e4SLinus Torvalds #endif
3911da177e4SLinus Torvalds 	case PTRACE_SETOPTIONS:
3921da177e4SLinus Torvalds 		ret = ptrace_setoptions(child, data);
3931da177e4SLinus Torvalds 		break;
3941da177e4SLinus Torvalds 	case PTRACE_GETEVENTMSG:
3951da177e4SLinus Torvalds 		ret = put_user(child->ptrace_message, (unsigned long __user *) data);
3961da177e4SLinus Torvalds 		break;
3971da177e4SLinus Torvalds 	case PTRACE_GETSIGINFO:
3981da177e4SLinus Torvalds 		ret = ptrace_getsiginfo(child, (siginfo_t __user *) data);
3991da177e4SLinus Torvalds 		break;
4001da177e4SLinus Torvalds 	case PTRACE_SETSIGINFO:
4011da177e4SLinus Torvalds 		ret = ptrace_setsiginfo(child, (siginfo_t __user *) data);
4021da177e4SLinus Torvalds 		break;
4031da177e4SLinus Torvalds 	default:
4041da177e4SLinus Torvalds 		break;
4051da177e4SLinus Torvalds 	}
4061da177e4SLinus Torvalds 
4071da177e4SLinus Torvalds 	return ret;
4081da177e4SLinus Torvalds }
409481bed45SChristoph Hellwig 
410481bed45SChristoph Hellwig #ifndef __ARCH_SYS_PTRACE
411481bed45SChristoph Hellwig static int ptrace_get_task_struct(long request, long pid,
412481bed45SChristoph Hellwig 		struct task_struct **childp)
413481bed45SChristoph Hellwig {
414481bed45SChristoph Hellwig 	struct task_struct *child;
415481bed45SChristoph Hellwig 	int ret;
416481bed45SChristoph Hellwig 
417481bed45SChristoph Hellwig 	/*
418481bed45SChristoph Hellwig 	 * Callers use child == NULL as an indication to exit early even
419481bed45SChristoph Hellwig 	 * when the return value is 0, so make sure it is non-NULL here.
420481bed45SChristoph Hellwig 	 */
421481bed45SChristoph Hellwig 	*childp = NULL;
422481bed45SChristoph Hellwig 
423481bed45SChristoph Hellwig 	if (request == PTRACE_TRACEME) {
424481bed45SChristoph Hellwig 		/*
425481bed45SChristoph Hellwig 		 * Are we already being traced?
426481bed45SChristoph Hellwig 		 */
427481bed45SChristoph Hellwig 		if (current->ptrace & PT_PTRACED)
428481bed45SChristoph Hellwig 			return -EPERM;
429481bed45SChristoph Hellwig 		ret = security_ptrace(current->parent, current);
430481bed45SChristoph Hellwig 		if (ret)
431481bed45SChristoph Hellwig 			return -EPERM;
432481bed45SChristoph Hellwig 		/*
433481bed45SChristoph Hellwig 		 * Set the ptrace bit in the process ptrace flags.
434481bed45SChristoph Hellwig 		 */
435481bed45SChristoph Hellwig 		current->ptrace |= PT_PTRACED;
436481bed45SChristoph Hellwig 		return 0;
437481bed45SChristoph Hellwig 	}
438481bed45SChristoph Hellwig 
439481bed45SChristoph Hellwig 	/*
440481bed45SChristoph Hellwig 	 * You may not mess with init
441481bed45SChristoph Hellwig 	 */
442481bed45SChristoph Hellwig 	if (pid == 1)
443481bed45SChristoph Hellwig 		return -EPERM;
444481bed45SChristoph Hellwig 
445481bed45SChristoph Hellwig 	ret = -ESRCH;
446481bed45SChristoph Hellwig 	read_lock(&tasklist_lock);
447481bed45SChristoph Hellwig 	child = find_task_by_pid(pid);
448481bed45SChristoph Hellwig 	if (child)
449481bed45SChristoph Hellwig 		get_task_struct(child);
450481bed45SChristoph Hellwig 	read_unlock(&tasklist_lock);
451481bed45SChristoph Hellwig 	if (!child)
452481bed45SChristoph Hellwig 		return -ESRCH;
453481bed45SChristoph Hellwig 
454481bed45SChristoph Hellwig 	*childp = child;
455481bed45SChristoph Hellwig 	return 0;
456481bed45SChristoph Hellwig }
457481bed45SChristoph Hellwig 
458481bed45SChristoph Hellwig asmlinkage long sys_ptrace(long request, long pid, long addr, long data)
459481bed45SChristoph Hellwig {
460481bed45SChristoph Hellwig 	struct task_struct *child;
461481bed45SChristoph Hellwig 	long ret;
462481bed45SChristoph Hellwig 
463481bed45SChristoph Hellwig 	/*
464481bed45SChristoph Hellwig 	 * This lock_kernel fixes a subtle race with suid exec
465481bed45SChristoph Hellwig 	 */
466481bed45SChristoph Hellwig 	lock_kernel();
467481bed45SChristoph Hellwig 	ret = ptrace_get_task_struct(request, pid, &child);
468481bed45SChristoph Hellwig 	if (!child)
469481bed45SChristoph Hellwig 		goto out;
470481bed45SChristoph Hellwig 
471481bed45SChristoph Hellwig 	if (request == PTRACE_ATTACH) {
472481bed45SChristoph Hellwig 		ret = ptrace_attach(child);
473481bed45SChristoph Hellwig 		goto out;
474481bed45SChristoph Hellwig 	}
475481bed45SChristoph Hellwig 
476481bed45SChristoph Hellwig 	ret = ptrace_check_attach(child, request == PTRACE_KILL);
477481bed45SChristoph Hellwig 	if (ret < 0)
478481bed45SChristoph Hellwig 		goto out_put_task_struct;
479481bed45SChristoph Hellwig 
480481bed45SChristoph Hellwig 	ret = arch_ptrace(child, request, addr, data);
481481bed45SChristoph Hellwig 	if (ret < 0)
482481bed45SChristoph Hellwig 		goto out_put_task_struct;
483481bed45SChristoph Hellwig 
484481bed45SChristoph Hellwig  out_put_task_struct:
485481bed45SChristoph Hellwig 	put_task_struct(child);
486481bed45SChristoph Hellwig  out:
487481bed45SChristoph Hellwig 	unlock_kernel();
488481bed45SChristoph Hellwig 	return ret;
489481bed45SChristoph Hellwig }
490481bed45SChristoph Hellwig #endif /* __ARCH_SYS_PTRACE */
491