diff options
Diffstat (limited to 'kernel/ptrace.c')
| -rw-r--r-- | kernel/ptrace.c | 74 | 
1 files changed, 59 insertions, 15 deletions
diff --git a/kernel/ptrace.c b/kernel/ptrace.c index 1599157336a..6cbeaae4406 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -117,11 +117,45 @@ void __ptrace_unlink(struct task_struct *child)  	 * TASK_KILLABLE sleeps.  	 */  	if (child->jobctl & JOBCTL_STOP_PENDING || task_is_traced(child)) -		signal_wake_up(child, task_is_traced(child)); +		ptrace_signal_wake_up(child, true);  	spin_unlock(&child->sighand->siglock);  } +/* Ensure that nothing can wake it up, even SIGKILL */ +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) && !__fatal_signal_pending(task)) { +		task->state = __TASK_TRACED; +		ret = true; +	} +	spin_unlock_irq(&task->sighand->siglock); + +	return ret; +} + +static void ptrace_unfreeze_traced(struct task_struct *task) +{ +	if (task->state != __TASK_TRACED) +		return; + +	WARN_ON(!task->ptrace || task->parent != current); + +	spin_lock_irq(&task->sighand->siglock); +	if (__fatal_signal_pending(task)) +		wake_up_state(task, __TASK_TRACED); +	else +		task->state = TASK_TRACED; +	spin_unlock_irq(&task->sighand->siglock); +} +  /**   * ptrace_check_attach - check whether ptracee is ready for ptrace operation   * @child: ptracee to check for @@ -139,7 +173,7 @@ void __ptrace_unlink(struct task_struct *child)   * RETURNS:   * 0 on success, -ESRCH if %child is not ready.   */ -int ptrace_check_attach(struct task_struct *child, bool ignore_state) +static int ptrace_check_attach(struct task_struct *child, bool ignore_state)  {  	int ret = -ESRCH; @@ -151,24 +185,29 @@ int ptrace_check_attach(struct task_struct *child, bool ignore_state)  	 * be changed by us so it's not changing right after this.  	 */  	read_lock(&tasklist_lock); -	if ((child->ptrace & PT_PTRACED) && child->parent == current) { +	if (child->ptrace && child->parent == current) { +		WARN_ON(child->state == __TASK_TRACED);  		/*  		 * child->sighand can't be NULL, release_task()  		 * does ptrace_unlink() before __exit_signal().  		 */ -		spin_lock_irq(&child->sighand->siglock); -		WARN_ON_ONCE(task_is_stopped(child)); -		if (ignore_state || (task_is_traced(child) && -				     !(child->jobctl & JOBCTL_LISTENING))) +		if (ignore_state || ptrace_freeze_traced(child))  			ret = 0; -		spin_unlock_irq(&child->sighand->siglock);  	}  	read_unlock(&tasklist_lock); -	if (!ret && !ignore_state) -		ret = wait_task_inactive(child, TASK_TRACED) ? 0 : -ESRCH; +	if (!ret && !ignore_state) { +		if (!wait_task_inactive(child, __TASK_TRACED)) { +			/* +			 * This can only happen if may_ptrace_stop() fails and +			 * ptrace_stop() changes ->state back to TASK_RUNNING, +			 * so we should not worry about leaking __TASK_TRACED. +			 */ +			WARN_ON(child->state == __TASK_TRACED); +			ret = -ESRCH; +		} +	} -	/* All systems go.. */  	return ret;  } @@ -317,7 +356,7 @@ static int ptrace_attach(struct task_struct *task, long request,  	 */  	if (task_is_stopped(task) &&  	    task_set_jobctl_pending(task, JOBCTL_TRAP_STOP | JOBCTL_TRAPPING)) -		signal_wake_up(task, 1); +		signal_wake_up_state(task, __TASK_STOPPED);  	spin_unlock(&task->sighand->siglock); @@ -737,7 +776,7 @@ int ptrace_request(struct task_struct *child, long request,  		 * tracee into STOP.  		 */  		if (likely(task_set_jobctl_pending(child, JOBCTL_TRAP_STOP))) -			signal_wake_up(child, child->jobctl & JOBCTL_LISTENING); +			ptrace_signal_wake_up(child, child->jobctl & JOBCTL_LISTENING);  		unlock_task_sighand(child, &flags);  		ret = 0; @@ -763,7 +802,7 @@ int ptrace_request(struct task_struct *child, long request,  			 * start of this trap and now.  Trigger re-trap.  			 */  			if (child->jobctl & JOBCTL_TRAP_NOTIFY) -				signal_wake_up(child, true); +				ptrace_signal_wake_up(child, true);  			ret = 0;  		}  		unlock_task_sighand(child, &flags); @@ -900,6 +939,8 @@ SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,  		goto out_put_task_struct;  	ret = arch_ptrace(child, request, addr, data); +	if (ret || request != PTRACE_DETACH) +		ptrace_unfreeze_traced(child);   out_put_task_struct:  	put_task_struct(child); @@ -1039,8 +1080,11 @@ asmlinkage long compat_sys_ptrace(compat_long_t request, compat_long_t pid,  	ret = ptrace_check_attach(child, request == PTRACE_KILL ||  				  request == PTRACE_INTERRUPT); -	if (!ret) +	if (!ret) {  		ret = compat_arch_ptrace(child, request, addr, data); +		if (ret || request != PTRACE_DETACH) +			ptrace_unfreeze_traced(child); +	}   out_put_task_struct:  	put_task_struct(child);  |