diff options
Diffstat (limited to 'tests/sys/kern/ptrace_test.c')
-rw-r--r-- | tests/sys/kern/ptrace_test.c | 4665 |
1 files changed, 4665 insertions, 0 deletions
diff --git a/tests/sys/kern/ptrace_test.c b/tests/sys/kern/ptrace_test.c new file mode 100644 index 000000000000..fee0bd2ffa38 --- /dev/null +++ b/tests/sys/kern/ptrace_test.c @@ -0,0 +1,4665 @@ +/*- + * Copyright (c) 2015 John Baldwin <jhb@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/cpuset.h> +#include <sys/elf.h> +#include <sys/event.h> +#include <sys/file.h> +#include <sys/mman.h> +#include <sys/time.h> +#include <sys/procctl.h> +#include <sys/procdesc.h> +#include <sys/ptrace.h> +#include <sys/procfs.h> +#include <sys/queue.h> +#include <sys/syscall.h> +#include <sys/sysctl.h> +#include <sys/user.h> +#include <sys/wait.h> +#include <errno.h> +#include <machine/cpufunc.h> +#include <pthread.h> +#include <sched.h> +#include <semaphore.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <atf-c.h> + +/* + * Architectures with a user-visible breakpoint(). + */ +#if defined(__aarch64__) || defined(__amd64__) || defined(__arm__) || \ + defined(__i386__) || defined(__riscv) +#define HAVE_BREAKPOINT +#endif + +/* + * Adjust PC to skip over a breakpoint when stopped for a breakpoint trap. + */ +#ifdef HAVE_BREAKPOINT +#if defined(__aarch64__) +#define SKIP_BREAK(reg) ((reg)->elr += 4) +#elif defined(__amd64__) || defined(__i386__) +#define SKIP_BREAK(reg) +#elif defined(__arm__) +#define SKIP_BREAK(reg) ((reg)->r_pc += 4) +#elif defined(__riscv) +#define SKIP_BREAK(reg) ((reg)->sepc += 4) +#endif +#endif + +/* + * 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\n"); \ +} while (0) + +#define CHILD_REQUIRE_EQ(actual, expected) do { \ + __typeof__(expected) _e = expected; \ + __typeof__(actual) _a = actual; \ + if (_e != _a) \ + child_fail_require(__FILE__, __LINE__, #actual \ + " (%jd) == " #expected " (%jd) not met\n", \ + (intmax_t)_a, (intmax_t)_e); \ +} while (0) + +static __dead2 void +child_fail_require(const char *file, int line, const char *fmt, ...) +{ + va_list ap; + char buf[1024]; + + /* Use write() not fprintf() to avoid possible duplicate output. */ + snprintf(buf, sizeof(buf), "%s:%d: ", file, line); + write(STDERR_FILENO, buf, strlen(buf)); + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + write(STDERR_FILENO, buf, strlen(buf)); + va_end(ap); + + _exit(32); +} + +#define REQUIRE_EQ(actual, expected) do { \ + __typeof__(expected) _e = expected; \ + __typeof__(actual) _a = actual; \ + ATF_REQUIRE_MSG(_e == _a, #actual " (%jd) == " \ + #expected " (%jd) not met", (intmax_t)_a, (intmax_t)_e); \ +} while (0) + +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; + + REQUIRE_EQ(ptrace(PT_ATTACH, pid, NULL, 0), 0); + + wpid = waitpid(pid, &status, 0); + REQUIRE_EQ(wpid, pid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(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. + * + * Prior to r325719, the kern.proc.pid.<pid> sysctl failed + * with ESRCH. After that change, a valid struct kinfo_proc + * is returned for zombies with ki_stat set to SZOMB. + */ + 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) { + REQUIRE_EQ(errno, ESRCH); + break; + } + if (kp.ki_stat == SZOMB) + break; + usleep(5000); + } +} + +/* + * Verify that a parent debugger process "sees" the exit of a debugged + * process exactly once when attached via PT_TRACE_ME. + */ +ATF_TC_WITHOUT_HEAD(ptrace__parent_wait_after_trace_me); +ATF_TC_BODY(ptrace__parent_wait_after_trace_me, tc) +{ + pid_t child, wpid; + int status; + + ATF_REQUIRE((child = fork()) != -1); + if (child == 0) { + /* Child process. */ + trace_me(); + + _exit(1); + } + + /* Parent process. */ + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + /* The second wait() should report the exit status. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + /* The child should no longer exist. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that a parent debugger process "sees" the exit of a debugged + * process exactly once when attached via PT_ATTACH. + */ +ATF_TC_WITHOUT_HEAD(ptrace__parent_wait_after_attach); +ATF_TC_BODY(ptrace__parent_wait_after_attach, tc) +{ + pid_t child, wpid; + int cpipe[2], status; + char c; + + REQUIRE_EQ(pipe(cpipe), 0); + ATF_REQUIRE((child = fork()) != -1); + if (child == 0) { + /* Child process. */ + close(cpipe[0]); + + /* Wait for the parent to attach. */ + CHILD_REQUIRE_EQ(0, read(cpipe[1], &c, sizeof(c))); + + _exit(1); + } + close(cpipe[1]); + + /* Parent process. */ + + /* Attach to the child process. */ + attach_child(child); + + /* Continue the child ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + /* Signal the child to exit. */ + close(cpipe[0]); + + /* The second wait() should report the exit status. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + /* The child should no longer exist. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(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; + + REQUIRE_EQ(pipe(cpipe), 0); + ATF_REQUIRE((child = fork()) != -1); + + if (child == 0) { + /* Child process. */ + close(cpipe[0]); + + /* Wait for parent to be ready. */ + CHILD_REQUIRE_EQ(read(cpipe[1], &c, sizeof(c)), + (ssize_t)sizeof(c)); + + _exit(1); + } + close(cpipe[1]); + + REQUIRE_EQ(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_EQ(wpid, child); + CHILD_REQUIRE(WIFSTOPPED(status)); + CHILD_REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + CHILD_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + /* Signal parent that debugger is attached. */ + CHILD_REQUIRE_EQ(write(dpipe[1], &c, sizeof(c)), + (ssize_t)sizeof(c)); + + /* Wait for parent's failed wait. */ + CHILD_REQUIRE_EQ(read(dpipe[1], &c, sizeof(c)), 0); + + wpid = waitpid(child, &status, 0); + CHILD_REQUIRE_EQ(wpid, child); + CHILD_REQUIRE(WIFEXITED(status)); + CHILD_REQUIRE_EQ(WEXITSTATUS(status), 1); + + _exit(0); + } + close(dpipe[1]); + + /* Parent process. */ + + /* Wait for the debugger to attach to the child. */ + REQUIRE_EQ(read(dpipe[0], &c, sizeof(c)), (ssize_t)sizeof(c)); + + /* Release the child. */ + REQUIRE_EQ(write(cpipe[0], &c, sizeof(c)), (ssize_t)sizeof(c)); + REQUIRE_EQ(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); + REQUIRE_EQ(wpid, 0); + + /* Signal the debugger to wait for the child. */ + close(dpipe[0]); + + /* Wait for the debugger. */ + wpid = waitpid(debugger, &status, 0); + REQUIRE_EQ(wpid, debugger); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + /* The child process should now be ready. */ + wpid = waitpid(child, &status, WNOHANG); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(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; + + REQUIRE_EQ(pipe(cpipe), 0); + ATF_REQUIRE((child = fork()) != -1); + + if (child == 0) { + /* Child process. */ + close(cpipe[0]); + + /* Wait for parent to be ready. */ + CHILD_REQUIRE_EQ(read(cpipe[1], &c, sizeof(c)), + (ssize_t)sizeof(c)); + + _exit(1); + } + close(cpipe[1]); + + REQUIRE_EQ(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_EQ(wpid, child); + CHILD_REQUIRE(WIFSTOPPED(status)); + CHILD_REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + CHILD_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + /* Signal parent that debugger is attached. */ + CHILD_REQUIRE_EQ(write(dpipe[1], &c, sizeof(c)), + (ssize_t)sizeof(c)); + + /* Wait for parent's failed wait. */ + CHILD_REQUIRE_EQ(read(dpipe[1], &c, sizeof(c)), + (ssize_t)sizeof(c)); + + wpid = waitpid(child, &status, 0); + CHILD_REQUIRE_EQ(wpid, child); + CHILD_REQUIRE(WIFEXITED(status)); + CHILD_REQUIRE_EQ(WEXITSTATUS(status), 1); + + _exit(0); + } + close(dpipe[1]); + + /* Parent process. */ + + /* Wait for the debugger parent process to exit. */ + wpid = waitpid(debugger, &status, 0); + REQUIRE_EQ(wpid, debugger); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + + /* A WNOHANG wait here should see the non-exited child. */ + wpid = waitpid(child, &status, WNOHANG); + REQUIRE_EQ(wpid, 0); + + /* Wait for the debugger to attach to the child. */ + REQUIRE_EQ(read(dpipe[0], &c, sizeof(c)), (ssize_t)sizeof(c)); + + /* Release the child. */ + REQUIRE_EQ(write(cpipe[0], &c, sizeof(c)), (ssize_t)sizeof(c)); + REQUIRE_EQ(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); + REQUIRE_EQ(wpid, 0); + + /* Signal the debugger to wait for the child. */ + REQUIRE_EQ(write(dpipe[0], &c, sizeof(c)), (ssize_t)sizeof(c)); + + /* Wait for the debugger. */ + REQUIRE_EQ(read(dpipe[0], &c, sizeof(c)), 0); + close(dpipe[0]); + + /* The child process should now be ready. */ + wpid = waitpid(child, &status, WNOHANG); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); +} + +/* + * Make sure that we can collect the exit status of an orphaned process. + */ +ATF_TC_WITHOUT_HEAD(ptrace__parent_exits_before_child); +ATF_TC_BODY(ptrace__parent_exits_before_child, tc) +{ + ssize_t n; + int cpipe1[2], cpipe2[2], gcpipe[2], status; + pid_t child, gchild; + + REQUIRE_EQ(pipe(cpipe1), 0); + REQUIRE_EQ(pipe(cpipe2), 0); + REQUIRE_EQ(pipe(gcpipe), 0); + + REQUIRE_EQ(procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL), 0); + + ATF_REQUIRE((child = fork()) != -1); + if (child == 0) { + CHILD_REQUIRE((gchild = fork()) != -1); + if (gchild == 0) { + status = 1; + do { + n = read(gcpipe[0], &status, sizeof(status)); + } while (n == -1 && errno == EINTR); + _exit(status); + } + + CHILD_REQUIRE_EQ(write(cpipe1[1], &gchild, sizeof(gchild)), + (ssize_t)sizeof(gchild)); + CHILD_REQUIRE_EQ(read(cpipe2[0], &status, sizeof(status)), + (ssize_t)sizeof(status)); + _exit(status); + } + + REQUIRE_EQ(read(cpipe1[0], &gchild, sizeof(gchild)), + (ssize_t)sizeof(gchild)); + + REQUIRE_EQ(ptrace(PT_ATTACH, gchild, NULL, 0), 0); + + status = 0; + REQUIRE_EQ(write(cpipe2[1], &status, sizeof(status)), + (ssize_t)sizeof(status)); + REQUIRE_EQ(waitpid(child, &status, 0), child); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + status = 0; + REQUIRE_EQ(write(gcpipe[1], &status, sizeof(status)), + (ssize_t)sizeof(status)); + REQUIRE_EQ(waitpid(gchild, &status, 0), gchild); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(ptrace(PT_DETACH, gchild, (caddr_t)1, 0), 0); + REQUIRE_EQ(waitpid(gchild, &status, 0), gchild); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + REQUIRE_EQ(close(cpipe1[0]), 0); + REQUIRE_EQ(close(cpipe1[1]), 0); + REQUIRE_EQ(close(cpipe2[0]), 0); + REQUIRE_EQ(close(cpipe2[1]), 0); + REQUIRE_EQ(close(gcpipe[0]), 0); + REQUIRE_EQ(close(gcpipe[1]), 0); +} + +/* + * The parent process should always act the same regardless of how the + * debugger is attached to it. + */ +static __dead2 void +follow_fork_parent(bool use_vfork) +{ + pid_t fpid, wpid; + int status; + + if (use_vfork) + CHILD_REQUIRE((fpid = vfork()) != -1); + else + CHILD_REQUIRE((fpid = fork()) != -1); + + if (fpid == 0) + /* Child */ + _exit(2); + + wpid = waitpid(fpid, &status, 0); + CHILD_REQUIRE_EQ(wpid, fpid); + CHILD_REQUIRE(WIFEXITED(status)); + CHILD_REQUIRE_EQ(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 *ppl) +{ + 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); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + ATF_REQUIRE(!fork_reported[1]); + if (child == -1) + child = wpid; + else + REQUIRE_EQ(child, wpid); + if (ppl != NULL) + ppl[1] = pl; + fork_reported[1] = true; + } else { + REQUIRE_EQ(wpid, parent); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + ATF_REQUIRE(!fork_reported[0]); + if (child == -1) + child = pl.pl_child_pid; + else + REQUIRE_EQ(child, pl.pl_child_pid); + if (ppl != NULL) + ppl[0] = pl; + 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[2], fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(false); + } + + /* Parent process. */ + children[0] = fpid; + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(children[0], &status, 0); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(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], NULL); + 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); + REQUIRE_EQ(wpid, children[1]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + + wpid = wait(&status); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(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[2], fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(false); + } + + /* Parent process. */ + children[0] = fpid; + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(children[0], &status, 0); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(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], NULL); + 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); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(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[2], fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(false); + } + + /* Parent process. */ + children[0] = fpid; + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(children[0], &status, 0); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(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], NULL); + 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); + REQUIRE_EQ(wpid, children[1]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + + wpid = wait(&status); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(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_EQ(write(cpipe[1], &fpid, sizeof(fpid)), + (ssize_t)sizeof(fpid)); + + /* Wait for the debugger to attach. */ + CHILD_REQUIRE_EQ(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[2], fpid, wpid; + int cpipe[2], status; + + REQUIRE_EQ(pipe(cpipe), 0); + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + attach_fork_parent(cpipe); + follow_fork_parent(false); + } + + /* Parent process. */ + close(cpipe[1]); + + /* Wait for the direct child to exit. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 3); + + /* Read the pid of the fork parent. */ + REQUIRE_EQ(read(cpipe[0], &children[0], sizeof(children[0])), + (ssize_t)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], NULL); + 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); + REQUIRE_EQ(wpid, children[1]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + + wpid = wait(&status); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(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[2], fpid, wpid; + int cpipe[2], status; + + REQUIRE_EQ(pipe(cpipe), 0); + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + attach_fork_parent(cpipe); + follow_fork_parent(false); + } + + /* Parent process. */ + close(cpipe[1]); + + /* Wait for the direct child to exit. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 3); + + /* Read the pid of the fork parent. */ + REQUIRE_EQ(read(cpipe[0], &children[0], sizeof(children[0])), + (ssize_t)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], NULL); + 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); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(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[2], fpid, wpid; + int cpipe[2], status; + + REQUIRE_EQ(pipe(cpipe), 0); + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + attach_fork_parent(cpipe); + follow_fork_parent(false); + } + + /* Parent process. */ + close(cpipe[1]); + + /* Wait for the direct child to exit. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 3); + + /* Read the pid of the fork parent. */ + REQUIRE_EQ(read(cpipe[0], &children[0], sizeof(children[0])), + (ssize_t)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], NULL); + 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); + REQUIRE_EQ(wpid, children[1]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that a child process does not see an unrelated debugger as its + * parent but sees its original parent process. + */ +ATF_TC_WITHOUT_HEAD(ptrace__getppid); +ATF_TC_BODY(ptrace__getppid, tc) +{ + pid_t child, debugger, ppid, wpid; + int cpipe[2], dpipe[2], status; + char c; + + REQUIRE_EQ(pipe(cpipe), 0); + ATF_REQUIRE((child = fork()) != -1); + + if (child == 0) { + /* Child process. */ + close(cpipe[0]); + + /* Wait for parent to be ready. */ + CHILD_REQUIRE_EQ(read(cpipe[1], &c, sizeof(c)), + (ssize_t)sizeof(c)); + + /* Report the parent PID to the parent. */ + ppid = getppid(); + CHILD_REQUIRE_EQ(write(cpipe[1], &ppid, sizeof(ppid)), + (ssize_t)sizeof(ppid)); + + _exit(1); + } + close(cpipe[1]); + + REQUIRE_EQ(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_EQ(wpid, child); + CHILD_REQUIRE(WIFSTOPPED(status)); + CHILD_REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + CHILD_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + /* Signal parent that debugger is attached. */ + CHILD_REQUIRE_EQ(write(dpipe[1], &c, sizeof(c)), + (ssize_t)sizeof(c)); + + /* Wait for traced child to exit. */ + wpid = waitpid(child, &status, 0); + CHILD_REQUIRE_EQ(wpid, child); + CHILD_REQUIRE(WIFEXITED(status)); + CHILD_REQUIRE_EQ(WEXITSTATUS(status), 1); + + _exit(0); + } + close(dpipe[1]); + + /* Parent process. */ + + /* Wait for the debugger to attach to the child. */ + REQUIRE_EQ(read(dpipe[0], &c, sizeof(c)), (ssize_t)sizeof(c)); + + /* Release the child. */ + REQUIRE_EQ(write(cpipe[0], &c, sizeof(c)), (ssize_t)sizeof(c)); + + /* Read the parent PID from the child. */ + REQUIRE_EQ(read(cpipe[0], &ppid, sizeof(ppid)), (ssize_t)sizeof(ppid)); + close(cpipe[0]); + + REQUIRE_EQ(ppid, getpid()); + + /* Wait for the debugger. */ + wpid = waitpid(debugger, &status, 0); + REQUIRE_EQ(wpid, debugger); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + /* The child process should now be ready. */ + wpid = waitpid(child, &status, WNOHANG); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); +} + +/* + * Verify that pl_syscall_code in struct ptrace_lwpinfo for a new + * child process created via fork() reports the correct value. + */ +ATF_TC_WITHOUT_HEAD(ptrace__new_child_pl_syscall_code_fork); +ATF_TC_BODY(ptrace__new_child_pl_syscall_code_fork, tc) +{ + struct ptrace_lwpinfo pl[2]; + pid_t children[2], fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(false); + } + + /* Parent process. */ + children[0] = fpid; + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(children[0], &status, 0); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(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); + + /* Wait for both halves of the fork event to get reported. */ + children[1] = handle_fork_events(children[0], pl); + ATF_REQUIRE(children[1] > 0); + + ATF_REQUIRE((pl[0].pl_flags & PL_FLAG_SCX) != 0); + ATF_REQUIRE((pl[1].pl_flags & PL_FLAG_SCX) != 0); + REQUIRE_EQ(pl[0].pl_syscall_code, (unsigned)SYS_fork); + REQUIRE_EQ(pl[0].pl_syscall_code, pl[1].pl_syscall_code); + REQUIRE_EQ(pl[0].pl_syscall_narg, pl[1].pl_syscall_narg); + + 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); + REQUIRE_EQ(wpid, children[1]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + + wpid = wait(&status); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that pl_syscall_code in struct ptrace_lwpinfo for a new + * child process created via vfork() reports the correct value. + */ +ATF_TC_WITHOUT_HEAD(ptrace__new_child_pl_syscall_code_vfork); +ATF_TC_BODY(ptrace__new_child_pl_syscall_code_vfork, tc) +{ + struct ptrace_lwpinfo pl[2]; + pid_t children[2], fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(true); + } + + /* Parent process. */ + children[0] = fpid; + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(children[0], &status, 0); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(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); + + /* Wait for both halves of the fork event to get reported. */ + children[1] = handle_fork_events(children[0], pl); + ATF_REQUIRE(children[1] > 0); + + ATF_REQUIRE((pl[0].pl_flags & PL_FLAG_SCX) != 0); + ATF_REQUIRE((pl[1].pl_flags & PL_FLAG_SCX) != 0); + REQUIRE_EQ(pl[0].pl_syscall_code, (unsigned)SYS_vfork); + REQUIRE_EQ(pl[0].pl_syscall_code, pl[1].pl_syscall_code); + REQUIRE_EQ(pl[0].pl_syscall_narg, pl[1].pl_syscall_narg); + + 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); + REQUIRE_EQ(wpid, children[1]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + + wpid = wait(&status); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +static void * +simple_thread(void *arg __unused) +{ + + pthread_exit(NULL); +} + +static __dead2 void +simple_thread_main(void) +{ + pthread_t thread; + + CHILD_REQUIRE_EQ(pthread_create(&thread, NULL, simple_thread, NULL), 0); + CHILD_REQUIRE_EQ(pthread_join(thread, NULL), 0); + exit(1); +} + +/* + * Verify that pl_syscall_code in struct ptrace_lwpinfo for a new + * thread reports the correct value. + */ +ATF_TC_WITHOUT_HEAD(ptrace__new_child_pl_syscall_code_thread); +ATF_TC_BODY(ptrace__new_child_pl_syscall_code_thread, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + lwpid_t mainlwp; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + simple_thread_main(); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + mainlwp = pl.pl_lwpid; + + /* + * Continue the child ignoring the SIGSTOP and tracing all + * system call exits. + */ + ATF_REQUIRE(ptrace(PT_TO_SCX, fpid, (caddr_t)1, 0) != -1); + + /* + * Wait for the new thread to arrive. pthread_create() might + * invoke any number of system calls. For now we just wait + * for the new thread to arrive and make sure it reports a + * valid system call code. If ptrace grows thread event + * reporting then this test can be made more precise. + */ + for (;;) { + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + ATF_REQUIRE((pl.pl_flags & PL_FLAG_SCX) != 0); + ATF_REQUIRE(pl.pl_syscall_code != 0); + if (pl.pl_lwpid != mainlwp) + /* New thread seen. */ + break; + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + } + + /* Wait for the child to exit. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + for (;;) { + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + if (WIFEXITED(status)) + break; + + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + } + + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that the expected LWP events are reported for a child thread. + */ +ATF_TC_WITHOUT_HEAD(ptrace__lwp_events); +ATF_TC_BODY(ptrace__lwp_events, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + lwpid_t lwps[2]; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + simple_thread_main(); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + lwps[0] = pl.pl_lwpid; + + REQUIRE_EQ(ptrace(PT_LWP_EVENTS, wpid, NULL, 1), 0); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The first event should be for the child thread's birth. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ((pl.pl_flags & (PL_FLAG_BORN | PL_FLAG_SCX)), + (PL_FLAG_BORN | PL_FLAG_SCX)); + ATF_REQUIRE(pl.pl_lwpid != lwps[0]); + lwps[1] = pl.pl_lwpid; + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next event should be for the child thread's death. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ((pl.pl_flags & (PL_FLAG_EXITED | PL_FLAG_SCE)), + (PL_FLAG_EXITED | PL_FLAG_SCE)); + REQUIRE_EQ(pl.pl_lwpid, lwps[1]); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last event should be for the child process's exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +static void * +exec_thread(void *arg __unused) +{ + + execl("/usr/bin/true", "true", NULL); + exit(127); +} + +static __dead2 void +exec_thread_main(void) +{ + pthread_t thread; + + CHILD_REQUIRE_EQ(pthread_create(&thread, NULL, exec_thread, NULL), 0); + for (;;) + sleep(60); + exit(1); +} + +/* + * Verify that the expected LWP events are reported for a multithreaded + * process that calls execve(2). + */ +ATF_TC_WITHOUT_HEAD(ptrace__lwp_events_exec); +ATF_TC_BODY(ptrace__lwp_events_exec, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + lwpid_t lwps[2]; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + exec_thread_main(); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + lwps[0] = pl.pl_lwpid; + + REQUIRE_EQ(ptrace(PT_LWP_EVENTS, wpid, NULL, 1), 0); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The first event should be for the child thread's birth. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ((pl.pl_flags & (PL_FLAG_BORN | PL_FLAG_SCX)), + (PL_FLAG_BORN | PL_FLAG_SCX)); + ATF_REQUIRE(pl.pl_lwpid != lwps[0]); + lwps[1] = pl.pl_lwpid; + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* + * The next event should be for the main thread's death due to + * single threading from execve(). + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ((pl.pl_flags & (PL_FLAG_EXITED | PL_FLAG_SCE)), + (PL_FLAG_EXITED)); + REQUIRE_EQ(pl.pl_lwpid, lwps[0]); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next event should be for the child process's exec. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ((pl.pl_flags & (PL_FLAG_EXEC | PL_FLAG_SCX)), + (PL_FLAG_EXEC | PL_FLAG_SCX)); + REQUIRE_EQ(pl.pl_lwpid, lwps[1]); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last event should be for the child process's exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +static void +handler(int sig __unused) +{ +} + +static void +signal_main(void) +{ + + signal(SIGINFO, handler); + raise(SIGINFO); + exit(0); +} + +/* + * Verify that the expected ptrace event is reported for a signal. + */ +ATF_TC_WITHOUT_HEAD(ptrace__siginfo); +ATF_TC_BODY(ptrace__siginfo, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + signal_main(); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next event should be for the SIGINFO. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGINFO); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ(pl.pl_event, PL_EVENT_SIGNAL); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI); + REQUIRE_EQ(pl.pl_siginfo.si_code, SI_LWP); + REQUIRE_EQ(pl.pl_siginfo.si_pid, wpid); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last event should be for the child process's exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that the expected ptrace events are reported for PTRACE_EXEC. + */ +ATF_TC_WITHOUT_HEAD(ptrace__ptrace_exec_disable); +ATF_TC_BODY(ptrace__ptrace_exec_disable, tc) +{ + pid_t fpid, wpid; + int events, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + exec_thread(NULL); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + events = 0; + ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* Should get one event at exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +ATF_TC_WITHOUT_HEAD(ptrace__ptrace_exec_enable); +ATF_TC_BODY(ptrace__ptrace_exec_enable, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int events, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + exec_thread(NULL); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + events = PTRACE_EXEC; + ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next event should be for the child process's exec. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ((pl.pl_flags & (PL_FLAG_EXEC | PL_FLAG_SCX)), + (PL_FLAG_EXEC | PL_FLAG_SCX)); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last event should be for the child process's exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +ATF_TC_WITHOUT_HEAD(ptrace__event_mask); +ATF_TC_BODY(ptrace__event_mask, tc) +{ + pid_t fpid, wpid; + int events, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + exit(0); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* PT_FOLLOW_FORK should toggle the state of PTRACE_FORK. */ + ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, fpid, NULL, 1) != -1); + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + ATF_REQUIRE(events & PTRACE_FORK); + ATF_REQUIRE(ptrace(PT_FOLLOW_FORK, fpid, NULL, 0) != -1); + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + ATF_REQUIRE(!(events & PTRACE_FORK)); + + /* PT_LWP_EVENTS should toggle the state of PTRACE_LWP. */ + ATF_REQUIRE(ptrace(PT_LWP_EVENTS, fpid, NULL, 1) != -1); + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + ATF_REQUIRE(events & PTRACE_LWP); + ATF_REQUIRE(ptrace(PT_LWP_EVENTS, fpid, NULL, 0) != -1); + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + ATF_REQUIRE(!(events & PTRACE_LWP)); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* Should get one event at exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that the expected ptrace events are reported for PTRACE_VFORK. + */ +ATF_TC_WITHOUT_HEAD(ptrace__ptrace_vfork); +ATF_TC_BODY(ptrace__ptrace_vfork, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int events, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(true); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + events |= PTRACE_VFORK; + ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + + /* Continue the child ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) != -1); + + /* The next event should report the end of the vfork. */ + wpid = wait(&status); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE((pl.pl_flags & PL_FLAG_VFORK_DONE) != 0); + + ATF_REQUIRE(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0) != -1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +ATF_TC_WITHOUT_HEAD(ptrace__ptrace_vfork_follow); +ATF_TC_BODY(ptrace__ptrace_vfork_follow, tc) +{ + struct ptrace_lwpinfo pl[2]; + pid_t children[2], fpid, wpid; + int events, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + follow_fork_parent(true); + } + + /* Parent process. */ + children[0] = fpid; + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(children[0], &status, 0); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, children[0], (caddr_t)&events, + sizeof(events)) == 0); + events |= PTRACE_FORK | PTRACE_VFORK; + ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, children[0], (caddr_t)&events, + sizeof(events)) == 0); + + /* Continue the child ignoring the SIGSTOP. */ + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + + /* Wait for both halves of the fork event to get reported. */ + children[1] = handle_fork_events(children[0], pl); + ATF_REQUIRE(children[1] > 0); + + ATF_REQUIRE((pl[0].pl_flags & PL_FLAG_VFORKED) != 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 = waitpid(children[1], &status, 0); + REQUIRE_EQ(wpid, children[1]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + + /* + * The child should report it's vfork() completion before it + * exits. + */ + wpid = wait(&status); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl[0], sizeof(pl[0])) != + -1); + ATF_REQUIRE((pl[0].pl_flags & PL_FLAG_VFORK_DONE) != 0); + + ATF_REQUIRE(ptrace(PT_CONTINUE, children[0], (caddr_t)1, 0) != -1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, children[0]); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +#ifdef HAVE_BREAKPOINT +/* + * Verify that no more events are reported after PT_KILL except for the + * process exit when stopped due to a breakpoint trap. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_breakpoint); +ATF_TC_BODY(ptrace__PT_KILL_breakpoint, tc) +{ + pid_t fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + breakpoint(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The second wait() should report hitting the breakpoint. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + /* Kill the child process. */ + REQUIRE_EQ(ptrace(PT_KILL, fpid, 0, 0), 0); + + /* The last wait() should report the SIGKILL. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSIGNALED(status)); + REQUIRE_EQ(WTERMSIG(status), SIGKILL); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} +#endif /* HAVE_BREAKPOINT */ + +/* + * Verify that no more events are reported after PT_KILL except for the + * process exit when stopped inside of a system call. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_system_call); +ATF_TC_BODY(ptrace__PT_KILL_system_call, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + getpid(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP and tracing system calls. */ + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + /* The second wait() should report a system call entry for getpid(). */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE); + + /* Kill the child process. */ + REQUIRE_EQ(ptrace(PT_KILL, fpid, 0, 0), 0); + + /* The last wait() should report the SIGKILL. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSIGNALED(status)); + REQUIRE_EQ(WTERMSIG(status), SIGKILL); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that no more events are reported after PT_KILL except for the + * process exit when killing a multithreaded process. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_threads); +ATF_TC_BODY(ptrace__PT_KILL_threads, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + lwpid_t main_lwp; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + simple_thread_main(); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + main_lwp = pl.pl_lwpid; + + REQUIRE_EQ(ptrace(PT_LWP_EVENTS, wpid, NULL, 1), 0); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The first event should be for the child thread's birth. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ((pl.pl_flags & (PL_FLAG_BORN | PL_FLAG_SCX)), + (PL_FLAG_BORN | PL_FLAG_SCX)); + ATF_REQUIRE(pl.pl_lwpid != main_lwp); + + /* Kill the child process. */ + REQUIRE_EQ(ptrace(PT_KILL, fpid, 0, 0), 0); + + /* The last wait() should report the SIGKILL. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSIGNALED(status)); + REQUIRE_EQ(WTERMSIG(status), SIGKILL); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +static void * +mask_usr1_thread(void *arg) +{ + pthread_barrier_t *pbarrier; + sigset_t sigmask; + + pbarrier = (pthread_barrier_t*)arg; + + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGUSR1); + CHILD_REQUIRE_EQ(pthread_sigmask(SIG_BLOCK, &sigmask, NULL), 0); + + /* Sync up with other thread after sigmask updated. */ + pthread_barrier_wait(pbarrier); + + for (;;) + sleep(60); + + return (NULL); +} + +/* + * Verify that the SIGKILL from PT_KILL takes priority over other signals + * and prevents spurious stops due to those other signals. + */ +ATF_TC(ptrace__PT_KILL_competing_signal); +ATF_TC_HEAD(ptrace__PT_KILL_competing_signal, tc) +{ + + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(ptrace__PT_KILL_competing_signal, tc) +{ + pid_t fpid, wpid; + int status; + cpuset_t setmask; + pthread_t t; + pthread_barrier_t barrier; + struct sched_param sched_param; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + /* Bind to one CPU so only one thread at a time will run. */ + CPU_ZERO(&setmask); + CPU_SET(0, &setmask); + cpusetid_t setid; + CHILD_REQUIRE_EQ(cpuset(&setid), 0); + CHILD_REQUIRE(cpuset_setaffinity(CPU_LEVEL_CPUSET, + CPU_WHICH_CPUSET, setid, sizeof(setmask), &setmask) == 0); + + CHILD_REQUIRE_EQ(pthread_barrier_init(&barrier, NULL, 2), 0); + + CHILD_REQUIRE(pthread_create(&t, NULL, mask_usr1_thread, + (void*)&barrier) == 0); + + /* + * Give the main thread higher priority. The test always + * assumes that, if both threads are able to run, the main + * thread runs first. + */ + sched_param.sched_priority = + (sched_get_priority_max(SCHED_FIFO) + + sched_get_priority_min(SCHED_FIFO)) / 2; + CHILD_REQUIRE(pthread_setschedparam(pthread_self(), + SCHED_FIFO, &sched_param) == 0); + sched_param.sched_priority -= 1; + CHILD_REQUIRE(pthread_setschedparam(t, SCHED_FIFO, + &sched_param) == 0); + + sigset_t sigmask; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGUSR2); + CHILD_REQUIRE_EQ(pthread_sigmask(SIG_BLOCK, &sigmask, NULL), 0); + + /* Sync up with other thread after sigmask updated. */ + pthread_barrier_wait(&barrier); + + trace_me(); + + for (;;) + sleep(60); + + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* Send a signal that only the second thread can handle. */ + REQUIRE_EQ(kill(fpid, SIGUSR2), 0); + + /* The second wait() should report the SIGUSR2. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGUSR2); + + /* Send a signal that only the first thread can handle. */ + REQUIRE_EQ(kill(fpid, SIGUSR1), 0); + + /* Replace the SIGUSR2 with a kill. */ + REQUIRE_EQ(ptrace(PT_KILL, fpid, 0, 0), 0); + + /* The last wait() should report the SIGKILL (not the SIGUSR signal). */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSIGNALED(status)); + REQUIRE_EQ(WTERMSIG(status), SIGKILL); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that the SIGKILL from PT_KILL takes priority over other stop events + * and prevents spurious stops caused by those events. + */ +ATF_TC(ptrace__PT_KILL_competing_stop); +ATF_TC_HEAD(ptrace__PT_KILL_competing_stop, tc) +{ + + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(ptrace__PT_KILL_competing_stop, tc) +{ + pid_t fpid, wpid; + int status; + cpuset_t setmask; + pthread_t t; + pthread_barrier_t barrier; + lwpid_t main_lwp; + struct ptrace_lwpinfo pl; + struct sched_param sched_param; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + + /* Bind to one CPU so only one thread at a time will run. */ + CPU_ZERO(&setmask); + CPU_SET(0, &setmask); + cpusetid_t setid; + CHILD_REQUIRE_EQ(cpuset(&setid), 0); + CHILD_REQUIRE(cpuset_setaffinity(CPU_LEVEL_CPUSET, + CPU_WHICH_CPUSET, setid, sizeof(setmask), &setmask) == 0); + + CHILD_REQUIRE_EQ(pthread_barrier_init(&barrier, NULL, 2), 0); + + CHILD_REQUIRE(pthread_create(&t, NULL, mask_usr1_thread, + (void*)&barrier) == 0); + + /* + * Give the main thread higher priority. The test always + * assumes that, if both threads are able to run, the main + * thread runs first. + */ + sched_param.sched_priority = + (sched_get_priority_max(SCHED_FIFO) + + sched_get_priority_min(SCHED_FIFO)) / 2; + CHILD_REQUIRE(pthread_setschedparam(pthread_self(), + SCHED_FIFO, &sched_param) == 0); + sched_param.sched_priority -= 1; + CHILD_REQUIRE(pthread_setschedparam(t, SCHED_FIFO, + &sched_param) == 0); + + sigset_t sigmask; + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGUSR2); + CHILD_REQUIRE_EQ(pthread_sigmask(SIG_BLOCK, &sigmask, NULL), 0); + + /* Sync up with other thread after sigmask updated. */ + pthread_barrier_wait(&barrier); + + /* Sync up with the test before doing the getpid(). */ + raise(SIGSTOP); + + getpid(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + main_lwp = pl.pl_lwpid; + + /* Continue the child ignoring the SIGSTOP and tracing system calls. */ + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + /* + * Continue until child is done with setup, which is indicated with + * SIGSTOP. Ignore system calls in the meantime. + */ + for (;;) { + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + if (WSTOPSIG(status) == SIGTRAP) { + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX)); + } else { + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + break; + } + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + } + + /* Proceed, allowing main thread to hit syscall entry for getpid(). */ + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + REQUIRE_EQ(pl.pl_lwpid, main_lwp); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE); + /* Prevent the main thread from hitting its syscall exit for now. */ + REQUIRE_EQ(ptrace(PT_SUSPEND, main_lwp, 0, 0), 0); + + /* + * Proceed, allowing second thread to hit syscall exit for + * pthread_barrier_wait(). + */ + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_lwpid != main_lwp); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCX); + + /* Send a signal that only the second thread can handle. */ + REQUIRE_EQ(kill(fpid, SIGUSR2), 0); + + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + /* The next wait() should report the SIGUSR2. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGUSR2); + + /* Allow the main thread to try to finish its system call. */ + REQUIRE_EQ(ptrace(PT_RESUME, main_lwp, 0, 0), 0); + + /* + * At this point, the main thread is in the middle of a system call and + * has been resumed. The second thread has taken a SIGUSR2 which will + * be replaced with a SIGKILL below. The main thread will get to run + * first. It should notice the kill request (even though the signal + * replacement occurred in the other thread) and exit accordingly. It + * should not stop for the system call exit event. + */ + + /* Replace the SIGUSR2 with a kill. */ + REQUIRE_EQ(ptrace(PT_KILL, fpid, 0, 0), 0); + + /* The last wait() should report the SIGKILL (not a syscall exit). */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSIGNALED(status)); + REQUIRE_EQ(WTERMSIG(status), SIGKILL); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +static void +sigusr1_handler(int sig) +{ + + CHILD_REQUIRE_EQ(sig, SIGUSR1); + _exit(2); +} + +/* + * Verify that even if the signal queue is full for a child process, + * a PT_KILL will kill the process. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_KILL_with_signal_full_sigqueue); +ATF_TC_BODY(ptrace__PT_KILL_with_signal_full_sigqueue, tc) +{ + pid_t fpid, wpid; + int status; + int max_pending_per_proc; + size_t len; + int i; + + ATF_REQUIRE(signal(SIGUSR1, sigusr1_handler) != SIG_ERR); + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + len = sizeof(max_pending_per_proc); + ATF_REQUIRE(sysctlbyname("kern.sigqueue.max_pending_per_proc", + &max_pending_per_proc, &len, NULL, 0) == 0); + + /* Fill the signal queue. */ + for (i = 0; i < max_pending_per_proc; ++i) + REQUIRE_EQ(kill(fpid, SIGUSR1), 0); + + /* Kill the child process. */ + REQUIRE_EQ(ptrace(PT_KILL, fpid, 0, 0), 0); + + /* The last wait() should report the SIGKILL. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSIGNALED(status)); + REQUIRE_EQ(WTERMSIG(status), SIGKILL); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that when stopped at a system call entry, a signal can be + * requested with PT_CONTINUE which will be delivered once the system + * call is complete. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_system_call_entry); +ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_system_call_entry, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE(signal(SIGUSR1, sigusr1_handler) != SIG_ERR); + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + getpid(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP and tracing system calls. */ + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + /* The second wait() should report a system call entry for getpid(). */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE); + + /* Continue the child process with a signal. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + for (;;) { + /* + * The last wait() should report exit 2, i.e., a normal _exit + * from the signal handler. In the meantime, catch and proceed + * past any syscall stops. + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX)); + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + } else { + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + break; + } + } + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +static void +sigusr1_counting_handler(int sig) +{ + static int counter = 0; + + CHILD_REQUIRE_EQ(sig, SIGUSR1); + counter++; + if (counter == 2) + _exit(2); +} + +/* + * Verify that, when continuing from a stop at system call entry and exit, + * a signal can be requested from both stops, and both will be delivered when + * the system call is complete. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_system_call_entry_and_exit); +ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_system_call_entry_and_exit, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE(signal(SIGUSR1, sigusr1_counting_handler) != SIG_ERR); + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + getpid(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP and tracing system calls. */ + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + /* The second wait() should report a system call entry for getpid(). */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE); + + /* Continue the child process with a signal. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + /* The third wait() should report a system call exit for getpid(). */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCX); + + /* Continue the child process with a signal. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + for (;;) { + /* + * The last wait() should report exit 2, i.e., a normal _exit + * from the signal handler. In the meantime, catch and proceed + * past any syscall stops. + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX)); + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + } else { + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + break; + } + } + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that even if the signal queue is full for a child process, + * a PT_CONTINUE with a signal will not result in loss of that signal. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_full_sigqueue); +ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_full_sigqueue, tc) +{ + pid_t fpid, wpid; + int status; + int max_pending_per_proc; + size_t len; + int i; + + ATF_REQUIRE(signal(SIGUSR2, handler) != SIG_ERR); + ATF_REQUIRE(signal(SIGUSR1, sigusr1_handler) != SIG_ERR); + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + len = sizeof(max_pending_per_proc); + ATF_REQUIRE(sysctlbyname("kern.sigqueue.max_pending_per_proc", + &max_pending_per_proc, &len, NULL, 0) == 0); + + /* Fill the signal queue. */ + for (i = 0; i < max_pending_per_proc; ++i) + REQUIRE_EQ(kill(fpid, SIGUSR2), 0); + + /* Continue with signal. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + for (;;) { + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + if (WIFSTOPPED(status)) { + REQUIRE_EQ(WSTOPSIG(status), SIGUSR2); + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + } else { + /* + * The last wait() should report normal _exit from the + * SIGUSR1 handler. + */ + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + break; + } + } + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +static sem_t sigusr1_sem; +static int got_usr1; + +static void +sigusr1_sempost_handler(int sig __unused) +{ + + got_usr1++; + CHILD_REQUIRE_EQ(sem_post(&sigusr1_sem), 0); +} + +/* + * Verify that even if the signal queue is full for a child process, + * and the signal is masked, a PT_CONTINUE with a signal will not + * result in loss of that signal. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_masked_full_sigqueue); +ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_masked_full_sigqueue, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status, err; + int max_pending_per_proc; + size_t len; + int i; + sigset_t sigmask; + + ATF_REQUIRE(signal(SIGUSR2, handler) != SIG_ERR); + REQUIRE_EQ(sem_init(&sigusr1_sem, 0, 0), 0); + ATF_REQUIRE(signal(SIGUSR1, sigusr1_sempost_handler) != SIG_ERR); + + got_usr1 = 0; + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + CHILD_REQUIRE_EQ(sigemptyset(&sigmask), 0); + CHILD_REQUIRE_EQ(sigaddset(&sigmask, SIGUSR1), 0); + CHILD_REQUIRE_EQ(sigprocmask(SIG_BLOCK, &sigmask, NULL), 0); + + trace_me(); + CHILD_REQUIRE_EQ(got_usr1, 0); + + /* Allow the pending SIGUSR1 in now. */ + CHILD_REQUIRE_EQ(sigprocmask(SIG_UNBLOCK, &sigmask, NULL), 0); + /* Wait to receive the SIGUSR1. */ + do { + err = sem_wait(&sigusr1_sem); + CHILD_REQUIRE(err == 0 || errno == EINTR); + } while (err != 0 && errno == EINTR); + CHILD_REQUIRE_EQ(got_usr1, 1); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + len = sizeof(max_pending_per_proc); + ATF_REQUIRE(sysctlbyname("kern.sigqueue.max_pending_per_proc", + &max_pending_per_proc, &len, NULL, 0) == 0); + + /* Fill the signal queue. */ + for (i = 0; i < max_pending_per_proc; ++i) + REQUIRE_EQ(kill(fpid, SIGUSR2), 0); + + /* Continue with signal. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + /* Collect and ignore all of the SIGUSR2. */ + for (i = 0; i < max_pending_per_proc; ++i) { + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGUSR2); + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + } + + /* Now our PT_CONTINUE'd SIGUSR1 should cause a stop after unmask. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGUSR1); + ATF_REQUIRE(ptrace(PT_LWPINFO, fpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGUSR1); + + /* Continue the child, ignoring the SIGUSR1. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last wait() should report exit after receiving SIGUSR1. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that, after stopping due to a signal, that signal can be + * replaced with another signal. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_change_sig); +ATF_TC_BODY(ptrace__PT_CONTINUE_change_sig, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + sleep(20); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* Send a signal without ptrace. */ + REQUIRE_EQ(kill(fpid, SIGINT), 0); + + /* The second wait() should report a SIGINT was received. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGINT); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGINT); + + /* Continue the child process with a different signal. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGTERM), 0); + + /* + * The last wait() should report having died due to the new + * signal, SIGTERM. + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSIGNALED(status)); + REQUIRE_EQ(WTERMSIG(status), SIGTERM); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that a signal can be passed through to the child even when there + * was no true signal originally. Such cases arise when a SIGTRAP is + * invented for e.g, system call stops. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_sigtrap_system_call_entry); +ATF_TC_BODY(ptrace__PT_CONTINUE_with_sigtrap_system_call_entry, tc) +{ + struct ptrace_lwpinfo pl; + struct rlimit rl; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + /* SIGTRAP expected to cause exit on syscall entry. */ + rl.rlim_cur = rl.rlim_max = 0; + REQUIRE_EQ(setrlimit(RLIMIT_CORE, &rl), 0); + getpid(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP and tracing system calls. */ + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + /* The second wait() should report a system call entry for getpid(). */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE); + + /* Continue the child process with a SIGTRAP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGTRAP), 0); + + for (;;) { + /* + * The last wait() should report exit due to SIGTRAP. In the + * meantime, catch and proceed past any syscall stops. + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX)); + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + } else { + ATF_REQUIRE(WIFSIGNALED(status)); + REQUIRE_EQ(WTERMSIG(status), SIGTRAP); + break; + } + } + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * A mixed bag PT_CONTINUE with signal test. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_mix); +ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_mix, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE(signal(SIGUSR1, sigusr1_counting_handler) != SIG_ERR); + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + getpid(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP and tracing system calls. */ + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + /* The second wait() should report a system call entry for getpid(). */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE); + + /* Continue with the first SIGUSR1. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + /* The next wait() should report a system call exit for getpid(). */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCX); + + /* Send an ABRT without ptrace. */ + REQUIRE_EQ(kill(fpid, SIGABRT), 0); + + /* Continue normally. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next wait() should report the SIGABRT. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGABRT); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGABRT); + + /* Continue, replacing the SIGABRT with another SIGUSR1. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + for (;;) { + /* + * The last wait() should report exit 2, i.e., a normal _exit + * from the signal handler. In the meantime, catch and proceed + * past any syscall stops. + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & (PL_FLAG_SCE | PL_FLAG_SCX)); + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + } else { + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 2); + break; + } + } + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify a signal delivered by ptrace is noticed by kevent(2). + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_kqueue); +ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_kqueue, tc) +{ + pid_t fpid, wpid; + int status, kq, nevents; + struct kevent kev; + + ATF_REQUIRE(signal(SIGUSR1, SIG_IGN) != SIG_ERR); + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + CHILD_REQUIRE((kq = kqueue()) > 0); + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, 0); + CHILD_REQUIRE_EQ(kevent(kq, &kev, 1, NULL, 0, NULL), 0); + + trace_me(); + + for (;;) { + nevents = kevent(kq, NULL, 0, &kev, 1, NULL); + if (nevents == -1 && errno == EINTR) + continue; + CHILD_REQUIRE(nevents > 0); + CHILD_REQUIRE_EQ(kev.filter, EVFILT_SIGNAL); + CHILD_REQUIRE_EQ(kev.ident, (uintptr_t)SIGUSR1); + break; + } + + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue with the SIGUSR1. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + /* + * The last wait() should report normal exit with code 1. + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +static void * +signal_thread(void *arg) +{ + int err; + sigset_t sigmask; + + pthread_barrier_t *pbarrier = (pthread_barrier_t*)arg; + + /* Wait for this thread to receive a SIGUSR1. */ + do { + err = sem_wait(&sigusr1_sem); + CHILD_REQUIRE(err == 0 || errno == EINTR); + } while (err != 0 && errno == EINTR); + + /* Free our companion thread from the barrier. */ + pthread_barrier_wait(pbarrier); + + /* + * Swap ignore duties; the next SIGUSR1 should go to the + * other thread. + */ + CHILD_REQUIRE_EQ(sigemptyset(&sigmask), 0); + CHILD_REQUIRE_EQ(sigaddset(&sigmask, SIGUSR1), 0); + CHILD_REQUIRE_EQ(pthread_sigmask(SIG_BLOCK, &sigmask, NULL), 0); + + /* Sync up threads after swapping signal masks. */ + pthread_barrier_wait(pbarrier); + + /* Wait until our companion has received its SIGUSR1. */ + pthread_barrier_wait(pbarrier); + + return (NULL); +} + +/* + * Verify that a traced process with blocked signal received the + * signal from kill() once unmasked. + */ +ATF_TC_WITHOUT_HEAD(ptrace__killed_with_sigmask); +ATF_TC_BODY(ptrace__killed_with_sigmask, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status, err; + sigset_t sigmask; + + REQUIRE_EQ(sem_init(&sigusr1_sem, 0, 0), 0); + ATF_REQUIRE(signal(SIGUSR1, sigusr1_sempost_handler) != SIG_ERR); + got_usr1 = 0; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + CHILD_REQUIRE_EQ(sigemptyset(&sigmask), 0); + CHILD_REQUIRE_EQ(sigaddset(&sigmask, SIGUSR1), 0); + CHILD_REQUIRE_EQ(sigprocmask(SIG_BLOCK, &sigmask, NULL), 0); + + trace_me(); + CHILD_REQUIRE_EQ(got_usr1, 0); + + /* Allow the pending SIGUSR1 in now. */ + CHILD_REQUIRE_EQ(sigprocmask(SIG_UNBLOCK, &sigmask, NULL), 0); + /* Wait to receive a SIGUSR1. */ + do { + err = sem_wait(&sigusr1_sem); + CHILD_REQUIRE(err == 0 || errno == EINTR); + } while (err != 0 && errno == EINTR); + CHILD_REQUIRE_EQ(got_usr1, 1); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + ATF_REQUIRE(ptrace(PT_LWPINFO, fpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGSTOP); + + /* Send blocked SIGUSR1 which should cause a stop. */ + REQUIRE_EQ(kill(fpid, SIGUSR1), 0); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next wait() should report the kill(SIGUSR1) was received. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGUSR1); + ATF_REQUIRE(ptrace(PT_LWPINFO, fpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGUSR1); + + /* Continue the child, allowing in the SIGUSR1. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + /* The last wait() should report normal exit with code 1. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that a traced process with blocked signal received the + * signal from PT_CONTINUE once unmasked. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_sigmask); +ATF_TC_BODY(ptrace__PT_CONTINUE_with_sigmask, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status, err; + sigset_t sigmask; + + REQUIRE_EQ(sem_init(&sigusr1_sem, 0, 0), 0); + ATF_REQUIRE(signal(SIGUSR1, sigusr1_sempost_handler) != SIG_ERR); + got_usr1 = 0; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + CHILD_REQUIRE_EQ(sigemptyset(&sigmask), 0); + CHILD_REQUIRE_EQ(sigaddset(&sigmask, SIGUSR1), 0); + CHILD_REQUIRE_EQ(sigprocmask(SIG_BLOCK, &sigmask, NULL), 0); + + trace_me(); + CHILD_REQUIRE_EQ(got_usr1, 0); + + /* Allow the pending SIGUSR1 in now. */ + CHILD_REQUIRE_EQ(sigprocmask(SIG_UNBLOCK, &sigmask, NULL), 0); + /* Wait to receive a SIGUSR1. */ + do { + err = sem_wait(&sigusr1_sem); + CHILD_REQUIRE(err == 0 || errno == EINTR); + } while (err != 0 && errno == EINTR); + + CHILD_REQUIRE_EQ(got_usr1, 1); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + ATF_REQUIRE(ptrace(PT_LWPINFO, fpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGSTOP); + + /* Continue the child replacing SIGSTOP with SIGUSR1. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + /* The next wait() should report the SIGUSR1 was received. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGUSR1); + ATF_REQUIRE(ptrace(PT_LWPINFO, fpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGUSR1); + + /* Continue the child, ignoring the SIGUSR1. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last wait() should report normal exit with code 1. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that if ptrace stops due to a signal but continues with + * a different signal that the new signal is routed to a thread + * that can accept it, and that the thread is awakened by the signal + * in a timely manner. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_with_signal_thread_sigmask); +ATF_TC_BODY(ptrace__PT_CONTINUE_with_signal_thread_sigmask, tc) +{ + pid_t fpid, wpid; + int status, err; + pthread_t t; + sigset_t sigmask; + pthread_barrier_t barrier; + + REQUIRE_EQ(pthread_barrier_init(&barrier, NULL, 2), 0); + REQUIRE_EQ(sem_init(&sigusr1_sem, 0, 0), 0); + ATF_REQUIRE(signal(SIGUSR1, sigusr1_sempost_handler) != SIG_ERR); + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + CHILD_REQUIRE_EQ(pthread_create(&t, NULL, signal_thread, + (void *)&barrier), 0); + + /* The other thread should receive the first SIGUSR1. */ + CHILD_REQUIRE_EQ(sigemptyset(&sigmask), 0); + CHILD_REQUIRE_EQ(sigaddset(&sigmask, SIGUSR1), 0); + CHILD_REQUIRE_EQ(pthread_sigmask(SIG_BLOCK, &sigmask, NULL), 0); + + trace_me(); + + /* Wait until other thread has received its SIGUSR1. */ + pthread_barrier_wait(&barrier); + + /* + * Swap ignore duties; the next SIGUSR1 should go to this + * thread. + */ + CHILD_REQUIRE_EQ(pthread_sigmask(SIG_UNBLOCK, &sigmask, NULL), + 0); + + /* Sync up threads after swapping signal masks. */ + pthread_barrier_wait(&barrier); + + /* + * Sync up with test code; we're ready for the next SIGUSR1 + * now. + */ + raise(SIGSTOP); + + /* Wait for this thread to receive a SIGUSR1. */ + do { + err = sem_wait(&sigusr1_sem); + CHILD_REQUIRE(err == 0 || errno == EINTR); + } while (err != 0 && errno == EINTR); + + /* Free the other thread from the barrier. */ + pthread_barrier_wait(&barrier); + + CHILD_REQUIRE_EQ(pthread_join(t, NULL), 0); + + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* + * Send a signal without ptrace that either thread will accept (USR2, + * in this case). + */ + REQUIRE_EQ(kill(fpid, SIGUSR2), 0); + + /* The second wait() should report a SIGUSR2 was received. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGUSR2); + + /* Continue the child, changing the signal to USR1. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + /* The next wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + REQUIRE_EQ(kill(fpid, SIGUSR2), 0); + + /* The next wait() should report a SIGUSR2 was received. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGUSR2); + + /* Continue the child, changing the signal to USR1. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, SIGUSR1), 0); + + /* The last wait() should report normal exit with code 1. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Verify that PT_GETREGSET returns registers and PT_SETREGSET updates them. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_REGSET); +ATF_TC_BODY(ptrace__PT_REGSET, tc) +{ +#if defined(__aarch64__) + struct arm64_addr_mask addr_mask; +#endif + struct prstatus prstatus; + struct iovec vec; + pid_t child, wpid; + int status; + + ATF_REQUIRE((child = fork()) != -1); + if (child == 0) { + trace_me(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Check the size is returned when vec.iov_base is NULL */ + vec.iov_base = NULL; + vec.iov_len = 0; + ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&vec, NT_PRSTATUS) != + -1); + ATF_REQUIRE(vec.iov_len == sizeof(prstatus)); + ATF_REQUIRE(vec.iov_base == NULL); + + /* Read the registers. */ + memset(&prstatus, 0, sizeof(prstatus)); + vec.iov_base = &prstatus; + ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&vec, NT_PRSTATUS) != + -1); + ATF_REQUIRE(vec.iov_len == sizeof(prstatus)); + ATF_REQUIRE(vec.iov_base == &prstatus); + ATF_REQUIRE(prstatus.pr_statussz == sizeof(prstatus)); + + /* Write the registers back. */ + ATF_REQUIRE(ptrace(PT_SETREGSET, wpid, (caddr_t)&vec, NT_PRSTATUS) != + -1); + +#if defined(__aarch64__) + vec.iov_base = &addr_mask; + vec.iov_len = sizeof(addr_mask); + ATF_REQUIRE(ptrace(PT_GETREGSET, wpid, (caddr_t)&vec, + NT_ARM_ADDR_MASK) != -1); + REQUIRE_EQ(addr_mask.code, addr_mask.data); + ATF_REQUIRE(addr_mask.code == 0xff00000000000000ul || + addr_mask.code == 0xff7f000000000000UL); +#endif + + REQUIRE_EQ(ptrace(PT_CONTINUE, child, (caddr_t)1, 0), 0); + + /* The second wait() should report the exit status. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + /* The child should no longer exist. */ + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +static void * +raise_sigstop_thread(void *arg __unused) +{ + + raise(SIGSTOP); + return NULL; +} + +static void * +sleep_thread(void *arg __unused) +{ + + sleep(60); + return NULL; +} + +static void +terminate_with_pending_sigstop(bool sigstop_from_main_thread) +{ + pid_t fpid, wpid; + int status, i; + cpuset_t setmask; + cpusetid_t setid; + pthread_t t; + + /* + * Become the reaper for this process tree. We need to be able to check + * that both child and grandchild have died. + */ + REQUIRE_EQ(procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL), 0); + + fpid = fork(); + ATF_REQUIRE(fpid >= 0); + if (fpid == 0) { + fpid = fork(); + CHILD_REQUIRE(fpid >= 0); + if (fpid == 0) { + trace_me(); + + /* Pin to CPU 0 to serialize thread execution. */ + CPU_ZERO(&setmask); + CPU_SET(0, &setmask); + CHILD_REQUIRE_EQ(cpuset(&setid), 0); + CHILD_REQUIRE(cpuset_setaffinity(CPU_LEVEL_CPUSET, + CPU_WHICH_CPUSET, setid, + sizeof(setmask), &setmask) == 0); + + if (sigstop_from_main_thread) { + /* + * We expect the SIGKILL sent when our parent + * dies to be delivered to the new thread. + * Raise the SIGSTOP in this thread so the + * threads compete. + */ + CHILD_REQUIRE(pthread_create(&t, NULL, + sleep_thread, NULL) == 0); + raise(SIGSTOP); + } else { + /* + * We expect the SIGKILL to be delivered to + * this thread. After creating the new thread, + * just get off the CPU so the other thread can + * raise the SIGSTOP. + */ + CHILD_REQUIRE(pthread_create(&t, NULL, + raise_sigstop_thread, NULL) == 0); + sleep(60); + } + + exit(0); + } + /* First stop is trace_me() immediately after fork. */ + wpid = waitpid(fpid, &status, 0); + CHILD_REQUIRE_EQ(wpid, fpid); + CHILD_REQUIRE(WIFSTOPPED(status)); + CHILD_REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + CHILD_REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* Second stop is from the raise(SIGSTOP). */ + wpid = waitpid(fpid, &status, 0); + CHILD_REQUIRE_EQ(wpid, fpid); + CHILD_REQUIRE(WIFSTOPPED(status)); + CHILD_REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* + * Terminate tracing process without detaching. Our child + * should be killed. + */ + exit(0); + } + + /* + * We should get a normal exit from our immediate child and a SIGKILL + * exit from our grandchild. The latter case is the interesting one. + * Our grandchild should not have stopped due to the SIGSTOP that was + * left dangling when its parent died. + */ + for (i = 0; i < 2; ++i) { + wpid = wait(&status); + if (wpid == fpid) { + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + } else { + ATF_REQUIRE(WIFSIGNALED(status)); + REQUIRE_EQ(WTERMSIG(status), SIGKILL); + } + } +} + +/* + * These two tests ensure that if the tracing process exits without detaching + * just after the child received a SIGSTOP, the child is cleanly killed and + * doesn't go to sleep due to the SIGSTOP. The parent's death will send a + * SIGKILL to the child. If the SIGKILL and the SIGSTOP are handled by + * different threads, the SIGKILL must win. There are two variants of this + * test, designed to catch the case where the SIGKILL is delivered to the + * younger thread (the first test) and the case where the SIGKILL is delivered + * to the older thread (the second test). This behavior has changed in the + * past, so make no assumption. + */ +ATF_TC(ptrace__parent_terminate_with_pending_sigstop1); +ATF_TC_HEAD(ptrace__parent_terminate_with_pending_sigstop1, tc) +{ + + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(ptrace__parent_terminate_with_pending_sigstop1, tc) +{ + + terminate_with_pending_sigstop(true); +} + +ATF_TC(ptrace__parent_terminate_with_pending_sigstop2); +ATF_TC_HEAD(ptrace__parent_terminate_with_pending_sigstop2, tc) +{ + + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(ptrace__parent_terminate_with_pending_sigstop2, tc) +{ + + terminate_with_pending_sigstop(false); +} + +/* + * Verify that after ptrace() discards a SIGKILL signal, the event mask + * is not modified. + */ +ATF_TC_WITHOUT_HEAD(ptrace__event_mask_sigkill_discard); +ATF_TC_BODY(ptrace__event_mask_sigkill_discard, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status, event_mask, new_event_mask; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + raise(SIGSTOP); + exit(0); + } + + /* The first wait() should report the stop from trace_me(). */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Set several unobtrusive event bits. */ + event_mask = PTRACE_EXEC | PTRACE_FORK | PTRACE_LWP; + ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, wpid, (caddr_t)&event_mask, + sizeof(event_mask)) == 0); + + /* Send a SIGKILL without using ptrace. */ + REQUIRE_EQ(kill(fpid, SIGKILL), 0); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next stop should be due to the SIGKILL. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGKILL); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGKILL); + + /* Continue the child ignoring the SIGKILL. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Check the current event mask. It should not have changed. */ + new_event_mask = 0; + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, wpid, (caddr_t)&new_event_mask, + sizeof(new_event_mask)) == 0); + REQUIRE_EQ(event_mask, new_event_mask); + + /* Continue the child to let it exit. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last event should be for the child process's exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +static void * +flock_thread(void *arg) +{ + int fd; + + fd = *(int *)arg; + (void)flock(fd, LOCK_EX); + (void)flock(fd, LOCK_UN); + return (NULL); +} + +/* + * Verify that PT_ATTACH will suspend threads sleeping in an SBDRY section. + * We rely on the fact that the lockf implementation sets SBDRY before blocking + * on a lock. This is a regression test for r318191. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_ATTACH_with_SBDRY_thread); +ATF_TC_BODY(ptrace__PT_ATTACH_with_SBDRY_thread, tc) +{ + pthread_barrier_t barrier; + pthread_barrierattr_t battr; + char tmpfile[64]; + pid_t child, wpid; + int error, fd, i, status; + + REQUIRE_EQ(pthread_barrierattr_init(&battr), 0); + ATF_REQUIRE(pthread_barrierattr_setpshared(&battr, + PTHREAD_PROCESS_SHARED) == 0); + REQUIRE_EQ(pthread_barrier_init(&barrier, &battr, 2), 0); + + (void)snprintf(tmpfile, sizeof(tmpfile), "./ptrace.XXXXXX"); + fd = mkstemp(tmpfile); + ATF_REQUIRE(fd >= 0); + + ATF_REQUIRE((child = fork()) != -1); + if (child == 0) { + pthread_t t[2]; + int cfd; + + error = pthread_barrier_wait(&barrier); + if (error != 0 && error != PTHREAD_BARRIER_SERIAL_THREAD) + _exit(1); + + cfd = open(tmpfile, O_RDONLY); + if (cfd < 0) + _exit(1); + + /* + * We want at least two threads blocked on the file lock since + * the SIGSTOP from PT_ATTACH may kick one of them out of + * sleep. + */ + if (pthread_create(&t[0], NULL, flock_thread, &cfd) != 0) + _exit(1); + if (pthread_create(&t[1], NULL, flock_thread, &cfd) != 0) + _exit(1); + if (pthread_join(t[0], NULL) != 0) + _exit(1); + if (pthread_join(t[1], NULL) != 0) + _exit(1); + _exit(0); + } + + REQUIRE_EQ(flock(fd, LOCK_EX), 0); + + error = pthread_barrier_wait(&barrier); + ATF_REQUIRE(error == 0 || error == PTHREAD_BARRIER_SERIAL_THREAD); + + /* + * Give the child some time to block. Is there a better way to do this? + */ + sleep(1); + + /* + * Attach and give the child 3 seconds to stop. + */ + REQUIRE_EQ(ptrace(PT_ATTACH, child, NULL, 0), 0); + for (i = 0; i < 3; i++) { + wpid = waitpid(child, &status, WNOHANG); + if (wpid == child && WIFSTOPPED(status) && + WSTOPSIG(status) == SIGSTOP) + break; + sleep(1); + } + ATF_REQUIRE_MSG(i < 3, "failed to stop child process after PT_ATTACH"); + + REQUIRE_EQ(ptrace(PT_DETACH, child, NULL, 0), 0); + + REQUIRE_EQ(flock(fd, LOCK_UN), 0); + REQUIRE_EQ(unlink(tmpfile), 0); + REQUIRE_EQ(close(fd), 0); +} + +static void +sigusr1_step_handler(int sig) +{ + + CHILD_REQUIRE_EQ(sig, SIGUSR1); + raise(SIGABRT); +} + +/* + * Verify that PT_STEP with a signal invokes the signal before + * stepping the next instruction (and that the next instruction is + * stepped correctly). + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_STEP_with_signal); +ATF_TC_BODY(ptrace__PT_STEP_with_signal, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + signal(SIGUSR1, sigusr1_step_handler); + raise(SIGABRT); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next stop should report the SIGABRT in the child body. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGABRT); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGABRT); + + /* Step the child process inserting SIGUSR1. */ + REQUIRE_EQ(ptrace(PT_STEP, fpid, (caddr_t)1, SIGUSR1), 0); + + /* The next stop should report the SIGABRT in the signal handler. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGABRT); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGABRT); + + /* Continue the child process discarding the signal. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next stop should report a trace trap from PT_STEP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGTRAP); + REQUIRE_EQ(pl.pl_siginfo.si_code, TRAP_TRACE); + + /* Continue the child to let it exit. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last event should be for the child process's exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +#ifdef HAVE_BREAKPOINT +/* + * Verify that a SIGTRAP event with the TRAP_BRKPT code is reported + * for a breakpoint trap. + */ +ATF_TC_WITHOUT_HEAD(ptrace__breakpoint_siginfo); +ATF_TC_BODY(ptrace__breakpoint_siginfo, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + breakpoint(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The second wait() should report hitting the breakpoint. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE((pl.pl_flags & PL_FLAG_SI) != 0); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGTRAP); + REQUIRE_EQ(pl.pl_siginfo.si_code, TRAP_BRKPT); + + /* Kill the child process. */ + REQUIRE_EQ(ptrace(PT_KILL, fpid, 0, 0), 0); + + /* The last wait() should report the SIGKILL. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSIGNALED(status)); + REQUIRE_EQ(WTERMSIG(status), SIGKILL); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} +#endif /* HAVE_BREAKPOINT */ + +/* + * Verify that a SIGTRAP event with the TRAP_TRACE code is reported + * for a single-step trap from PT_STEP. + */ +ATF_TC_WITHOUT_HEAD(ptrace__step_siginfo); +ATF_TC_BODY(ptrace__step_siginfo, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Step the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_STEP, fpid, (caddr_t)1, 0), 0); + + /* The second wait() should report a single-step trap. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE((pl.pl_flags & PL_FLAG_SI) != 0); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGTRAP); + REQUIRE_EQ(pl.pl_siginfo.si_code, TRAP_TRACE); + + /* Continue the child process. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last event should be for the child process's exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +#if defined(HAVE_BREAKPOINT) && defined(SKIP_BREAK) +static void * +continue_thread(void *arg __unused) +{ + breakpoint(); + return (NULL); +} + +static __dead2 void +continue_thread_main(void) +{ + pthread_t threads[2]; + + CHILD_REQUIRE(pthread_create(&threads[0], NULL, continue_thread, + NULL) == 0); + CHILD_REQUIRE(pthread_create(&threads[1], NULL, continue_thread, + NULL) == 0); + CHILD_REQUIRE_EQ(pthread_join(threads[0], NULL), 0); + CHILD_REQUIRE_EQ(pthread_join(threads[1], NULL), 0); + exit(1); +} + +/* + * Ensure that PT_CONTINUE clears the status of the thread that + * triggered the stop even if a different thread's LWP was passed to + * PT_CONTINUE. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_CONTINUE_different_thread); +ATF_TC_BODY(ptrace__PT_CONTINUE_different_thread, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + lwpid_t lwps[2]; + bool hit_break[2]; + struct reg reg; + int i, j, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + continue_thread_main(); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + + REQUIRE_EQ(ptrace(PT_LWP_EVENTS, wpid, NULL, 1), 0); + + /* Continue the child ignoring the SIGSTOP. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* One of the new threads should report it's birth. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ((pl.pl_flags & (PL_FLAG_BORN | PL_FLAG_SCX)), + (PL_FLAG_BORN | PL_FLAG_SCX)); + lwps[0] = pl.pl_lwpid; + + /* + * Suspend this thread to ensure both threads are alive before + * hitting the breakpoint. + */ + ATF_REQUIRE(ptrace(PT_SUSPEND, lwps[0], NULL, 0) != -1); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* Second thread should report it's birth. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + REQUIRE_EQ((pl.pl_flags & (PL_FLAG_BORN | PL_FLAG_SCX)), + (PL_FLAG_BORN | PL_FLAG_SCX)); + ATF_REQUIRE(pl.pl_lwpid != lwps[0]); + lwps[1] = pl.pl_lwpid; + + /* Resume both threads waiting for breakpoint events. */ + hit_break[0] = hit_break[1] = false; + ATF_REQUIRE(ptrace(PT_RESUME, lwps[0], NULL, 0) != -1); + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* One thread should report a breakpoint. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE((pl.pl_flags & PL_FLAG_SI) != 0); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGTRAP); + REQUIRE_EQ(pl.pl_siginfo.si_code, TRAP_BRKPT); + if (pl.pl_lwpid == lwps[0]) + i = 0; + else + i = 1; + hit_break[i] = true; + ATF_REQUIRE(ptrace(PT_GETREGS, pl.pl_lwpid, (caddr_t)®, 0) != -1); + SKIP_BREAK(®); + ATF_REQUIRE(ptrace(PT_SETREGS, pl.pl_lwpid, (caddr_t)®, 0) != -1); + + /* + * Resume both threads but pass the other thread's LWPID to + * PT_CONTINUE. + */ + REQUIRE_EQ(ptrace(PT_CONTINUE, lwps[i ^ 1], (caddr_t)1, 0), 0); + + /* + * Will now get two thread exit events and one more breakpoint + * event. + */ + for (j = 0; j < 3; j++) { + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, + sizeof(pl)) != -1); + + if (pl.pl_lwpid == lwps[0]) + i = 0; + else + i = 1; + + ATF_REQUIRE_MSG(lwps[i] != 0, "event for exited thread"); + if (pl.pl_flags & PL_FLAG_EXITED) { + ATF_REQUIRE_MSG(hit_break[i], + "exited thread did not report breakpoint"); + lwps[i] = 0; + } else { + ATF_REQUIRE((pl.pl_flags & PL_FLAG_SI) != 0); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGTRAP); + REQUIRE_EQ(pl.pl_siginfo.si_code, TRAP_BRKPT); + ATF_REQUIRE_MSG(!hit_break[i], + "double breakpoint event"); + hit_break[i] = true; + ATF_REQUIRE(ptrace(PT_GETREGS, pl.pl_lwpid, (caddr_t)®, + 0) != -1); + SKIP_BREAK(®); + ATF_REQUIRE(ptrace(PT_SETREGS, pl.pl_lwpid, (caddr_t)®, + 0) != -1); + } + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + } + + /* Both threads should have exited. */ + REQUIRE_EQ(lwps[0], 0); + REQUIRE_EQ(lwps[1], 0); + + /* The last event should be for the child process's exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} +#endif + +/* + * Verify that PT_LWPINFO doesn't return stale siginfo. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_LWPINFO_stale_siginfo); +ATF_TC_BODY(ptrace__PT_LWPINFO_stale_siginfo, tc) +{ + struct ptrace_lwpinfo pl; + pid_t fpid, wpid; + int events, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + raise(SIGABRT); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The next stop should report the SIGABRT in the child body. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGABRT); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SI); + REQUIRE_EQ(pl.pl_siginfo.si_signo, SIGABRT); + + /* + * Continue the process ignoring the signal, but enabling + * syscall traps. + */ + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + /* + * The next stop should report a system call entry from + * exit(). PL_FLAGS_SI should not be set. + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE); + REQUIRE_EQ((pl.pl_flags & PL_FLAG_SI), 0); + + /* Disable syscall tracing and continue the child to let it exit. */ + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + events &= ~PTRACE_SYSCALL; + ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last event should be for the child process's exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * A simple test of PT_GET_SC_ARGS and PT_GET_SC_RET. + */ +ATF_TC_WITHOUT_HEAD(ptrace__syscall_args); +ATF_TC_BODY(ptrace__syscall_args, tc) +{ + struct ptrace_lwpinfo pl; + struct ptrace_sc_ret psr; + pid_t fpid, wpid; + register_t args[2]; + int events, status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + kill(getpid(), 0); + /* Close a fd that should not exist. */ + close(12345); + exit(1); + } + + /* The first wait() should report the stop from SIGSTOP. */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* + * Continue the process ignoring the signal, but enabling + * syscall traps. + */ + REQUIRE_EQ(ptrace(PT_SYSCALL, fpid, (caddr_t)1, 0), 0); + + /* + * The next stop should be the syscall entry from getpid(). + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE); + REQUIRE_EQ(pl.pl_syscall_code, (unsigned)SYS_getpid); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* + * The next stop should be the syscall exit from getpid(). + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCX); + REQUIRE_EQ(pl.pl_syscall_code, (unsigned)SYS_getpid); + + ATF_REQUIRE(ptrace(PT_GET_SC_RET, wpid, (caddr_t)&psr, + sizeof(psr)) != -1); + REQUIRE_EQ(psr.sr_error, 0); + REQUIRE_EQ(psr.sr_retval[0], wpid); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* + * The next stop should be the syscall entry from kill(). + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE); + REQUIRE_EQ(pl.pl_syscall_code, (unsigned)SYS_kill); + REQUIRE_EQ(pl.pl_syscall_narg, 2u); + + ATF_REQUIRE(ptrace(PT_GET_SC_ARGS, wpid, (caddr_t)args, + sizeof(args)) != -1); + REQUIRE_EQ(args[0], wpid); + REQUIRE_EQ(args[1], 0); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* + * The next stop should be the syscall exit from kill(). + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCX); + REQUIRE_EQ(pl.pl_syscall_code, (unsigned)SYS_kill); + + ATF_REQUIRE(ptrace(PT_GET_SC_RET, wpid, (caddr_t)&psr, + sizeof(psr)) != -1); + REQUIRE_EQ(psr.sr_error, 0); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* + * The next stop should be the syscall entry from close(). + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCE); + REQUIRE_EQ(pl.pl_syscall_code, (unsigned)SYS_close); + REQUIRE_EQ(pl.pl_syscall_narg, 1u); + + ATF_REQUIRE(ptrace(PT_GET_SC_ARGS, wpid, (caddr_t)args, + sizeof(args)) != -1); + REQUIRE_EQ(args[0], 12345); + + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* + * The next stop should be the syscall exit from close(). + */ + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGTRAP); + + ATF_REQUIRE(ptrace(PT_LWPINFO, wpid, (caddr_t)&pl, sizeof(pl)) != -1); + ATF_REQUIRE(pl.pl_flags & PL_FLAG_SCX); + REQUIRE_EQ(pl.pl_syscall_code, (unsigned)SYS_close); + + ATF_REQUIRE(ptrace(PT_GET_SC_RET, wpid, (caddr_t)&psr, + sizeof(psr)) != -1); + REQUIRE_EQ(psr.sr_error, EBADF); + + /* Disable syscall tracing and continue the child to let it exit. */ + ATF_REQUIRE(ptrace(PT_GET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + events &= ~PTRACE_SYSCALL; + ATF_REQUIRE(ptrace(PT_SET_EVENT_MASK, fpid, (caddr_t)&events, + sizeof(events)) == 0); + REQUIRE_EQ(ptrace(PT_CONTINUE, fpid, (caddr_t)1, 0), 0); + + /* The last event should be for the child process's exit. */ + wpid = waitpid(fpid, &status, 0); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Check that syscall info is available whenever kernel has valid td_sa. + * Assumes that libc nanosleep(2) is the plain syscall wrapper. + */ +ATF_TC_WITHOUT_HEAD(ptrace__syscall_args_anywhere); +ATF_TC_BODY(ptrace__syscall_args_anywhere, tc) +{ + struct timespec rqt; + struct ptrace_lwpinfo lwpi; + register_t args[8]; + pid_t debuggee, wpid; + int error, status; + + debuggee = fork(); + ATF_REQUIRE(debuggee >= 0); + if (debuggee == 0) { + rqt.tv_sec = 100000; + rqt.tv_nsec = 0; + for (;;) + nanosleep(&rqt, NULL); + _exit(0); + } + + /* Give the debuggee some time to go to sleep. */ + sleep(2); + error = ptrace(PT_ATTACH, debuggee, 0, 0); + ATF_REQUIRE(error == 0); + wpid = waitpid(debuggee, &status, 0); + REQUIRE_EQ(wpid, debuggee); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + error = ptrace(PT_LWPINFO, debuggee, (caddr_t)&lwpi, sizeof(lwpi)); + ATF_REQUIRE(error == 0); + ATF_REQUIRE(lwpi.pl_syscall_code == SYS_nanosleep); + ATF_REQUIRE(lwpi.pl_syscall_narg == 2); + error = ptrace(PT_GET_SC_ARGS, debuggee, (caddr_t)&args[0], + lwpi.pl_syscall_narg * sizeof(register_t)); + ATF_REQUIRE(error == 0); + ATF_REQUIRE(args[0] == (register_t)&rqt); + ATF_REQUIRE(args[1] == 0); + + error = ptrace(PT_DETACH, debuggee, 0, 0); + ATF_REQUIRE(error == 0); + kill(SIGKILL, debuggee); +} + +/* + * Verify that when the process is traced that it isn't reparent + * to the init process when we close all process descriptors. + */ +ATF_TC(ptrace__proc_reparent); +ATF_TC_HEAD(ptrace__proc_reparent, tc) +{ + + atf_tc_set_md_var(tc, "timeout", "2"); +} +ATF_TC_BODY(ptrace__proc_reparent, tc) +{ + pid_t traced, debuger, wpid; + int pd, status; + + traced = pdfork(&pd, 0); + ATF_REQUIRE(traced >= 0); + if (traced == 0) { + raise(SIGSTOP); + exit(0); + } + ATF_REQUIRE(pd >= 0); + + debuger = fork(); + ATF_REQUIRE(debuger >= 0); + if (debuger == 0) { + /* The traced process is reparented to debuger. */ + REQUIRE_EQ(ptrace(PT_ATTACH, traced, 0, 0), 0); + wpid = waitpid(traced, &status, 0); + REQUIRE_EQ(wpid, traced); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + REQUIRE_EQ(close(pd), 0); + REQUIRE_EQ(ptrace(PT_DETACH, traced, (caddr_t)1, 0), 0); + + /* We closed pd so we should not have any child. */ + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); + + exit(0); + } + + REQUIRE_EQ(close(pd), 0); + wpid = waitpid(debuger, &status, 0); + REQUIRE_EQ(wpid, debuger); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + /* Check if we still have any child. */ + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); +} + +/* + * Ensure that traced processes created with pdfork(2) are visible to + * waitid(P_ALL). + */ +ATF_TC_WITHOUT_HEAD(ptrace__procdesc_wait_child); +ATF_TC_BODY(ptrace__procdesc_wait_child, tc) +{ + pid_t child, wpid; + int pd, status; + + child = pdfork(&pd, 0); + ATF_REQUIRE(child >= 0); + + if (child == 0) { + trace_me(); + (void)raise(SIGSTOP); + exit(0); + } + + wpid = waitpid(child, &status, 0); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + wpid = wait(&status); + REQUIRE_EQ(wpid, child); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_CONTINUE, child, (caddr_t)1, 0) != -1); + + /* + * If process was created by pdfork, the return code have to + * be collected through process descriptor. + */ + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); + + ATF_REQUIRE(close(pd) != -1); +} + +/* + * Ensure that traced processes created with pdfork(2) are not visible + * after returning to parent - waitid(P_ALL). + */ +ATF_TC_WITHOUT_HEAD(ptrace__procdesc_reparent_wait_child); +ATF_TC_BODY(ptrace__procdesc_reparent_wait_child, tc) +{ + pid_t traced, debuger, wpid; + int pd, status; + + traced = pdfork(&pd, 0); + ATF_REQUIRE(traced >= 0); + if (traced == 0) { + raise(SIGSTOP); + exit(0); + } + ATF_REQUIRE(pd >= 0); + + /* Wait until the child process has stopped before fork()ing again. */ + REQUIRE_EQ(traced, waitpid(traced, &status, WSTOPPED)); + debuger = fork(); + ATF_REQUIRE(debuger >= 0); + if (debuger == 0) { + /* The traced process is reparented to debuger. */ + REQUIRE_EQ(ptrace(PT_ATTACH, traced, 0, 0), 0); + wpid = waitpid(traced, &status, 0); + REQUIRE_EQ(wpid, traced); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Allow process to die. */ + REQUIRE_EQ(ptrace(PT_CONTINUE, traced, (caddr_t)1, 0), 0); + wpid = waitpid(traced, &status, 0); + REQUIRE_EQ(wpid, traced); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + /* Reparent back to the orginal process. */ + REQUIRE_EQ(close(pd), 0); + exit(0); + } + + wpid = waitpid(debuger, &status, 0); + REQUIRE_EQ(wpid, debuger); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + /* + * We have a child but it has a process descriptori + * so we should not be able to collect it process. + */ + wpid = wait(&status); + REQUIRE_EQ(wpid, -1); + REQUIRE_EQ(errno, ECHILD); + + REQUIRE_EQ(close(pd), 0); +} + +/* + * Try using PT_SC_REMOTE to get the PID of a traced child process. + */ +ATF_TC_WITHOUT_HEAD(ptrace__PT_SC_REMOTE_getpid); +ATF_TC_BODY(ptrace__PT_SC_REMOTE_getpid, tc) +{ + struct ptrace_sc_remote pscr; + pid_t fpid, wpid; + int status; + + ATF_REQUIRE((fpid = fork()) != -1); + if (fpid == 0) { + trace_me(); + exit(0); + } + + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + pscr.pscr_syscall = SYS_getpid; + pscr.pscr_nargs = 0; + pscr.pscr_args = NULL; + ATF_REQUIRE(ptrace(PT_SC_REMOTE, fpid, (caddr_t)&pscr, sizeof(pscr)) != + -1); + ATF_REQUIRE_MSG(pscr.pscr_ret.sr_error == 0, + "remote getpid failed with error %d", pscr.pscr_ret.sr_error); + ATF_REQUIRE_MSG(pscr.pscr_ret.sr_retval[0] == fpid, + "unexpected return value %jd instead of %d", + (intmax_t)pscr.pscr_ret.sr_retval[0], fpid); + + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + pscr.pscr_syscall = SYS_getppid; + pscr.pscr_nargs = 0; + pscr.pscr_args = NULL; + ATF_REQUIRE(ptrace(PT_SC_REMOTE, fpid, (caddr_t)&pscr, sizeof(pscr)) != + -1); + ATF_REQUIRE_MSG(pscr.pscr_ret.sr_error == 0, + "remote getppid failed with error %d", pscr.pscr_ret.sr_error); + ATF_REQUIRE_MSG(pscr.pscr_ret.sr_retval[0] == getpid(), + "unexpected return value %jd instead of %d", + (intmax_t)pscr.pscr_ret.sr_retval[0], fpid); + + wpid = waitpid(fpid, &status, 0); + REQUIRE_EQ(wpid, fpid); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + ATF_REQUIRE(ptrace(PT_DETACH, fpid, (caddr_t)1, 0) != -1); +} + +/* + * Ensure that procctl(PROC_REAP_KILL) won't block forever waiting for a target + * process that stopped to report its status to a debugger. + */ +ATF_TC_WITHOUT_HEAD(ptrace__reap_kill_stopped); +ATF_TC_BODY(ptrace__reap_kill_stopped, tc) +{ + struct procctl_reaper_kill prk; + pid_t debuggee, wpid; + int error, status; + + REQUIRE_EQ(procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL), 0); + + debuggee = fork(); + ATF_REQUIRE(debuggee >= 0); + if (debuggee == 0) { + trace_me(); + for (;;) + sleep(10); + _exit(0); + } + wpid = waitpid(debuggee, &status, 0); + REQUIRE_EQ(wpid, debuggee); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + /* Resume the child and ask it to stop during syscall exits. */ + ATF_REQUIRE(ptrace(PT_TO_SCX, debuggee, (caddr_t)1, 0) != -1); + + /* Give the debuggee some time to go to sleep. */ + usleep(100000); + + /* + * Kill the child process. procctl() may attempt to stop the target + * process to prevent it from adding new children to the reaper subtree, + * and this should not conflict with the child stopping itself for the + * debugger. + */ + memset(&prk, 0, sizeof(prk)); + prk.rk_sig = SIGTERM; + error = procctl(P_PID, getpid(), PROC_REAP_KILL, &prk); + REQUIRE_EQ(error, 0); + REQUIRE_EQ(1u, prk.rk_killed); + REQUIRE_EQ(-1, prk.rk_fpid); +} + +struct child_res { + struct timespec sleep_time; + int nanosleep_res; + int nanosleep_errno; +}; + +static const long nsec = 1000000000L; +static const struct timespec ten_sec = { + .tv_sec = 10, + .tv_nsec = 0, +}; +static const struct timespec twelve_sec = { + .tv_sec = 12, + .tv_nsec = 0, +}; + +ATF_TC_WITHOUT_HEAD(ptrace__PT_ATTACH_no_EINTR); +ATF_TC_BODY(ptrace__PT_ATTACH_no_EINTR, tc) +{ + struct child_res *shm; + struct timespec rqt, now, wake; + pid_t debuggee; + int status; + + shm = mmap(NULL, sizeof(*shm), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANON, -1, 0); + ATF_REQUIRE(shm != MAP_FAILED); + + ATF_REQUIRE((debuggee = fork()) != -1); + if (debuggee == 0) { + rqt.tv_sec = 10; + rqt.tv_nsec = 0; + clock_gettime(CLOCK_MONOTONIC_PRECISE, &now); + errno = 0; + shm->nanosleep_res = nanosleep(&rqt, NULL); + shm->nanosleep_errno = errno; + clock_gettime(CLOCK_MONOTONIC_PRECISE, &wake); + timespecsub(&wake, &now, &shm->sleep_time); + _exit(0); + } + + /* Give the debuggee some time to go to sleep. */ + sleep(2); + REQUIRE_EQ(ptrace(PT_ATTACH, debuggee, 0, 0), 0); + REQUIRE_EQ(waitpid(debuggee, &status, 0), debuggee); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + REQUIRE_EQ(ptrace(PT_DETACH, debuggee, 0, 0), 0); + REQUIRE_EQ(waitpid(debuggee, &status, 0), debuggee); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + ATF_REQUIRE(shm->nanosleep_res == 0); + ATF_REQUIRE(shm->nanosleep_errno == 0); + ATF_REQUIRE(timespeccmp(&shm->sleep_time, &ten_sec, >=)); + ATF_REQUIRE(timespeccmp(&shm->sleep_time, &twelve_sec, <=)); +} + +ATF_TC_WITHOUT_HEAD(ptrace__PT_DETACH_continued); +ATF_TC_BODY(ptrace__PT_DETACH_continued, tc) +{ + char buf[256]; + pid_t debuggee, debugger; + int dpipe[2] = {-1, -1}, status; + + /* Setup the debuggee's pipe, which we'll use to let it terminate. */ + ATF_REQUIRE(pipe(dpipe) == 0); + ATF_REQUIRE((debuggee = fork()) != -1); + + if (debuggee == 0) { + ssize_t readsz; + + /* + * The debuggee will just absorb everything until the parent + * closes it. In the process, we expect it to get SIGSTOP'd, + * then ptrace(2)d and finally, it should resume after we detach + * and the parent will be notified. + */ + close(dpipe[1]); + while ((readsz = read(dpipe[0], buf, sizeof(buf))) != 0) { + if (readsz > 0 || errno == EINTR) + continue; + _exit(1); + } + + _exit(0); + } + + close(dpipe[0]); + + ATF_REQUIRE(kill(debuggee, SIGSTOP) == 0); + REQUIRE_EQ(waitpid(debuggee, &status, WUNTRACED), debuggee); + ATF_REQUIRE(WIFSTOPPED(status)); + + /* Child is stopped, enter the debugger to attach/detach. */ + ATF_REQUIRE((debugger = fork()) != -1); + if (debugger == 0) { + REQUIRE_EQ(ptrace(PT_ATTACH, debuggee, 0, 0), 0); + REQUIRE_EQ(waitpid(debuggee, &status, 0), debuggee); + ATF_REQUIRE(WIFSTOPPED(status)); + REQUIRE_EQ(WSTOPSIG(status), SIGSTOP); + + REQUIRE_EQ(ptrace(PT_DETACH, debuggee, 0, 0), 0); + _exit(0); + } + + REQUIRE_EQ(waitpid(debugger, &status, 0), debugger); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + + REQUIRE_EQ(waitpid(debuggee, &status, WCONTINUED), debuggee); + ATF_REQUIRE(WIFCONTINUED(status)); + + /* + * Closing the pipe will trigger the debuggee to exit now that the + * child has resumed following detach. + */ + close(dpipe[1]); + + REQUIRE_EQ(waitpid(debuggee, &status, 0), debuggee); + ATF_REQUIRE(WIFEXITED(status)); + REQUIRE_EQ(WEXITSTATUS(status), 0); + +} + +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__parent_exits_before_child); + 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); + ATF_TP_ADD_TC(tp, ptrace__getppid); + ATF_TP_ADD_TC(tp, ptrace__new_child_pl_syscall_code_fork); + ATF_TP_ADD_TC(tp, ptrace__new_child_pl_syscall_code_vfork); + ATF_TP_ADD_TC(tp, ptrace__new_child_pl_syscall_code_thread); + ATF_TP_ADD_TC(tp, ptrace__lwp_events); + ATF_TP_ADD_TC(tp, ptrace__lwp_events_exec); + ATF_TP_ADD_TC(tp, ptrace__siginfo); + ATF_TP_ADD_TC(tp, ptrace__ptrace_exec_disable); + ATF_TP_ADD_TC(tp, ptrace__ptrace_exec_enable); + ATF_TP_ADD_TC(tp, ptrace__event_mask); + ATF_TP_ADD_TC(tp, ptrace__ptrace_vfork); + ATF_TP_ADD_TC(tp, ptrace__ptrace_vfork_follow); +#ifdef HAVE_BREAKPOINT + ATF_TP_ADD_TC(tp, ptrace__PT_KILL_breakpoint); +#endif + ATF_TP_ADD_TC(tp, ptrace__PT_KILL_system_call); + ATF_TP_ADD_TC(tp, ptrace__PT_KILL_threads); + ATF_TP_ADD_TC(tp, ptrace__PT_KILL_competing_signal); + ATF_TP_ADD_TC(tp, ptrace__PT_KILL_competing_stop); + ATF_TP_ADD_TC(tp, ptrace__PT_KILL_with_signal_full_sigqueue); + ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_system_call_entry); + ATF_TP_ADD_TC(tp, + ptrace__PT_CONTINUE_with_signal_system_call_entry_and_exit); + ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_full_sigqueue); + ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_masked_full_sigqueue); + ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_change_sig); + ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_sigtrap_system_call_entry); + ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_mix); + ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_kqueue); + ATF_TP_ADD_TC(tp, ptrace__killed_with_sigmask); + ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_sigmask); + ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_with_signal_thread_sigmask); + ATF_TP_ADD_TC(tp, ptrace__PT_REGSET); + ATF_TP_ADD_TC(tp, ptrace__parent_terminate_with_pending_sigstop1); + ATF_TP_ADD_TC(tp, ptrace__parent_terminate_with_pending_sigstop2); + ATF_TP_ADD_TC(tp, ptrace__event_mask_sigkill_discard); + ATF_TP_ADD_TC(tp, ptrace__PT_ATTACH_with_SBDRY_thread); + ATF_TP_ADD_TC(tp, ptrace__PT_STEP_with_signal); +#ifdef HAVE_BREAKPOINT + ATF_TP_ADD_TC(tp, ptrace__breakpoint_siginfo); +#endif + ATF_TP_ADD_TC(tp, ptrace__step_siginfo); +#if defined(HAVE_BREAKPOINT) && defined(SKIP_BREAK) + ATF_TP_ADD_TC(tp, ptrace__PT_CONTINUE_different_thread); +#endif + ATF_TP_ADD_TC(tp, ptrace__PT_LWPINFO_stale_siginfo); + ATF_TP_ADD_TC(tp, ptrace__syscall_args); + ATF_TP_ADD_TC(tp, ptrace__syscall_args_anywhere); + ATF_TP_ADD_TC(tp, ptrace__proc_reparent); + ATF_TP_ADD_TC(tp, ptrace__procdesc_wait_child); + ATF_TP_ADD_TC(tp, ptrace__procdesc_reparent_wait_child); + ATF_TP_ADD_TC(tp, ptrace__PT_SC_REMOTE_getpid); + ATF_TP_ADD_TC(tp, ptrace__reap_kill_stopped); + ATF_TP_ADD_TC(tp, ptrace__PT_ATTACH_no_EINTR); + ATF_TP_ADD_TC(tp, ptrace__PT_DETACH_continued); + + return (atf_no_error()); +} |