lkml.org 
[lkml]   [2011]   [Feb]   [3]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
Subject[PATCH 1/1] ptrace: make sure do_wait() won't hang after PTRACE_ATTACH
Test-case:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <assert.h>

int main(void)
{
int pid, stat;

pid = fork();
if (!pid) {
kill(getpid(), SIGSTOP);
assert(0);
}

if (!fork()) {
assert(ptrace(PTRACE_ATTACH, pid, 0,0) == 0);
/* eat ->exit_code */
assert(wait(&stat) == pid);
/* exit instead of DETACH to avoid the extra wakeup */
assert(stat == 0x137f);
return 0;
}
wait(NULL);

assert(ptrace(PTRACE_ATTACH, pid, 0,0) == 0);
alarm(1);
assert(wait(&stat) == pid);
assert(stat == 0x137f);
assert(ptrace(PTRACE_DETACH, pid, 0,0) == 0);

kill(pid, SIGKILL);
return 0;
}

Without this patch wait() hangs forever after the 2nd PTRACE_DETACH.
This is because task->exit_code was already cleared by the previous
debugger. Change ptrace_attach() to restore ->exit_code in this case.

The new exit_code is not necessarily accurate and we can't use
->group_exit_code because it can be cleared by ->real_parent too,
but I think this doesn't really matter.

Even with this patch SIGCONT can "steal" exit_code and the pending
SIGSTOP, but in this case the tracee will report SIGCONT to the new
debugger, so wait() won't hang anyway.

(Of course, SIGCONT after wait() can break PTRACE_DETACH but this
is another story, any ptrace request can fail if the tracee is
TASK_STOPPED).

Signed-off-by: Oleg Nesterov <oleg@redhat.com>
---

kernel/ptrace.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)

--- 37/kernel/ptrace.c~attach_exit_code 2010-11-02 19:48:08.000000000 +0100
+++ 37/kernel/ptrace.c 2011-02-03 20:39:26.000000000 +0100
@@ -202,9 +202,22 @@ int ptrace_attach(struct task_struct *ta
task->ptrace |= PT_PTRACE_CAP;

__ptrace_link(task, current);
- send_sig_info(SIGSTOP, SEND_SIG_FORCED, task);

+ if (task_is_stopped(task)) {
+ /* safe, we checked ->exit_state */
+ spin_lock(&task->sighand->siglock);
+ /*
+ * This can only happen if the previous tracer cleared
+ * ->exit_code, make sure do_wait() will not hang.
+ */
+ if (task_is_stopped(task) && !task->exit_code)
+ task->exit_code = SIGSTOP;
+ spin_unlock(&task->sighand->siglock);
+ }
+
+ send_sig_info(SIGSTOP, SEND_SIG_FORCED, task);
retval = 0;
+
unlock_tasklist:
write_unlock_irq(&tasklist_lock);
unlock_creds:


\
 
 \ /
  Last update: 2011-02-03 21:53    [W:0.209 / U:0.744 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site