aboutsummaryrefslogtreecommitdiff
path: root/tests/sys/kern/ptrace_test.c
diff options
context:
space:
mode:
authorJohn Baldwin <jhb@FreeBSD.org>2015-05-26 10:29:37 +0000
committerJohn Baldwin <jhb@FreeBSD.org>2015-05-26 10:29:37 +0000
commit57c74f5b64581aa3a7e1369a001a09333e6db8dd (patch)
tree4c92a81554f975aa5b4ca91db4d1703f48316886 /tests/sys/kern/ptrace_test.c
parentb6b2fb599b0e3e606c923e678563d04959a32ad2 (diff)
downloadsrc-57c74f5b64581aa3a7e1369a001a09333e6db8dd.tar.gz
src-57c74f5b64581aa3a7e1369a001a09333e6db8dd.zip
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
Notes
Notes: svn path=/head/; revision=283562
Diffstat (limited to 'tests/sys/kern/ptrace_test.c')
-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 28b059255794..6bd5eebfec38 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());
}