[PATCH 1/1] ptrace: make sure do_wait() won't hang afterPTRACE_ATTACH

From: Oleg Nesterov
Date: Thu Feb 03 2011 - 15:50:24 EST


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@xxxxxxxxxx>
---

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:

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/