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

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: Addressed reviewer's comments 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, NULL) {
35 Sandbox::supportsSeccompSandbox(-1); 37 Sandbox::supportsSeccompSandbox(-1);
36 Sandbox::supportsSeccompSandbox(-1); 38 Sandbox::supportsSeccompSandbox(-1);
37 } 39 }
38 40
39 __attribute__((noreturn)) void DoCrash() {
40 // Cause a #PF. This only works if we assume that we have the default
41 // SIGSEGV handler.
42 *(reinterpret_cast<volatile char*>(NULL)) = '\0';
43 for (;;) {
44 // If we didn't manage to crash there is really nothing we can do reliably
45 // but spin.
46 }
47 }
48
49 __attribute__((noreturn)) void ExitGroup(int status) {
50 syscall(__NR_exit_group, status);
51 // If exit_group() failed, there is a high likelihood this
52 // happened due to a bug in the sandbox. We therefore cannot
53 // blindly assume that the failure happened because we are
54 // running on an old kernel that supports exit(), but not
55 // exit_group(). We cannot even trust "errno" returning
56 // "ENOSYS". So, calling exit() as the fallback for
57 // exit_group() would at this point almost certainly be a
58 // bug. Instead, try some more aggressive methods to make
59 // the program stop.
60 DoCrash();
61 }
62
63 // Helper function to start a sandbox with a policy specified in 41 // Helper function to start a sandbox with a policy specified in
64 // evaluator 42 // evaluator
65 void StartSandboxOrDie(Sandbox::EvaluateSyscall evaluator) { 43 void StartSandboxOrDie() {
66 int proc_fd = open("/proc", O_RDONLY|O_DIRECTORY); 44 // Ensure the the sandbox is actually available at this time
67 if (proc_fd < 0 || !evaluator) { 45 int proc_fd;
68 ExitGroup(1); 46 BPF_ASSERT((proc_fd = open("/proc", O_RDONLY|O_DIRECTORY)) >= 0);
69 } 47 BPF_ASSERT(Sandbox::supportsSeccompSandbox(proc_fd) ==
70 if (Sandbox::supportsSeccompSandbox(proc_fd) != 48 Sandbox::STATUS_AVAILABLE);
71 Sandbox::STATUS_AVAILABLE) { 49
72 ExitGroup(1); 50 // Initialize and then start the sandbox with our custom policy
73 }
74 Sandbox::setProcFd(proc_fd); 51 Sandbox::setProcFd(proc_fd);
75 Sandbox::setSandboxPolicy(evaluator, NULL);
76 Sandbox::startSandbox(); 52 Sandbox::startSandbox();
77 } 53 }
78 54
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 55
111 // A simple blacklist test 56 // A simple blacklist test
112 57
113 Sandbox::ErrorCode BlacklistNanosleepPolicy(int sysno) { 58 Sandbox::ErrorCode BlacklistNanosleepPolicy(int sysno) {
114 if (sysno < static_cast<int>(MIN_SYSCALL) || 59 if (sysno < static_cast<int>(MIN_SYSCALL) ||
115 sysno > static_cast<int>(MAX_SYSCALL)) { 60 sysno > static_cast<int>(MAX_SYSCALL)) {
116 // FIXME: we should really not have to do that in a trivial policy 61 // FIXME: we should really not have to do that in a trivial policy
117 return ENOSYS; 62 return ENOSYS;
118 } 63 }
119 switch (sysno) { 64 switch (sysno) {
120 case __NR_nanosleep: 65 case __NR_nanosleep:
121 return EACCES; 66 return EACCES;
122 default: 67 default:
123 return Sandbox::SB_ALLOWED; 68 return Sandbox::SB_ALLOWED;
124 } 69 }
125 } 70 }
126 71
127 void NanosleepProcess(void) { 72 BPF_TEST(SandboxBpf, ApplyBasicBlacklistPolicy, BlacklistNanosleepPolicy) {
73 StartSandboxOrDie();
74
75 // nanosleep() should be denied
128 const struct timespec ts = {0, 0}; 76 const struct timespec ts = {0, 0};
129 errno = 0; 77 errno = 0;
130 if(syscall(__NR_nanosleep, &ts, NULL) != -1 || errno != EACCES) { 78 BPF_ASSERT(syscall(__NR_nanosleep, &ts, NULL) == -1);
131 ExitGroup(1); 79 BPF_ASSERT(errno == EACCES);
132 }
133 ExitGroup(kExpectedReturnValue);
134 }
135
136 TEST(SandboxBpf, ApplyBasicBlacklistPolicy) {
137 TryPolicyInProcess(BlacklistNanosleepPolicy, NanosleepProcess);
138 } 80 }
139 81
140 // Now do a simple whitelist test 82 // Now do a simple whitelist test
141 83
142 Sandbox::ErrorCode WhitelistGetpidPolicy(int sysno) { 84 Sandbox::ErrorCode WhitelistGetpidPolicy(int sysno) {
143 switch (sysno) { 85 switch (sysno) {
144 case __NR_getpid: 86 case __NR_getpid:
145 case __NR_exit_group: 87 case __NR_exit_group:
146 return Sandbox::SB_ALLOWED; 88 return Sandbox::SB_ALLOWED;
147 default: 89 default:
148 return ENOMEM; 90 return ENOMEM;
149 } 91 }
150 } 92 }
151 93
152 void GetpidProcess(void) { 94 BPF_TEST(SandboxBpf, ApplyBasicWhitelistPolicy, WhitelistGetpidPolicy) {
95 StartSandboxOrDie();
96
97 // getpid() should be allowed
153 errno = 0; 98 errno = 0;
154 // getpid() should be allowed 99 BPF_ASSERT(syscall(__NR_getpid) > 0);
155 if (syscall(__NR_getpid) < 0 || errno) 100 BPF_ASSERT(errno == 0);
156 ExitGroup(1); 101
157 // getpgid() should be denied 102 // getpgid() should be denied
158 if (getpgid(0) != -1 || errno != ENOMEM) 103 BPF_ASSERT(getpgid(0) == -1);
159 ExitGroup(1); 104 BPF_ASSERT(errno == ENOMEM);
160 ExitGroup(kExpectedReturnValue);
161 }
162
163 TEST(SandboxBpf, ApplyBasicWhitelistPolicy) {
164 TryPolicyInProcess(WhitelistGetpidPolicy, GetpidProcess);
165 } 105 }
166 106
167 // A simple blacklist policy, with a SIGSYS handler 107 // A simple blacklist policy, with a SIGSYS handler
168 108
169 // TODO: provide an API to provide the auxiliary data pointer 109 // TODO: provide an API to provide the auxiliary data pointer
170 // to the evaluator 110 // to the evaluator
171 111
172 static int BlacklistNanosleepPolicySigsysAuxData; 112 static int BlacklistNanosleepPolicySigsysAuxData;
173 113
174 intptr_t EnomemHandler(const struct arch_seccomp_data& args, void *aux) { 114 intptr_t EnomemHandler(const struct arch_seccomp_data& args, void *aux) {
175 // We also check that the auxiliary data is correct 115 // We also check that the auxiliary data is correct
176 if (!aux) 116 SANDBOX_ASSERT(aux);
177 ExitGroup(1);
178 *(static_cast<int*>(aux)) = kExpectedReturnValue; 117 *(static_cast<int*>(aux)) = kExpectedReturnValue;
179 return -ENOMEM; 118 return -ENOMEM;
180 } 119 }
181 120
182 Sandbox::ErrorCode BlacklistNanosleepPolicySigsys(int sysno) { 121 Sandbox::ErrorCode BlacklistNanosleepPolicySigsys(int sysno) {
183 if (sysno < static_cast<int>(MIN_SYSCALL) || 122 if (sysno < static_cast<int>(MIN_SYSCALL) ||
184 sysno > static_cast<int>(MAX_SYSCALL)) { 123 sysno > static_cast<int>(MAX_SYSCALL)) {
185 // FIXME: we should really not have to do that in a trivial policy 124 // FIXME: we should really not have to do that in a trivial policy
186 return ENOSYS; 125 return ENOSYS;
187 } 126 }
188 switch (sysno) { 127 switch (sysno) {
189 case __NR_nanosleep: 128 case __NR_nanosleep:
190 return Sandbox::ErrorCode(EnomemHandler, 129 return Sandbox::ErrorCode(EnomemHandler,
191 static_cast<void *>(&BlacklistNanosleepPolicySigsysAuxData)); 130 static_cast<void *>(&BlacklistNanosleepPolicySigsysAuxData));
192 default: 131 default:
193 return Sandbox::SB_ALLOWED; 132 return Sandbox::SB_ALLOWED;
194 } 133 }
195 } 134 }
196 135
197 void NanosleepProcessSigsys(void) { 136 BPF_TEST(SandboxBpf, BasicBlacklistWithSigsys,
198 const struct timespec ts = {0, 0}; 137 BlacklistNanosleepPolicySigsys) {
138 StartSandboxOrDie();
139
140 // getpid() should work properly
199 errno = 0; 141 errno = 0;
200 // getpid() should work properly 142 BPF_ASSERT(syscall(__NR_getpid) > 0);
201 if (syscall(__NR_getpid) < 0) 143 BPF_ASSERT(errno == 0);
202 ExitGroup(1); 144
203 // Our Auxiliary Data, should be reset by the signal handler 145 // Our Auxiliary Data, should be reset by the signal handler
204 BlacklistNanosleepPolicySigsysAuxData = -1; 146 BlacklistNanosleepPolicySigsysAuxData = -1;
205 errno = 0; 147 const struct timespec ts = {0, 0};
206 if (syscall(__NR_nanosleep, &ts, NULL) != -1 || errno != ENOMEM) 148 BPF_ASSERT(syscall(__NR_nanosleep, &ts, NULL) == -1);
207 ExitGroup(1); 149 BPF_ASSERT(errno == ENOMEM);
150
208 // We expect the signal handler to modify AuxData 151 // We expect the signal handler to modify AuxData
209 if (BlacklistNanosleepPolicySigsysAuxData != kExpectedReturnValue) 152 BPF_ASSERT(
210 ExitGroup(1); 153 BlacklistNanosleepPolicySigsysAuxData == kExpectedReturnValue);
211 else
212 ExitGroup(kExpectedReturnValue);
213 }
214
215 TEST(SandboxBpf, BasicBlacklistWithSigsys) {
216 TryPolicyInProcess(BlacklistNanosleepPolicySigsys, NanosleepProcessSigsys);
217 } 154 }
218 155
219 // A more complex, but synthetic policy. This tests the correctness of the BPF 156 // 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 157 // 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 158 // depends on the syscall number. Unlike the Verifier, this exercises the BPF
222 // interpreter in the kernel. 159 // interpreter in the kernel.
223 160
224 // We try to make sure we exercise optimizations in the BPF compiler. We make 161 // 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 162 // 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 163 // contiguous numbers and we also make sure that disjoint sets can return the
(...skipping 18 matching lines...) Expand all
245 #endif 182 #endif
246 183
247 if (sysno == __NR_exit_group) { 184 if (sysno == __NR_exit_group) {
248 // exit_group() is special, we really need it to work. 185 // exit_group() is special, we really need it to work.
249 return Sandbox::SB_ALLOWED; 186 return Sandbox::SB_ALLOWED;
250 } else { 187 } else {
251 return SysnoToRandomErrno(sysno); 188 return SysnoToRandomErrno(sysno);
252 } 189 }
253 } 190 }
254 191
255 void SyntheticProcess(void) { 192 BPF_TEST(SandboxBpf, SyntheticPolicy, SyntheticPolicy) {
193 StartSandboxOrDie();
194
256 // Ensure that that kExpectedReturnValue + syscallnumber + 1 does not int 195 // Ensure that that kExpectedReturnValue + syscallnumber + 1 does not int
257 // overflow. 196 // overflow.
258 if (std::numeric_limits<int>::max() - kExpectedReturnValue - 1 < 197 BPF_ASSERT(
259 static_cast<int>(MAX_SYSCALL)) { 198 std::numeric_limits<int>::max() - kExpectedReturnValue - 1 >=
260 ExitGroup(1); 199 static_cast<int>(MAX_SYSCALL));
261 }
262 200
263 // TODO(jorgelo): remove this limit once crbug.com/141694 is fixed. 201 // TODO(jorgelo): remove this limit once crbug.com/141694 is fixed.
264 #if defined(__arm__) 202 #if defined(__arm__)
265 const int sysno_ceiling = kArmPublicSysnoCeiling; 203 const int sysno_ceiling = kArmPublicSysnoCeiling;
266 #else 204 #else
267 const int sysno_ceiling = static_cast<int>(MAX_SYSCALL); 205 const int sysno_ceiling = static_cast<int>(MAX_SYSCALL);
268 #endif 206 #endif
269 207
270 for (int syscall_number = static_cast<int>(MIN_SYSCALL); 208 for (int syscall_number = static_cast<int>(MIN_SYSCALL);
271 syscall_number <= sysno_ceiling; 209 syscall_number <= sysno_ceiling;
272 ++syscall_number) { 210 ++syscall_number) {
273 if (syscall_number == __NR_exit_group) { 211 if (syscall_number == __NR_exit_group) {
274 // exit_group() is special 212 // exit_group() is special
275 continue; 213 continue;
276 } 214 }
277 errno = 0; 215 errno = 0;
278 if (syscall(syscall_number) != -1 || 216 BPF_ASSERT(syscall(syscall_number) == -1);
jln (very slow on Chromium) 2012/08/24 04:00:32 This won't output anything in this test to stderr
279 errno != SysnoToRandomErrno(syscall_number)) { 217 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 } 218 }
286 ExitGroup(kExpectedReturnValue);
287 }
288
289 TEST(SandboxBpf, SyntheticPolicy) {
290 TryPolicyInProcess(SyntheticPolicy, SyntheticProcess);
291 } 219 }
292 220
293 } // namespace 221 } // namespace
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698