| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * linux/arch/sh/kernel/ptrace.c | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Original x86 implementation: | 
					
						
							|  |  |  |  *	By Ross Biro 1/23/92 | 
					
						
							|  |  |  |  *	edited by Linus Torvalds | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * SuperH version:   Copyright (C) 1999, 2000  Kaz Kojima & Niibe Yutaka | 
					
						
							| 
									
										
										
										
											2007-11-10 19:21:34 +09:00
										 |  |  |  * Audit support: Yuichi Nakamura <ynakam@hitachisoft.jp> | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  |  */ | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/sched.h>
 | 
					
						
							|  |  |  | #include <linux/mm.h>
 | 
					
						
							|  |  |  | #include <linux/smp.h>
 | 
					
						
							|  |  |  | #include <linux/errno.h>
 | 
					
						
							|  |  |  | #include <linux/ptrace.h>
 | 
					
						
							|  |  |  | #include <linux/user.h>
 | 
					
						
							|  |  |  | #include <linux/slab.h>
 | 
					
						
							|  |  |  | #include <linux/security.h>
 | 
					
						
							| 
									
										
										
										
											2005-05-01 08:59:14 -07:00
										 |  |  | #include <linux/signal.h>
 | 
					
						
							| 
									
										
										
										
											2007-02-23 13:22:17 +09:00
										 |  |  | #include <linux/io.h>
 | 
					
						
							| 
									
										
										
										
											2007-11-10 19:21:34 +09:00
										 |  |  | #include <linux/audit.h>
 | 
					
						
							| 
									
										
										
										
											2008-07-30 15:30:52 +09:00
										 |  |  | #include <linux/seccomp.h>
 | 
					
						
							| 
									
										
										
										
											2008-07-30 19:55:30 +09:00
										 |  |  | #include <linux/tracehook.h>
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #include <asm/uaccess.h>
 | 
					
						
							|  |  |  | #include <asm/pgtable.h>
 | 
					
						
							|  |  |  | #include <asm/system.h>
 | 
					
						
							|  |  |  | #include <asm/processor.h>
 | 
					
						
							|  |  |  | #include <asm/mmu_context.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * does not yet catch signals sent when the child dies. | 
					
						
							|  |  |  |  * in exit.c or in signal.c. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * This routine will get a word off of the process kernel stack. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static inline int get_stack_long(struct task_struct *task, int offset) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned char *stack; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-01-12 01:05:44 -08:00
										 |  |  | 	stack = (unsigned char *)task_pt_regs(task); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	stack += offset; | 
					
						
							|  |  |  | 	return (*((int *)stack)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * This routine will put a word on the process kernel stack. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static inline int put_stack_long(struct task_struct *task, int offset, | 
					
						
							|  |  |  | 				 unsigned long data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned char *stack; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-01-12 01:05:44 -08:00
										 |  |  | 	stack = (unsigned char *)task_pt_regs(task); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	stack += offset; | 
					
						
							|  |  |  | 	*(unsigned long *) stack = data; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-07-30 19:09:31 +09:00
										 |  |  | void user_enable_single_step(struct task_struct *child) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct pt_regs *regs = task_pt_regs(child); | 
					
						
							|  |  |  | 	long pc; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pc = get_stack_long(child, (long)®s->pc); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Next scheduling will set up UBC */ | 
					
						
							|  |  |  | 	if (child->thread.ubc_pc == 0) | 
					
						
							|  |  |  | 		ubc_usercnt += 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	child->thread.ubc_pc = pc; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	set_tsk_thread_flag(child, TIF_SINGLESTEP); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void user_disable_single_step(struct task_struct *child) | 
					
						
							| 
									
										
										
										
											2007-02-23 13:22:17 +09:00
										 |  |  | { | 
					
						
							|  |  |  | 	clear_tsk_thread_flag(child, TIF_SINGLESTEP); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * Ensure the UBC is not programmed at the next context switch. | 
					
						
							|  |  |  | 	 * | 
					
						
							|  |  |  | 	 * Normally this is not needed but there are sequences such as | 
					
						
							|  |  |  | 	 * singlestep, signal delivery, and continue that leave the | 
					
						
							|  |  |  | 	 * ubc_pc non-zero leading to spurious SIGTRAPs. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	if (child->thread.ubc_pc != 0) { | 
					
						
							|  |  |  | 		ubc_usercnt -= 1; | 
					
						
							|  |  |  | 		child->thread.ubc_pc = 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Called by kernel/ptrace.c when detaching.. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Make sure single step bits etc are not set. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | void ptrace_disable(struct task_struct *child) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2008-07-30 19:09:31 +09:00
										 |  |  | 	user_disable_single_step(child); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-07 00:59:47 -08:00
										 |  |  | long arch_ptrace(struct task_struct *child, long request, long addr, long data) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct user * dummy = NULL; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (request) { | 
					
						
							|  |  |  | 	/* read the word at location addr in the USER area. */ | 
					
						
							|  |  |  | 	case PTRACE_PEEKUSR: { | 
					
						
							|  |  |  | 		unsigned long tmp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = -EIO; | 
					
						
							| 
									
										
										
										
											2007-02-23 13:22:17 +09:00
										 |  |  | 		if ((addr & 3) || addr < 0 || | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		    addr > sizeof(struct user) - 3) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (addr < sizeof(struct pt_regs)) | 
					
						
							|  |  |  | 			tmp = get_stack_long(child, addr); | 
					
						
							|  |  |  | 		else if (addr >= (long) &dummy->fpu && | 
					
						
							|  |  |  | 			 addr < (long) &dummy->u_fpvalid) { | 
					
						
							|  |  |  | 			if (!tsk_used_math(child)) { | 
					
						
							|  |  |  | 				if (addr == (long)&dummy->fpu.fpscr) | 
					
						
							|  |  |  | 					tmp = FPSCR_INIT; | 
					
						
							|  |  |  | 				else | 
					
						
							|  |  |  | 					tmp = 0; | 
					
						
							|  |  |  | 			} else | 
					
						
							|  |  |  | 				tmp = ((long *)&child->thread.fpu) | 
					
						
							|  |  |  | 					[(addr - (long)&dummy->fpu) >> 2]; | 
					
						
							|  |  |  | 		} else if (addr == (long) &dummy->u_fpvalid) | 
					
						
							|  |  |  | 			tmp = !!tsk_used_math(child); | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			tmp = 0; | 
					
						
							| 
									
										
										
										
											2007-05-14 12:52:56 +09:00
										 |  |  | 		ret = put_user(tmp, (unsigned long __user *)data); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ | 
					
						
							|  |  |  | 		ret = -EIO; | 
					
						
							| 
									
										
										
										
											2007-02-23 13:22:17 +09:00
										 |  |  | 		if ((addr & 3) || addr < 0 || | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		    addr > sizeof(struct user) - 3) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (addr < sizeof(struct pt_regs)) | 
					
						
							|  |  |  | 			ret = put_stack_long(child, addr, data); | 
					
						
							|  |  |  | 		else if (addr >= (long) &dummy->fpu && | 
					
						
							|  |  |  | 			 addr < (long) &dummy->u_fpvalid) { | 
					
						
							|  |  |  | 			set_stopped_child_used_math(child); | 
					
						
							|  |  |  | 			((long *)&child->thread.fpu) | 
					
						
							|  |  |  | 				[(addr - (long)&dummy->fpu) >> 2] = data; | 
					
						
							|  |  |  | 			ret = 0; | 
					
						
							|  |  |  | 		} else if (addr == (long) &dummy->u_fpvalid) { | 
					
						
							|  |  |  | 			conditional_stopped_child_used_math(data, child); | 
					
						
							|  |  |  | 			ret = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_SH_DSP
 | 
					
						
							|  |  |  | 	case PTRACE_GETDSPREGS: { | 
					
						
							|  |  |  | 		unsigned long dp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = -EIO; | 
					
						
							|  |  |  | 		dp = ((unsigned long) child) + THREAD_SIZE - | 
					
						
							|  |  |  | 			 sizeof(struct pt_dspregs); | 
					
						
							|  |  |  | 		if (*((int *) (dp - 4)) == SR_FD) { | 
					
						
							| 
									
										
										
										
											2008-02-08 17:26:54 +09:00
										 |  |  | 			copy_to_user((void *)addr, (void *) dp, | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				sizeof(struct pt_dspregs)); | 
					
						
							|  |  |  | 			ret = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case PTRACE_SETDSPREGS: { | 
					
						
							|  |  |  | 		unsigned long dp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = -EIO; | 
					
						
							|  |  |  | 		dp = ((unsigned long) child) + THREAD_SIZE - | 
					
						
							|  |  |  | 			 sizeof(struct pt_dspregs); | 
					
						
							|  |  |  | 		if (*((int *) (dp - 4)) == SR_FD) { | 
					
						
							| 
									
										
										
										
											2008-02-08 17:26:54 +09:00
										 |  |  | 			copy_from_user((void *) dp, (void *)addr, | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				sizeof(struct pt_dspregs)); | 
					
						
							|  |  |  | 			ret = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2008-05-19 13:40:12 +09:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | #ifdef CONFIG_BINFMT_ELF_FDPIC
 | 
					
						
							|  |  |  | 	case PTRACE_GETFDPIC: { | 
					
						
							|  |  |  | 		unsigned long tmp = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		switch (addr) { | 
					
						
							|  |  |  | 		case PTRACE_GETFDPIC_EXEC: | 
					
						
							|  |  |  | 			tmp = child->mm->context.exec_fdpic_loadmap; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		case PTRACE_GETFDPIC_INTERP: | 
					
						
							|  |  |  | 			tmp = child->mm->context.interp_fdpic_loadmap; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = 0; | 
					
						
							|  |  |  | 		if (put_user(tmp, (unsigned long *) data)) { | 
					
						
							|  |  |  | 			ret = -EFAULT; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		ret = ptrace_request(child, request, addr, data); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-11-07 00:59:47 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-07-30 20:05:35 +09:00
										 |  |  | static inline int audit_arch(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int arch = EM_SH; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_CPU_LITTLE_ENDIAN
 | 
					
						
							|  |  |  | 	arch |= __AUDIT_ARCH_LE; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return arch; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-07-30 19:55:30 +09:00
										 |  |  | asmlinkage long do_syscall_trace_enter(struct pt_regs *regs) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2008-07-30 19:55:30 +09:00
										 |  |  | 	long ret = 0; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-07-30 15:30:52 +09:00
										 |  |  | 	secure_computing(regs->regs[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-07-30 19:55:30 +09:00
										 |  |  | 	if (test_thread_flag(TIF_SYSCALL_TRACE) && | 
					
						
							|  |  |  | 	    tracehook_report_syscall_entry(regs)) | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * Tracing decided this syscall should not happen. | 
					
						
							|  |  |  | 		 * We'll return a bogus call number to get an ENOSYS | 
					
						
							|  |  |  | 		 * error, but leave the original number in regs->regs[0]. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		ret = -1L; | 
					
						
							| 
									
										
										
										
											2007-11-10 19:21:34 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-07-30 19:55:30 +09:00
										 |  |  | 	if (unlikely(current->audit_context)) | 
					
						
							| 
									
										
										
										
											2008-07-30 20:05:35 +09:00
										 |  |  | 		audit_syscall_entry(audit_arch(), regs->regs[3], | 
					
						
							| 
									
										
										
										
											2007-11-10 19:21:34 +09:00
										 |  |  | 				    regs->regs[4], regs->regs[5], | 
					
						
							|  |  |  | 				    regs->regs[6], regs->regs[7]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-07-30 19:55:30 +09:00
										 |  |  | 	return ret ?: regs->regs[0]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | asmlinkage void do_syscall_trace_leave(struct pt_regs *regs) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int step; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (unlikely(current->audit_context)) | 
					
						
							|  |  |  | 		audit_syscall_exit(AUDITSC_RESULT(regs->regs[0]), | 
					
						
							|  |  |  | 				   regs->regs[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	step = test_thread_flag(TIF_SINGLESTEP); | 
					
						
							|  |  |  | 	if (step || test_thread_flag(TIF_SYSCALL_TRACE)) | 
					
						
							|  |  |  | 		tracehook_report_syscall_exit(regs, step); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | } |