Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(437)

Side by Side Diff: sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc

Issue 10878033: Simplified unit testing of sandboxing code. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <ostream> 5 #include <ostream>
6 6
7 #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" 7 #include "sandbox/linux/seccomp-bpf/bpf_tests.h"
8 #include "sandbox/linux/seccomp-bpf/verifier.h" 8 #include "sandbox/linux/seccomp-bpf/verifier.h"
9 #include "testing/gtest/include/gtest/gtest.h" 9 #include "testing/gtest/include/gtest/gtest.h"
10 10
11 using namespace playground2; 11 using namespace playground2;
12 12
13 namespace { 13 namespace {
14 14
15 const int kExpectedReturnValue = 42; 15 const int kExpectedReturnValue = 42;
16 #if defined(__arm__) 16 #if defined(__arm__)
17 const int kArmPublicSysnoCeiling = __NR_SYSCALL_BASE + 1024; 17 const int kArmPublicSysnoCeiling = __NR_SYSCALL_BASE + 1024;
18 #endif 18 #endif
19 19
20 // This test should execute no matter whether we have kernel support. So,
21 // we make it a TEST() instead of a BPF_TEST().
20 TEST(SandboxBpf, CallSupports) { 22 TEST(SandboxBpf, CallSupports) {
21 // We check that we don't crash, but it's ok if the kernel doesn't 23 // We check that we don't crash, but it's ok if the kernel doesn't
22 // support it. 24 // support it.
23 bool seccomp_bpf_supported = 25 bool seccomp_bpf_supported =
24 Sandbox::supportsSeccompSandbox(-1) == Sandbox::STATUS_AVAILABLE; 26 Sandbox::supportsSeccompSandbox(-1) == Sandbox::STATUS_AVAILABLE;
25 // We want to log whether or not seccomp BPF is actually supported 27 // We want to log whether or not seccomp BPF is actually supported
26 // since actual test coverage depends on it. 28 // since actual test coverage depends on it.
27 RecordProperty("SeccompBPFSupported", 29 RecordProperty("SeccompBPFSupported",
28 seccomp_bpf_supported ? "true." : "false."); 30 seccomp_bpf_supported ? "true." : "false.");
29 std::cout << "Seccomp BPF supported: " 31 std::cout << "Seccomp BPF supported: "
30 << (seccomp_bpf_supported ? "true." : "false.") 32 << (seccomp_bpf_supported ? "true." : "false.")
31 << "\n"; 33 << "\n";
32 } 34 }
33 35
34 TEST(SandboxBpf, CallSupportsTwice) { 36 BPF_TEST(SandboxBpf, CallSupportsTwice) {
35 Sandbox::supportsSeccompSandbox(-1); 37 Sandbox::supportsSeccompSandbox(-1);
36 Sandbox::supportsSeccompSandbox(-1); 38 Sandbox::supportsSeccompSandbox(-1);
39 return true;
37 } 40 }
38 41
39 __attribute__((noreturn)) void DoCrash() { 42 __attribute__((noreturn)) void DoCrash() {
40 // Cause a #PF. This only works if we assume that we have the default 43 // Cause a #PF. This only works if we assume that we have the default
41 // SIGSEGV handler. 44 // SIGSEGV handler.
42 *(reinterpret_cast<volatile char*>(NULL)) = '\0'; 45 *(reinterpret_cast<volatile char*>(NULL)) = '\0';
43 for (;;) { 46 for (;;) {
44 // If we didn't manage to crash there is really nothing we can do reliably 47 // If we didn't manage to crash there is really nothing we can do reliably
45 // but spin. 48 // but spin.
46 } 49 }
47 } 50 }
48 51
49 __attribute__((noreturn)) void ExitGroup(int status) { 52 __attribute__((noreturn)) void ExitGroup(int status) {
50 syscall(__NR_exit_group, status); 53 syscall(__NR_exit_group, status);
51 // If exit_group() failed, there is a high likelihood this 54 // If exit_group() failed, there is a high likelihood this
52 // happened due to a bug in the sandbox. We therefore cannot 55 // happened due to a bug in the sandbox. We therefore cannot
53 // blindly assume that the failure happened because we are 56 // blindly assume that the failure happened because we are
54 // running on an old kernel that supports exit(), but not 57 // running on an old kernel that supports exit(), but not
55 // exit_group(). We cannot even trust "errno" returning 58 // exit_group(). We cannot even trust "errno" returning
56 // "ENOSYS". So, calling exit() as the fallback for 59 // "ENOSYS". So, calling exit() as the fallback for
57 // exit_group() would at this point almost certainly be a 60 // exit_group() would at this point almost certainly be a
58 // bug. Instead, try some more aggressive methods to make 61 // bug. Instead, try some more aggressive methods to make
59 // the program stop. 62 // the program stop.
60 DoCrash(); 63 DoCrash();
61 } 64 }
62 65
63 // Helper function to start a sandbox with a policy specified in 66 // Helper function to start a sandbox with a policy specified in
64 // evaluator 67 // evaluator
65 void StartSandboxOrDie(Sandbox::EvaluateSyscall evaluator) { 68 void StartSandboxOrDie(Sandbox::EvaluateSyscall evaluator) {
66 int proc_fd = open("/proc", O_RDONLY|O_DIRECTORY); 69 BPF_ASSERT(evaluator);
67 if (proc_fd < 0 || !evaluator) { 70
68 ExitGroup(1); 71 // Ensure the the sandbox is actually available at this time
69 } 72 int proc_fd;
70 if (Sandbox::supportsSeccompSandbox(proc_fd) != 73 BPF_ASSERT((proc_fd = open("/proc", O_RDONLY|O_DIRECTORY)) >= 0);
71 Sandbox::STATUS_AVAILABLE) { 74 BPF_ASSERT(Sandbox::supportsSeccompSandbox(proc_fd) ==
72 ExitGroup(1); 75 Sandbox::STATUS_AVAILABLE);
73 } 76
77 // Initialize and then start the sandbox with our custom policy
74 Sandbox::setProcFd(proc_fd); 78 Sandbox::setProcFd(proc_fd);
75 Sandbox::setSandboxPolicy(evaluator, NULL); 79 Sandbox::setSandboxPolicy(evaluator, NULL);
76 Sandbox::startSandbox(); 80 Sandbox::startSandbox();
77 } 81 }
78 82
79 void RunInSandbox(Sandbox::EvaluateSyscall evaluator,
80 void (*SandboxedCode)()) {
81 // TODO(markus): Implement IsEqual for ErrorCode
82 // IsEqual(evaluator(__NR_exit_group), Sandbox::SB_ALLOWED) <<
83 // "You need to always allow exit_group() in your test policy";
84 StartSandboxOrDie(evaluator);
85 // TODO(jln): find a way to use the testing framework inside
86 // SandboxedCode() or at the very least to surface errors
87 SandboxedCode();
88 // SandboxedCode() should have exited, this is a failure
89 ExitGroup(1);
90 }
91
92 // evaluator should always allow ExitGroup
93 // SandboxedCode should ExitGroup(kExpectedReturnValue) if and only if
94 // things go as expected.
95 void TryPolicyInProcess(Sandbox::EvaluateSyscall evaluator,
96 void (*SandboxedCode)()) {
97 // TODO(jln) figure out a way to surface whether we're actually testing
98 // something or not.
99 if (Sandbox::supportsSeccompSandbox(-1) == Sandbox::STATUS_AVAILABLE) {
100 EXPECT_EXIT(RunInSandbox(evaluator, SandboxedCode),
101 ::testing::ExitedWithCode(kExpectedReturnValue),
102 "");
103 } else {
104 // The sandbox is not available. We should still try to exercise what we
105 // can.
106 // TODO(markus): (crbug.com/141545) let us call the compiler from here.
107 Sandbox::setSandboxPolicy(evaluator, NULL);
108 }
109 }
110 83
111 // A simple blacklist test 84 // A simple blacklist test
112 85
113 Sandbox::ErrorCode BlacklistNanosleepPolicy(int sysno) { 86 Sandbox::ErrorCode BlacklistNanosleepPolicy(int sysno) {
114 if (sysno < static_cast<int>(MIN_SYSCALL) || 87 if (sysno < static_cast<int>(MIN_SYSCALL) ||
115 sysno > static_cast<int>(MAX_SYSCALL)) { 88 sysno > static_cast<int>(MAX_SYSCALL)) {
116 // FIXME: we should really not have to do that in a trivial policy 89 // FIXME: we should really not have to do that in a trivial policy
117 return ENOSYS; 90 return ENOSYS;
118 } 91 }
119 switch (sysno) { 92 switch (sysno) {
120 case __NR_nanosleep: 93 case __NR_nanosleep:
121 return EACCES; 94 return EACCES;
122 default: 95 default:
123 return Sandbox::SB_ALLOWED; 96 return Sandbox::SB_ALLOWED;
124 } 97 }
125 } 98 }
126 99
127 void NanosleepProcess(void) { 100 BPF_TEST(SandboxBpf, ApplyBasicBlacklistPolicy) {
101 StartSandboxOrDie(BlacklistNanosleepPolicy);
102
103 // nanosleep() should be denied
128 const struct timespec ts = {0, 0}; 104 const struct timespec ts = {0, 0};
129 errno = 0; 105 errno = 0;
130 if(syscall(__NR_nanosleep, &ts, NULL) != -1 || errno != EACCES) { 106 BPF_ASSERT(syscall(__NR_nanosleep, &ts, NULL) == -1);
131 ExitGroup(1); 107 BPF_ASSERT(errno == EACCES);
132 }
133 ExitGroup(kExpectedReturnValue);
134 }
135 108
136 TEST(SandboxBpf, ApplyBasicBlacklistPolicy) { 109 return true;
137 TryPolicyInProcess(BlacklistNanosleepPolicy, NanosleepProcess);
138 } 110 }
139 111
140 // Now do a simple whitelist test 112 // Now do a simple whitelist test
141 113
142 Sandbox::ErrorCode WhitelistGetpidPolicy(int sysno) { 114 Sandbox::ErrorCode WhitelistGetpidPolicy(int sysno) {
143 switch (sysno) { 115 switch (sysno) {
144 case __NR_getpid: 116 case __NR_getpid:
145 case __NR_exit_group: 117 case __NR_exit_group:
146 return Sandbox::SB_ALLOWED; 118 return Sandbox::SB_ALLOWED;
147 default: 119 default:
148 return ENOMEM; 120 return ENOMEM;
149 } 121 }
150 } 122 }
151 123
152 void GetpidProcess(void) { 124 BPF_TEST(SandboxBpf, ApplyBasicWhitelistPolicy) {
125 StartSandboxOrDie(WhitelistGetpidPolicy);
126
127 // getpid() should be allowed
153 errno = 0; 128 errno = 0;
154 // getpid() should be allowed 129 BPF_ASSERT(syscall(__NR_getpid) > 0);
155 if (syscall(__NR_getpid) < 0 || errno) 130 BPF_ASSERT(errno == 0);
156 ExitGroup(1); 131
157 // getpgid() should be denied 132 // getpgid() should be denied
158 if (getpgid(0) != -1 || errno != ENOMEM) 133 BPF_ASSERT(getpgid(0) == -1);
159 ExitGroup(1); 134 BPF_ASSERT(errno == ENOMEM);
160 ExitGroup(kExpectedReturnValue);
161 }
162 135
163 TEST(SandboxBpf, ApplyBasicWhitelistPolicy) { 136 return true;
164 TryPolicyInProcess(WhitelistGetpidPolicy, GetpidProcess);
165 } 137 }
166 138
167 // A simple blacklist policy, with a SIGSYS handler 139 // A simple blacklist policy, with a SIGSYS handler
168 140
169 // TODO: provide an API to provide the auxiliary data pointer 141 // TODO: provide an API to provide the auxiliary data pointer
170 // to the evaluator 142 // to the evaluator
171 143
172 static int BlacklistNanosleepPolicySigsysAuxData; 144 static int BlacklistNanosleepPolicySigsysAuxData;
173 145
174 intptr_t EnomemHandler(const struct arch_seccomp_data& args, void *aux) { 146 intptr_t EnomemHandler(const struct arch_seccomp_data& args, void *aux) {
(...skipping 12 matching lines...) Expand all
187 } 159 }
188 switch (sysno) { 160 switch (sysno) {
189 case __NR_nanosleep: 161 case __NR_nanosleep:
190 return Sandbox::ErrorCode(EnomemHandler, 162 return Sandbox::ErrorCode(EnomemHandler,
191 static_cast<void *>(&BlacklistNanosleepPolicySigsysAuxData)); 163 static_cast<void *>(&BlacklistNanosleepPolicySigsysAuxData));
192 default: 164 default:
193 return Sandbox::SB_ALLOWED; 165 return Sandbox::SB_ALLOWED;
194 } 166 }
195 } 167 }
196 168
197 void NanosleepProcessSigsys(void) { 169 BPF_TEST(SandboxBpf, BasicBlacklistWithSigsys) {
198 const struct timespec ts = {0, 0}; 170 StartSandboxOrDie(BlacklistNanosleepPolicySigsys);
171
172 // getpid() should work properly
199 errno = 0; 173 errno = 0;
200 // getpid() should work properly 174 BPF_ASSERT(syscall(__NR_getpid) > 0);
201 if (syscall(__NR_getpid) < 0) 175 BPF_ASSERT(errno == 0);
202 ExitGroup(1); 176
203 // Our Auxiliary Data, should be reset by the signal handler 177 // Our Auxiliary Data, should be reset by the signal handler
204 BlacklistNanosleepPolicySigsysAuxData = -1; 178 BlacklistNanosleepPolicySigsysAuxData = -1;
205 errno = 0; 179 const struct timespec ts = {0, 0};
206 if (syscall(__NR_nanosleep, &ts, NULL) != -1 || errno != ENOMEM) 180 BPF_ASSERT(syscall(__NR_nanosleep, &ts, NULL) == -1);
207 ExitGroup(1); 181 BPF_ASSERT(errno == ENOMEM);
182
208 // We expect the signal handler to modify AuxData 183 // We expect the signal handler to modify AuxData
209 if (BlacklistNanosleepPolicySigsysAuxData != kExpectedReturnValue) 184 BPF_ASSERT(
210 ExitGroup(1); 185 BlacklistNanosleepPolicySigsysAuxData == kExpectedReturnValue);
211 else
212 ExitGroup(kExpectedReturnValue);
213 }
214 186
215 TEST(SandboxBpf, BasicBlacklistWithSigsys) { 187 return true;
216 TryPolicyInProcess(BlacklistNanosleepPolicySigsys, NanosleepProcessSigsys);
217 } 188 }
218 189
219 // A more complex, but synthetic policy. This tests the correctness of the BPF 190 // A more complex, but synthetic policy. This tests the correctness of the BPF
220 // program by iterating through all syscalls and checking for an errno that 191 // program by iterating through all syscalls and checking for an errno that
221 // depends on the syscall number. Unlike the Verifier, this exercises the BPF 192 // depends on the syscall number. Unlike the Verifier, this exercises the BPF
222 // interpreter in the kernel. 193 // interpreter in the kernel.
223 194
224 // We try to make sure we exercise optimizations in the BPF compiler. We make 195 // We try to make sure we exercise optimizations in the BPF compiler. We make
225 // sure that the compiler can have an opportunity to coalesce syscalls with 196 // sure that the compiler can have an opportunity to coalesce syscalls with
226 // contiguous numbers and we also make sure that disjoint sets can return the 197 // contiguous numbers and we also make sure that disjoint sets can return the
(...skipping 18 matching lines...) Expand all
245 #endif 216 #endif
246 217
247 if (sysno == __NR_exit_group) { 218 if (sysno == __NR_exit_group) {
248 // exit_group() is special, we really need it to work. 219 // exit_group() is special, we really need it to work.
249 return Sandbox::SB_ALLOWED; 220 return Sandbox::SB_ALLOWED;
250 } else { 221 } else {
251 return SysnoToRandomErrno(sysno); 222 return SysnoToRandomErrno(sysno);
252 } 223 }
253 } 224 }
254 225
255 void SyntheticProcess(void) { 226 BPF_TEST(SandboxBpf, SyntheticPolicy) {
256 // Ensure that that kExpectedReturnValue + syscallnumber + 1 does not int 227 // Ensure that that kExpectedReturnValue + syscallnumber + 1 does not int
257 // overflow. 228 // overflow.
258 if (std::numeric_limits<int>::max() - kExpectedReturnValue - 1 < 229 BPF_ASSERT(
259 static_cast<int>(MAX_SYSCALL)) { 230 std::numeric_limits<int>::max() - kExpectedReturnValue - 1 >=
260 ExitGroup(1); 231 static_cast<int>(MAX_SYSCALL));
261 } 232
233 StartSandboxOrDie(SyntheticPolicy);
262 234
263 // TODO(jorgelo): remove this limit once crbug.com/141694 is fixed. 235 // TODO(jorgelo): remove this limit once crbug.com/141694 is fixed.
264 #if defined(__arm__) 236 #if defined(__arm__)
265 const int sysno_ceiling = kArmPublicSysnoCeiling; 237 const int sysno_ceiling = kArmPublicSysnoCeiling;
266 #else 238 #else
267 const int sysno_ceiling = static_cast<int>(MAX_SYSCALL); 239 const int sysno_ceiling = static_cast<int>(MAX_SYSCALL);
268 #endif 240 #endif
269 241
270 for (int syscall_number = static_cast<int>(MIN_SYSCALL); 242 for (int syscall_number = static_cast<int>(MIN_SYSCALL);
271 syscall_number <= sysno_ceiling; 243 syscall_number <= sysno_ceiling;
272 ++syscall_number) { 244 ++syscall_number) {
273 if (syscall_number == __NR_exit_group) { 245 if (syscall_number == __NR_exit_group) {
274 // exit_group() is special 246 // exit_group() is special
275 continue; 247 continue;
276 } 248 }
277 errno = 0; 249 errno = 0;
278 if (syscall(syscall_number) != -1 || 250 BPF_ASSERT(syscall(syscall_number) == -1);
279 errno != SysnoToRandomErrno(syscall_number)) { 251 BPF_ASSERT(errno == SysnoToRandomErrno(syscall_number));
280 // Exit with a return value that is different than kExpectedReturnValue
281 // to signal an error. Make it easy to see what syscall_number failed in
282 // the test report.
283 ExitGroup(kExpectedReturnValue + syscall_number + 1);
284 }
285 } 252 }
286 ExitGroup(kExpectedReturnValue); 253 return true;
287 }
288
289 TEST(SandboxBpf, SyntheticPolicy) {
290 TryPolicyInProcess(SyntheticPolicy, SyntheticProcess);
291 } 254 }
292 255
293 } // namespace 256 } // namespace
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698