Skip to content

Commit dfccbb5

Browse files
oleg-nesterovtorvalds
authored andcommitted
wait: fix reparent_leader() vs EXIT_DEAD->EXIT_ZOMBIE race
wait_task_zombie() first does EXIT_ZOMBIE->EXIT_DEAD transition and drops tasklist_lock. If this task is not the natural child and it is traced, we change its state back to EXIT_ZOMBIE for ->real_parent. The last transition is racy, this is even documented in 50b8d25 "ptrace: partially fix the do_wait(WEXITED) vs EXIT_DEAD->EXIT_ZOMBIE race". wait_consider_task() tries to detect this transition and clear ->notask_error but we can't rely on ptrace_reparented(), debugger can exit and do ptrace_unlink() before its sub-thread sets EXIT_ZOMBIE. And there is another problem which were missed before: this transition can also race with reparent_leader() which doesn't reset >exit_signal if EXIT_DEAD, assuming that this task must be reaped by someone else. So the tracee can be re-parented with ->exit_signal != SIGCHLD, and if /sbin/init doesn't use __WALL it becomes unreapable. Change reparent_leader() to update ->exit_signal even if EXIT_DEAD. Note: this is the simple temporary hack for -stable, it doesn't try to solve all problems, it will be reverted by the next changes. Signed-off-by: Oleg Nesterov <oleg@redhat.com> Reported-by: Jan Kratochvil <jan.kratochvil@redhat.com> Reported-by: Michal Schmidt <mschmidt@redhat.com> Tested-by: Michal Schmidt <mschmidt@redhat.com> Cc: Al Viro <viro@ZenIV.linux.org.uk> Cc: Lennart Poettering <lpoetter@redhat.com> Cc: Roland McGrath <roland@hack.frob.com> Cc: Tejun Heo <tj@kernel.org> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent 23aebe1 commit dfccbb5

File tree

1 file changed

+11
-4
lines changed

1 file changed

+11
-4
lines changed

kernel/exit.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -560,19 +560,26 @@ static void reparent_leader(struct task_struct *father, struct task_struct *p,
560560
struct list_head *dead)
561561
{
562562
list_move_tail(&p->sibling, &p->real_parent->children);
563-
564-
if (p->exit_state == EXIT_DEAD)
565-
return;
566563
/*
567564
* If this is a threaded reparent there is no need to
568565
* notify anyone anything has happened.
569566
*/
570567
if (same_thread_group(p->real_parent, father))
571568
return;
572569

573-
/* We don't want people slaying init. */
570+
/*
571+
* We don't want people slaying init.
572+
*
573+
* Note: we do this even if it is EXIT_DEAD, wait_task_zombie()
574+
* can change ->exit_state to EXIT_ZOMBIE. If this is the final
575+
* state, do_notify_parent() was already called and ->exit_signal
576+
* doesn't matter.
577+
*/
574578
p->exit_signal = SIGCHLD;
575579

580+
if (p->exit_state == EXIT_DEAD)
581+
return;
582+
576583
/* If it has exited notify the new parent about this child's death. */
577584
if (!p->ptrace &&
578585
p->exit_state == EXIT_ZOMBIE && thread_group_empty(p)) {

0 commit comments

Comments
 (0)