summaryrefslogtreecommitdiffstats
path: root/tests/sys/kern
diff options
context:
space:
mode:
authorjhb <jhb@FreeBSD.org>2015-05-26 10:29:37 +0000
committerjhb <jhb@FreeBSD.org>2015-05-26 10:29:37 +0000
commit588ce3236a20b467813e2321265e258c0a84f5a1 (patch)
tree4c92a81554f975aa5b4ca91db4d1703f48316886 /tests/sys/kern
parent328092022899ce08a20da1b885e132ea590269cb (diff)
downloadFreeBSD-src-588ce3236a20b467813e2321265e258c0a84f5a1.zip
FreeBSD-src-588ce3236a20b467813e2321265e258c0a84f5a1.tar.gz
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). Differential Revision: https://reviews.freebsd.org/D2644 Reviewed by: kib MFC after: 2 weeks
Diffstat (limited to 'tests/sys/kern')
-rw-r--r--tests/sys/kern/ptrace_test.c246
1 files changed, 246 insertions, 0 deletions
diff --git a/tests/sys/kern/ptrace_test.c b/tests/sys/kern/ptrace_test.c
index 28b0592..6bd5eeb 100644
--- a/tests/sys/kern/ptrace_test.c
+++ b/tests/sys/kern/ptrace_test.c
@@ -29,6 +29,8 @@ __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>
@@ -133,11 +135,255 @@ ATF_TC_BODY(ptrace__parent_wait_after_attach, tc)
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());
}
OpenPOWER on IntegriCloud