aboutsummaryrefslogtreecommitdiffstats
path: root/capsicum-test.h
blob: 4251302e86815f90c482285fc4f846c457ae5d4f (plain) (blame)
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
/* -*- C++ -*- */
#ifndef CAPSICUM_TEST_H
#define CAPSICUM_TEST_H

#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <signal.h>

#include <ios>
#include <ostream>

#include "gtest/gtest.h"

extern bool verbose;
extern std::string tmpdir;
extern bool tmpdir_on_tmpfs;
extern bool force_mt;
extern bool force_nofork;
extern uid_t other_uid;

static inline void *WaitingThreadFn(void *) {
  // Loop until cancelled
  while (true) {
    usleep(10000);
    pthread_testcancel();
  }
  return NULL;
}

// If force_mt is set, run another thread in parallel with the test.  This forces
// the kernel into multi-threaded mode.
template <typename T, typename Function>
void MaybeRunWithThread(T *self, Function fn) {
  pthread_t subthread;
  if (force_mt) {
    pthread_create(&subthread, NULL, WaitingThreadFn, NULL);
  }
  (self->*fn)();
  if (force_mt) {
    pthread_cancel(subthread);
    pthread_join(subthread, NULL);
  }
}
template <typename Function>
void MaybeRunWithThread(Function fn) {
  pthread_t subthread;
  if (force_mt) {
    pthread_create(&subthread, NULL, WaitingThreadFn, NULL);
  }
  (fn)();
  if (force_mt) {
    pthread_cancel(subthread);
    pthread_join(subthread, NULL);
  }
}

// Return the absolute path of a filename in the temp directory, `tmpdir`,
// with the given pathname, e.g., "/tmp/<pathname>", if `tmpdir` was set to
// "/tmp".
const char *TmpFile(const char *pathname);

// Run the given test function in a forked process, so that trapdoor
// entry doesn't affect other tests, and watch out for hung processes.
// Implemented as a macro to allow access to the test case instance's
// HasFailure() method, which is reported as the forked process's
// exit status.
#define _RUN_FORKED(INNERCODE, TESTCASENAME, TESTNAME)         \
    pid_t pid = force_nofork ? 0 : fork();                     \
    if (pid == 0) {                                            \
      INNERCODE;                                               \
      if (!force_nofork) {                                     \
        exit(HasFailure());                                    \
      }                                                        \
    } else if (pid > 0) {                                      \
      int rc, status;                                          \
      int remaining_us = 10000000;                             \
      while (remaining_us > 0) {                               \
        status = 0;                                            \
        rc = waitpid(pid, &status, WNOHANG);                   \
        if (rc != 0) break;                                    \
        remaining_us -= 10000;                                 \
        usleep(10000);                                         \
      }                                                        \
      if (remaining_us <= 0) {                                 \
        fprintf(stderr, "Warning: killing unresponsive test "  \
                        "%s.%s (pid %d)\n",                    \
                        TESTCASENAME, TESTNAME, pid);          \
        kill(pid, SIGKILL);                                    \
        ADD_FAILURE() << "Test hung";                          \
      } else if (rc < 0) {                                     \
        fprintf(stderr, "Warning: waitpid error %s (%d)\n",    \
                        strerror(errno), errno);               \
        ADD_FAILURE() << "Failed to wait for child";           \
      } else {                                                 \
        int rc = WIFEXITED(status) ? WEXITSTATUS(status) : -1; \
        EXPECT_EQ(0, rc);                                      \
      }                                                        \
    }
#define _RUN_FORKED_MEM(THIS, TESTFN, TESTCASENAME, TESTNAME)  \
  _RUN_FORKED(MaybeRunWithThread(THIS, &TESTFN), TESTCASENAME, TESTNAME);
#define _RUN_FORKED_FN(TESTFN, TESTCASENAME, TESTNAME)   \
  _RUN_FORKED(MaybeRunWithThread(&TESTFN), TESTCASENAME, TESTNAME);

// Run a test case in a forked process, possibly cleaning up a
// test file after completion
#define FORK_TEST_ON(test_case_name, test_name, test_file)     \
    static void test_case_name##_##test_name##_ForkTest();     \
    TEST(test_case_name, test_name ## Forked) {                \
      _RUN_FORKED_FN(test_case_name##_##test_name##_ForkTest,  \
                     #test_case_name, #test_name);             \
      const char *filename = test_file;                        \
      if (filename) unlink(filename);                          \
    }                                                          \
    static void test_case_name##_##test_name##_ForkTest()

#define FORK_TEST(test_case_name, test_name) FORK_TEST_ON(test_case_name, test_name, NULL)

// Run a test case fixture in a forked process, so that trapdoors don't
// affect other tests.
#define ICLASS_NAME(test_case_name, test_name) Forked##test_case_name##_##test_name
#define FORK_TEST_F(test_case_name, test_name)                \
  class ICLASS_NAME(test_case_name, test_name) : public test_case_name { \
    public:                                                    \
      ICLASS_NAME(test_case_name, test_name)() {}              \
      void InnerTestBody();                                    \
    };                                                         \
    TEST_F(ICLASS_NAME(test_case_name, test_name), _) {        \
      _RUN_FORKED_MEM(this,                                    \
                      ICLASS_NAME(test_case_name, test_name)::InnerTestBody,  \
                      #test_case_name, #test_name);            \
    }                                                          \
    void ICLASS_NAME(test_case_name, test_name)::InnerTestBody()

// Emit errno information on failure
#define EXPECT_OK(v) EXPECT_LE(0, v) << "   errno " << errno << " " << strerror(errno)

// Expect a syscall to fail with the given error.
#define EXPECT_SYSCALL_FAIL(E, C) \
    do { \
      EXPECT_GT(0, C); \
      EXPECT_EQ(E, errno); \
    } while (0)

// Expect a syscall to fail with anything other than the given error.
#define EXPECT_SYSCALL_FAIL_NOT(E, C) \
    do { \
      EXPECT_GT(0, C); \
      EXPECT_NE(E, errno); \
    } while (0)

// Expect a void syscall to fail with anything other than the given error.
#define EXPECT_VOID_SYSCALL_FAIL_NOT(E, C)   \
    do { \
      errno = 0; \
      C; \
      EXPECT_NE(E, errno) << #C << " failed with ECAPMODE"; \
    } while (0)

// Expect a system call to fail due to path traversal; exact error
// code is OS-specific.
#ifdef O_BENEATH
#define EXPECT_OPENAT_FAIL_TRAVERSAL(fd, path, flags) \
    do { \
      const int result = openat((fd), (path), (flags)); \
      if (((flags) & O_BENEATH) == O_BENEATH) { \
        EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_O_BENEATH, result); \
      } else { \
        EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, result); \
      } \
    } while (0)
#else
#define EXPECT_OPENAT_FAIL_TRAVERSAL(fd, path, flags) \
    do { \
      const int result = openat((fd), (path), (flags)); \
      EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, result); \
    } while (0)
#endif

// Expect a system call to fail with ECAPMODE.
#define EXPECT_CAPMODE(C) EXPECT_SYSCALL_FAIL(ECAPMODE, C)

// Expect a system call to fail, but not with ECAPMODE.
#define EXPECT_FAIL_NOT_CAPMODE(C) EXPECT_SYSCALL_FAIL_NOT(ECAPMODE, C)
#define EXPECT_FAIL_VOID_NOT_CAPMODE(C) EXPECT_VOID_SYSCALL_FAIL_NOT(ECAPMODE, C)

// Expect a system call to fail with ENOTCAPABLE.
#define EXPECT_NOTCAPABLE(C) EXPECT_SYSCALL_FAIL(ENOTCAPABLE, C)

// Expect a system call to fail, but not with ENOTCAPABLE.
#define EXPECT_FAIL_NOT_NOTCAPABLE(C) EXPECT_SYSCALL_FAIL_NOT(ENOTCAPABLE, C)

// Expect a system call to fail with either ENOTCAPABLE or ECAPMODE.
#define EXPECT_CAPFAIL(C) \
    do { \
      int rc = C; \
      EXPECT_GT(0, rc); \
      EXPECT_TRUE(errno == ECAPMODE || errno == ENOTCAPABLE) \
        << #C << " did not fail with ECAPMODE/ENOTCAPABLE but " << errno; \
    } while (0)

// Ensure that 'rights' are a subset of 'max'.
#define EXPECT_RIGHTS_IN(rights, max) \
    EXPECT_TRUE(cap_rights_contains((max), (rights)))  \
    << "rights " << std::hex << *(rights) \
    << " not a subset of " << std::hex << *(max)

// Ensure rights are identical
#define EXPECT_RIGHTS_EQ(a, b) \
  do { \
    EXPECT_RIGHTS_IN((a), (b)); \
    EXPECT_RIGHTS_IN((b), (a)); \
  } while (0)

// Get the state of a process as a single character.
//  - 'D': disk wait
//  - 'R': runnable
//  - 'S': sleeping/idle
//  - 'T': stopped
//  - 'Z': zombie
// On error, return either '?' or '\0'.
char ProcessState(int pid);

// Check process state reaches a particular expected state (or two).
// Retries a few times to allow for timing issues.
#define EXPECT_PID_REACHES_STATES(pid, expected1, expected2) { \
  int counter = 5; \
  char state; \
  do { \
    state = ProcessState(pid); \
    if (state == expected1 || state == expected2) break; \
    usleep(100000); \
  } while (--counter > 0); \
  EXPECT_TRUE(state == expected1 || state == expected2) \
      << " pid " << pid << " in state " << state; \
}

#define EXPECT_PID_ALIVE(pid)   EXPECT_PID_REACHES_STATES(pid, 'R', 'S')
#define EXPECT_PID_DEAD(pid)    EXPECT_PID_REACHES_STATES(pid, 'Z', '\0')
#define EXPECT_PID_ZOMBIE(pid)  EXPECT_PID_REACHES_STATES(pid, 'Z', 'Z');
#define EXPECT_PID_GONE(pid)    EXPECT_PID_REACHES_STATES(pid, '\0', '\0');

void ShowSkippedTests(std::ostream& os);
void TestSkipped(const char *testcase, const char *test, const std::string& reason);
#define TEST_SKIPPED(reason) \
  do { \
    const ::testing::TestInfo* const info = ::testing::UnitTest::GetInstance()->current_test_info(); \
    std::cerr << "Skipping " << info->test_case_name() << "::" << info->name() << " because: " << reason << std::endl; \
    TestSkipped(info->test_case_name(), info->name(), reason);          \
  } while (0)

// Mark a test that can only be run as root.
#define REQUIRE_ROOT() \
  if (getuid() != 0) { \
    TEST_SKIPPED("requires root"); \
    return; \
  }

#endif  // CAPSICUM_TEST_H