aboutsummaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorOleg Nesterov <oleg@redhat.com>2014-04-07 18:38:47 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2014-04-07 19:36:06 -0400
commit377d75dafa07ee0da64223c9169f4e17b26c2b9a (patch)
treedd760d7610790e0e338239592b41bda9e98ca652 /kernel
parentad86622b478eaafdc25b74237df82b10fce6326d (diff)
wait: WSTOPPED|WCONTINUED hangs if a zombie child is traced by real_parent
"A zombie is only visible to its ptracer" logic in wait_consider_task() is very wrong. Trivial test-case: #include <unistd.h> #include <sys/ptrace.h> #include <sys/wait.h> #include <assert.h> int main(void) { int child = fork(); if (!child) { assert(ptrace(PTRACE_TRACEME, 0,0,0) == 0); return 0x23; } assert(waitid(P_ALL, child, NULL, WEXITED | WNOWAIT) == 0); assert(waitid(P_ALL, 0, NULL, WSTOPPED) == -1); return 0; } it hangs in waitpid(WSTOPPED) despite the fact it has a single zombie child. This is because wait_consider_task(ptrace => 0) sees p->ptrace and cleares ->notask_error assuming that the debugger should detach and notify us. Change wait_consider_task(ptrace => 0) to pretend that ptrace == T if the child is traced by us. This really simplifies the logic and allows us to do more fixes, see the next changes. This also hides the unwanted group stop state automatically, we can remove another ptrace_reparented() check. Unfortunately, this adds the following behavioural changes: 1. Before this patch wait(WEXITED | __WNOTHREAD) does not reap a natural child if it is traced by the caller's sub-thread. Hopefully nobody will ever notice this change, and I think that nobody should rely on this behaviour anyway. 2. SIGNAL_STOP_CONTINUED is no longer hidden from debugger if it is real parent. While this change comes as a side effect, I think it is good by itself. The group continued state can not be consumed by another process in this case, it doesn't depend on ptrace, it doesn't make sense to hide it from real parent. Perhaps we should add the thread_group_leader() check before wait_task_continued()? May be, but this shouldn't depend on ptrace_reparented(). Signed-off-by: Oleg Nesterov <oleg@redhat.com> Cc: Al Viro <viro@ZenIV.linux.org.uk> Cc: Jan Kratochvil <jan.kratochvil@redhat.com> Cc: Lennart Poettering <lpoetter@redhat.com> Cc: Michal Schmidt <mschmidt@redhat.com> Cc: Roland McGrath <roland@hack.frob.com> Cc: Tejun Heo <tj@kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'kernel')
-rw-r--r--kernel/exit.c29
1 files changed, 16 insertions, 13 deletions
diff --git a/kernel/exit.c b/kernel/exit.c
index 33cf8dba0a61..92d38d4da4b1 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -1362,6 +1362,22 @@ static int wait_consider_task(struct wait_opts *wo, int ptrace,
1362 return 0; 1362 return 0;
1363 } 1363 }
1364 1364
1365 if (likely(!ptrace) && unlikely(p->ptrace)) {
1366 /*
1367 * If it is traced by its real parent's group, just pretend
1368 * the caller is ptrace_do_wait() and reap this child if it
1369 * is zombie.
1370 *
1371 * This also hides group stop state from real parent; otherwise
1372 * a single stop can be reported twice as group and ptrace stop.
1373 * If a ptracer wants to distinguish these two events for its
1374 * own children it should create a separate process which takes
1375 * the role of real parent.
1376 */
1377 if (!ptrace_reparented(p))
1378 ptrace = 1;
1379 }
1380
1365 /* slay zombie? */ 1381 /* slay zombie? */
1366 if (p->exit_state == EXIT_ZOMBIE) { 1382 if (p->exit_state == EXIT_ZOMBIE) {
1367 /* 1383 /*
@@ -1403,19 +1419,6 @@ static int wait_consider_task(struct wait_opts *wo, int ptrace,
1403 wo->notask_error = 0; 1419 wo->notask_error = 0;
1404 } else { 1420 } else {
1405 /* 1421 /*
1406 * If @p is ptraced by a task in its real parent's group,
1407 * hide group stop/continued state when looking at @p as
1408 * the real parent; otherwise, a single stop can be
1409 * reported twice as group and ptrace stops.
1410 *
1411 * If a ptracer wants to distinguish the two events for its
1412 * own children, it should create a separate process which
1413 * takes the role of real parent.
1414 */
1415 if (likely(!ptrace) && p->ptrace && !ptrace_reparented(p))
1416 return 0;
1417
1418 /*
1419 * @p is alive and it's gonna stop, continue or exit, so 1422 * @p is alive and it's gonna stop, continue or exit, so
1420 * there always is something to wait for. 1423 * there always is something to wait for.
1421 */ 1424 */