summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/ptrace.cc
diff options
context:
space:
mode:
authorBrian Geffon <bgeffon@google.com>2018-12-10 14:41:40 -0800
committerShentubot <shentubot@google.com>2018-12-10 14:42:34 -0800
commitd3bc79bc8438206ac6a14fde4eaa288fc07eee82 (patch)
treee820398591bfd1503456e877fa0c2bdd0f994959 /test/syscalls/linux/ptrace.cc
parent833edbd10b49db1f934dcb2495dcb41c1310eea4 (diff)
Open source system call tests.
PiperOrigin-RevId: 224886231 Change-Id: I0fccb4d994601739d8b16b1d4e6b31f40297fb22
Diffstat (limited to 'test/syscalls/linux/ptrace.cc')
-rw-r--r--test/syscalls/linux/ptrace.cc948
1 files changed, 948 insertions, 0 deletions
diff --git a/test/syscalls/linux/ptrace.cc b/test/syscalls/linux/ptrace.cc
new file mode 100644
index 000000000..d3b3b8b02
--- /dev/null
+++ b/test/syscalls/linux/ptrace.cc
@@ -0,0 +1,948 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <elf.h>
+#include <signal.h>
+#include <stddef.h>
+#include <sys/ptrace.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "test/util/logging.h"
+#include "test/util/multiprocess_util.h"
+#include "test/util/signal_util.h"
+#include "test/util/test_util.h"
+#include "test/util/thread_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+// Sends sig to the current process with tgkill(2).
+//
+// glibc's raise(2) may change the signal mask before sending the signal. These
+// extra syscalls make tests of syscall, signal interception, etc. difficult to
+// write.
+void RaiseSignal(int sig) {
+ pid_t pid = getpid();
+ TEST_PCHECK(pid > 0);
+ pid_t tid = gettid();
+ TEST_PCHECK(tid > 0);
+ TEST_PCHECK(tgkill(pid, tid, sig) == 0);
+}
+
+// Returns the Yama ptrace scope.
+PosixErrorOr<int> YamaPtraceScope() {
+ constexpr char kYamaPtraceScopePath[] = "/proc/sys/kernel/yama/ptrace_scope";
+
+ ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(kYamaPtraceScopePath));
+ if (!exists) {
+ // File doesn't exist means no Yama, so the scope is disabled -> 0.
+ return 0;
+ }
+
+ std::string contents;
+ RETURN_IF_ERRNO(GetContents(kYamaPtraceScopePath, &contents));
+
+ int scope;
+ if (!absl::SimpleAtoi(contents, &scope)) {
+ return PosixError(EINVAL, absl::StrCat(contents, ": not a valid number"));
+ }
+
+ return scope;
+}
+
+TEST(PtraceTest, AttachSelf) {
+ EXPECT_THAT(ptrace(PTRACE_ATTACH, gettid(), 0, 0),
+ SyscallFailsWithErrno(EPERM));
+}
+
+TEST(PtraceTest, AttachSameThreadGroup) {
+ pid_t const tid = gettid();
+ ScopedThread([&] {
+ EXPECT_THAT(ptrace(PTRACE_ATTACH, tid, 0, 0), SyscallFailsWithErrno(EPERM));
+ });
+}
+
+TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) {
+ // Yama prevents attaching to a parent. Skip the test if the scope is anything
+ // except disabled.
+ SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) > 0);
+
+ constexpr long kBeforePokeDataValue = 10;
+ constexpr long kAfterPokeDataValue = 20;
+
+ volatile long word = kBeforePokeDataValue;
+
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+
+ // Attach to the parent.
+ pid_t const parent_pid = getppid();
+ TEST_PCHECK(ptrace(PTRACE_ATTACH, parent_pid, 0, 0) == 0);
+ MaybeSave();
+
+ // Block until the parent enters signal-delivery-stop as a result of the
+ // SIGSTOP sent by PTRACE_ATTACH.
+ int status;
+ TEST_PCHECK(waitpid(parent_pid, &status, 0) == parent_pid);
+ MaybeSave();
+ TEST_CHECK(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
+
+ // Replace the value of word in the parent process with kAfterPokeDataValue.
+ long const parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, &word, 0);
+ MaybeSave();
+ TEST_CHECK(parent_word == kBeforePokeDataValue);
+ TEST_PCHECK(
+ ptrace(PTRACE_POKEDATA, parent_pid, &word, kAfterPokeDataValue) == 0);
+ MaybeSave();
+
+ // Detach from the parent and suppress the SIGSTOP. If the SIGSTOP is not
+ // suppressed, the parent will hang in group-stop, causing the test to time
+ // out.
+ TEST_PCHECK(ptrace(PTRACE_DETACH, parent_pid, 0, 0) == 0);
+ MaybeSave();
+ _exit(0);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Wait for the child to complete.
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << " status " << status;
+
+ // Check that the child's PTRACE_POKEDATA was effective.
+ EXPECT_EQ(kAfterPokeDataValue, word);
+}
+
+TEST(PtraceTest, GetSigMask) {
+ // <sys/user.h> doesn't define these until Linux 4.4, even though the features
+ // were added in 3.11.
+ constexpr auto kPtraceGetSigMask = static_cast<enum __ptrace_request>(0x420a);
+ constexpr auto kPtraceSetSigMask = static_cast<enum __ptrace_request>(0x420b);
+ // glibc and the Linux kernel define a sigset_t with different sizes. To avoid
+ // creating a kernel_sigset_t and recreating all the modification functions
+ // (sigemptyset, etc), we just hardcode the kernel sigset size.
+ constexpr int kSizeofKernelSigset = 8;
+ constexpr int kBlockSignal = SIGUSR1;
+ sigset_t blocked;
+ sigemptyset(&blocked);
+ sigaddset(&blocked, kBlockSignal);
+
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+
+ // Install a signal handler for kBlockSignal to avoid termination and block
+ // it.
+ TEST_PCHECK(signal(kBlockSignal, +[](int signo) {}) != SIG_ERR);
+ MaybeSave();
+ TEST_PCHECK(sigprocmask(SIG_SETMASK, &blocked, nullptr) == 0);
+ MaybeSave();
+
+ // Enable tracing.
+ TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
+ MaybeSave();
+
+ // This should be blocked.
+ RaiseSignal(kBlockSignal);
+
+ // This should be suppressed by parent, who will change signal mask in the
+ // meantime, which means kBlockSignal should be delivered once this resumes.
+ RaiseSignal(SIGSTOP);
+
+ _exit(0);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
+ << " status " << status;
+
+ // Get current signal mask.
+ sigset_t set;
+ EXPECT_THAT(ptrace(kPtraceGetSigMask, child_pid, kSizeofKernelSigset, &set),
+ SyscallSucceeds());
+ EXPECT_THAT(blocked, EqualsSigset(set));
+
+ // Try to get current signal mask with bad size argument.
+ EXPECT_THAT(ptrace(kPtraceGetSigMask, child_pid, 0, nullptr),
+ SyscallFailsWithErrno(EINVAL));
+
+ // Try to set bad signal mask.
+ sigset_t* bad_addr = reinterpret_cast<sigset_t*>(-1);
+ EXPECT_THAT(
+ ptrace(kPtraceSetSigMask, child_pid, kSizeofKernelSigset, bad_addr),
+ SyscallFailsWithErrno(EFAULT));
+
+ // Set signal mask to empty set.
+ sigset_t set1;
+ sigemptyset(&set1);
+ EXPECT_THAT(ptrace(kPtraceSetSigMask, child_pid, kSizeofKernelSigset, &set1),
+ SyscallSucceeds());
+
+ // Suppress SIGSTOP and resume the child. It should re-enter
+ // signal-delivery-stop for kBlockSignal.
+ ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == kBlockSignal)
+ << " status " << status;
+
+ ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ // Let's see that process exited normally.
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << " status " << status;
+}
+
+TEST(PtraceTest, GetSiginfo_SetSiginfo_SignalInjection) {
+ constexpr int kOriginalSigno = SIGUSR1;
+ constexpr int kInjectedSigno = SIGUSR2;
+
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+
+ // Override all signal handlers.
+ struct sigaction sa = {};
+ sa.sa_handler = +[](int signo) { _exit(signo); };
+ TEST_PCHECK(sigfillset(&sa.sa_mask) == 0);
+ for (int signo = 1; signo < 32; signo++) {
+ if (signo == SIGKILL || signo == SIGSTOP) {
+ continue;
+ }
+ TEST_PCHECK(sigaction(signo, &sa, nullptr) == 0);
+ }
+ for (int signo = SIGRTMIN; signo <= SIGRTMAX; signo++) {
+ TEST_PCHECK(sigaction(signo, &sa, nullptr) == 0);
+ }
+
+ // Unblock all signals.
+ TEST_PCHECK(sigprocmask(SIG_UNBLOCK, &sa.sa_mask, nullptr) == 0);
+ MaybeSave();
+
+ // Send ourselves kOriginalSignal while ptraced and exit with the signal we
+ // actually receive via the signal handler, if any, or 0 if we don't receive
+ // a signal.
+ TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
+ MaybeSave();
+ RaiseSignal(kOriginalSigno);
+ _exit(0);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Wait for the child to send itself kOriginalSigno and enter
+ // signal-delivery-stop.
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == kOriginalSigno)
+ << " status " << status;
+
+ siginfo_t siginfo = {};
+ ASSERT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo),
+ SyscallSucceeds());
+ EXPECT_EQ(kOriginalSigno, siginfo.si_signo);
+ EXPECT_EQ(SI_TKILL, siginfo.si_code);
+
+ // Replace the signal with kInjectedSigno, and check that the child exits
+ // with kInjectedSigno, indicating that signal injection was successful.
+ siginfo.si_signo = kInjectedSigno;
+ ASSERT_THAT(ptrace(PTRACE_SETSIGINFO, child_pid, 0, &siginfo),
+ SyscallSucceeds());
+ ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, kInjectedSigno),
+ SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == kInjectedSigno)
+ << " status " << status;
+}
+
+TEST(PtraceTest, SIGKILLDoesNotCauseSignalDeliveryStop) {
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+ TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
+ MaybeSave();
+ RaiseSignal(SIGKILL);
+ TEST_CHECK_MSG(false, "Survived SIGKILL?");
+ _exit(1);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Expect the child to die to SIGKILL without entering signal-delivery-stop.
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
+ << " status " << status;
+}
+
+TEST(PtraceTest, PtraceKill) {
+ constexpr int kOriginalSigno = SIGUSR1;
+
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+ TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
+ MaybeSave();
+
+ // PTRACE_KILL only works if tracee has entered signal-delivery-stop.
+ RaiseSignal(kOriginalSigno);
+ TEST_CHECK_MSG(false, "Failed to kill the process?");
+ _exit(0);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Wait for the child to send itself kOriginalSigno and enter
+ // signal-delivery-stop.
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == kOriginalSigno)
+ << " status " << status;
+
+ ASSERT_THAT(ptrace(PTRACE_KILL, child_pid, 0, 0), SyscallSucceeds());
+
+ // Expect the child to die with SIGKILL.
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
+ << " status " << status;
+}
+
+TEST(PtraceTest, GetRegSet) {
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+
+ // Enable tracing.
+ TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
+ MaybeSave();
+
+ // Use kill explicitly because we check the syscall argument register below.
+ kill(getpid(), SIGSTOP);
+
+ _exit(0);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
+ << " status " << status;
+
+ // Get the general registers.
+ struct user_regs_struct regs;
+ struct iovec iov;
+ iov.iov_base = &regs;
+ iov.iov_len = sizeof(regs);
+ EXPECT_THAT(ptrace(PTRACE_GETREGSET, child_pid, NT_PRSTATUS, &iov),
+ SyscallSucceeds());
+
+ // Read exactly the full register set.
+ EXPECT_EQ(iov.iov_len, sizeof(regs));
+
+#ifdef __x86_64__
+ // Child called kill(2), with SIGSTOP as arg 2.
+ EXPECT_EQ(regs.rsi, SIGSTOP);
+#endif
+
+ // Suppress SIGSTOP and resume the child.
+ ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ // Let's see that process exited normally.
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << " status " << status;
+}
+
+TEST(PtraceTest, AttachingConvertsGroupStopToPtraceStop) {
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+ while (true) {
+ pause();
+ }
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // SIGSTOP the child and wait for it to stop.
+ ASSERT_THAT(kill(child_pid, SIGSTOP), SyscallSucceeds());
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, WUNTRACED),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
+ << " status " << status;
+
+ // Attach to the child and expect it to re-enter a traced group-stop despite
+ // already being stopped.
+ ASSERT_THAT(ptrace(PTRACE_ATTACH, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
+ << " status " << status;
+
+ // Verify that the child is ptrace-stopped by checking that it can receive
+ // ptrace commands requiring a ptrace-stop.
+ EXPECT_THAT(ptrace(PTRACE_SETOPTIONS, child_pid, 0, 0), SyscallSucceeds());
+
+ // Group-stop is distinguished from signal-delivery-stop by PTRACE_GETSIGINFO
+ // failing with EINVAL.
+ siginfo_t siginfo = {};
+ EXPECT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo),
+ SyscallFailsWithErrno(EINVAL));
+
+ // Detach from the child and expect it to stay stopped without a notification.
+ ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, WUNTRACED | WNOHANG),
+ SyscallSucceedsWithValue(0));
+
+ // Sending it SIGCONT should cause it to leave its stop.
+ ASSERT_THAT(kill(child_pid, SIGCONT), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, WCONTINUED),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFCONTINUED(status)) << " status " << status;
+
+ // Clean up the child.
+ ASSERT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
+ << " status " << status;
+}
+
+// Fixture for tests parameterized by whether or not to use PTRACE_O_TRACEEXEC.
+class PtraceExecveTest : public ::testing::TestWithParam<bool> {
+ protected:
+ bool TraceExec() const { return GetParam(); }
+};
+
+TEST_P(PtraceExecveTest, Execve_GetRegs_PeekUser_SIGKILL_TraceClone_TraceExit) {
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+
+ // Enable tracing, then raise SIGSTOP and expect our parent to suppress it.
+ TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
+ MaybeSave();
+ RaiseSignal(SIGSTOP);
+ MaybeSave();
+
+ // Call execve in a non-leader thread.
+ ExecveArray const owned_child_argv = {"/proc/self/exe"};
+ char* const* const child_argv = owned_child_argv.get();
+ ScopedThread t([&] {
+ execve(child_argv[0], child_argv, /* envp = */ nullptr);
+ TEST_CHECK_MSG(false, "Survived execve? (thread)");
+ });
+ t.Join();
+ TEST_CHECK_MSG(false, "Survived execve? (main)");
+ _exit(1);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
+ << " status " << status;
+
+ // Enable PTRACE_O_TRACECLONE so we can get the ID of the child's non-leader
+ // thread, PTRACE_O_TRACEEXIT so we can observe the leader's death, and
+ // PTRACE_O_TRACEEXEC if required by the test. (The leader doesn't call
+ // execve, but options should be inherited across clone.)
+ long opts = PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT;
+ if (TraceExec()) {
+ opts |= PTRACE_O_TRACEEXEC;
+ }
+ ASSERT_THAT(ptrace(PTRACE_SETOPTIONS, child_pid, 0, opts), SyscallSucceeds());
+
+ // Suppress the SIGSTOP and wait for the child's leader thread to report
+ // PTRACE_EVENT_CLONE. Get the new thread's ID from the event.
+ ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_CLONE << 8), status >> 8);
+ unsigned long eventmsg;
+ ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &eventmsg),
+ SyscallSucceeds());
+ pid_t const nonleader_tid = eventmsg;
+ pid_t const leader_tid = child_pid;
+
+ // The new thread should be ptraced and in signal-delivery-stop by SIGSTOP due
+ // to PTRACE_O_TRACECLONE.
+ //
+ // Before bf959931ddb88c4e4366e96dd22e68fa0db9527c "wait/ptrace: assume __WALL
+ // if the child is traced" (4.7) , waiting on it requires __WCLONE since, as a
+ // non-leader, its termination signal is 0. After, a standard wait is
+ // sufficient.
+ ASSERT_THAT(waitpid(nonleader_tid, &status, __WCLONE),
+ SyscallSucceedsWithValue(nonleader_tid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
+ << " status " << status;
+
+ // Resume both child threads.
+ for (pid_t const tid : {leader_tid, nonleader_tid}) {
+ ASSERT_THAT(ptrace(PTRACE_CONT, tid, 0, 0), SyscallSucceeds());
+ }
+
+ // The non-leader child thread should call execve, causing the leader thread
+ // to enter PTRACE_EVENT_EXIT with an apparent exit code of 0. At this point,
+ // the leader has not yet exited, so the non-leader should be blocked in
+ // execve.
+ ASSERT_THAT(waitpid(leader_tid, &status, 0),
+ SyscallSucceedsWithValue(leader_tid));
+ EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_EXIT << 8), status >> 8);
+ ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, leader_tid, 0, &eventmsg),
+ SyscallSucceeds());
+ EXPECT_TRUE(WIFEXITED(eventmsg) && WEXITSTATUS(eventmsg) == 0)
+ << " eventmsg " << eventmsg;
+ EXPECT_THAT(waitpid(nonleader_tid, &status, __WCLONE | WNOHANG),
+ SyscallSucceedsWithValue(0));
+
+ // Allow the leader to continue exiting. This should allow the non-leader to
+ // complete its execve, causing the original leader to be reaped without
+ // further notice and the non-leader to steal its ID.
+ ASSERT_THAT(ptrace(PTRACE_CONT, leader_tid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(leader_tid, &status, 0),
+ SyscallSucceedsWithValue(leader_tid));
+ if (TraceExec()) {
+ // If PTRACE_O_TRACEEXEC was enabled, the execing thread should be in
+ // PTRACE_EVENT_EXEC-stop, with the event message set to its old thread ID.
+ EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_EXEC << 8), status >> 8);
+ ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, leader_tid, 0, &eventmsg),
+ SyscallSucceeds());
+ EXPECT_EQ(nonleader_tid, eventmsg);
+ } else {
+ // Otherwise, the execing thread should have received SIGTRAP and should now
+ // be in signal-delivery-stop.
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
+ << " status " << status;
+ }
+
+#ifdef __x86_64__
+ {
+ // CS should be 0x33, indicating an 64-bit binary.
+ constexpr uint64_t kAMD64UserCS = 0x33;
+ EXPECT_THAT(ptrace(PTRACE_PEEKUSER, leader_tid,
+ offsetof(struct user_regs_struct, cs), 0),
+ SyscallSucceedsWithValue(kAMD64UserCS));
+ struct user_regs_struct regs = {};
+ ASSERT_THAT(ptrace(PTRACE_GETREGS, leader_tid, 0, &regs),
+ SyscallSucceeds());
+ EXPECT_EQ(kAMD64UserCS, regs.cs);
+ }
+#endif // defined(__x86_64__)
+
+ // PTRACE_O_TRACEEXIT should have been inherited across execve. Send SIGKILL,
+ // which should end the PTRACE_EVENT_EXEC-stop or signal-delivery-stop and
+ // leave the child in PTRACE_EVENT_EXIT-stop.
+ ASSERT_THAT(kill(leader_tid, SIGKILL), SyscallSucceeds());
+ ASSERT_THAT(waitpid(leader_tid, &status, 0),
+ SyscallSucceedsWithValue(leader_tid));
+ EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_EXIT << 8), status >> 8);
+ ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, leader_tid, 0, &eventmsg),
+ SyscallSucceeds());
+ EXPECT_TRUE(WIFSIGNALED(eventmsg) && WTERMSIG(eventmsg) == SIGKILL)
+ << " eventmsg " << eventmsg;
+
+ // End the PTRACE_EVENT_EXIT stop, allowing the child to exit.
+ ASSERT_THAT(ptrace(PTRACE_CONT, leader_tid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(leader_tid, &status, 0),
+ SyscallSucceedsWithValue(leader_tid));
+ EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
+ << " status " << status;
+}
+
+INSTANTIATE_TEST_CASE_P(TraceExec, PtraceExecveTest, ::testing::Bool());
+
+// This test has expectations on when syscall-enter/exit-stops occur that are
+// violated if saving occurs, since saving interrupts all syscalls, causing
+// premature syscall-exit.
+TEST(PtraceTest,
+ ExitWhenParentIsNotTracer_Syscall_TraceVfork_TraceVforkDone_NoRandomSave) {
+ constexpr int kExitTraceeExitCode = 99;
+
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+
+ // Block SIGCHLD so it doesn't interrupt wait4.
+ sigset_t mask;
+ TEST_PCHECK(sigemptyset(&mask) == 0);
+ TEST_PCHECK(sigaddset(&mask, SIGCHLD) == 0);
+ TEST_PCHECK(sigprocmask(SIG_SETMASK, &mask, nullptr) == 0);
+ MaybeSave();
+
+ // Enable tracing, then raise SIGSTOP and expect our parent to suppress it.
+ TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
+ MaybeSave();
+ RaiseSignal(SIGSTOP);
+ MaybeSave();
+
+ // Spawn a vfork child that exits immediately, and reap it. Don't save
+ // after vfork since the parent expects to see wait4 as the next syscall.
+ pid_t const pid = vfork();
+ if (pid == 0) {
+ _exit(kExitTraceeExitCode);
+ }
+ TEST_PCHECK_MSG(pid > 0, "vfork failed");
+
+ int status;
+ TEST_PCHECK(wait4(pid, &status, 0, nullptr) > 0);
+ MaybeSave();
+ TEST_CHECK(WIFEXITED(status) && WEXITSTATUS(status) == kExitTraceeExitCode);
+ _exit(0);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
+ int status;
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
+ << " status " << status;
+
+ // Enable PTRACE_O_TRACEVFORK so we can get the ID of the grandchild,
+ // PTRACE_O_TRACEVFORKDONE so we can observe PTRACE_EVENT_VFORK_DONE, and
+ // PTRACE_O_TRACESYSGOOD so syscall-enter/exit-stops are unambiguously
+ // indicated by a stop signal of SIGTRAP|0x80 rather than just SIGTRAP.
+ ASSERT_THAT(ptrace(PTRACE_SETOPTIONS, child_pid, 0,
+ PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE |
+ PTRACE_O_TRACESYSGOOD),
+ SyscallSucceeds());
+
+ // Suppress the SIGSTOP and wait for the child to report PTRACE_EVENT_VFORK.
+ // Get the new process' ID from the event.
+ ASSERT_THAT(ptrace(PTRACE_CONT, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_VFORK << 8), status >> 8);
+ unsigned long eventmsg;
+ ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &eventmsg),
+ SyscallSucceeds());
+ pid_t const grandchild_pid = eventmsg;
+
+ // The grandchild should be traced by us and in signal-delivery-stop by
+ // SIGSTOP due to PTRACE_O_TRACEVFORK. This allows us to wait on it even
+ // though we're not its parent.
+ ASSERT_THAT(waitpid(grandchild_pid, &status, 0),
+ SyscallSucceedsWithValue(grandchild_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
+ << " status " << status;
+
+ // Resume the child with PTRACE_SYSCALL. Since the grandchild is still in
+ // signal-delivery-stop, the child should remain in vfork() waiting for the
+ // grandchild to exec or exit.
+ ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
+ absl::SleepFor(absl::Seconds(1));
+ ASSERT_THAT(waitpid(child_pid, &status, WNOHANG),
+ SyscallSucceedsWithValue(0));
+
+ // Suppress the grandchild's SIGSTOP and wait for the grandchild to exit. Pass
+ // WNOWAIT to waitid() so that we don't acknowledge the grandchild's exit yet.
+ ASSERT_THAT(ptrace(PTRACE_CONT, grandchild_pid, 0, 0), SyscallSucceeds());
+ siginfo_t siginfo = {};
+ ASSERT_THAT(waitid(P_PID, grandchild_pid, &siginfo, WEXITED | WNOWAIT),
+ SyscallSucceeds());
+ EXPECT_EQ(SIGCHLD, siginfo.si_signo);
+ EXPECT_EQ(CLD_EXITED, siginfo.si_code);
+ EXPECT_EQ(kExitTraceeExitCode, siginfo.si_status);
+ EXPECT_EQ(grandchild_pid, siginfo.si_pid);
+ EXPECT_EQ(getuid(), siginfo.si_uid);
+
+ // The child should now be in PTRACE_EVENT_VFORK_DONE stop. The event
+ // message should still be the grandchild's PID.
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_EQ(SIGTRAP | (PTRACE_EVENT_VFORK_DONE << 8), status >> 8);
+ ASSERT_THAT(ptrace(PTRACE_GETEVENTMSG, child_pid, 0, &eventmsg),
+ SyscallSucceeds());
+ EXPECT_EQ(grandchild_pid, eventmsg);
+
+ // Resume the child with PTRACE_SYSCALL again and expect it to enter
+ // syscall-exit-stop for vfork() or clone(), either of which should return the
+ // grandchild's PID from the syscall. Aside from PTRACE_O_TRACESYSGOOD,
+ // syscall-stops are distinguished from signal-delivery-stop by
+ // PTRACE_GETSIGINFO returning a siginfo for which si_code == SIGTRAP or
+ // SIGTRAP|0x80.
+ ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80))
+ << " status " << status;
+ ASSERT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo),
+ SyscallSucceeds());
+ EXPECT_TRUE(siginfo.si_code == SIGTRAP || siginfo.si_code == (SIGTRAP | 0x80))
+ << "si_code = " << siginfo.si_code;
+#ifdef __x86_64__
+ {
+ struct user_regs_struct regs = {};
+ ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, &regs), SyscallSucceeds());
+ EXPECT_TRUE(regs.orig_rax == SYS_vfork || regs.orig_rax == SYS_clone)
+ << "orig_rax = " << regs.orig_rax;
+ EXPECT_EQ(grandchild_pid, regs.rax);
+ }
+#endif // defined(__x86_64__)
+
+ // After this point, the child will be making wait4 syscalls that will be
+ // interrupted by saving, so saving is not permitted. Note that this is
+ // explicitly released below once the grandchild exits.
+ DisableSave ds;
+
+ // Resume the child with PTRACE_SYSCALL again and expect it to enter
+ // syscall-enter-stop for wait4().
+ ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80))
+ << " status " << status;
+ ASSERT_THAT(ptrace(PTRACE_GETSIGINFO, child_pid, 0, &siginfo),
+ SyscallSucceeds());
+ EXPECT_TRUE(siginfo.si_code == SIGTRAP || siginfo.si_code == (SIGTRAP | 0x80))
+ << "si_code = " << siginfo.si_code;
+#ifdef __x86_64__
+ {
+ EXPECT_THAT(ptrace(PTRACE_PEEKUSER, child_pid,
+ offsetof(struct user_regs_struct, orig_rax), 0),
+ SyscallSucceedsWithValue(SYS_wait4));
+ }
+#endif // defined(__x86_64__)
+
+ // Resume the child with PTRACE_SYSCALL again. Since the grandchild is
+ // waiting for the tracer (us) to acknowledge its exit first, wait4 should
+ // block.
+ ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
+ absl::SleepFor(absl::Seconds(1));
+ ASSERT_THAT(waitpid(child_pid, &status, WNOHANG),
+ SyscallSucceedsWithValue(0));
+
+ // Acknowledge the grandchild's exit.
+ ASSERT_THAT(waitpid(grandchild_pid, &status, 0),
+ SyscallSucceedsWithValue(grandchild_pid));
+ ds.reset();
+
+ // Now the child should enter syscall-exit-stop for wait4, returning with the
+ // grandchild's PID.
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80))
+ << " status " << status;
+#ifdef __x86_64__
+ {
+ struct user_regs_struct regs = {};
+ ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, &regs), SyscallSucceeds());
+ EXPECT_EQ(SYS_wait4, regs.orig_rax);
+ EXPECT_EQ(grandchild_pid, regs.rax);
+ }
+#endif // defined(__x86_64__)
+
+ // Detach from the child and wait for it to exit.
+ ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << " status " << status;
+}
+
+// These tests requires knowledge of architecture-specific syscall convention.
+#ifdef __x86_64__
+TEST(PtraceTest, Sysemu_PokeUser) {
+ constexpr int kSysemuHelperFirstExitCode = 126;
+ constexpr uint64_t kSysemuInjectedExitGroupReturn = 42;
+
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+
+ // Enable tracing, then raise SIGSTOP and expect our parent to suppress it.
+ TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
+ RaiseSignal(SIGSTOP);
+
+ // Try to exit_group, expecting the tracer to skip the syscall and set its
+ // own return value.
+ int const rv = syscall(SYS_exit_group, kSysemuHelperFirstExitCode);
+ TEST_PCHECK_MSG(rv == kSysemuInjectedExitGroupReturn,
+ "exit_group returned incorrect value");
+
+ _exit(0);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
+ << " status " << status;
+
+ // Suppress the SIGSTOP and wait for the child to enter syscall-enter-stop
+ // for its first exit_group syscall. glibc doesn't necessarily define
+ // PTRACE_SYSEMU.
+ constexpr auto kPtraceSysemu = static_cast<__ptrace_request>(31);
+ ASSERT_THAT(ptrace(kPtraceSysemu, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
+ << " status " << status;
+
+ struct user_regs_struct regs = {};
+ ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, &regs), SyscallSucceeds());
+ EXPECT_EQ(SYS_exit_group, regs.orig_rax);
+ EXPECT_EQ(-ENOSYS, regs.rax);
+ EXPECT_EQ(kSysemuHelperFirstExitCode, regs.rdi);
+
+ // Replace the exit_group return value, then resume the child, which should
+ // automatically skip the syscall.
+ ASSERT_THAT(
+ ptrace(PTRACE_POKEUSER, child_pid, offsetof(struct user_regs_struct, rax),
+ kSysemuInjectedExitGroupReturn),
+ SyscallSucceeds());
+ ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
+
+ // The child should validate the injected return value and then exit normally.
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << " status " << status;
+}
+
+// This test also cares about syscall-exit-stop.
+TEST(PtraceTest, ERESTART_NoRandomSave) {
+ constexpr int kSigno = SIGUSR1;
+
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // In child process.
+
+ // Ignore, but unblock, kSigno.
+ struct sigaction sa = {};
+ sa.sa_handler = SIG_IGN;
+ TEST_PCHECK(sigfillset(&sa.sa_mask) == 0);
+ TEST_PCHECK(sigaction(kSigno, &sa, nullptr) == 0);
+ MaybeSave();
+ TEST_PCHECK(sigprocmask(SIG_UNBLOCK, &sa.sa_mask, nullptr) == 0);
+ MaybeSave();
+
+ // Enable tracing, then raise SIGSTOP and expect our parent to suppress it.
+ TEST_PCHECK(ptrace(PTRACE_TRACEME, 0, 0, 0) == 0);
+ RaiseSignal(SIGSTOP);
+
+ // Invoke the pause syscall, which normally should not return until we
+ // receive a signal that "either terminates the process or causes the
+ // invocation of a signal-catching function".
+ pause();
+
+ _exit(0);
+ }
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Wait for the child to send itself SIGSTOP and enter signal-delivery-stop.
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP)
+ << " status " << status;
+
+ // After this point, the child's pause syscall will be interrupted by saving,
+ // so saving is not permitted. Note that this is explicitly released below
+ // once the child is stopped.
+ DisableSave ds;
+
+ // Suppress the SIGSTOP and wait for the child to enter syscall-enter-stop for
+ // its pause syscall.
+ ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
+ << " status " << status;
+
+ struct user_regs_struct regs = {};
+ ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, &regs), SyscallSucceeds());
+ EXPECT_EQ(SYS_pause, regs.orig_rax);
+ EXPECT_EQ(-ENOSYS, regs.rax);
+
+ // Resume the child with PTRACE_SYSCALL and expect it to block in the pause
+ // syscall.
+ ASSERT_THAT(ptrace(PTRACE_SYSCALL, child_pid, 0, 0), SyscallSucceeds());
+ absl::SleepFor(absl::Seconds(1));
+ ASSERT_THAT(waitpid(child_pid, &status, WNOHANG),
+ SyscallSucceedsWithValue(0));
+
+ // Send the child kSigno, causing it to return ERESTARTNOHAND and enter
+ // syscall-exit-stop from the pause syscall.
+ constexpr int ERESTARTNOHAND = 514;
+ ASSERT_THAT(kill(child_pid, kSigno), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
+ << " status " << status;
+ ds.reset();
+
+ ASSERT_THAT(ptrace(PTRACE_GETREGS, child_pid, 0, &regs), SyscallSucceeds());
+ EXPECT_EQ(SYS_pause, regs.orig_rax);
+ EXPECT_EQ(-ERESTARTNOHAND, regs.rax);
+
+ // Replace the return value from pause with 0, causing pause to not be
+ // restarted despite kSigno being ignored.
+ ASSERT_THAT(ptrace(PTRACE_POKEUSER, child_pid,
+ offsetof(struct user_regs_struct, rax), 0),
+ SyscallSucceeds());
+
+ // Detach from the child and wait for it to exit.
+ ASSERT_THAT(ptrace(PTRACE_DETACH, child_pid, 0, 0), SyscallSucceeds());
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << " status " << status;
+}
+#endif // defined(__x86_64__)
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor