summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/sigaltstack.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/sigaltstack.cc')
-rw-r--r--test/syscalls/linux/sigaltstack.cc274
1 files changed, 274 insertions, 0 deletions
diff --git a/test/syscalls/linux/sigaltstack.cc b/test/syscalls/linux/sigaltstack.cc
new file mode 100644
index 000000000..fa991545c
--- /dev/null
+++ b/test/syscalls/linux/sigaltstack.cc
@@ -0,0 +1,274 @@
+// 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 <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <functional>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "gtest/gtest.h"
+#include "test/util/cleanup.h"
+#include "test/util/fs_util.h"
+#include "test/util/multiprocess_util.h"
+#include "test/util/posix_error.h"
+#include "test/util/signal_util.h"
+#include "test/util/test_util.h"
+#include "test/util/thread_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+PosixErrorOr<Cleanup> ScopedSigaltstack(stack_t const& stack) {
+ stack_t old_stack;
+ int rc = sigaltstack(&stack, &old_stack);
+ MaybeSave();
+ if (rc < 0) {
+ return PosixError(errno, "sigaltstack failed");
+ }
+ return Cleanup([old_stack] {
+ EXPECT_THAT(sigaltstack(&old_stack, nullptr), SyscallSucceeds());
+ });
+}
+
+volatile bool got_signal = false;
+volatile int sigaltstack_errno = 0;
+volatile int ss_flags = 0;
+
+void sigaltstack_handler(int sig, siginfo_t* siginfo, void* arg) {
+ got_signal = true;
+
+ stack_t stack;
+ int ret = sigaltstack(nullptr, &stack);
+ MaybeSave();
+ if (ret < 0) {
+ sigaltstack_errno = errno;
+ return;
+ }
+ ss_flags = stack.ss_flags;
+}
+
+TEST(SigaltstackTest, Success) {
+ std::vector<char> stack_mem(SIGSTKSZ);
+ stack_t stack = {};
+ stack.ss_sp = stack_mem.data();
+ stack.ss_size = stack_mem.size();
+ auto const cleanup_sigstack =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack));
+
+ struct sigaction sa = {};
+ sa.sa_sigaction = sigaltstack_handler;
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
+ auto const cleanup_sa =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa));
+
+ // Send signal to this thread, as sigaltstack is per-thread.
+ EXPECT_THAT(tgkill(getpid(), gettid(), SIGUSR1), SyscallSucceeds());
+
+ EXPECT_TRUE(got_signal);
+ EXPECT_EQ(sigaltstack_errno, 0);
+ EXPECT_NE(0, ss_flags & SS_ONSTACK);
+}
+
+TEST(SigaltstackTest, ResetByExecve) {
+ std::vector<char> stack_mem(SIGSTKSZ);
+ stack_t stack = {};
+ stack.ss_sp = stack_mem.data();
+ stack.ss_size = stack_mem.size();
+ auto const cleanup_sigstack =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack));
+
+ std::string full_path;
+ char* test_src = getenv("TEST_SRCDIR");
+ if (test_src) {
+ full_path = JoinPath(test_src, "../../linux/sigaltstack_check");
+ }
+ ASSERT_FALSE(full_path.empty());
+
+ pid_t child_pid = -1;
+ int execve_errno = 0;
+ auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(
+ ForkAndExec(full_path, {"sigaltstack_check"}, {}, nullptr, &child_pid,
+ &execve_errno));
+
+ ASSERT_GT(child_pid, 0);
+ ASSERT_EQ(execve_errno, 0);
+
+ int status = 0;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0), SyscallSucceeds());
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+}
+
+volatile bool badhandler_on_sigaltstack = true; // Set by the handler.
+char* volatile badhandler_low_water_mark = nullptr; // Set by the handler.
+volatile uint8_t badhandler_recursive_faults = 0; // Consumed by the handler.
+
+void badhandler(int sig, siginfo_t* siginfo, void* arg) {
+ char stack_var = 0;
+ char* current_ss = &stack_var;
+
+ stack_t stack;
+ int ret = sigaltstack(nullptr, &stack);
+ if (ret < 0 || (stack.ss_flags & SS_ONSTACK) != SS_ONSTACK) {
+ // We should always be marked as being on the stack. Don't allow this to hit
+ // the bottom if this is ever not true (the main test will fail as a
+ // result, but we still need to unwind the recursive faults).
+ badhandler_on_sigaltstack = false;
+ }
+ if (current_ss < badhandler_low_water_mark) {
+ // Record the low point for the signal stack. We never expected this to be
+ // before stack bottom, but this is asserted in the actual test.
+ badhandler_low_water_mark = current_ss;
+ }
+ if (badhandler_recursive_faults > 0) {
+ badhandler_recursive_faults--;
+ Fault();
+ }
+ FixupFault(reinterpret_cast<ucontext*>(arg));
+}
+
+TEST(SigaltstackTest, WalksOffBottom) {
+ // This test marks the upper half of the stack_mem array as the signal stack.
+ // It asserts that when a fault occurs in the handler (already on the signal
+ // stack), we eventually continue to fault our way off the stack. We should
+ // not revert to the top of the signal stack when we fall off the bottom and
+ // the signal stack should remain "in use". When we fall off the signal stack,
+ // we should have an unconditional signal delivered and not start using the
+ // first part of the stack_mem array.
+ std::vector<char> stack_mem(SIGSTKSZ * 2);
+ stack_t stack = {};
+ stack.ss_sp = stack_mem.data() + SIGSTKSZ; // See above: upper half.
+ stack.ss_size = SIGSTKSZ; // Only one half the array.
+ auto const cleanup_sigstack =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack));
+
+ // Setup the handler: this must be for SIGSEGV, and it must allow proper
+ // nesting (no signal mask, no defer) so that we can trigger multiple times.
+ //
+ // When we walk off the bottom of the signal stack and force signal delivery
+ // of a SIGSEGV, the handler will revert to the default behavior (kill).
+ struct sigaction sa = {};
+ sa.sa_sigaction = badhandler;
+ sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_NODEFER;
+ auto const cleanup_sa =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
+
+ // Trigger a single fault.
+ badhandler_low_water_mark =
+ reinterpret_cast<char*>(&stack.ss_sp) + SIGSTKSZ; // Expected top.
+ badhandler_recursive_faults = 0; // Disable refault.
+ Fault();
+ EXPECT_TRUE(badhandler_on_sigaltstack);
+ EXPECT_THAT(sigaltstack(nullptr, &stack), SyscallSucceeds());
+ EXPECT_EQ(stack.ss_flags & SS_ONSTACK, 0);
+ EXPECT_LT(badhandler_low_water_mark,
+ reinterpret_cast<char*>(stack.ss_sp) + 2 * SIGSTKSZ);
+ EXPECT_GT(badhandler_low_water_mark, reinterpret_cast<char*>(stack.ss_sp));
+
+ // Trigger two faults.
+ char* prev_low_water_mark = badhandler_low_water_mark; // Previous top.
+ badhandler_recursive_faults = 1; // One refault.
+ Fault();
+ ASSERT_TRUE(badhandler_on_sigaltstack);
+ EXPECT_THAT(sigaltstack(nullptr, &stack), SyscallSucceeds());
+ EXPECT_EQ(stack.ss_flags & SS_ONSTACK, 0);
+ EXPECT_LT(badhandler_low_water_mark, prev_low_water_mark);
+ EXPECT_GT(badhandler_low_water_mark, reinterpret_cast<char*>(stack.ss_sp));
+
+ // Calculate the stack growth for a fault, and set the recursive faults to
+ // ensure that the signal handler stack required exceeds our marked stack area
+ // by a minimal amount. It should remain in the valid stack_mem area so that
+ // we can test the signal is forced merely by going out of the signal stack
+ // bounds, not by a genuine fault.
+ uintptr_t frame_size =
+ static_cast<uintptr_t>(prev_low_water_mark - badhandler_low_water_mark);
+ badhandler_recursive_faults = (SIGSTKSZ + frame_size) / frame_size;
+ EXPECT_EXIT(Fault(), ::testing::KilledBySignal(SIGSEGV), "");
+}
+
+volatile int setonstack_retval = 0; // Set by the handler.
+volatile int setonstack_errno = 0; // Set by the handler.
+
+void setonstack(int sig, siginfo_t* siginfo, void* arg) {
+ char stack_mem[SIGSTKSZ];
+ stack_t stack = {};
+ stack.ss_sp = &stack_mem[0];
+ stack.ss_size = SIGSTKSZ;
+ setonstack_retval = sigaltstack(&stack, nullptr);
+ setonstack_errno = errno;
+ FixupFault(reinterpret_cast<ucontext*>(arg));
+}
+
+TEST(SigaltstackTest, SetWhileOnStack) {
+ // Reserve twice as much stack here, since the handler will allocate a vector
+ // of size SIGTKSZ and attempt to set the sigaltstack to that value.
+ std::vector<char> stack_mem(2 * SIGSTKSZ);
+ stack_t stack = {};
+ stack.ss_sp = stack_mem.data();
+ stack.ss_size = stack_mem.size();
+ auto const cleanup_sigstack =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaltstack(stack));
+
+ // See above.
+ struct sigaction sa = {};
+ sa.sa_sigaction = setonstack;
+ sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
+ auto const cleanup_sa =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGSEGV, sa));
+
+ // Trigger a fault.
+ Fault();
+
+ // The set should have failed.
+ EXPECT_EQ(setonstack_retval, -1);
+ EXPECT_EQ(setonstack_errno, EPERM);
+}
+
+TEST(SigaltstackTest, SetCurrentStack) {
+ // This is executed as an exit test because once the signal stack is set to
+ // the local stack, there's no good way to unwind. We don't want to taint the
+ // test of any other tests that might run within this process.
+ EXPECT_EXIT(
+ {
+ char stack_value = 0;
+ stack_t stack = {};
+ stack.ss_sp = &stack_value - kPageSize; // Lower than current level.
+ stack.ss_size = 2 * kPageSize; // => &stack_value +/- kPageSize.
+ TEST_CHECK(sigaltstack(&stack, nullptr) == 0);
+ TEST_CHECK(sigaltstack(nullptr, &stack) == 0);
+ TEST_CHECK((stack.ss_flags & SS_ONSTACK) != 0);
+
+ // Should not be able to change the stack (even no-op).
+ TEST_CHECK(sigaltstack(&stack, nullptr) == -1 && errno == EPERM);
+
+ // Should not be able to disable the stack.
+ stack.ss_flags = SS_DISABLE;
+ TEST_CHECK(sigaltstack(&stack, nullptr) == -1 && errno == EPERM);
+ exit(0);
+ },
+ ::testing::ExitedWithCode(0), "");
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor