diff options
author | markus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-15 00:34:53 +0000 |
---|---|---|
committer | markus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-15 00:34:53 +0000 |
commit | 8a40387c7e086fdda3892099929101c8a2a801ce (patch) | |
tree | e9fa076fce8691fd130394123d9032fcd493fe80 /sandbox | |
parent | e1bc2f766d9a86bc35b35c8323d2e0eb29383893 (diff) | |
download | chromium_src-8a40387c7e086fdda3892099929101c8a2a801ce.zip chromium_src-8a40387c7e086fdda3892099929101c8a2a801ce.tar.gz chromium_src-8a40387c7e086fdda3892099929101c8a2a801ce.tar.bz2 |
SECCOMP-BPF: Added supported for inspection system call arguments from BPF filters.
BUG=130662
TEST=sandbox_linux_unittests
NOTRY=true
Review URL: https://chromiumcodereview.appspot.com/11411254
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@173243 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sandbox')
-rw-r--r-- | sandbox/linux/seccomp-bpf/bpf_tests.h | 35 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/demo.cc | 135 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.cc | 288 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf.h | 165 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc | 410 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/syscall_iterator.cc | 9 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/syscall_iterator.h | 4 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/syscall_unittest.cc | 2 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/util.cc | 6 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/util.h | 6 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/verifier.cc | 89 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/verifier.h | 3 | ||||
-rw-r--r-- | sandbox/linux/tests/unit_tests.cc | 91 | ||||
-rw-r--r-- | sandbox/linux/tests/unit_tests.h | 76 |
14 files changed, 1029 insertions, 290 deletions
diff --git a/sandbox/linux/seccomp-bpf/bpf_tests.h b/sandbox/linux/seccomp-bpf/bpf_tests.h index 8da25f9..3ac631c 100644 --- a/sandbox/linux/seccomp-bpf/bpf_tests.h +++ b/sandbox/linux/seccomp-bpf/bpf_tests.h @@ -11,6 +11,19 @@ namespace sandbox { +// A BPF_DEATH_TEST is just the same as a BPF_TEST, but it assumes that the +// test will fail with a particular known error condition. Use the DEATH_XXX() +// macros from unit_tests.h to specify the expected error condition. +#define BPF_DEATH_TEST(test_case_name, test_name, death, policy, aux...) \ + void BPF_TEST_##test_name(sandbox::BpfTests<aux>::AuxType& BPF_AUX); \ + TEST(test_case_name, test_name) { \ + sandbox::BpfTests<aux>::TestArgs arg(BPF_TEST_##test_name, policy); \ + sandbox::BpfTests<aux>::RunTestInProcess( \ + sandbox::BpfTests<aux>::TestWrapper, &arg, \ + death); \ + } \ + void BPF_TEST_##test_name(sandbox::BpfTests<aux>::AuxType& BPF_AUX) + // BPF_TEST() is a special version of SANDBOX_TEST(). It turns into a no-op, // if the host does not have kernel support for running BPF filters. // Also, it takes advantage of the Die class to avoid calling LOG(FATAL), from @@ -22,13 +35,8 @@ namespace sandbox { // would typically use it as an argument to Sandbox::Trap(), if they want to // communicate data between the BPF_TEST() and a Trap() function. #define BPF_TEST(test_case_name, test_name, policy, aux...) \ - void BPF_TEST_##test_name(sandbox::BpfTests<aux>::AuxType& BPF_AUX); \ - TEST(test_case_name, test_name) { \ - sandbox::BpfTests<aux>::TestArgs arg(BPF_TEST_##test_name, policy); \ - sandbox::BpfTests<aux>::RunTestInProcess( \ - sandbox::BpfTests<aux>::TestWrapper, &arg);\ - } \ - void BPF_TEST_##test_name(sandbox::BpfTests<aux>::AuxType& BPF_AUX) + BPF_DEATH_TEST(test_case_name, test_name, DEATH_SUCCESS(), policy, aux) + // Assertions are handled exactly the same as with a normal SANDBOX_TEST() #define BPF_ASSERT SANDBOX_ASSERT @@ -64,24 +72,25 @@ class BpfTests : public UnitTests { static void TestWrapper(void *void_arg) { TestArgs *arg = reinterpret_cast<TestArgs *>(void_arg); playground2::Die::EnableSimpleExit(); - if (playground2::Sandbox::supportsSeccompSandbox(-1) == + if (playground2::Sandbox::SupportsSeccompSandbox(-1) == playground2::Sandbox::STATUS_AVAILABLE) { // Ensure the the sandbox is actually available at this time int proc_fd; BPF_ASSERT((proc_fd = open("/proc", O_RDONLY|O_DIRECTORY)) >= 0); - BPF_ASSERT(playground2::Sandbox::supportsSeccompSandbox(proc_fd) == + BPF_ASSERT(playground2::Sandbox::SupportsSeccompSandbox(proc_fd) == playground2::Sandbox::STATUS_AVAILABLE); // Initialize and then start the sandbox with our custom policy - playground2::Sandbox::setProcFd(proc_fd); - playground2::Sandbox::setSandboxPolicy(arg->policy(), &arg->aux_); - playground2::Sandbox::startSandbox(); + playground2::Sandbox::set_proc_fd(proc_fd); + playground2::Sandbox::SetSandboxPolicy(arg->policy(), &arg->aux_); + playground2::Sandbox::StartSandbox(); arg->test()(arg->aux_); } else { // TODO(markus): (crbug.com/141545) Call the compiler and verify the // policy. That's the least we can do, if we don't have kernel support. - playground2::Sandbox::setSandboxPolicy(arg->policy(), NULL); + playground2::Sandbox::SetSandboxPolicy(arg->policy(), NULL); + sandbox::UnitTests::IgnoreThisTest(); } } diff --git a/sandbox/linux/seccomp-bpf/demo.cc b/sandbox/linux/seccomp-bpf/demo.cc index 02fd8a0..fcae399 100644 --- a/sandbox/linux/seccomp-bpf/demo.cc +++ b/sandbox/linux/seccomp-bpf/demo.cc @@ -137,92 +137,102 @@ static intptr_t defaultHandler(const struct arch_seccomp_data& data, return -ERR; } -static ErrorCode evaluator(int sysno) { +static ErrorCode evaluator(int sysno, void *) { switch (sysno) { - #if defined(__NR_accept) - case __NR_accept: case __NR_accept4: +#if defined(__NR_accept) + case __NR_accept: case __NR_accept4: #endif - case __NR_alarm: - case __NR_brk: - case __NR_clock_gettime: - case __NR_close: - case __NR_dup: case __NR_dup2: - case __NR_epoll_create: case __NR_epoll_ctl: case __NR_epoll_wait: - case __NR_exit: case __NR_exit_group: - case __NR_fcntl: + case __NR_alarm: + case __NR_brk: + case __NR_clock_gettime: + case __NR_close: + case __NR_dup: case __NR_dup2: + case __NR_epoll_create: case __NR_epoll_ctl: case __NR_epoll_wait: + case __NR_exit: case __NR_exit_group: + case __NR_fcntl: #if defined(__NR_fcntl64) - case __NR_fcntl64: + case __NR_fcntl64: #endif - case __NR_fdatasync: - case __NR_fstat: + case __NR_fdatasync: + case __NR_fstat: #if defined(__NR_fstat64) - case __NR_fstat64: + case __NR_fstat64: #endif - case __NR_ftruncate: - case __NR_futex: - case __NR_getdents: case __NR_getdents64: - case __NR_getegid: + case __NR_ftruncate: + case __NR_futex: + case __NR_getdents: case __NR_getdents64: + case __NR_getegid: #if defined(__NR_getegid32) - case __NR_getegid32: + case __NR_getegid32: #endif - case __NR_geteuid: + case __NR_geteuid: #if defined(__NR_geteuid32) - case __NR_geteuid32: + case __NR_geteuid32: #endif - case __NR_getgid: + case __NR_getgid: #if defined(__NR_getgid32) - case __NR_getgid32: + case __NR_getgid32: #endif - case __NR_getitimer: case __NR_setitimer: + case __NR_getitimer: case __NR_setitimer: #if defined(__NR_getpeername) - case __NR_getpeername: + case __NR_getpeername: #endif - case __NR_getpid: case __NR_gettid: + case __NR_getpid: case __NR_gettid: #if defined(__NR_getsockname) - case __NR_getsockname: + case __NR_getsockname: #endif - case __NR_gettimeofday: - case __NR_getuid: + case __NR_gettimeofday: + case __NR_getuid: #if defined(__NR_getuid32) - case __NR_getuid32: + case __NR_getuid32: #endif #if defined(__NR__llseek) - case __NR__llseek: + case __NR__llseek: #endif - case __NR_lseek: - case __NR_nanosleep: - case __NR_pipe: case __NR_pipe2: - case __NR_poll: - case __NR_pread64: case __NR_preadv: - case __NR_pwrite64: case __NR_pwritev: - case __NR_read: case __NR_readv: - case __NR_restart_syscall: - case __NR_set_robust_list: - case __NR_rt_sigaction: + case __NR_lseek: + case __NR_nanosleep: + case __NR_pipe: case __NR_pipe2: + case __NR_poll: + case __NR_pread64: case __NR_preadv: + case __NR_pwrite64: case __NR_pwritev: + case __NR_read: case __NR_readv: + case __NR_restart_syscall: + case __NR_set_robust_list: + case __NR_rt_sigaction: #if defined(__NR_sigaction) - case __NR_sigaction: + case __NR_sigaction: #endif #if defined(__NR_signal) - case __NR_signal: + case __NR_signal: #endif - case __NR_rt_sigprocmask: + case __NR_rt_sigprocmask: #if defined(__NR_sigprocmask) - case __NR_sigprocmask: + case __NR_sigprocmask: #endif #if defined(__NR_shutdown) - case __NR_shutdown: + case __NR_shutdown: #endif - case __NR_rt_sigreturn: + case __NR_rt_sigreturn: #if defined(__NR_sigreturn) - case __NR_sigreturn: + case __NR_sigreturn: #endif #if defined(__NR_socketpair) - case __NR_socketpair: + case __NR_socketpair: #endif - case __NR_time: - case __NR_uname: - case __NR_write: case __NR_writev: - return ErrorCode(ErrorCode::ERR_ALLOWED); + case __NR_time: + case __NR_uname: + case __NR_write: case __NR_writev: + return ErrorCode(ErrorCode::ERR_ALLOWED); + + case __NR_prctl: + // Allow PR_SET_DUMPABLE and PR_GET_DUMPABLE. Do not allow anything else. + return Sandbox::Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + PR_SET_DUMPABLE, + ErrorCode(ErrorCode::ERR_ALLOWED), + Sandbox::Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + PR_GET_DUMPABLE, + ErrorCode(ErrorCode::ERR_ALLOWED), + Sandbox::Trap(defaultHandler, NULL))); // The following system calls are temporarily permitted. This must be // tightened later. But we currently don't implement enough of the sandboxing @@ -250,7 +260,6 @@ static ErrorCode evaluator(int sysno) { #endif case __NR_getrlimit: case __NR_ioctl: - case __NR_prctl: case __NR_clone: case __NR_munmap: case __NR_mprotect: case __NR_madvise: case __NR_remap_file_pages: @@ -278,8 +287,8 @@ static void *sendmsgStressThreadFnc(void *arg) { } size_t len = 4; char buf[4]; - if (!Util::sendFds(fds[0], "test", 4, fds[1], fds[1], fds[1], -1) || - !Util::getFds(fds[1], buf, &len, fds+2, fds+3, fds+4, NULL) || + if (!Util::SendFds(fds[0], "test", 4, fds[1], fds[1], fds[1], -1) || + !Util::GetFds(fds[1], buf, &len, fds+2, fds+3, fds+4, NULL) || len != 4 || memcmp(buf, "test", len) || write(fds[2], "demo", 4) != 4 || @@ -302,14 +311,14 @@ int main(int argc, char *argv[]) { if (argc) { } if (argv) { } int proc_fd = open("/proc", O_RDONLY|O_DIRECTORY); - if (Sandbox::supportsSeccompSandbox(proc_fd) != + if (Sandbox::SupportsSeccompSandbox(proc_fd) != Sandbox::STATUS_AVAILABLE) { perror("sandbox"); _exit(1); } - Sandbox::setProcFd(proc_fd); - Sandbox::setSandboxPolicy(evaluator, NULL); - Sandbox::startSandbox(); + Sandbox::set_proc_fd(proc_fd); + Sandbox::SetSandboxPolicy(evaluator, NULL); + Sandbox::StartSandbox(); // Check that we can create threads pthread_t thr; @@ -367,8 +376,8 @@ int main(int argc, char *argv[]) { } size_t len = 4; char buf[4]; - if (!Util::sendFds(fds[0], "test", 4, fds[1], -1) || - !Util::getFds(fds[1], buf, &len, fds+2, NULL) || + if (!Util::SendFds(fds[0], "test", 4, fds[1], -1) || + !Util::GetFds(fds[1], buf, &len, fds+2, NULL) || len != 4 || memcmp(buf, "test", len) || write(fds[2], "demo", 4) != 4 || diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc index 0f144ce..740320f 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -2,24 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <endian.h> -#if __BYTE_ORDER == __BIG_ENDIAN -// The BPF "struct seccomp_data" layout has to deal with storing 64bit -// values that need to be inspected by a virtual machine that only ever -// operates on 32bit values. The kernel developers decided how values -// should be split into two 32bit words to achieve this goal. But at this -// time, there is no existing BPF implementation in the kernel that uses -// 64bit big endian values. So, all we have to go by is the consensus -// from a discussion on LKLM. Actual implementations, if and when they -// happen, might very well differ. -// If this code is ever going to be used with such a kernel, you should -// disable the "#error" and carefully test the code (e.g. run the unit -// tests). If things don't work, search for all occurrences of __BYTE_ORDER -// and verify that the proposed implementation agrees with what the kernel -// actually does. -#error Big endian operation is untested and expected to be broken -#endif - #ifndef SECCOMP_BPF_STANDALONE #include "base/logging.h" #include "base/posix/eintr_wrapper.h" @@ -79,7 +61,7 @@ const int kExpectedExitCode = 100; // We define a really simple sandbox policy. It is just good enough for us // to tell that the sandbox has actually been activated. -ErrorCode Sandbox::probeEvaluator(int sysnum, void *) { +ErrorCode Sandbox::ProbeEvaluator(int sysnum, void *) { switch (sysnum) { case __NR_getpid: // Return EPERM so that we can check that the filter actually ran. @@ -93,24 +75,24 @@ ErrorCode Sandbox::probeEvaluator(int sysnum, void *) { } } -void Sandbox::probeProcess(void) { +void Sandbox::ProbeProcess(void) { if (syscall(__NR_getpid) < 0 && errno == EPERM) { syscall(__NR_exit_group, static_cast<intptr_t>(kExpectedExitCode)); } } -bool Sandbox::isValidSyscallNumber(int sysnum) { +bool Sandbox::IsValidSyscallNumber(int sysnum) { return SyscallIterator::IsValid(sysnum); } -ErrorCode Sandbox::allowAllEvaluator(int sysnum, void *) { - if (!isValidSyscallNumber(sysnum)) { +ErrorCode Sandbox::AllowAllEvaluator(int sysnum, void *) { + if (!IsValidSyscallNumber(sysnum)) { return ErrorCode(ENOSYS); } return ErrorCode(ErrorCode::ERR_ALLOWED); } -void Sandbox::tryVsyscallProcess(void) { +void Sandbox::TryVsyscallProcess(void) { time_t current_time; // time() is implemented as a vsyscall. With an older glibc, with // vsyscall=emulate and some versions of the seccomp BPF patch @@ -120,15 +102,15 @@ void Sandbox::tryVsyscallProcess(void) { } } -bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), - EvaluateSyscall syscallEvaluator, +bool Sandbox::RunFunctionInPolicy(void (*code_in_sandbox)(), + EvaluateSyscall syscall_evaluator, void *aux, int proc_fd) { // Block all signals before forking a child process. This prevents an // attacker from manipulating our test by sending us an unexpected signal. - sigset_t oldMask, newMask; - if (sigfillset(&newMask) || - sigprocmask(SIG_BLOCK, &newMask, &oldMask)) { + sigset_t old_mask, new_mask; + if (sigfillset(&new_mask) || + sigprocmask(SIG_BLOCK, &new_mask, &old_mask)) { SANDBOX_DIE("sigprocmask() failed"); } int fds[2]; @@ -148,7 +130,7 @@ bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), // But what we don't want to do is return "false", as a crafty // attacker might cause fork() to fail at will and could trick us // into running without a sandbox. - sigprocmask(SIG_SETMASK, &oldMask, NULL); // OK, if it fails + sigprocmask(SIG_SETMASK, &old_mask, NULL); // OK, if it fails SANDBOX_DIE("fork() failed unexpectedly"); } @@ -191,18 +173,18 @@ bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), } evaluators_.clear(); - setSandboxPolicy(syscallEvaluator, aux); - setProcFd(proc_fd); + SetSandboxPolicy(syscall_evaluator, aux); + set_proc_fd(proc_fd); // By passing "quiet=true" to "startSandboxInternal()" we suppress // messages for expected and benign failures (e.g. if the current // kernel lacks support for BPF filters). - startSandboxInternal(true); + StartSandboxInternal(true); // Run our code in the sandbox. - CodeInSandbox(); + code_in_sandbox(); - // CodeInSandbox() is not supposed to return here. + // code_in_sandbox() is not supposed to return here. SANDBOX_DIE(NULL); } @@ -210,7 +192,7 @@ bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), if (HANDLE_EINTR(close(fds[1]))) { SANDBOX_DIE("close() failed"); } - if (sigprocmask(SIG_SETMASK, &oldMask, NULL)) { + if (sigprocmask(SIG_SETMASK, &old_mask, NULL)) { SANDBOX_DIE("sigprocmask() failed"); } int status; @@ -242,7 +224,7 @@ bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), return rc; } -bool Sandbox::kernelSupportSeccompBPF(int proc_fd) { +bool Sandbox::KernelSupportSeccompBPF(int proc_fd) { #if defined(SECCOMP_BPF_VALGRIND_HACKS) if (RUNNING_ON_VALGRIND) { // Valgrind doesn't like our run-time test. Disable testing and assume we @@ -253,12 +235,12 @@ bool Sandbox::kernelSupportSeccompBPF(int proc_fd) { #endif return - RunFunctionInPolicy(probeProcess, Sandbox::probeEvaluator, 0, proc_fd) && - RunFunctionInPolicy(tryVsyscallProcess, Sandbox::allowAllEvaluator, 0, + RunFunctionInPolicy(ProbeProcess, Sandbox::ProbeEvaluator, 0, proc_fd) && + RunFunctionInPolicy(TryVsyscallProcess, Sandbox::AllowAllEvaluator, 0, proc_fd); } -Sandbox::SandboxStatus Sandbox::supportsSeccompSandbox(int proc_fd) { +Sandbox::SandboxStatus Sandbox::SupportsSeccompSandbox(int proc_fd) { // It the sandbox is currently active, we clearly must have support for // sandboxing. if (status_ == STATUS_ENABLED) { @@ -268,13 +250,13 @@ Sandbox::SandboxStatus Sandbox::supportsSeccompSandbox(int proc_fd) { // Even if the sandbox was previously available, something might have // changed in our run-time environment. Check one more time. if (status_ == STATUS_AVAILABLE) { - if (!isSingleThreaded(proc_fd)) { + if (!IsSingleThreaded(proc_fd)) { status_ = STATUS_UNAVAILABLE; } return status_; } - if (status_ == STATUS_UNAVAILABLE && isSingleThreaded(proc_fd)) { + if (status_ == STATUS_UNAVAILABLE && IsSingleThreaded(proc_fd)) { // All state transitions resulting in STATUS_UNAVAILABLE are immediately // preceded by STATUS_AVAILABLE. Furthermore, these transitions all // happen, if and only if they are triggered by the process being multi- @@ -290,25 +272,25 @@ Sandbox::SandboxStatus Sandbox::supportsSeccompSandbox(int proc_fd) { // we otherwise don't believe to have a good cached value, we have to // perform a thorough check now. if (status_ == STATUS_UNKNOWN) { - status_ = kernelSupportSeccompBPF(proc_fd) + status_ = KernelSupportSeccompBPF(proc_fd) ? STATUS_AVAILABLE : STATUS_UNSUPPORTED; // As we are performing our tests from a child process, the run-time // environment that is visible to the sandbox is always guaranteed to be // single-threaded. Let's check here whether the caller is single- // threaded. Otherwise, we mark the sandbox as temporarily unavailable. - if (status_ == STATUS_AVAILABLE && !isSingleThreaded(proc_fd)) { + if (status_ == STATUS_AVAILABLE && !IsSingleThreaded(proc_fd)) { status_ = STATUS_UNAVAILABLE; } } return status_; } -void Sandbox::setProcFd(int proc_fd) { +void Sandbox::set_proc_fd(int proc_fd) { proc_fd_ = proc_fd; } -void Sandbox::startSandboxInternal(bool quiet) { +void Sandbox::StartSandboxInternal(bool quiet) { if (status_ == STATUS_UNSUPPORTED || status_ == STATUS_UNAVAILABLE) { SANDBOX_DIE("Trying to start sandbox, even though it is known to be " "unavailable"); @@ -323,7 +305,7 @@ void Sandbox::startSandboxInternal(bool quiet) { // For now, continue in degraded mode, if we can't access /proc. // In the future, we might want to tighten this requirement. } - if (!isSingleThreaded(proc_fd_)) { + if (!IsSingleThreaded(proc_fd_)) { SANDBOX_DIE("Cannot start sandbox, if process is already multi-threaded"); } @@ -338,13 +320,13 @@ void Sandbox::startSandboxInternal(bool quiet) { } // Install the filters. - installFilter(quiet); + InstallFilter(quiet); // We are now inside the sandbox. status_ = STATUS_ENABLED; } -bool Sandbox::isSingleThreaded(int proc_fd) { +bool Sandbox::IsSingleThreaded(int proc_fd) { if (proc_fd < 0) { // Cannot determine whether program is single-threaded. Hope for // the best... @@ -365,17 +347,17 @@ bool Sandbox::isSingleThreaded(int proc_fd) { return true; } -bool Sandbox::isDenied(const ErrorCode& code) { +bool Sandbox::IsDenied(const ErrorCode& code) { return (code.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP || (code.err() >= (SECCOMP_RET_ERRNO + ErrorCode::ERR_MIN_ERRNO) && code.err() <= (SECCOMP_RET_ERRNO + ErrorCode::ERR_MAX_ERRNO)); } -void Sandbox::policySanityChecks(EvaluateSyscall syscallEvaluator, +void Sandbox::PolicySanityChecks(EvaluateSyscall syscall_evaluator, void *aux) { for (SyscallIterator iter(true); !iter.Done(); ) { uint32_t sysnum = iter.Next(); - if (!isDenied(syscallEvaluator(sysnum, aux))) { + if (!IsDenied(syscall_evaluator(sysnum, aux))) { SANDBOX_DIE("Policies should deny system calls that are outside the " "expected range (typically MIN_SYSCALL..MAX_SYSCALL)"); } @@ -386,8 +368,8 @@ void Sandbox::policySanityChecks(EvaluateSyscall syscallEvaluator, void Sandbox::CheckForUnsafeErrorCodes(Instruction *insn, void *aux) { if (BPF_CLASS(insn->code) == BPF_RET && insn->k > SECCOMP_RET_TRAP && - insn->k - SECCOMP_RET_TRAP <= trapArraySize_) { - const ErrorCode& err = trapArray_[insn->k - SECCOMP_RET_TRAP - 1]; + insn->k - SECCOMP_RET_TRAP <= trap_array_size_) { + const ErrorCode& err = trap_array_[insn->k - SECCOMP_RET_TRAP - 1]; if (!err.safe_) { bool *is_unsafe = static_cast<bool *>(aux); *is_unsafe = true; @@ -395,7 +377,7 @@ void Sandbox::CheckForUnsafeErrorCodes(Instruction *insn, void *aux) { } } -void Sandbox::RedirectToUserspace(Instruction *insn, void *aux) { +void Sandbox::RedirectToUserspace(Instruction *insn, void *) { // When inside an UnsafeTrap() callback, we want to allow all system calls. // This means, we must conditionally disable the sandbox -- and that's not // something that kernel-side BPF filters can do, as they cannot inspect @@ -425,15 +407,15 @@ ErrorCode Sandbox::RedirectToUserspaceEvalWrapper(int sysnum, void *aux) { return err; } -void Sandbox::setSandboxPolicy(EvaluateSyscall syscallEvaluator, void *aux) { +void Sandbox::SetSandboxPolicy(EvaluateSyscall syscall_evaluator, void *aux) { if (status_ == STATUS_ENABLED) { SANDBOX_DIE("Cannot change policy after sandbox has started"); } - policySanityChecks(syscallEvaluator, aux); - evaluators_.push_back(std::make_pair(syscallEvaluator, aux)); + PolicySanityChecks(syscall_evaluator, aux); + evaluators_.push_back(std::make_pair(syscall_evaluator, aux)); } -void Sandbox::installFilter(bool quiet) { +void Sandbox::InstallFilter(bool quiet) { // Verify that the user pushed a policy. if (evaluators_.empty()) { filter_failed: @@ -443,7 +425,7 @@ void Sandbox::installFilter(bool quiet) { // Set new SIGSYS handler struct sigaction sa; memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = sigSys; + sa.sa_sigaction = SigSys; sa.sa_flags = SA_SIGINFO | SA_NODEFER; if (sigaction(SIGSYS, &sa, NULL) < 0) { goto filter_failed; @@ -473,24 +455,22 @@ void Sandbox::installFilter(bool quiet) { // system call. Instruction *tail; Instruction *head = - gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, - offsetof(struct arch_seccomp_data, arch), + gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, SECCOMP_ARCH_IDX, tail = gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, SECCOMP_ARCH, NULL, gen->MakeInstruction(BPF_RET+BPF_K, - Kill( - "Invalid audit architecture in BPF filter").err_))); + Kill("Invalid audit architecture in BPF filter")))); { // Evaluate all possible system calls and group their ErrorCodes into // ranges of identical codes. Ranges ranges; - findRanges(&ranges); + FindRanges(&ranges); // Compile the system call ranges to an optimized BPF jumptable Instruction *jumptable = - assembleJumpTable(gen, ranges.begin(), ranges.end()); + AssembleJumpTable(gen, ranges.begin(), ranges.end()); // If there is at least one UnsafeTrap() in our program, the entire sandbox // is unsafe. We need to modify the program so that all non- @@ -502,8 +482,7 @@ void Sandbox::installFilter(bool quiet) { // Grab the system call number, so that we can implement jump tables. Instruction *load_nr = - gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, - offsetof(struct arch_seccomp_data, nr)); + gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, SECCOMP_NR_IDX); // If our BPF program has unsafe jumps, enable support for them. This // test happens very early in the BPF filter program. Even before we @@ -550,21 +529,14 @@ void Sandbox::installFilter(bool quiet) { #endif // BPF cannot do native 64bit comparisons. On 64bit architectures, we - // have to compare both 32bit halfs of the instruction pointer. If they + // have to compare both 32bit halves of the instruction pointer. If they // match what we expect, we return ERR_ALLOWED. If either or both don't // match, we continue evalutating the rest of the sandbox policy. Instruction *escape_hatch = - gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, - offsetof(struct arch_seccomp_data, - instruction_pointer) + - (__SIZEOF_POINTER__ > 4 && - __BYTE_ORDER == __BIG_ENDIAN ? 4 : 0), + gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, SECCOMP_IP_LSB_IDX, gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, low, #if __SIZEOF_POINTER__ > 4 - gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, - offsetof(struct arch_seccomp_data, - instruction_pointer) + - (__BYTE_ORDER == __BIG_ENDIAN ? 0 : 4), + gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, SECCOMP_IP_MSB_IDX, gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, hi, #endif gen->MakeInstruction(BPF_RET+BPF_K, ErrorCode(ErrorCode::ERR_ALLOWED)), @@ -646,6 +618,7 @@ void Sandbox::installFilter(bool quiet) { // Release memory that is no longer needed evaluators_.clear(); + conds_.clear(); #if defined(SECCOMP_BPF_VALGRIND_HACKS) // Valgrind is really not happy about our sandbox. Disable it when running @@ -667,36 +640,36 @@ void Sandbox::installFilter(bool quiet) { return; } -void Sandbox::findRanges(Ranges *ranges) { +void Sandbox::FindRanges(Ranges *ranges) { // Please note that "struct seccomp_data" defines system calls as a signed // int32_t, but BPF instructions always operate on unsigned quantities. We // deal with this disparity by enumerating from MIN_SYSCALL to MAX_SYSCALL, // and then verifying that the rest of the number range (both positive and // negative) all return the same ErrorCode. - EvaluateSyscall evaluateSyscall = evaluators_.begin()->first; - void *aux = evaluators_.begin()->second; - uint32_t oldSysnum = 0; - ErrorCode oldErr = evaluateSyscall(oldSysnum, aux); - ErrorCode invalidErr = evaluateSyscall(MIN_SYSCALL - 1, aux); + EvaluateSyscall evaluate_syscall = evaluators_.begin()->first; + void *aux = evaluators_.begin()->second; + uint32_t old_sysnum = 0; + ErrorCode old_err = evaluate_syscall(old_sysnum, aux); + ErrorCode invalid_err = evaluate_syscall(MIN_SYSCALL - 1, aux); for (SyscallIterator iter(false); !iter.Done(); ) { uint32_t sysnum = iter.Next(); - ErrorCode err = evaluateSyscall(static_cast<int>(sysnum), aux); - if (!iter.IsValid(sysnum) && !invalidErr.Equals(err)) { + ErrorCode err = evaluate_syscall(static_cast<int>(sysnum), aux); + if (!iter.IsValid(sysnum) && !invalid_err.Equals(err)) { // A proper sandbox policy should always treat system calls outside of // the range MIN_SYSCALL..MAX_SYSCALL (i.e. anything that returns // "false" for SyscallIterator::IsValid()) identically. Typically, all // of these system calls would be denied with the same ErrorCode. SANDBOX_DIE("Invalid seccomp policy"); } - if (!err.Equals(oldErr) || iter.Done()) { - ranges->push_back(Range(oldSysnum, sysnum - 1, oldErr)); - oldSysnum = sysnum; - oldErr = err; + if (!err.Equals(old_err) || iter.Done()) { + ranges->push_back(Range(old_sysnum, sysnum - 1, old_err)); + old_sysnum = sysnum; + old_err = err; } } } -Instruction *Sandbox::assembleJumpTable(CodeGen *gen, +Instruction *Sandbox::AssembleJumpTable(CodeGen *gen, Ranges::const_iterator start, Ranges::const_iterator stop) { // We convert the list of system call ranges into jump table that performs @@ -708,7 +681,7 @@ Instruction *Sandbox::assembleJumpTable(CodeGen *gen, } else if (stop - start == 1) { // If we have narrowed things down to a single range object, we can // return from the BPF filter program. - return gen->MakeInstruction(BPF_RET+BPF_K, start->err); + return RetExpression(gen, start->err); } // Pick the range object that is located at the mid point of our list. @@ -718,18 +691,108 @@ Instruction *Sandbox::assembleJumpTable(CodeGen *gen, Ranges::const_iterator mid = start + (stop - start)/2; // Sub-divide the list of ranges and continue recursively. - Instruction *jf = assembleJumpTable(gen, start, mid); - Instruction *jt = assembleJumpTable(gen, mid, stop); + Instruction *jf = AssembleJumpTable(gen, start, mid); + Instruction *jt = AssembleJumpTable(gen, mid, stop); return gen->MakeInstruction(BPF_JMP+BPF_JGE+BPF_K, mid->from, jt, jf); } -void Sandbox::sigSys(int nr, siginfo_t *info, void *void_context) { +Instruction *Sandbox::RetExpression(CodeGen *gen, const ErrorCode& cond) { + if (cond.error_type_ == ErrorCode::ET_COND) { + return CondExpression(gen, cond); + } else { + return gen->MakeInstruction(BPF_RET+BPF_K, cond); + } +} + +Instruction *Sandbox::CondExpression(CodeGen *gen, const ErrorCode& cond) { + // We can only inspect the six system call arguments that are passed in + // CPU registers. + if (cond.argno_ < 0 || cond.argno_ >= 6) { + SANDBOX_DIE("Internal compiler error; invalid argument number " + "encountered"); + } + + // BPF programs operate on 32bit entities. Load both halfs of the 64bit + // system call argument and then generate suitable conditional statements. + Instruction *msb_head = + gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, + SECCOMP_ARG_MSB_IDX(cond.argno_)); + Instruction *msb_tail = msb_head; + Instruction *lsb_head = + gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, + SECCOMP_ARG_LSB_IDX(cond.argno_)); + Instruction *lsb_tail = lsb_head; + + // Emit a suitable comparison statement. + switch (cond.op_) { + case ErrorCode::OP_EQUAL: + // Compare the least significant bits for equality + lsb_tail = gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, + static_cast<uint32_t>(cond.value_), + RetExpression(gen, *cond.passed_), + RetExpression(gen, *cond.failed_)); + gen->JoinInstructions(lsb_head, lsb_tail); + + // If we are looking at a 64bit argument, we need to also compare the + // most significant bits. + if (cond.width_ == ErrorCode::TP_64BIT) { + msb_tail = gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, + static_cast<uint32_t>(cond.value_ >> 32), + NULL, + RetExpression(gen, *cond.failed_)); + gen->JoinInstructions(msb_head, msb_tail); + } + break; + default: + // TODO(markus): We can only check for equality so far. + SANDBOX_DIE("Not implemented"); + break; + } + + // Ensure that we never pass a 64bit value, when we only expect a 32bit + // value. This is somewhat complicated by the fact that on 64bit systems, + // callers could legitimately pass in a non-zero value in the MSB, iff the + // LSB has been sign-extended into the MSB. + if (cond.width_ == ErrorCode::TP_32BIT) { + if (cond.value_ >> 32) { + SANDBOX_DIE("Invalid comparison of a 32bit system call argument " + "against a 64bit constant; this test is always false."); + } + + Instruction *invalid_64bit = RetExpression(gen, Unexpected64bitArgument()); + #if __SIZEOF_POINTER__ > 4 + invalid_64bit = + gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 0xFFFFFFFF, + gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, + SECCOMP_ARG_LSB_IDX(cond.argno_), + gen->MakeInstruction(BPF_JMP+BPF_JGE+BPF_K, 0x80000000, + lsb_head, + invalid_64bit)), + invalid_64bit); + #endif + gen->JoinInstructions( + msb_tail, + gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 0, + lsb_head, + invalid_64bit)); + } else { + gen->JoinInstructions(msb_tail, lsb_head); + } + + return msb_head; +} + +ErrorCode Sandbox::Unexpected64bitArgument() { + return Kill("Unexpected 64bit argument detected"); +} + +void Sandbox::SigSys(int nr, siginfo_t *info, void *void_context) { // Various sanity checks to make sure we actually received a signal // triggered by a BPF filter. If something else triggered SIGSYS // (e.g. kill()), there is really nothing we can do with this signal. if (nr != SIGSYS || info->si_code != SYS_SECCOMP || !void_context || info->si_errno <= 0 || - static_cast<size_t>(info->si_errno) > trapArraySize_) { + static_cast<size_t>(info->si_errno) > trap_array_size_) { // SANDBOX_DIE() can call LOG(FATAL). This is not normally async-signal // safe and can lead to bugs. We should eventually implement a different // logging and reporting mechanism that is safe to be called from @@ -773,7 +836,7 @@ void Sandbox::sigSys(int nr, siginfo_t *info, void *void_context) { SECCOMP_PARM3(ctx), SECCOMP_PARM4(ctx), SECCOMP_PARM5(ctx), SECCOMP_PARM6(ctx)); } else { - const ErrorCode& err = trapArray_[info->si_errno - 1]; + const ErrorCode& err = trap_array_[info->si_errno - 1]; if (!err.safe_) { SetIsInSigHandler(); } @@ -824,9 +887,9 @@ ErrorCode Sandbox::MakeTrap(ErrorCode::TrapFnc fnc, const void *aux, // Each unique pair of TrapFnc and auxiliary data make up a distinct instance // of a SECCOMP_RET_TRAP. TrapKey key(fnc, aux, safe); - TrapIds::const_iterator iter = trapIds_.find(key); + TrapIds::const_iterator iter = trap_ids_.find(key); uint16_t id; - if (iter != trapIds_.end()) { + if (iter != trap_ids_.end()) { // We have seen this pair before. Return the same id that we assigned // earlier. id = iter->second; @@ -847,7 +910,7 @@ ErrorCode Sandbox::MakeTrap(ErrorCode::TrapFnc fnc, const void *aux, id = traps_->size() + 1; traps_->push_back(ErrorCode(fnc, aux, safe, id)); - trapIds_[key] = id; + trap_ids_[key] = id; // We want to access the traps_ vector from our signal handler. But // we are not assured that doing so is async-signal safe. On the other @@ -855,8 +918,8 @@ ErrorCode Sandbox::MakeTrap(ErrorCode::TrapFnc fnc, const void *aux, // contiguous C-style array. // So, we look up the address and size of this array outside of the // signal handler, where we can safely do so. - trapArray_ = &(*traps_)[0]; - trapArraySize_ = id; + trap_array_ = &(*traps_)[0]; + trap_array_size_ = id; return traps_->back(); } @@ -890,21 +953,30 @@ intptr_t Sandbox::ReturnErrno(const struct arch_seccomp_data&, void *aux) { return -err; } -intptr_t Sandbox::bpfFailure(const struct arch_seccomp_data&, void *aux) { +ErrorCode Sandbox::Cond(int argno, ErrorCode::ArgType width, + ErrorCode::Operation op, uint64_t value, + const ErrorCode& passed, const ErrorCode& failed) { + return ErrorCode(argno, width, op, value, + &*conds_.insert(passed).first, + &*conds_.insert(failed).first); +} + +intptr_t Sandbox::BpfFailure(const struct arch_seccomp_data&, void *aux) { SANDBOX_DIE(static_cast<char *>(aux)); } ErrorCode Sandbox::Kill(const char *msg) { - return Trap(bpfFailure, const_cast<char *>(msg)); + return Trap(BpfFailure, const_cast<char *>(msg)); } Sandbox::SandboxStatus Sandbox::status_ = STATUS_UNKNOWN; -int Sandbox::proc_fd_ = -1; +int Sandbox::proc_fd_ = -1; Sandbox::Evaluators Sandbox::evaluators_; Sandbox::Traps *Sandbox::traps_ = NULL; -Sandbox::TrapIds Sandbox::trapIds_; -ErrorCode *Sandbox::trapArray_ = NULL; -size_t Sandbox::trapArraySize_ = 0; +Sandbox::TrapIds Sandbox::trap_ids_; +ErrorCode *Sandbox::trap_array_ = NULL; +size_t Sandbox::trap_array_size_ = 0; bool Sandbox::has_unsafe_traps_ = false; +Sandbox::Conds Sandbox::conds_; } // namespace diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h index 0e781ea..4771325 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.h +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -37,17 +37,18 @@ #include <algorithm> #include <limits> #include <map> +#include <set> #include <utility> #include <vector> -#ifndef SECCOMP_BPF_STANDALONE +#if !defined(SECCOMP_BPF_STANDALONE) #include "base/basictypes.h" #include "base/logging.h" #include "base/posix/eintr_wrapper.h" #endif #if defined(SECCOMP_BPF_VALGRIND_HACKS) -#ifndef SECCOMP_BPF_STANDALONE +#if !defined(SECCOMP_BPF_STANDALONE) #include "base/third_party/valgrind/valgrind.h" #endif #endif @@ -64,20 +65,32 @@ #ifndef IPC_64 #define IPC_64 0x0100 #endif + +// In order to build will older tool chains, we currently have to avoid +// including <linux/seccomp.h>. Until that can be fixed (if ever). Rely on +// our own definitions of the seccomp kernel ABI. #ifndef SECCOMP_MODE_FILTER #define SECCOMP_MODE_DISABLED 0 #define SECCOMP_MODE_STRICT 1 #define SECCOMP_MODE_FILTER 2 // User user-supplied filter +#endif + +#ifndef SECCOMP_RET_KILL +// Return values supported for BPF filter programs. Please note that the +// "illegal" SECCOMP_RET_INVALID is not supported by the kernel, should only +// ever be used internally, and would result in the kernel killing our process. #define SECCOMP_RET_KILL 0x00000000U // Kill the task immediately +#define SECCOMP_RET_INVALID 0x00010000U // Illegal return value #define SECCOMP_RET_TRAP 0x00030000U // Disallow and force a SIGSYS #define SECCOMP_RET_ERRNO 0x00050000U // Returns an errno #define SECCOMP_RET_TRACE 0x7ff00000U // Pass to a tracer or disallow #define SECCOMP_RET_ALLOW 0x7fff0000U // Allow -#define SECCOMP_RET_INVALID 0x8f8f8f8fU // Illegal return value #define SECCOMP_RET_ACTION 0xffff0000U // Masks for the return value #define SECCOMP_RET_DATA 0x0000ffffU // sections +#else +#define SECCOMP_RET_INVALID 0x00010000U // Illegal return value #endif -#define SECCOMP_DENY_ERRNO EPERM + #ifndef SYS_SECCOMP #define SYS_SECCOMP 1 #endif @@ -91,7 +104,7 @@ #define MIN_SYSCALL 0u #define MAX_PUBLIC_SYSCALL 1024u #define MAX_SYSCALL MAX_PUBLIC_SYSCALL -#define SECCOMP_ARCH AUDIT_ARCH_I386 +#define SECCOMP_ARCH AUDIT_ARCH_I386 #define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)]) #define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, REG_EAX) @@ -103,12 +116,22 @@ #define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, REG_ESI) #define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, REG_EDI) #define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, REG_EBP) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) #elif defined(__x86_64__) #define MIN_SYSCALL 0u #define MAX_PUBLIC_SYSCALL 1024u #define MAX_SYSCALL MAX_PUBLIC_SYSCALL -#define SECCOMP_ARCH AUDIT_ARCH_X86_64 +#define SECCOMP_ARCH AUDIT_ARCH_X86_64 #define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)]) #define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, REG_RAX) @@ -120,6 +143,16 @@ #define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, REG_R10) #define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, REG_R8) #define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, REG_R9) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) #elif defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__)) // ARM EABI includes "ARM private" system calls starting at |__ARM_NR_BASE|, @@ -143,15 +176,25 @@ // See </arch/arm/include/asm/sigcontext.h> in the Linux kernel. #define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.arm_##_reg) // ARM EABI syscall convention. -#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, r0) -#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, r7) -#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, pc) -#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, r0) -#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, r1) -#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, r2) -#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, r3) -#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, r4) -#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, r5) +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, r0) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, r7) +#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, pc) +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, r0) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, r1) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, r2) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, r3) +#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, r4) +#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, r5) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) #else #error Unsupported target platform @@ -220,20 +263,6 @@ class Sandbox { // be undone afterwards. typedef intptr_t (*TrapFnc)(const struct arch_seccomp_data& args, void *aux); - enum Operation { - OP_NOP, OP_EQUAL, OP_NOTEQUAL, OP_LESS, - OP_LESS_EQUAL, OP_GREATER, OP_GREATER_EQUAL, - OP_HAS_BITS, OP_DOES_NOT_HAVE_BITS - }; - - struct Constraint { - bool is32bit; - Operation op; - uint32_t value; - ErrorCode passed; - ErrorCode failed; - }; - // When calling setSandboxPolicy(), the caller can provide an arbitrary // pointer. This pointer will then be forwarded to the sandbox policy // each time a call is made through an EvaluateSyscall function pointer. @@ -245,21 +274,21 @@ class Sandbox { // Checks whether a particular system call number is valid on the current // architecture. E.g. on ARM there's a non-contiguous range of private // system calls. - static bool isValidSyscallNumber(int sysnum); + static bool IsValidSyscallNumber(int sysnum); // There are a lot of reasons why the Seccomp sandbox might not be available. // This could be because the kernel does not support Seccomp mode, or it // could be because another sandbox is already active. // "proc_fd" should be a file descriptor for "/proc", or -1 if not // provided by the caller. - static SandboxStatus supportsSeccompSandbox(int proc_fd); + static SandboxStatus SupportsSeccompSandbox(int proc_fd); // The sandbox needs to be able to access files in "/proc/self". If this // directory is not accessible when "startSandbox()" gets called, the caller - // can provide an already opened file descriptor by calling "setProcFd()". + // can provide an already opened file descriptor by calling "set_proc_fd()". // The sandbox becomes the new owner of this file descriptor and will // eventually close it when "startSandbox()" executes. - static void setProcFd(int proc_fd); + static void set_proc_fd(int proc_fd); // The system call evaluator function is called with the system // call number. It can decide to allow the system call unconditionally @@ -272,7 +301,7 @@ class Sandbox { // handler. In this case, of course, the data that is pointed to must remain // valid for the entire time that Trap() handlers can be called; typically, // this would be the lifetime of the program. - static void setSandboxPolicy(EvaluateSyscall syscallEvaluator, void *aux); + static void SetSandboxPolicy(EvaluateSyscall syscallEvaluator, void *aux); // We can use ErrorCode to request calling of a trap handler. This method // performs the required wrapping of the callback function into an @@ -301,18 +330,33 @@ class Sandbox { // directly suitable as a return value for a trap handler. static intptr_t ForwardSyscall(const struct arch_seccomp_data& args); + // We can also use ErrorCode to request evaluation of a conditional + // statement based on inspection of system call parameters. + // This method wrap an ErrorCode object around the conditional statement. + // Argument "argno" (1..6) will be compared to "value" using comparator + // "op". If the condition is true "passed" will be returned, otherwise + // "failed". + // If "is32bit" is set, the argument must in the range of 0x0..(1u << 32 - 1) + // If it is outside this range, the sandbox treats the system call just + // the same as any other ABI violation (i.e. it aborts with an error + // message). + static ErrorCode Cond(int argno, ErrorCode::ArgType is_32bit, + ErrorCode::Operation op, + uint64_t value, const ErrorCode& passed, + const ErrorCode& failed); + // Kill the program and print an error message. static ErrorCode Kill(const char *msg); // This is the main public entry point. It finds all system calls that // need rewriting, sets up the resources needed by the sandbox, and // enters Seccomp mode. - static void startSandbox() { startSandboxInternal(false); } + static void StartSandbox() { StartSandboxInternal(false); } private: - friend class ErrorCode; friend class CodeGen; friend class SandboxUnittestHelper; + friend class ErrorCode; friend class Util; friend class Verifier; @@ -342,24 +386,25 @@ class Sandbox { typedef std::map<uint32_t, ErrorCode> ErrMap; typedef std::vector<ErrorCode> Traps; typedef std::map<TrapKey, uint16_t> TrapIds; + typedef std::set<ErrorCode, struct ErrorCode::LessThan> Conds; // Get a file descriptor pointing to "/proc", if currently available. static int proc_fd() { return proc_fd_; } - static ErrorCode probeEvaluator(int sysnum, void *) __attribute__((const)); - static void probeProcess(void); - static ErrorCode allowAllEvaluator(int sysnum, void *aux); - static void tryVsyscallProcess(void); - static bool kernelSupportSeccompBPF(int proc_fd); + static ErrorCode ProbeEvaluator(int sysnum, void *) __attribute__((const)); + static void ProbeProcess(void); + static ErrorCode AllowAllEvaluator(int sysnum, void *aux); + static void TryVsyscallProcess(void); + static bool KernelSupportSeccompBPF(int proc_fd); static bool RunFunctionInPolicy(void (*function)(), - EvaluateSyscall syscallEvaluator, + EvaluateSyscall syscall_evaluator, void *aux, int proc_fd); - static void startSandboxInternal(bool quiet); - static bool isSingleThreaded(int proc_fd); - static bool isDenied(const ErrorCode& code); - static bool disableFilesystem(); - static void policySanityChecks(EvaluateSyscall syscallEvaluator, + static void StartSandboxInternal(bool quiet); + static bool IsSingleThreaded(int proc_fd); + static bool IsDenied(const ErrorCode& code); + static bool DisableFilesystem(); + static void PolicySanityChecks(EvaluateSyscall syscall_evaluator, void *aux); // Function that can be passed as a callback function to CodeGen::Traverse(). @@ -379,29 +424,37 @@ class Sandbox { // evaluator. static ErrorCode RedirectToUserspaceEvalWrapper(int sysnum, void *aux); - static void installFilter(bool quiet); - static void findRanges(Ranges *ranges); - static Instruction *assembleJumpTable(CodeGen *gen, + static void InstallFilter(bool quiet); + static void FindRanges(Ranges *ranges); + static Instruction *AssembleJumpTable(CodeGen *gen, Ranges::const_iterator start, Ranges::const_iterator stop); - static void sigSys(int nr, siginfo_t *info, void *void_context); + static Instruction *RetExpression(CodeGen *gen, const ErrorCode& cond); + static Instruction *CondExpression(CodeGen *gen, const ErrorCode& cond); + + // Returns the fatal ErrorCode that is used to indicate that somebody + // attempted to pass a 64bit value in a 32bit system call argument. + static ErrorCode Unexpected64bitArgument(); + + static void SigSys(int nr, siginfo_t *info, void *void_context); static ErrorCode MakeTrap(ErrorCode::TrapFnc fn, const void *aux, bool safe); // A Trap() handler that returns an "errno" value. The value is encoded // in the "aux" parameter. static intptr_t ReturnErrno(const struct arch_seccomp_data&, void *aux); - static intptr_t bpfFailure(const struct arch_seccomp_data& data, void *aux); - static int getTrapId(TrapFnc fnc, const void *aux); + static intptr_t BpfFailure(const struct arch_seccomp_data& data, void *aux); static SandboxStatus status_; static int proc_fd_; static Evaluators evaluators_; static Traps *traps_; - static TrapIds trapIds_; - static ErrorCode *trapArray_; - static size_t trapArraySize_; + static TrapIds trap_ids_; + static ErrorCode *trap_array_; + static size_t trap_array_size_; static bool has_unsafe_traps_; + static Conds conds_; + DISALLOW_IMPLICIT_CONSTRUCTORS(Sandbox); }; diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc index a5e3b25..f3952b0 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc @@ -9,6 +9,7 @@ #include "base/memory/scoped_ptr.h" #include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" #include "sandbox/linux/seccomp-bpf/verifier.h" #include "sandbox/linux/services/broker_process.h" #include "testing/gtest/include/gtest/gtest.h" @@ -26,7 +27,7 @@ TEST(SandboxBpf, CallSupports) { // We check that we don't crash, but it's ok if the kernel doesn't // support it. bool seccomp_bpf_supported = - Sandbox::supportsSeccompSandbox(-1) == Sandbox::STATUS_AVAILABLE; + Sandbox::SupportsSeccompSandbox(-1) == Sandbox::STATUS_AVAILABLE; // We want to log whether or not seccomp BPF is actually supported // since actual test coverage depends on it. RecordProperty("SeccompBPFSupported", @@ -39,8 +40,8 @@ TEST(SandboxBpf, CallSupports) { } SANDBOX_TEST(SandboxBpf, CallSupportsTwice) { - Sandbox::supportsSeccompSandbox(-1); - Sandbox::supportsSeccompSandbox(-1); + Sandbox::SupportsSeccompSandbox(-1); + Sandbox::SupportsSeccompSandbox(-1); } // BPF_TEST does a lot of the boiler-plate code around setting up a @@ -57,7 +58,7 @@ intptr_t FakeGetPid(const struct arch_seccomp_data& args, void *aux) { } ErrorCode VerboseAPITestingPolicy(int sysno, void *aux) { - if (!Sandbox::isValidSyscallNumber(sysno)) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { return ErrorCode(ENOSYS); } else if (sysno == __NR_getpid) { return Sandbox::Trap(FakeGetPid, aux); @@ -67,11 +68,11 @@ ErrorCode VerboseAPITestingPolicy(int sysno, void *aux) { } SANDBOX_TEST(SandboxBpf, VerboseAPITesting) { - if (Sandbox::supportsSeccompSandbox(-1) == + if (Sandbox::SupportsSeccompSandbox(-1) == playground2::Sandbox::STATUS_AVAILABLE) { pid_t test_var = 0; - playground2::Sandbox::setSandboxPolicy(VerboseAPITestingPolicy, &test_var); - playground2::Sandbox::startSandbox(); + playground2::Sandbox::SetSandboxPolicy(VerboseAPITestingPolicy, &test_var); + playground2::Sandbox::StartSandbox(); BPF_ASSERT(test_var == 0); BPF_ASSERT(syscall(__NR_getpid) == 0); @@ -88,7 +89,7 @@ SANDBOX_TEST(SandboxBpf, VerboseAPITesting) { // A simple blacklist test ErrorCode BlacklistNanosleepPolicy(int sysno, void *) { - if (!Sandbox::isValidSyscallNumber(sysno)) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); } @@ -142,7 +143,7 @@ intptr_t EnomemHandler(const struct arch_seccomp_data& args, void *aux) { } ErrorCode BlacklistNanosleepPolicySigsys(int sysno, void *aux) { - if (!Sandbox::isValidSyscallNumber(sysno)) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); } @@ -188,7 +189,7 @@ int SysnoToRandomErrno(int sysno) { } ErrorCode SyntheticPolicy(int sysno, void *) { - if (!Sandbox::isValidSyscallNumber(sysno)) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); } @@ -246,7 +247,7 @@ int ArmPrivateSysnoToErrno(int sysno) { } ErrorCode ArmPrivatePolicy(int sysno, void *) { - if (!Sandbox::isValidSyscallNumber(sysno)) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy. return ErrorCode(ENOSYS); } @@ -308,7 +309,7 @@ ErrorCode GreyListedPolicy(int sysno, void *aux) { } else if (sysno == __NR_getpid) { // Disallow getpid() return ErrorCode(EPERM); - } else if (Sandbox::isValidSyscallNumber(sysno)) { + } else if (Sandbox::IsValidSyscallNumber(sysno)) { // Allow (and count) all other system calls. return Sandbox::UnsafeTrap(CountSyscalls, aux); } else { @@ -347,7 +348,7 @@ ErrorCode PrctlPolicy(int sysno, void *aux) { if (sysno == __NR_prctl) { // Handle prctl() inside an UnsafeTrap() return Sandbox::UnsafeTrap(PrctlHandler, NULL); - } else if (Sandbox::isValidSyscallNumber(sysno)) { + } else if (Sandbox::IsValidSyscallNumber(sysno)) { // Allow all other system calls. return ErrorCode(ErrorCode::ERR_ALLOWED); } else { @@ -397,7 +398,7 @@ ErrorCode RedirectAllSyscallsPolicy(int sysno, void *aux) { #endif ) { return ErrorCode(ErrorCode::ERR_ALLOWED); - } else if (Sandbox::isValidSyscallNumber(sysno)) { + } else if (Sandbox::IsValidSyscallNumber(sysno)) { return Sandbox::UnsafeTrap(AllowRedirectedSyscall, aux); } else { return ErrorCode(ENOSYS); @@ -529,7 +530,7 @@ intptr_t BrokerOpenTrapHandler(const struct arch_seccomp_data& args, ErrorCode DenyOpenPolicy(int sysno, void *aux) { InitializedOpenBroker* iob = static_cast<InitializedOpenBroker*>(aux); - if (!Sandbox::isValidSyscallNumber(sysno)) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { return ErrorCode(ENOSYS); } @@ -580,4 +581,383 @@ BPF_TEST(SandboxBpf, UseOpenBroker, DenyOpenPolicy, BPF_ASSERT(read(cpu_info_fd, buf, sizeof(buf)) > 0); } +// This test exercises the Sandbox::Cond() method by building a complex +// tree of conditional equality operations. It then makes system calls and +// verifies that they return the values that we expected from our BPF +// program. +class EqualityStressTest { + public: + EqualityStressTest() { + // We want a deterministic test + srand(0); + + // Iterates over system call numbers and builds a random tree of + // equality tests. + // We are actually constructing a graph of ArgValue objects. This + // graph will later be used to a) compute our sandbox policy, and + // b) drive the code that verifies the output from the BPF program. + COMPILE_ASSERT(kNumTestCases < (int)(MAX_PUBLIC_SYSCALL-MIN_SYSCALL-10), + num_test_cases_must_be_significantly_smaller_than_num_system_calls); + for (int sysno = MIN_SYSCALL, end = kNumTestCases; sysno < end; ++sysno) { + if (IsReservedSyscall(sysno)) { + // Skip reserved system calls. This ensures that our test frame + // work isn't impacted by the fact that we are overriding + // a lot of different system calls. + ++end; + arg_values_.push_back(NULL); + } else { + arg_values_.push_back(RandomArgValue(rand() % kMaxArgs, 0, + rand() % kMaxArgs)); + } + } + } + + ~EqualityStressTest() { + for (std::vector<ArgValue *>::iterator iter = arg_values_.begin(); + iter != arg_values_.end(); + ++iter) { + DeleteArgValue(*iter); + } + } + + ErrorCode Policy(int sysno) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { + // FIXME: we should really not have to do that in a trivial policy + return ErrorCode(ENOSYS); + } else if (sysno < 0 || sysno >= (int)arg_values_.size() || + IsReservedSyscall(sysno)) { + // We only return ErrorCode values for the system calls that + // are part of our test data. Every other system call remains + // allowed. + return ErrorCode(ErrorCode::ERR_ALLOWED); + } else { + // ToErrorCode() turns an ArgValue object into an ErrorCode that is + // suitable for use by a sandbox policy. + return ToErrorCode(arg_values_[sysno]); + } + } + + void VerifyFilter() { + // Iterate over all system calls. Skip the system calls that have + // previously been determined as being reserved. + for (int sysno = 0; sysno < (int)arg_values_.size(); ++sysno) { + if (!arg_values_[sysno]) { + // Skip reserved system calls. + continue; + } + // Verify that system calls return the values that we expect them to + // return. This involves passing different combinations of system call + // parameters in order to exercise all possible code paths through the + // BPF filter program. + // We arbitrarily start by setting all six system call arguments to + // zero. And we then recursive traverse our tree of ArgValues to + // determine the necessary combinations of parameters. + intptr_t args[6] = { }; + Verify(sysno, args, *arg_values_[sysno]); + } + } + + private: + struct ArgValue { + int argno; // Argument number to inspect. + int size; // Number of test cases (must be > 0). + struct Tests { + uint32_t k_value; // Value to compare syscall arg against. + int err; // If non-zero, errno value to return. + struct ArgValue *arg_value; // Otherwise, more args needs inspecting. + } *tests; + int err; // If none of the tests passed, this is what + struct ArgValue *arg_value; // we'll return (this is the "else" branch). + }; + + bool IsReservedSyscall(int sysno) { + // There are a handful of system calls that we should never use in our + // test cases. These system calls are needed to allow the test framework + // to run properly. + // If we wanted to write fully generic code, there are more system calls + // that could be listed here, and it is quite difficult to come up with a + // truly comprehensive list. After all, we are deliberately making system + // calls unavailable. In practice, we have a pretty good idea of the system + // calls that will be made by this particular test. So, this small list is + // sufficient. But if anybody copy'n'pasted this code for other uses, they + // would have to review that the list. + return sysno == __NR_read || + sysno == __NR_write || + sysno == __NR_exit || + sysno == __NR_exit_group || + sysno == __NR_restart_syscall; + } + + ArgValue *RandomArgValue(int argno, int args_mask, int remaining_args) { + // Create a new ArgValue and fill it with random data. We use as bit mask + // to keep track of the system call parameters that have previously been + // set; this ensures that we won't accidentally define a contradictory + // set of equality tests. + struct ArgValue *arg_value = new ArgValue(); + args_mask |= 1 << argno; + arg_value->argno = argno; + + // Apply some restrictions on just how complex our tests can be. + // Otherwise, we end up with a BPF program that is too complicated for + // the kernel to load. + int fan_out = kMaxFanOut; + if (remaining_args > 3) { + fan_out = 1; + } else if (remaining_args > 2) { + fan_out = 2; + } + + // Create a couple of different test cases with randomized values that + // we want to use when comparing system call parameter number "argno". + arg_value->size = rand() % fan_out + 1; + arg_value->tests = new ArgValue::Tests[arg_value->size]; + + uint32_t k_value = rand(); + for (int n = 0; n < arg_value->size; ++n) { + // Ensure that we have unique values + k_value += rand() % (RAND_MAX/(kMaxFanOut+1)) + 1; + + // There are two possible types of nodes. Either this is a leaf node; + // in that case, we have completed all the equality tests that we + // wanted to perform, and we can now compute a random "errno" value that + // we should return. Or this is part of a more complex boolean + // expression; in that case, we have to recursively add tests for some + // of system call parameters that we have not yet included in our + // tests. + arg_value->tests[n].k_value = k_value; + if (!remaining_args || (rand() & 1)) { + arg_value->tests[n].err = (rand() % 1000) + 1; + arg_value->tests[n].arg_value = NULL; + } else { + arg_value->tests[n].err = 0; + arg_value->tests[n].arg_value = + RandomArgValue(RandomArg(args_mask), args_mask, remaining_args - 1); + } + } + // Finally, we have to define what we should return if none of the + // previous equality tests pass. Again, we can either deal with a leaf + // node, or we can randomly add another couple of tests. + if (!remaining_args || (rand() & 1)) { + arg_value->err = (rand() % 1000) + 1; + arg_value->arg_value = NULL; + } else { + arg_value->err = 0; + arg_value->arg_value = + RandomArgValue(RandomArg(args_mask), args_mask, remaining_args - 1); + } + // We have now built a new (sub-)tree of ArgValues defining a set of + // boolean expressions for testing random system call arguments against + // random values. Return this tree to our caller. + return arg_value; + } + + int RandomArg(int args_mask) { + // Compute a random system call parameter number. + int argno = rand() % kMaxArgs; + + // Make sure that this same parameter number has not previously been + // used. Otherwise, we could end up with a test that is impossible to + // satisfy (e.g. args[0] == 1 && args[0] == 2). + while (args_mask & (1 << argno)) { + argno = (argno + 1) % kMaxArgs; + } + return argno; + } + + void DeleteArgValue(ArgValue *arg_value) { + // Delete an ArgValue and all of its child nodes. This requires + // recursively descending into the tree. + if (arg_value) { + if (arg_value->size) { + for (int n = 0; n < arg_value->size; ++n) { + if (!arg_value->tests[n].err) { + DeleteArgValue(arg_value->tests[n].arg_value); + } + } + delete[] arg_value->tests; + } + if (!arg_value->err) { + DeleteArgValue(arg_value->arg_value); + } + delete arg_value; + } + } + + ErrorCode ToErrorCode(ArgValue *arg_value) { + // Compute the ErrorCode that should be returned, if none of our + // tests succeed (i.e. the system call parameter doesn't match any + // of the values in arg_value->tests[].k_value). + ErrorCode err; + if (arg_value->err) { + // If this was a leaf node, return the errno value that we expect to + // return from the BPF filter program. + err = ErrorCode(arg_value->err); + } else { + // If this wasn't a leaf node yet, recursively descend into the rest + // of the tree. This will end up adding a few more Sandbox::Cond() + // tests to our ErrorCode. + err = ToErrorCode(arg_value->arg_value); + } + + // Now, iterate over all the test cases that we want to compare against. + // This builds a chain of Sandbox::Cond() tests + // (aka "if ... elif ... elif ... elif ... fi") + for (int n = arg_value->size; n-- > 0; ) { + ErrorCode matched; + // Again, we distinguish between leaf nodes and subtrees. + if (arg_value->tests[n].err) { + matched = ErrorCode(arg_value->tests[n].err); + } else { + matched = ToErrorCode(arg_value->tests[n].arg_value); + } + // For now, all of our tests are limited to 32bit. + // We have separate tests that check the behavior of 32bit vs. 64bit + // conditional expressions. + err = Sandbox::Cond(arg_value->argno, ErrorCode::TP_32BIT, + ErrorCode::OP_EQUAL, arg_value->tests[n].k_value, + matched, err); + } + return err; + } + + void Verify(int sysno, intptr_t *args, const ArgValue& arg_value) { + uint32_t mismatched = 0; + // Iterate over all the k_values in arg_value.tests[] and verify that + // we see the expected return values from system calls, when we pass + // the k_value as a parameter in a system call. + for (int n = arg_value.size; n-- > 0; ) { + mismatched += arg_value.tests[n].k_value; + args[arg_value.argno] = arg_value.tests[n].k_value; + if (arg_value.tests[n].err) { + VerifyErrno(sysno, args, arg_value.tests[n].err); + } else { + Verify(sysno, args, *arg_value.tests[n].arg_value); + } + } + // Find a k_value that doesn't match any of the k_values in + // arg_value.tests[]. In most cases, the current value of "mismatched" + // would fit this requirement. But on the off-chance that it happens + // to collide, we double-check. + try_again: + for (int n = arg_value.size; n-- > 0; ) { + if (mismatched == arg_value.tests[n].k_value) { + ++mismatched; + goto try_again; + } + } + // Now verify that we see the expected return value from system calls, + // if we pass a value that doesn't match any of the conditions (i.e. this + // is testing the "else" clause of the conditions). + args[arg_value.argno] = mismatched; + if (arg_value.err) { + VerifyErrno(sysno, args, arg_value.err); + } else { + Verify(sysno, args, *arg_value.arg_value); + } + // Reset args[arg_value.argno]. This is not technically needed, but it + // makes it easier to reason about the correctness of our tests. + args[arg_value.argno] = 0; + } + + void VerifyErrno(int sysno, intptr_t *args, int err) { + // We installed BPF filters that return different errno values + // based on the system call number and the parameters that we decided + // to pass in. Verify that this condition holds true. + BPF_ASSERT(SandboxSyscall(sysno, + args[0], args[1], args[2], + args[3], args[4], args[5]) == -err); + } + + // Vector of ArgValue trees. These trees define all the possible boolean + // expressions that we want to turn into a BPF filter program. + std::vector<ArgValue *> arg_values_; + + // Don't increase these values. We are pushing the limits of the maximum + // BPF program that the kernel will allow us to load. If the values are + // increased too much, the test will start failing. + static const int kNumIterations = 3; + static const int kNumTestCases = 40; + static const int kMaxFanOut = 3; + static const int kMaxArgs = 6; +}; + +ErrorCode EqualityStressTestPolicy(int sysno, void *aux) { + return reinterpret_cast<EqualityStressTest *>(aux)->Policy(sysno); +} + +BPF_TEST(SandboxBpf, EqualityTests, EqualityStressTestPolicy, + EqualityStressTest /* BPF_AUX */) { + BPF_AUX.VerifyFilter(); +} + +ErrorCode EqualityArgumentWidthPolicy(int sysno, void *) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { + // FIXME: we should really not have to do that in a trivial policy + return ErrorCode(ENOSYS); + } else if (sysno == __NR_uname) { + return Sandbox::Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 0, + Sandbox::Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + 0x55555555, ErrorCode(1), ErrorCode(2)), + Sandbox::Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_EQUAL, + 0x55555555AAAAAAAAull, ErrorCode(1), ErrorCode(2))); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +BPF_TEST(SandboxBpf, EqualityArgumentWidth, EqualityArgumentWidthPolicy) { + BPF_ASSERT(SandboxSyscall(__NR_uname, 0, 0x55555555) == -1); + BPF_ASSERT(SandboxSyscall(__NR_uname, 0, 0xAAAAAAAA) == -2); +#if __SIZEOF_POINTER__ > 4 + // On 32bit machines, there is no way to pass a 64bit argument through the + // syscall interface. So, we have to skip the part of the test that requires + // 64bit arguments. + BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x55555555AAAAAAAAull) == -1); + BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x5555555500000000ull) == -2); + BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x5555555511111111ull) == -2); + BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x11111111AAAAAAAAull) == -2); +#endif +} + +#if __SIZEOF_POINTER__ > 4 +// On 32bit machines, there is no way to pass a 64bit argument through the +// syscall interface. So, we have to skip the part of the test that requires +// 64bit arguments. +BPF_DEATH_TEST(SandboxBpf, EqualityArgumentUnallowed64bit, + DEATH_MESSAGE("Unexpected 64bit argument detected"), + EqualityArgumentWidthPolicy) { + SandboxSyscall(__NR_uname, 0, 0x5555555555555555ull); +} +#endif + +ErrorCode EqualityWithNegativeArgumentsPolicy(int sysno, void *) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { + // FIXME: we should really not have to do that in a trivial policy + return ErrorCode(ENOSYS); + } else if (sysno == __NR_uname) { + return Sandbox::Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + 0xFFFFFFFF, ErrorCode(1), ErrorCode(2)); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +BPF_TEST(SandboxBpf, EqualityWithNegativeArguments, + EqualityWithNegativeArgumentsPolicy) { + BPF_ASSERT(SandboxSyscall(__NR_uname, 0xFFFFFFFF) == -1); + BPF_ASSERT(SandboxSyscall(__NR_uname, -1) == -1); + BPF_ASSERT(SandboxSyscall(__NR_uname, -1ll) == -1); +} + +#if __SIZEOF_POINTER__ > 4 +BPF_DEATH_TEST(SandboxBpf, EqualityWithNegative64bitArguments, + DEATH_MESSAGE("Unexpected 64bit argument detected"), + EqualityWithNegativeArgumentsPolicy) { + // When expecting a 32bit system call argument, we look at the MSB of the + // 64bit value and allow both "0" and "-1". But the latter is allowed only + // iff the LSB was negative. So, this death test should error out. + BPF_ASSERT(SandboxSyscall(__NR_uname, 0xFFFFFFFF00000000ll) == -1); +} +#endif + } // namespace diff --git a/sandbox/linux/seccomp-bpf/syscall_iterator.cc b/sandbox/linux/seccomp-bpf/syscall_iterator.cc index 583dcf6..59a4e863 100644 --- a/sandbox/linux/seccomp-bpf/syscall_iterator.cc +++ b/sandbox/linux/seccomp-bpf/syscall_iterator.cc @@ -16,7 +16,8 @@ uint32_t SyscallIterator::Next() { do { // |num_| has been initialized to 0, which we assume is also MIN_SYSCALL. // This true for supported architectures (Intel and ARM EABI). - CHECK_EQ(MIN_SYSCALL, 0u); + COMPILE_ASSERT(MIN_SYSCALL == 0u, + min_syscall_should_always_be_zero); val = num_; // First we iterate up to MAX_PUBLIC_SYSCALL, which is equal to MAX_SYSCALL @@ -78,14 +79,16 @@ bool SyscallIterator::IsValid(uint32_t num) { return false; } -bool SyscallIterator::IsArmPrivate(uint32_t num) { #if defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__)) +bool SyscallIterator::IsArmPrivate(uint32_t num) { return (num >= MIN_PRIVATE_SYSCALL && num <= MAX_PRIVATE_SYSCALL) || (num >= MIN_GHOST_SYSCALL && num <= MAX_SYSCALL); +} #else +bool SyscallIterator::IsArmPrivate(uint32_t) { return false; -#endif } +#endif } // namespace diff --git a/sandbox/linux/seccomp-bpf/syscall_iterator.h b/sandbox/linux/seccomp-bpf/syscall_iterator.h index 39568d8..e17593d 100644 --- a/sandbox/linux/seccomp-bpf/syscall_iterator.h +++ b/sandbox/linux/seccomp-bpf/syscall_iterator.h @@ -7,8 +7,6 @@ #include <stdint.h> -#include <base/logging.h> - namespace playground2 { // Iterates over the entire system call range from 0..0xFFFFFFFFu. This @@ -49,7 +47,7 @@ class SyscallIterator { bool done_; uint32_t num_; - DISALLOW_COPY_AND_ASSIGN(SyscallIterator); + DISALLOW_IMPLICIT_CONSTRUCTORS(SyscallIterator); }; } // namespace playground2 diff --git a/sandbox/linux/seccomp-bpf/syscall_unittest.cc b/sandbox/linux/seccomp-bpf/syscall_unittest.cc index 7e3ae8a..5209493 100644 --- a/sandbox/linux/seccomp-bpf/syscall_unittest.cc +++ b/sandbox/linux/seccomp-bpf/syscall_unittest.cc @@ -78,7 +78,7 @@ intptr_t CopySyscallArgsToAux(const struct arch_seccomp_data& args, void *aux) { } ErrorCode CopyAllArgsOnUnamePolicy(int sysno, void *aux) { - if (!Sandbox::isValidSyscallNumber(sysno)) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { return ErrorCode(ENOSYS); } if (sysno == __NR_uname) { diff --git a/sandbox/linux/seccomp-bpf/util.cc b/sandbox/linux/seccomp-bpf/util.cc index 904a169..77a92d9 100644 --- a/sandbox/linux/seccomp-bpf/util.cc +++ b/sandbox/linux/seccomp-bpf/util.cc @@ -17,7 +17,7 @@ namespace playground2 { -bool Util::sendFds(int transport, const void *buf, size_t len, ...) { +bool Util::SendFds(int transport, const void *buf, size_t len, ...) { int count = 0; va_list ap; va_start(ap, len); @@ -55,7 +55,7 @@ bool Util::sendFds(int transport, const void *buf, size_t len, ...) { static_cast<ssize_t>(sizeof(dummy) + ((buf && len > 0) ? len : 0)); } -bool Util::getFds(int transport, void *buf, size_t *len, ...) { +bool Util::GetFds(int transport, void *buf, size_t *len, ...) { int count = 0; va_list ap; va_start(ap, len); @@ -115,7 +115,7 @@ bool Util::getFds(int transport, void *buf, size_t *len, ...) { return true; } -void Util::closeAllBut(int fd, ...) { +void Util::CloseAllBut(int fd, ...) { int proc_fd; int fdir; if ((proc_fd = Sandbox::proc_fd()) < 0 || diff --git a/sandbox/linux/seccomp-bpf/util.h b/sandbox/linux/seccomp-bpf/util.h index 3e4d41b..2b3f22a 100644 --- a/sandbox/linux/seccomp-bpf/util.h +++ b/sandbox/linux/seccomp-bpf/util.h @@ -9,9 +9,9 @@ namespace playground2 { class Util { public: - static bool sendFds(int transport, const void *buf, size_t len, ...); - static bool getFds(int transport, void *buf, size_t *len, ...); - static void closeAllBut(int fd, ...); + static bool SendFds(int transport, const void *buf, size_t len, ...); + static bool GetFds(int transport, void *buf, size_t *len, ...); + static void CloseAllBut(int fd, ...); }; } // namespace diff --git a/sandbox/linux/seccomp-bpf/verifier.cc b/sandbox/linux/seccomp-bpf/verifier.cc index 40a1aa2..1c99619 100644 --- a/sandbox/linux/seccomp-bpf/verifier.cc +++ b/sandbox/linux/seccomp-bpf/verifier.cc @@ -42,13 +42,83 @@ bool Verifier::VerifyBPF(const std::vector<struct sock_filter>& program, #endif #endif ErrorCode code = evaluate_syscall(sysnum, aux); - uint32_t computed_ret = EvaluateBPF(program, data, err); + if (!VerifyErrorCode(program, &data, code, err)) { + return false; + } + } + return true; +} + +bool Verifier::VerifyErrorCode(const std::vector<struct sock_filter>& program, + struct arch_seccomp_data *data, + const ErrorCode& code, const char **err) { + if (code.error_type_ == ErrorCode::ET_SIMPLE || + code.error_type_ == ErrorCode::ET_TRAP) { + uint32_t computed_ret = EvaluateBPF(program, *data, err); if (*err) { return false; } else if (computed_ret != code.err()) { *err = "Exit code from BPF program doesn't match"; return false; } + } else if (code.error_type_ == ErrorCode::ET_COND) { + if (code.argno_ < 0 || code.argno_ >= 6) { + *err = "Invalid argument number in error code"; + return false; + } + switch (code.op_) { + case ErrorCode::OP_EQUAL: + // Verify that we can check a 32bit value (or the LSB of a 64bit value) + // for equality. + data->args[code.argno_] = code.value_; + if (!VerifyErrorCode(program, data, *code.passed_, err)) { + return false; + } + + // Change the value to no longer match and verify that this is detected + // as an inequality. + data->args[code.argno_] = code.value_ ^ 0x55AA55AA; + if (!VerifyErrorCode(program, data, *code.failed_, err)) { + return false; + } + + // BPF programs can only ever operate on 32bit values. So, we have + // generated additional BPF instructions that inspect the MSB. Verify + // that they behave as intended. + if (code.width_ == ErrorCode::TP_32BIT) { + if (code.value_ >> 32) { + SANDBOX_DIE("Invalid comparison of a 32bit system call argument " + "against a 64bit constant; this test is always false."); + } + + // If the system call argument was intended to be a 32bit parameter, + // verify that it is a fatal error if a 64bit value is ever passed + // here. + data->args[code.argno_] = 0x100000000ull; + if (!VerifyErrorCode(program, data, Sandbox::Unexpected64bitArgument(), + err)) { + return false; + } + } else { + // If the system call argument was intended to be a 64bit parameter, + // verify that we can handle (in-)equality for the MSB. This is + // essentially the same test that we did earlier for the LSB. + // We only need to verify the behavior of the inequality test. We + // know that the equality test already passed, as unlike the kernel + // the Verifier does operate on 64bit quantities. + data->args[code.argno_] = code.value_ ^ 0x55AA55AA00000000ull; + if (!VerifyErrorCode(program, data, *code.failed_, err)) { + return false; + } + } + break; + default: // TODO(markus): We can only check for equality so far. + *err = "Unsupported operation in conditional error code"; + return false; + } + } else { + *err = "Attempting to return invalid error code from BPF program"; + return false; } return true; } @@ -74,8 +144,21 @@ uint32_t Verifier::EvaluateBPF(const std::vector<struct sock_filter>& program, case BPF_JMP: Jmp(&state, insn, err); break; - case BPF_RET: - return Ret(&state, insn, err); + case BPF_RET: { + uint32_t r = Ret(&state, insn, err); + switch (r & SECCOMP_RET_ACTION) { + case SECCOMP_RET_TRAP: + case SECCOMP_RET_ERRNO: + case SECCOMP_RET_ALLOW: + break; + case SECCOMP_RET_KILL: // We don't ever generate this + case SECCOMP_RET_TRACE: // We don't ever generate this + case SECCOMP_RET_INVALID: // Should never show up in BPF program + default: + *err = "Unexpected return code found in BPF program"; + return 0; + } + return r; } default: *err = "Unexpected instruction in BPF program"; break; diff --git a/sandbox/linux/seccomp-bpf/verifier.h b/sandbox/linux/seccomp-bpf/verifier.h index 505015e..f66e7e93 100644 --- a/sandbox/linux/seccomp-bpf/verifier.h +++ b/sandbox/linux/seccomp-bpf/verifier.h @@ -60,6 +60,9 @@ class Verifier { DISALLOW_IMPLICIT_CONSTRUCTORS(State); }; + static bool VerifyErrorCode(const std::vector<struct sock_filter>& prg, + struct arch_seccomp_data *data, + const ErrorCode& code, const char **err); static void Ld (State *state, const struct sock_filter& insn, const char **err); static void Jmp(State *state, const struct sock_filter& insn, diff --git a/sandbox/linux/tests/unit_tests.cc b/sandbox/linux/tests/unit_tests.cc index 105c45b..e770bf5 100644 --- a/sandbox/linux/tests/unit_tests.cc +++ b/sandbox/linux/tests/unit_tests.cc @@ -9,11 +9,20 @@ #include "base/file_util.h" #include "sandbox/linux/tests/unit_tests.h" +namespace { + std::string TestFailedMessage(const std::string& msg) { + return msg.empty() ? "" : "Actual test failure: " + msg; + } +} + namespace sandbox { static const int kExpectedValue = 42; +static const int kIgnoreThisTest = 43; +static const int kExitWithAssertionFailure = 1; -void UnitTests::RunTestInProcess(UnitTests::Test test, void *arg) { +void UnitTests::RunTestInProcess(UnitTests::Test test, void *arg, + DeathCheck death, const void *death_aux) { // Runs a test in a sub-process. This is necessary for most of the code // in the BPF sandbox, as it potentially makes global state changes and as // it also tends to raise fatal errors, if the code has been used in an @@ -41,24 +50,41 @@ void UnitTests::RunTestInProcess(UnitTests::Test test, void *arg) { } (void)HANDLE_EINTR(close(fds[1])); - std::vector<char> msg; + std::vector<char> msg_buf; ssize_t rc; do { const unsigned int kCapacity = 256; - size_t len = msg.size(); - msg.resize(len + kCapacity); - rc = HANDLE_EINTR(read(fds[0], &msg[len], kCapacity)); - msg.resize(len + std::max(rc, static_cast<ssize_t>(0))); + size_t len = msg_buf.size(); + msg_buf.resize(len + kCapacity); + rc = HANDLE_EINTR(read(fds[0], &msg_buf[len], kCapacity)); + msg_buf.resize(len + std::max(rc, static_cast<ssize_t>(0))); } while (rc > 0); - std::string details; - if (!msg.empty()) { - details = "Actual test failure: " + std::string(msg.begin(), msg.end()); - } (void)HANDLE_EINTR(close(fds[0])); + std::string msg(msg_buf.begin(), msg_buf.end()); int status = 0; int waitpid_returned = HANDLE_EINTR(waitpid(pid, &status, 0)); - ASSERT_EQ(pid, waitpid_returned) << details; + ASSERT_EQ(pid, waitpid_returned) << TestFailedMessage(msg); + + // At run-time, we sometimes decide that a test shouldn't actually + // run (e.g. when testing sandbox features on a kernel that doesn't + // have sandboxing support). When that happens, don't attempt to + // call the "death" function, as it might be looking for a + // death-test condition that would never have triggered. + if (!WIFEXITED(status) || WEXITSTATUS(status) != kIgnoreThisTest || + !msg.empty()) { + // We use gtest's ASSERT_XXX() macros instead of the DeathCheck + // functions. This means, on failure, "return" is called. This + // only works correctly, if the call of the "death" callback is + // the very last thing in our function. + death(status, msg, death_aux); + } +} + +void UnitTests::DeathSuccess(int status, const std::string& msg, + const void *) { + std::string details(TestFailedMessage(msg)); + bool subprocess_terminated_normally = WIFEXITED(status); ASSERT_TRUE(subprocess_terminated_normally) << details; int subprocess_exit_status = WEXITSTATUS(status); @@ -67,11 +93,52 @@ void UnitTests::RunTestInProcess(UnitTests::Test test, void *arg) { EXPECT_FALSE(subprocess_exited_but_printed_messages) << details; } +void UnitTests::DeathMessage(int status, const std::string& msg, + const void *aux) { + std::string details(TestFailedMessage(msg)); + const char *expected_msg = static_cast<const char *>(aux); + + bool subprocess_terminated_normally = WIFEXITED(status); + ASSERT_TRUE(subprocess_terminated_normally) << details; + int subprocess_exit_status = WEXITSTATUS(status); + ASSERT_EQ(kExitWithAssertionFailure, subprocess_exit_status) << details; + bool subprocess_exited_without_matching_message = + msg.find(expected_msg) == std::string::npos; + EXPECT_FALSE(subprocess_exited_without_matching_message) << details; +} + +void UnitTests::DeathExitCode(int status, const std::string& msg, + const void *aux) { + int expected_exit_code = static_cast<int>(reinterpret_cast<intptr_t>(aux)); + std::string details(TestFailedMessage(msg)); + + bool subprocess_terminated_normally = WIFEXITED(status); + ASSERT_TRUE(subprocess_terminated_normally) << details; + int subprocess_exit_status = WEXITSTATUS(status); + ASSERT_EQ(subprocess_exit_status, expected_exit_code) << details; +} + +void UnitTests::DeathBySignal(int status, const std::string& msg, + const void *aux) { + int expected_signo = static_cast<int>(reinterpret_cast<intptr_t>(aux)); + std::string details(TestFailedMessage(msg)); + + bool subprocess_terminated_by_signal = WIFSIGNALED(status); + ASSERT_TRUE(subprocess_terminated_by_signal) << details; + int subprocess_signal_number = WTERMSIG(status); + ASSERT_EQ(subprocess_signal_number, expected_signo) << details; +} + void UnitTests::AssertionFailure(const char *expr, const char *file, int line) { fprintf(stderr, "%s:%d:%s", file, line, expr); fflush(stderr); - _exit(1); + _exit(kExitWithAssertionFailure); +} + +void UnitTests::IgnoreThisTest() { + fflush(stderr); + _exit(kIgnoreThisTest); } } // namespace diff --git a/sandbox/linux/tests/unit_tests.h b/sandbox/linux/tests/unit_tests.h index d6b4761..78bf9bc 100644 --- a/sandbox/linux/tests/unit_tests.h +++ b/sandbox/linux/tests/unit_tests.h @@ -10,17 +10,41 @@ namespace sandbox { -// Define a new test case that runs inside of a death test. This is necessary, -// as most of our tests by definition make global and irreversible changes to -// the system (i.e. they install a sandbox). GTest provides death tests as a -// tool to isolate global changes from the rest of the tests. -#define SANDBOX_TEST(test_case_name, test_name) \ +// While it is perfectly OK for a complex test to provide its own DeathCheck +// function. Most death tests have very simple requirements. These tests should +// use one of the predefined DEATH_XXX macros as an argument to +// SANDBOX_DEATH_TEST(). You can check for a (sub-)string in the output of the +// test, for a particular exit code, or for a particular death signal. +// NOTE: If you do decide to write your own DeathCheck, make sure to use +// gtests's ASSERT_XXX() macros instead of SANDBOX_ASSERT(). See +// unit_tests.cc for examples. +#define DEATH_SUCCESS() sandbox::UnitTests::DeathSuccess, NULL +#define DEATH_MESSAGE(msg) sandbox::UnitTests::DeathMessage, \ + static_cast<const void *>( \ + static_cast<const char *>(msg)) +#define DEATH_EXIT_CODE(rc) sandbox::UnitTests::DeathExitCode, \ + reinterpret_cast<void *>(static_cast<intptr_t>(rc)) +#define DEATH_BY_SIGNAL(s) sandbox::UnitTests::DeathExitCode, \ + reinterpret_cast<void *>(static_cast<intptr_t>(s)) + +// A SANDBOX_DEATH_TEST is just like a SANDBOX_TEST (see below), but it assumes +// that the test actually dies. The death test only passes if the death occurs +// in the expected fashion, as specified by "death" and "death_aux". These two +// parameters are typically set to one of the DEATH_XXX() macros. +#define SANDBOX_DEATH_TEST(test_case_name, test_name, death) \ void TEST_##test_name(void *); \ TEST(test_case_name, test_name) { \ - sandbox::UnitTests::RunTestInProcess(TEST_##test_name, NULL); \ + sandbox::UnitTests::RunTestInProcess(TEST_##test_name, NULL, death); \ } \ void TEST_##test_name(void *) +// Define a new test case that runs inside of a GTest death test. This is +// necessary, as most of our tests by definition make global and irreversible +// changes to the system (i.e. they install a sandbox). GTest provides death +// tests as a tool to isolate global changes from the rest of the tests. +#define SANDBOX_TEST(test_case_name, test_name) \ + SANDBOX_DEATH_TEST(test_case_name, test_name, DEATH_SUCCESS()) + // Simple assertion macro that is compatible with running inside of a death // test. We unfortunately cannot use any of the GTest macros. #define SANDBOX_STR(x) #x @@ -33,18 +57,56 @@ namespace sandbox { class UnitTests { public: typedef void (*Test)(void *); + typedef void (*DeathCheck)(int status, const std::string& msg, + const void *aux); // Runs a test inside a short-lived process. Do not call this function // directly. It is automatically invoked by SANDBOX_TEST(). Most sandboxing // functions make global irreversible changes to the execution environment // and must therefore execute in their own isolated process. - static void RunTestInProcess(Test test, void *arg); + static void RunTestInProcess(Test test, void *arg, DeathCheck death, + const void *death_aux); // Report a useful error message and terminate the current SANDBOX_TEST(). // Calling this function from outside a SANDBOX_TEST() is unlikely to do // anything useful. static void AssertionFailure(const char *expr, const char *file, int line); + // Sometimes we determine at run-time that a test should be disabled. + // Call this method if we want to return from a test and completely + // ignore its results. + // You should not call this method, if the test already ran any test-relevant + // code. Most notably, you should not call it, you already wrote any messages + // to stderr. + static void IgnoreThisTest(); + + // A DeathCheck method that verifies that the test completed succcessfully. + // This is the default test mode for SANDBOX_TEST(). The "aux" parameter + // of this DeathCheck is unused (and thus unnamed) + static void DeathSuccess(int status, const std::string& msg, const void *); + + // A DeathCheck method that verifies that the test completed with error + // code "1" and printed a message containing a particular substring. The + // "aux" pointer should point to a C-string containing the expected error + // message. This method is useful for checking assertion failures such as + // in SANDBOX_ASSERT() and/or SANDBOX_DIE(). + static void DeathMessage(int status, const std::string& msg, + const void *aux); + + // A DeathCheck method that verifies that the test completed with a + // particular exit code. If the test output any messages to stderr, they are + // silently ignored. The expected exit code should be passed in by + // casting the its "int" value to a "void *", which is then used for "aux". + static void DeathExitCode(int status, const std::string& msg, + const void *aux); + + // A DeathCheck method that verifies that the test was terminated by a + // particular signal. If the test output any messages to stderr, they are + // silently ignore. The expected signal number should be passed in by + // casting the its "int" value to a "void *", which is then used for "aux". + static void DeathBySignal(int status, const std::string& msg, + const void *aux); + private: DISALLOW_IMPLICIT_CONSTRUCTORS(UnitTests); }; |