Date: Wed, 9 Sep 2015 23:39:31 +0000 (UTC) From: John Baldwin <jhb@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-10@freebsd.org Subject: svn commit: r287604 - in stable/10: sys/fs/procfs sys/kern tests/sys/kern Message-ID: <201509092339.t89NdV7S058424@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: jhb Date: Wed Sep 9 23:39:30 2015 New Revision: 287604 URL: https://svnweb.freebsd.org/changeset/base/287604 Log: MFC 283281,283282,283562,283647,283836,284000,286158: Various fixes to orphan handling which also fix issues with following forks. 283281: Always set p_oppid when attaching to an existing process via procfs tracing. This matches the behavior of ptrace(PT_ATTACH). Also, the procfs detach request assumes p_oppid is always set. 283282: Only reparent a traced process to its old parent if the tracing process is not the old parent. Otherwise, proc_reap() will leave the zombie in place resulting in the process' status being returned twice to its parent. Add test cases for PT_TRACE_ME and PT_ATTACH which are fixed by this change. 283562: Do not allow a process to reap an orphan (a child currently being traced by another process such as a debugger). The parent process does need to check for matching orphan pids to avoid returning ECHILD if an orphan has exited, but it should not return the exited status for the child until after the debugger has detached from the orphan process either explicitly or implicitly via wait(). Add two tests for for this case: one where the debugger is the direct child (thus the parent has a non-empty children list) and one where the debugger is not a direct child (so the only "child" of the parent is the orphan). 283647: Tweak the description of when waitpid() doesn't return any status for a non-blocking wait to avoid the word "empty". 283836: Consistently only use one end of the pipe in the parent and debugger processes and do not rely on EOF due to a close() in the debugger. 284000: Add a CHILD_REQUIRE macro similar to ATF_REQUIRE for use in child processes of the main test process. 286158: Clear P_TRACED before reparenting a detached process back to its original parent. Otherwise the debugee will be set as an orphan of the debugger. Add tests for tracing forks via PT_FOLLOW_FORK. Added: stable/10/tests/sys/kern/ptrace_test.c - copied, changed from r283282, head/tests/sys/kern/ptrace_test.c Modified: stable/10/sys/fs/procfs/procfs_ctl.c stable/10/sys/kern/kern_exit.c stable/10/sys/kern/sys_process.c stable/10/tests/sys/kern/Makefile Directory Properties: stable/10/ (props changed) Modified: stable/10/sys/fs/procfs/procfs_ctl.c ============================================================================== --- stable/10/sys/fs/procfs/procfs_ctl.c Wed Sep 9 23:05:52 2015 (r287603) +++ stable/10/sys/fs/procfs/procfs_ctl.c Wed Sep 9 23:39:30 2015 (r287604) @@ -143,8 +143,8 @@ procfs_control(struct thread *td, struct p->p_flag |= P_TRACED; faultin(p); p->p_xstat = 0; /* XXX ? */ + p->p_oppid = p->p_pptr->p_pid; if (p->p_pptr != td->td_proc) { - p->p_oppid = p->p_pptr->p_pid; proc_reparent(p, td->td_proc); } kern_psignal(p, SIGSTOP); Modified: stable/10/sys/kern/kern_exit.c ============================================================================== --- stable/10/sys/kern/kern_exit.c Wed Sep 9 23:05:52 2015 (r287603) +++ stable/10/sys/kern/kern_exit.c Wed Sep 9 23:39:30 2015 (r287604) @@ -857,13 +857,13 @@ proc_reap(struct thread *td, struct proc PROC_LOCK(q); sigqueue_take(p->p_ksi); PROC_UNLOCK(q); - PROC_UNLOCK(p); /* * If we got the child via a ptrace 'attach', we need to give it back * to the old parent. */ - if (p->p_oppid != 0) { + if (p->p_oppid != 0 && p->p_oppid != p->p_pptr->p_pid) { + PROC_UNLOCK(p); t = proc_realparent(p); PROC_LOCK(t); PROC_LOCK(p); @@ -880,6 +880,8 @@ proc_reap(struct thread *td, struct proc sx_xunlock(&proctree_lock); return; } + p->p_oppid = 0; + PROC_UNLOCK(p); /* * Remove other references to this process to ensure we have an @@ -962,7 +964,8 @@ proc_reap(struct thread *td, struct proc 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 rusage *rup; @@ -1098,7 +1101,7 @@ proc_to_reap(struct thread *td, struct p calccru(p, &rup->ru_utime, &rup->ru_stime); } - if (p->p_state == PRS_ZOMBIE) { + if (p->p_state == PRS_ZOMBIE && !check_only) { proc_reap(td, p, status, options); return (-1); } @@ -1192,7 +1195,7 @@ loop: 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) @@ -1294,15 +1297,17 @@ loop: * 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); Modified: stable/10/sys/kern/sys_process.c ============================================================================== --- stable/10/sys/kern/sys_process.c Wed Sep 9 23:05:52 2015 (r287603) +++ stable/10/sys/kern/sys_process.c Wed Sep 9 23:39:30 2015 (r287604) @@ -946,7 +946,15 @@ kern_ptrace(struct thread *td, int req, } break; case PT_DETACH: - /* reset process parent */ + /* + * Reset the process parent. + * + * NB: This clears P_TRACED before reparenting + * a detached process back to its original + * parent. Otherwise the debugee will be set + * as an orphan of the debugger. + */ + p->p_flag &= ~(P_TRACED | P_WAITED | P_FOLLOWFORK); if (p->p_oppid != p->p_pptr->p_pid) { PROC_LOCK(p->p_pptr); sigqueue_take(p->p_ksi); @@ -962,7 +970,6 @@ kern_ptrace(struct thread *td, int req, } else CTR1(KTR_PTRACE, "PT_DETACH: pid %d", p->p_pid); p->p_oppid = 0; - p->p_flag &= ~(P_TRACED | P_WAITED | P_FOLLOWFORK); p->p_stops = 0; /* should we send SIGCHLD? */ Modified: stable/10/tests/sys/kern/Makefile ============================================================================== --- stable/10/tests/sys/kern/Makefile Wed Sep 9 23:05:52 2015 (r287603) +++ stable/10/tests/sys/kern/Makefile Wed Sep 9 23:39:30 2015 (r287604) @@ -3,6 +3,7 @@ TESTSDIR= ${TESTSBASE}/sys/kern ATF_TESTS_C+= kern_descrip_test +ATF_TESTS_C+= ptrace_test ATF_TESTS_C+= unix_seqpacket_test TEST_METADATA.unix_seqpacket_test+= timeout="15" Copied and modified: stable/10/tests/sys/kern/ptrace_test.c (from r283282, head/tests/sys/kern/ptrace_test.c) ============================================================================== --- head/tests/sys/kern/ptrace_test.c Fri May 22 11:04:54 2015 (r283282, copy source) +++ stable/10/tests/sys/kern/ptrace_test.c Wed Sep 9 23:39:30 2015 (r287604) @@ -29,14 +29,90 @@ __FBSDID("$FreeBSD$"); #include <sys/types.h> #include <sys/ptrace.h> +#include <sys/sysctl.h> +#include <sys/user.h> #include <sys/wait.h> #include <errno.h> #include <signal.h> +#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <atf-c.h> /* + * A variant of ATF_REQUIRE that is suitable for use in child + * processes. This only works if the parent process is tripped up by + * the early exit and fails some requirement itself. + */ +#define CHILD_REQUIRE(exp) do { \ + if (!(exp)) \ + child_fail_require(__FILE__, __LINE__, \ + #exp " not met"); \ + } while (0) + +static __dead2 void +child_fail_require(const char *file, int line, const char *str) +{ + char buf[128]; + + snprintf(buf, sizeof(buf), "%s:%d: %s\n", file, line, str); + write(2, buf, strlen(buf)); + _exit(32); +} + +static void +trace_me(void) +{ + + /* Attach the parent process as a tracer of this process. */ + CHILD_REQUIRE(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1); + + /* Trigger a stop. */ + raise(SIGSTOP); +} + +static void +attach_child(pid_t pid) +{ + pid_t wpid; + int status; + + ATF_REQUIRE(ptrace(PT_ATTACH, pid, NULL, 0) == 0); + + wpid = waitpid(pid, &status, 0); + ATF_REQUIRE(wpid == pid); + ATF_REQUIRE(WIFSTOPPED(status)); + ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP); +} + +static void +wait_for_zombie(pid_t pid) +{ + + /* + * Wait for a process 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] = pid; + 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); + } +} + +/* * Verify that a parent debugger process "sees" the exit of a debugged * process exactly once when attached via PT_TRACE_ME. */ @@ -49,10 +125,7 @@ ATF_TC_BODY(ptrace__parent_wait_after_tr ATF_REQUIRE((child = fork()) != -1); if (child == 0) { /* Child process. */ - ATF_REQUIRE(ptrace(PT_TRACE_ME, 0, NULL, 0) != -1); - - /* Trigger a stop. */ - raise(SIGSTOP); + trace_me(); exit(1); } @@ -98,7 +171,7 @@ ATF_TC_BODY(ptrace__parent_wait_after_at close(cpipe[0]); /* Wait for the parent to attach. */ - ATF_REQUIRE(read(cpipe[1], &c, sizeof(c)) == 0); + CHILD_REQUIRE(read(cpipe[1], &c, sizeof(c)) == 0); exit(1); } @@ -107,13 +180,7 @@ ATF_TC_BODY(ptrace__parent_wait_after_at /* Parent process. */ /* Attach to the child process. */ - ATF_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) == 0); - - /* The first wait() should report the SIGSTOP from PT_ATTACH. */ - wpid = waitpid(child, &status, 0); - ATF_REQUIRE(wpid == child); - ATF_REQUIRE(WIFSTOPPED(status)); - ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP); + attach_child(child); /* Continue the child ignoring the SIGSTOP. */ ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); @@ -133,11 +200,687 @@ ATF_TC_BODY(ptrace__parent_wait_after_at 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. */ + CHILD_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]); + + CHILD_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) != -1); + + wpid = waitpid(child, &status, 0); + CHILD_REQUIRE(wpid == child); + CHILD_REQUIRE(WIFSTOPPED(status)); + CHILD_REQUIRE(WSTOPSIG(status) == SIGSTOP); + + CHILD_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + /* Signal parent that debugger is attached. */ + CHILD_REQUIRE(write(dpipe[1], &c, sizeof(c)) == sizeof(c)); + + /* Wait for parent's failed wait. */ + CHILD_REQUIRE(read(dpipe[1], &c, sizeof(c)) == 0); + + wpid = waitpid(child, &status, 0); + CHILD_REQUIRE(wpid == child); + CHILD_REQUIRE(WIFEXITED(status)); + CHILD_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_zombie(child); + + /* + * This wait should return a pid of 0 to indicate no status to + * report. 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. */ + CHILD_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. + */ + CHILD_REQUIRE((fpid = fork()) != -1); + if (fpid != 0) + exit(2); + + /* Debugger process. */ + close(dpipe[0]); + + CHILD_REQUIRE(ptrace(PT_ATTACH, child, NULL, 0) != -1); + + wpid = waitpid(child, &status, 0); + CHILD_REQUIRE(wpid == child); + CHILD_REQUIRE(WIFSTOPPED(status)); + CHILD_REQUIRE(WSTOPSIG(status) == SIGSTOP); + + CHILD_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + /* Signal parent that debugger is attached. */ + CHILD_REQUIRE(write(dpipe[1], &c, sizeof(c)) == sizeof(c)); + + /* Wait for parent's failed wait. */ + CHILD_REQUIRE(read(dpipe[1], &c, sizeof(c)) == sizeof(c)); + + wpid = waitpid(child, &status, 0); + CHILD_REQUIRE(wpid == child); + CHILD_REQUIRE(WIFEXITED(status)); + CHILD_REQUIRE(WEXITSTATUS(status) == 1); + + exit(0); + } + close(dpipe[1]); + + /* 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_zombie(child); + + /* + * This wait should return a pid of 0 to indicate no status to + * report. 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. */ + ATF_REQUIRE(write(dpipe[0], &c, sizeof(c)) == sizeof(c)); + + /* Wait for the debugger. */ + ATF_REQUIRE(read(dpipe[0], &c, sizeof(c)) == 0); + close(dpipe[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); +} + +/* + * The parent process should always act the same regardless of how the + * debugger is attached to it. + */ +static __dead2 void +follow_fork_parent(void) +{ + pid_t fpid, wpid; + int status; + + CHILD_REQUIRE((fpid = fork()) != -1); + + if (fpid == 0) + /* Child */ + exit(2); + + wpid = waitpid(fpid, &status, 0); + CHILD_REQUIRE(wpid == fpid); + CHILD_REQUIRE(WIFEXITED(status)); + CHILD_REQUIRE(WEXITSTATUS(status) == 2); + + exit(1); +} + +/* + * Helper routine for follow fork tests. This waits for two stops + * that report both "sides" of a fork. It returns the pid of the new + * child process. + */ +static pid_t +handle_fork_events(pid_t parent) +{ + struct ptrace_lwpinfo pl; + bool fork_reported[2]; + pid_t child, wpid; + int i, status; + + fork_reported[0] = false; + fork_reported[1] = false; + child = -1; + + /* + * Each process should report a fork event. The parent should + * report a PL_FLAG_FORKED event, and the child should report + * a PL_FLAG_CHILD event. + */ + for (i = 0; i < 2; i++) { + wpid = wait(&status); + ATF_REQUIRE(wpid > 0); + ATF_REQUIRE(WIFSTOPPED(status)); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + ATF_REQUIRE((pl.pl_flags & (PL_FLAG_FORKED | PL_FLAG_CHILD)) != + 0); + ATF_REQUIRE((pl.pl_flags & (PL_FLAG_FORKED | PL_FLAG_CHILD)) != + (PL_FLAG_FORKED | PL_FLAG_CHILD)); + if (pl.pl_flags & PL_FLAG_CHILD) { + ATF_REQUIRE(wpid != parent); + ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP); + ATF_REQUIRE(!fork_reported[1]); + if (child == -1) + child = wpid; + else + ATF_REQUIRE(child == wpid); + fork_reported[1] = true; + } else { + ATF_REQUIRE(wpid == parent); + ATF_REQUIRE(WSTOPSIG(status) == SIGTRAP); + ATF_REQUIRE(!fork_reported[0]); + if (child == -1) + child = pl.pl_child_pid; + else + ATF_REQUIRE(child == pl.pl_child_pid); + fork_reported[0] = true; + } + } + + return (child); +} + +/* + * Verify that a new child process is stopped after a followed fork and + * that the traced parent sees the exit of the child after the debugger + * when both processes remain attached to the debugger. + */ +ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_both_attached); +ATF_TC_BODY(ptrace__follow_fork_both_attached, tc) +{ + pid_t children[0], fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(); + } + + /* Parent process. */ + children[0] = fpid; + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(children[0], &status, 0); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP); + + ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1); + + /* Continue the child ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + + children[1] = handle_fork_events(children[0]); + ATF_REQUIRE(children[1] > 0); + + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1); + + /* + * The child can't exit until the grandchild reports status, so the + * grandchild should report its exit first to the debugger. + */ + wpid = wait(&status); + ATF_REQUIRE(wpid == children[1]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 2); + + wpid = wait(&status); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); + + wpid = wait(&status); + ATF_REQUIRE(wpid == -1); + ATF_REQUIRE(errno == ECHILD); +} + +/* + * Verify that a new child process is stopped after a followed fork + * and that the traced parent sees the exit of the child when the new + * child process is detached after it reports its fork. + */ +ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_child_detached); +ATF_TC_BODY(ptrace__follow_fork_child_detached, tc) +{ + pid_t children[0], fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(); + } + + /* Parent process. */ + children[0] = fpid; + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(children[0], &status, 0); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP); + + ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1); + + /* Continue the child ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + + children[1] = handle_fork_events(children[0]); + ATF_REQUIRE(children[1] > 0); + + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + ATF_REQUIRE(ptrace(PT_DETACH, children[1], (caddr_t)1, 0) != -1); + + /* + * Should not see any status from the grandchild now, only the + * child. + */ + wpid = wait(&status); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); + + wpid = wait(&status); + ATF_REQUIRE(wpid == -1); + ATF_REQUIRE(errno == ECHILD); +} + +/* + * Verify that a new child process is stopped after a followed fork + * and that the traced parent sees the exit of the child when the + * traced parent is detached after the fork. + */ +ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_parent_detached); +ATF_TC_BODY(ptrace__follow_fork_parent_detached, tc) +{ + pid_t children[0], fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(); + } + + /* Parent process. */ + children[0] = fpid; + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(children[0], &status, 0); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + ATF_REQUIRE(WSTOPSIG(status) == SIGSTOP); + + ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1); + + /* Continue the child ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + + children[1] = handle_fork_events(children[0]); + ATF_REQUIRE(children[1] > 0); + + ATF_REQUIRE(ptrace(PT_DETACH, children[0], (caddr_t)1, 0) != -1); + ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1); + + /* + * The child can't exit until the grandchild reports status, so the + * grandchild should report its exit first to the debugger. + * + * Even though the child process is detached, it is still a + * child of the debugger, so it will still report it's exit + * after the grandchild. + */ + wpid = wait(&status); + ATF_REQUIRE(wpid == children[1]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 2); + + wpid = wait(&status); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); + + wpid = wait(&status); + ATF_REQUIRE(wpid == -1); + ATF_REQUIRE(errno == ECHILD); +} + +static void +attach_fork_parent(int cpipe[2]) +{ + pid_t fpid; + + close(cpipe[0]); + + /* Double-fork to disassociate from the debugger. */ + CHILD_REQUIRE((fpid = fork()) != -1); + if (fpid != 0) + exit(3); + + /* Send the pid of the disassociated child to the debugger. */ + fpid = getpid(); + CHILD_REQUIRE(write(cpipe[1], &fpid, sizeof(fpid)) == sizeof(fpid)); + + /* Wait for the debugger to attach. */ + CHILD_REQUIRE(read(cpipe[1], &fpid, sizeof(fpid)) == 0); +} + +/* + * Verify that a new child process is stopped after a followed fork and + * that the traced parent sees the exit of the child after the debugger + * when both processes remain attached to the debugger. In this test + * the parent that forks is not a direct child of the debugger. + */ +ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_both_attached_unrelated_debugger); +ATF_TC_BODY(ptrace__follow_fork_both_attached_unrelated_debugger, tc) +{ + pid_t children[0], fpid, wpid; + int cpipe[2], status; + + ATF_REQUIRE(pipe(cpipe) == 0); + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + attach_fork_parent(cpipe); + follow_fork_parent(); + } + + /* Parent process. */ + close(cpipe[1]); + + /* Wait for the direct child to exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(wpid == fpid); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 3); + + /* Read the pid of the fork parent. */ + ATF_REQUIRE(read(cpipe[0], &children[0], sizeof(children[0])) == + sizeof(children[0])); + + /* Attach to the fork parent. */ + attach_child(children[0]); + + ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1); + + /* Continue the fork parent ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + + /* Signal the fork parent to continue. */ + close(cpipe[0]); + + children[1] = handle_fork_events(children[0]); + ATF_REQUIRE(children[1] > 0); + + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1); + + /* + * The fork parent can't exit until the child reports status, + * so the child should report its exit first to the debugger. + */ + wpid = wait(&status); + ATF_REQUIRE(wpid == children[1]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 2); + + wpid = wait(&status); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); + + wpid = wait(&status); + ATF_REQUIRE(wpid == -1); + ATF_REQUIRE(errno == ECHILD); +} + +/* + * Verify that a new child process is stopped after a followed fork + * and that the traced parent sees the exit of the child when the new + * child process is detached after it reports its fork. In this test + * the parent that forks is not a direct child of the debugger. + */ +ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_child_detached_unrelated_debugger); +ATF_TC_BODY(ptrace__follow_fork_child_detached_unrelated_debugger, tc) +{ + pid_t children[0], fpid, wpid; + int cpipe[2], status; + + ATF_REQUIRE(pipe(cpipe) == 0); + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + attach_fork_parent(cpipe); + follow_fork_parent(); + } + + /* Parent process. */ + close(cpipe[1]); + + /* Wait for the direct child to exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(wpid == fpid); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 3); + + /* Read the pid of the fork parent. */ + ATF_REQUIRE(read(cpipe[0], &children[0], sizeof(children[0])) == + sizeof(children[0])); + + /* Attach to the fork parent. */ + attach_child(children[0]); + + ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1); + + /* Continue the fork parent ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + + /* Signal the fork parent to continue. */ + close(cpipe[0]); + + children[1] = handle_fork_events(children[0]); + ATF_REQUIRE(children[1] > 0); + + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + ATF_REQUIRE(ptrace(PT_DETACH, children[1], (caddr_t)1, 0) != -1); + + /* + * Should not see any status from the child now, only the fork + * parent. + */ + wpid = wait(&status); + ATF_REQUIRE(wpid == children[0]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 1); + + wpid = wait(&status); + ATF_REQUIRE(wpid == -1); + ATF_REQUIRE(errno == ECHILD); +} + +/* + * Verify that a new child process is stopped after a followed fork + * and that the traced parent sees the exit of the child when the + * traced parent is detached after the fork. In this test the parent + * that forks is not a direct child of the debugger. + */ +ATF_TC_WITHOUT_HEAD(ptrace__follow_fork_parent_detached_unrelated_debugger); +ATF_TC_BODY(ptrace__follow_fork_parent_detached_unrelated_debugger, tc) +{ + pid_t children[0], fpid, wpid; + int cpipe[2], status; + + ATF_REQUIRE(pipe(cpipe) == 0); + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + attach_fork_parent(cpipe); + follow_fork_parent(); + } + + /* Parent process. */ + close(cpipe[1]); + + /* Wait for the direct child to exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(wpid == fpid); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 3); + + /* Read the pid of the fork parent. */ + ATF_REQUIRE(read(cpipe[0], &children[0], sizeof(children[0])) == + sizeof(children[0])); + + /* Attach to the fork parent. */ + attach_child(children[0]); + + ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, children[0], NULL, 1) != -1); + + /* Continue the fork parent ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + + /* Signal the fork parent to continue. */ + close(cpipe[0]); + + children[1] = handle_fork_events(children[0]); + ATF_REQUIRE(children[1] > 0); + + ATF_REQUIRE(ptrace(PT_DETACH, children[0], (caddr_t)1, 0) != -1); + ATF_REQUIRE(ptrace(PT_CONTINUE, children[1], (caddr_t)1, 0) != -1); + + /* + * Should not see any status from the fork parent now, only + * the child. + */ + wpid = wait(&status); + ATF_REQUIRE(wpid == children[1]); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE(WEXITSTATUS(status) == 2); + + wpid = wait(&status); + ATF_REQUIRE(wpid == -1); + ATF_REQUIRE(errno == ECHILD); +} + 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); + ATF_TP_ADD_TC(tp, ptrace__follow_fork_both_attached); + ATF_TP_ADD_TC(tp, ptrace__follow_fork_child_detached); + ATF_TP_ADD_TC(tp, ptrace__follow_fork_parent_detached); + ATF_TP_ADD_TC(tp, ptrace__follow_fork_both_attached_unrelated_debugger); + ATF_TP_ADD_TC(tp, + ptrace__follow_fork_child_detached_unrelated_debugger); + ATF_TP_ADD_TC(tp, + ptrace__follow_fork_parent_detached_unrelated_debugger); return (atf_no_error()); }
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201509092339.t89NdV7S058424>