Deflake PtraceTest.SeizeSetOptions.

PiperOrigin-RevId: 242226319
Change-Id: Iefc78656841315f6b7d48bd85db451486850264d
This commit is contained in:
Jamie Liu
2019-04-05 17:54:31 -07:00
committed by Shentubot
parent ee7e6d33b2
commit 124bafc81c
+37 -1
View File
@@ -1152,10 +1152,46 @@ TEST(PtraceTest, SeizeSetOptions) {
EXPECT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80))
<< " status " << status;
// SIGKILL the child (detaching the tracer) and wait for it to exit.
// Clean up the child.
ASSERT_THAT(kill(child_pid, SIGKILL), SyscallSucceeds());
ASSERT_THAT(waitpid(child_pid, &status, 0),
SyscallSucceedsWithValue(child_pid));
if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) {
// "SIGKILL kills even within system calls (syscall-exit-stop is not
// generated prior to death by SIGKILL). The net effect is that SIGKILL
// always kills the process (all its threads), even if some threads of the
// process are ptraced." - ptrace(2). This is technically true, but...
//
// When we send SIGKILL to the child, kernel/signal.c:complete_signal() =>
// signal_wake_up(resume=1) kicks the tracee out of the syscall-enter-stop.
// The pending SIGKILL causes the syscall to be skipped, but the child
// thread still reports syscall-exit before checking for pending signals; in
// current kernels, this is
// arch/x86/entry/common.c:syscall_return_slowpath() =>
// syscall_slow_exit_work() =>
// include/linux/tracehook.h:tracehook_report_syscall_exit() =>
// ptrace_report_syscall() => kernel/signal.c:ptrace_notify() =>
// ptrace_do_notify() => ptrace_stop().
//
// ptrace_stop() sets the task's state to TASK_TRACED and the task's
// exit_code to SIGTRAP|0x80 (passed by ptrace_report_syscall()), then calls
// freezable_schedule(). freezable_schedule() eventually reaches
// __schedule(), which detects signal_pending_state() due to the pending
// SIGKILL, sets the task's state back to TASK_RUNNING, and returns without
// descheduling. Thus, the task never enters syscall-exit-stop. However, if
// our wait4() => kernel/exit.c:wait_task_stopped() racily observes the
// TASK_TRACED state and the non-zero exit code set by ptrace_stop() before
// __schedule() sets the state back to TASK_RUNNING, it will return the
// task's exit_code as status W_STOPCODE(SIGTRAP|0x80). So we get a spurious
// syscall-exit-stop notification, and need to wait4() again for task exit.
//
// gVisor is not susceptible to this race because
// kernel.Task.waitCollectTraceeStopLocked() checks specifically for an
// active ptraceStop, which is not initiated if SIGKILL is pending.
LOG(INFO) << "Observed syscall-exit after SIGKILL";
ASSERT_THAT(waitpid(child_pid, &status, 0),
SyscallSucceedsWithValue(child_pid));
}
EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
<< " status " << status;
}