Index: head/sys/kern/kern_exit.c =================================================================== --- head/sys/kern/kern_exit.c +++ head/sys/kern/kern_exit.c @@ -955,7 +955,8 @@ static int proc_to_reap(struct thread *td, struct proc *p, idtype_t idtype, id_t id, - int *status, int options, struct __wrusage *wrusage, siginfo_t *siginfo) + int *status, int options, struct __wrusage *wrusage, siginfo_t *siginfo, + int check_only) { struct proc *q; struct rusage *rup; @@ -1093,7 +1094,7 @@ calccru(p, &rup->ru_utime, &rup->ru_stime); } - if (p->p_state == PRS_ZOMBIE) { + if (p->p_state == PRS_ZOMBIE && !check_only) { PROC_SLOCK(p); proc_reap(td, p, status, options); return (-1); @@ -1187,7 +1188,7 @@ sx_xlock(&proctree_lock); LIST_FOREACH(p, &q->p_children, p_sibling) { ret = proc_to_reap(td, p, idtype, id, status, options, - wrusage, siginfo); + wrusage, siginfo, 0); if (ret == 0) continue; else if (ret == 1) @@ -1289,15 +1290,17 @@ * for. By maintaining a list of orphans we allow the parent * to successfully wait until the child becomes a zombie. */ - LIST_FOREACH(p, &q->p_orphans, p_orphan) { - ret = proc_to_reap(td, p, idtype, id, status, options, - wrusage, siginfo); - if (ret == 0) - continue; - else if (ret == 1) - nfound++; - else - return (0); + if (nfound == 0) { + LIST_FOREACH(p, &q->p_orphans, p_orphan) { + ret = proc_to_reap(td, p, idtype, id, NULL, options, + NULL, NULL, 1); + if (ret != 0) { + KASSERT(ret != -1, ("reaped an orphan (pid %d)", + (int)td->td_retval[0])); + nfound++; + break; + } + } } if (nfound == 0) { sx_xunlock(&proctree_lock); Index: head/tests/sys/kern/ptrace_test.c =================================================================== --- head/tests/sys/kern/ptrace_test.c +++ head/tests/sys/kern/ptrace_test.c @@ -29,6 +29,8 @@ #include #include +#include +#include #include #include #include @@ -133,11 +135,255 @@ ATF_REQUIRE(errno == ECHILD); } +/* + * Verify that a parent process "sees" the exit of a debugged process only + * after the debugger has seen it. + */ +ATF_TC_WITHOUT_HEAD(ptrace__parent_sees_exit_after_child_debugger); +ATF_TC_BODY(ptrace__parent_sees_exit_after_child_debugger, tc) +{ + pid_t child, debugger, wpid; + int cpipe[2], dpipe[2], status; + char c; + + ATF_REQUIRE(pipe(cpipe) == 0); + ATF_REQUIRE((child = fork()) != -1); + + if (child == 0) { + /* Child process. */ + close(cpipe[0]); + + /* Wait for parent to be ready. */ + ATF_REQUIRE(read(cpipe[1], &c, sizeof(c)) == sizeof(c)); + + exit(1); + } + close(cpipe[1]); + + ATF_REQUIRE(pipe(dpipe) == 0); + ATF_REQUIRE((debugger = fork()) != -1); + + if (debugger == 0) { + /* Debugger process. */ + close(dpipe[0]); + + ATF_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) != -1); + + wpid = waitpid(child, &status, 0); + ATF_REQUIRE(wpid == child); + ATF_REQUIRE(WIFSTOPPED(status)); + ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP); + + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + /* Signal parent that debugger is attached. */ + ATF_REQUIRE(write(dpipe[1], &c, sizeof(c)) == sizeof(c)); + + /* Wait for parent's failed wait. */ + ATF_REQUIRE(read(dpipe[1], &c, sizeof(c)) == 0); + + wpid = waitpid(child, &status, 0); + ATF_REQUIRE(wpid == child); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); + + exit(0); + } + close(dpipe[1]); + + /* Parent process. */ + + /* Wait for the debugger to attach to the child. */ + ATF_REQUIRE(read(dpipe[0], &c, sizeof(c)) == sizeof(c)); + + /* Release the child. */ + ATF_REQUIRE(write(cpipe[0], &c, sizeof(c)) == sizeof(c)); + ATF_REQUIRE(read(cpipe[0], &c, sizeof(c)) == 0); + close(cpipe[0]); + + /* + * Wait for the child to exit. This is kind of gross, but + * there is not a better way. + */ + for (;;) { + struct kinfo_proc kp; + size_t len; + int mib[4]; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = child; + len = sizeof(kp); + if (sysctl(mib, nitems(mib), &kp, &len, NULL, 0) == -1) { + /* The KERN_PROC_PID sysctl fails for zombies. */ + ATF_REQUIRE(errno == ESRCH); + break; + } + usleep(5000); + } + + /* + * This wait should return an empty pid. The parent should + * see the child as non-exited until the debugger sees the + * exit. + */ + wpid = waitpid(child, &status, WNOHANG); + ATF_REQUIRE(wpid == 0); + + /* Signal the debugger to wait for the child. */ + close(dpipe[0]); + + /* Wait for the debugger. */ + wpid = waitpid(debugger, &status, 0); + ATF_REQUIRE(wpid == debugger); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 0); + + /* The child process should now be ready. */ + wpid = waitpid(child, &status, WNOHANG); + ATF_REQUIRE(wpid == child); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); +} + +/* + * Verify that a parent process "sees" the exit of a debugged process + * only after a non-direct-child debugger has seen it. In particular, + * various wait() calls in the parent must avoid failing with ESRCH by + * checking the parent's orphan list for the debugee. + */ +ATF_TC_WITHOUT_HEAD(ptrace__parent_sees_exit_after_unrelated_debugger); +ATF_TC_BODY(ptrace__parent_sees_exit_after_unrelated_debugger, tc) +{ + pid_t child, debugger, fpid, wpid; + int cpipe[2], dpipe[2], status; + char c; + + ATF_REQUIRE(pipe(cpipe) == 0); + ATF_REQUIRE((child = fork()) != -1); + + if (child == 0) { + /* Child process. */ + close(cpipe[0]); + + /* Wait for parent to be ready. */ + ATF_REQUIRE(read(cpipe[1], &c, sizeof(c)) == sizeof(c)); + + exit(1); + } + close(cpipe[1]); + + ATF_REQUIRE(pipe(dpipe) == 0); + ATF_REQUIRE((debugger = fork()) != -1); + + if (debugger == 0) { + /* Debugger parent. */ + + /* + * Fork again and drop the debugger parent so that the + * debugger is not a child of the main parent. + */ + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid != 0) + exit(2); + + /* Debugger process. */ + close(dpipe[0]); + + ATF_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) != -1); + + wpid = waitpid(child, &status, 0); + ATF_REQUIRE(wpid == child); + ATF_REQUIRE(WIFSTOPPED(status)); + ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP); + + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + /* Signal parent that debugger is attached. */ + ATF_REQUIRE(write(dpipe[1], &c, sizeof(c)) == sizeof(c)); + + /* Wait for parent's failed wait. */ + ATF_REQUIRE(read(dpipe[1], &c, sizeof(c)) == 0); + + wpid = waitpid(child, &status, 0); + ATF_REQUIRE(wpid == child); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); + + exit(0); + } + + /* Parent process. */ + + /* Wait for the debugger parent process to exit. */ + wpid = waitpid(debugger, &status, 0); + ATF_REQUIRE(wpid == debugger); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 2); + + /* A WNOHANG wait here should see the non-exited child. */ + wpid = waitpid(child, &status, WNOHANG); + ATF_REQUIRE(wpid == 0); + + /* Wait for the debugger to attach to the child. */ + ATF_REQUIRE(read(dpipe[0], &c, sizeof(c)) == sizeof(c)); + + /* Release the child. */ + ATF_REQUIRE(write(cpipe[0], &c, sizeof(c)) == sizeof(c)); + ATF_REQUIRE(read(cpipe[0], &c, sizeof(c)) == 0); + close(cpipe[0]); + + /* + * Wait for the child to exit. This is kind of gross, but + * there is not a better way. + */ + for (;;) { + struct kinfo_proc kp; + size_t len; + int mib[4]; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = child; + len = sizeof(kp); + if (sysctl(mib, nitems(mib), &kp, &len, NULL, 0) == -1) { + /* The KERN_PROC_PID sysctl fails for zombies. */ + ATF_REQUIRE(errno == ESRCH); + break; + } + usleep(5000); + } + + /* + * This wait should return an empty pid. The parent should + * see the child as non-exited until the debugger sees the + * exit. + */ + wpid = waitpid(child, &status, WNOHANG); + ATF_REQUIRE(wpid == 0); + + /* Signal the debugger to wait for the child. */ + close(dpipe[0]); + + /* Wait for the debugger. */ + ATF_REQUIRE(read(dpipe[1], &c, sizeof(c)) == 0); + + /* The child process should now be ready. */ + wpid = waitpid(child, &status, WNOHANG); + ATF_REQUIRE(wpid == child); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, ptrace__parent_wait_after_trace_me); ATF_TP_ADD_TC(tp, ptrace__parent_wait_after_attach); + ATF_TP_ADD_TC(tp, ptrace__parent_sees_exit_after_child_debugger); + ATF_TP_ADD_TC(tp, ptrace__parent_sees_exit_after_unrelated_debugger); return (atf_no_error()); }