summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/sigaltstack.cc
blob: 24e7c496042a748d4710684d4351db28c7fe1cee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
// 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 <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <functional>
#include <vector>

#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 = RunfilePath("test/syscalls/linux/sigaltstack_check");

  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_t*>(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 =
      static_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_t*>(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