diff options
author | Michael Pratt <mpratt@google.com> | 2019-04-17 13:42:16 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2019-04-17 13:43:20 -0700 |
commit | b52cbd60280342f25411561702e97fe650fdaa9c (patch) | |
tree | 66cdc3cc800c07b708ab282d9323f11886834d83 /test/syscalls | |
parent | c8cee7108f1a1b37e89961c6dd69ccab97952c86 (diff) |
Don't allow sigtimedwait to catch unblockable signals
The existing logic attempting to do this is incorrect. Unary ^ has
higher precedence than &^, so mask always has UnblockableSignals
cleared, allowing dequeueSignalLocked to dequeue unblockable signals
(which allows userspace to ignore them).
Switch the logic so that unblockable signals are always masked.
PiperOrigin-RevId: 244058487
Change-Id: Ib19630ac04068a1fbfb9dc4a8eab1ccbdb21edc3
Diffstat (limited to 'test/syscalls')
-rw-r--r-- | test/syscalls/linux/BUILD | 1 | ||||
-rw-r--r-- | test/syscalls/linux/sigtimedwait.cc | 76 |
2 files changed, 77 insertions, 0 deletions
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 38faba267..d99733fc9 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1825,6 +1825,7 @@ cc_binary( srcs = ["sigtimedwait.cc"], linkstatic = 1, deps = [ + "//test/util:file_descriptor", "//test/util:logging", "//test/util:signal_util", "//test/util:test_util", diff --git a/test/syscalls/linux/sigtimedwait.cc b/test/syscalls/linux/sigtimedwait.cc index 3a350fc28..1df9c013f 100644 --- a/test/syscalls/linux/sigtimedwait.cc +++ b/test/syscalls/linux/sigtimedwait.cc @@ -18,6 +18,7 @@ #include "gtest/gtest.h" #include "absl/time/clock.h" #include "absl/time/time.h" +#include "test/util/file_descriptor.h" #include "test/util/logging.h" #include "test/util/signal_util.h" #include "test/util/test_util.h" @@ -163,6 +164,81 @@ TEST(SigtimedwaitTest, ChildExitGeneratedSIGCHLDWithHandler) { EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) << status; } +// sigtimedwait cannot catch SIGKILL. +TEST(SigtimedwaitTest, SIGKILLUncaught) { + // This is a regression test for sigtimedwait dequeuing SIGKILLs, thus + // preventing the task from exiting. + // + // The explanation below is specific to behavior in gVisor. The Linux behavior + // here is irrelevant because without a bug that prevents delivery of SIGKILL, + // none of this behavior is visible (in Linux or gVisor). + // + // SIGKILL is rather intrusive. Simply sending the SIGKILL marks + // ThreadGroup.exitStatus as exiting with SIGKILL, before the SIGKILL is even + // delivered. + // + // As a result, we cannot simply exit the child with a different exit code if + // it survives and expect to see that code in waitpid because: + // 1. PrepareGroupExit will override Task.exitStatus with + // ThreadGroup.exitStatus. + // 2. waitpid(2) will always return ThreadGroup.exitStatus rather than + // Task.exitStatus. + // + // We could use exit(2) to set Task.exitStatus without override, and a SIGCHLD + // handler to receive Task.exitStatus in the parent, but with that much + // test complexity, it is cleaner to simply use a pipe to notify the parent + // that we survived. + constexpr auto kSigtimedwaitSetupTime = absl::Seconds(2); + + int pipe_fds[2]; + ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); + FileDescriptor rfd(pipe_fds[0]); + FileDescriptor wfd(pipe_fds[1]); + + pid_t pid = fork(); + if (pid == 0) { + rfd.reset(); + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGKILL); + RetryEINTR(sigtimedwait)(&mask, nullptr, nullptr); + + // Survived. + char c = 'a'; + TEST_PCHECK(WriteFd(wfd.get(), &c, 1) == 1); + _exit(1); + } + ASSERT_THAT(pid, SyscallSucceeds()); + + wfd.reset(); + + // Wait for child to block in sigtimedwait, then kill it. + absl::SleepFor(kSigtimedwaitSetupTime); + + // Sending SIGKILL will attempt to enqueue the signal twice: once in the + // normal signal sending path, and once to all Tasks in the ThreadGroup when + // applying SIGKILL side-effects. + // + // If we use kill(2), the former will be on the ThreadGroup signal queue and + // the latter will be on the Task signal queue. sigtimedwait can only dequeue + // one signal, so the other would kill the Task, masking bugs. + // + // If we use tkill(2), the former will be on the Task signal queue and the + // latter will be dropped as a duplicate. Then sigtimedwait can theoretically + // dequeue the single SIGKILL. + EXPECT_THAT(syscall(SYS_tkill, pid, SIGKILL), SyscallSucceeds()); + + int status; + EXPECT_THAT(RetryEINTR(waitpid)(pid, &status, 0), + SyscallSucceedsWithValue(pid)); + EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) << status; + + // Child shouldn't have survived. + char c; + EXPECT_THAT(ReadFd(rfd.get(), &c, 1), SyscallSucceedsWithValue(0)); +} + TEST(SigtimedwaitTest, IgnoredUnmaskedSignal) { constexpr int kSigno = SIGUSR1; constexpr auto kSigtimedwaitSetupTime = absl::Seconds(2); |