diff options
Diffstat (limited to 'test/syscalls/linux/rtsignal.cc')
-rw-r--r-- | test/syscalls/linux/rtsignal.cc | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/test/syscalls/linux/rtsignal.cc b/test/syscalls/linux/rtsignal.cc new file mode 100644 index 000000000..ed27e2566 --- /dev/null +++ b/test/syscalls/linux/rtsignal.cc @@ -0,0 +1,171 @@ +// Copyright 2018 The gVisor Authors. +// +// 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 <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <cerrno> +#include <csignal> + +#include "gtest/gtest.h" +#include "test/util/cleanup.h" +#include "test/util/logging.h" +#include "test/util/posix_error.h" +#include "test/util/signal_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +// saved_info is set by the handler. +siginfo_t saved_info; + +// has_saved_info is set to true by the handler. +volatile bool has_saved_info; + +void SigHandler(int sig, siginfo_t* info, void* context) { + // Copy to the given info. + saved_info = *info; + has_saved_info = true; +} + +void ClearSavedInfo() { + // Clear the cached info. + memset(&saved_info, 0, sizeof(saved_info)); + has_saved_info = false; +} + +PosixErrorOr<Cleanup> SetupSignalHandler(int sig) { + struct sigaction sa; + sa.sa_sigaction = SigHandler; + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + return ScopedSigaction(sig, sa); +} + +class RtSignalTest : public ::testing::Test { + protected: + void SetUp() override { + action_cleanup_ = ASSERT_NO_ERRNO_AND_VALUE(SetupSignalHandler(SIGUSR1)); + mask_cleanup_ = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGUSR1)); + } + + void TearDown() override { ClearSavedInfo(); } + + private: + Cleanup action_cleanup_; + Cleanup mask_cleanup_; +}; + +static int rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t* uinfo) { + int ret; + do { + // NOTE(b/25434735): rt_sigqueueinfo(2) could return EAGAIN for RT signals. + ret = syscall(SYS_rt_sigqueueinfo, tgid, sig, uinfo); + } while (ret == -1 && errno == EAGAIN); + return ret; +} + +TEST_F(RtSignalTest, InvalidTID) { + siginfo_t uinfo; + // Depending on the kernel version, these calls may fail with + // ESRCH (goobunutu machines) or EPERM (production machines). Thus, + // the test simply ensures that they do fail. + EXPECT_THAT(rt_sigqueueinfo(-1, SIGUSR1, &uinfo), SyscallFails()); + EXPECT_FALSE(has_saved_info); + EXPECT_THAT(rt_sigqueueinfo(0, SIGUSR1, &uinfo), SyscallFails()); + EXPECT_FALSE(has_saved_info); +} + +TEST_F(RtSignalTest, InvalidCodes) { + siginfo_t uinfo; + + // We need a child for the code checks to apply. If the process is delivering + // to itself, then it can use whatever codes it wants and they will go + // through. + pid_t child = fork(); + if (child == 0) { + _exit(1); + } + ASSERT_THAT(child, SyscallSucceeds()); + + // These are not allowed for child processes. + uinfo.si_code = 0; // SI_USER. + EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo), + SyscallFailsWithErrno(EPERM)); + uinfo.si_code = 0x80; // SI_KERNEL. + EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo), + SyscallFailsWithErrno(EPERM)); + uinfo.si_code = -6; // SI_TKILL. + EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo), + SyscallFailsWithErrno(EPERM)); + uinfo.si_code = -1; // SI_QUEUE (allowed). + EXPECT_THAT(rt_sigqueueinfo(child, SIGUSR1, &uinfo), SyscallSucceeds()); + + // Join the child process. + EXPECT_THAT(waitpid(child, nullptr, 0), SyscallSucceeds()); +} + +TEST_F(RtSignalTest, ValueDelivered) { + siginfo_t uinfo; + uinfo.si_code = -1; // SI_QUEUE (allowed). + uinfo.si_errno = 0x1234; + + EXPECT_EQ(saved_info.si_errno, 0x0); + EXPECT_THAT(rt_sigqueueinfo(getpid(), SIGUSR1, &uinfo), SyscallSucceeds()); + EXPECT_TRUE(has_saved_info); + EXPECT_EQ(saved_info.si_errno, 0x1234); +} + +TEST_F(RtSignalTest, SignoMatch) { + auto action2_cleanup = ASSERT_NO_ERRNO_AND_VALUE(SetupSignalHandler(SIGUSR2)); + auto mask2_cleanup = + ASSERT_NO_ERRNO_AND_VALUE(ScopedSignalMask(SIG_UNBLOCK, SIGUSR2)); + + siginfo_t uinfo; + uinfo.si_code = -1; // SI_QUEUE (allowed). + + EXPECT_THAT(rt_sigqueueinfo(getpid(), SIGUSR1, &uinfo), SyscallSucceeds()); + EXPECT_TRUE(has_saved_info); + EXPECT_EQ(saved_info.si_signo, SIGUSR1); + + ClearSavedInfo(); + + EXPECT_THAT(rt_sigqueueinfo(getpid(), SIGUSR2, &uinfo), SyscallSucceeds()); + EXPECT_TRUE(has_saved_info); + EXPECT_EQ(saved_info.si_signo, SIGUSR2); +} + +} // namespace + +} // namespace testing +} // namespace gvisor + +int main(int argc, char** argv) { + // These tests depend on delivering SIGUSR1/2 to the main thread (so they can + // synchronously check has_saved_info). Block these so that any other threads + // created by TestInit will also have them blocked. + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + sigaddset(&set, SIGUSR2); + TEST_PCHECK(sigprocmask(SIG_BLOCK, &set, nullptr) == 0); + + gvisor::testing::TestInit(&argc, &argv); + return gvisor::testing::RunAllTests(); +} |