summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorOleg Nesterov <oleg@redhat.com>2011-07-21 22:00:43 +0400
committerOleg Nesterov <oleg@redhat.com>2011-07-22 17:10:49 +0400
commiteac1b5e57d7abc836e78fd3fbcf77dbeed01edc9 (patch)
tree50d267e7a4edd645543abfeb33d12eb18d84d00e /fs
parent8a35241803eeb0e9fd3fe27835d6b2775c73b641 (diff)
downloadlinux-eac1b5e57d7abc836e78fd3fbcf77dbeed01edc9.tar.xz
ptrace: do_wait(traced_leader_killed_by_mt_exec) can block forever
Test-case: void *tfunc(void *arg) { execvp("true", NULL); return NULL; } int main(void) { int pid; if (fork()) { pthread_t t; kill(getpid(), SIGSTOP); pthread_create(&t, NULL, tfunc, NULL); for (;;) pause(); } pid = getppid(); assert(ptrace(PTRACE_ATTACH, pid, 0,0) == 0); while (wait(NULL) > 0) ptrace(PTRACE_CONT, pid, 0,0); return 0; } It is racy, exit_notify() does __wake_up_parent() too. But in the likely case it triggers the problem: de_thread() does release_task() and the old leader goes away without the notification, the tracer sleeps in do_wait() without children/tracees. Change de_thread() to do __wake_up_parent(traced_leader->parent). Since it is already EXIT_DEAD we can do this without ptrace_unlink(), EXIT_DEAD threads do not exist from do_wait's pov. Signed-off-by: Oleg Nesterov <oleg@redhat.com> Acked-by: Tejun Heo <tj@kernel.org>
Diffstat (limited to 'fs')
-rw-r--r--fs/exec.c8
1 files changed, 8 insertions, 0 deletions
diff --git a/fs/exec.c b/fs/exec.c
index b08367abf30e..d219541db06c 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -967,6 +967,14 @@ static int de_thread(struct task_struct *tsk)
BUG_ON(leader->exit_state != EXIT_ZOMBIE);
leader->exit_state = EXIT_DEAD;
+
+ /*
+ * We are going to release_task()->ptrace_unlink() silently,
+ * the tracer can sleep in do_wait(). EXIT_DEAD guarantees
+ * the tracer wont't block again waiting for this thread.
+ */
+ if (unlikely(leader->ptrace))
+ __wake_up_parent(leader, leader->parent);
write_unlock_irq(&tasklist_lock);
release_task(leader);