summaryrefslogtreecommitdiffstats
path: root/sandbox
diff options
context:
space:
mode:
authormarkus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-12-15 00:34:53 +0000
committermarkus@chromium.org <markus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-12-15 00:34:53 +0000
commit8a40387c7e086fdda3892099929101c8a2a801ce (patch)
treee9fa076fce8691fd130394123d9032fcd493fe80 /sandbox
parente1bc2f766d9a86bc35b35c8323d2e0eb29383893 (diff)
downloadchromium_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.h35
-rw-r--r--sandbox/linux/seccomp-bpf/demo.cc135
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.cc288
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.h165
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc410
-rw-r--r--sandbox/linux/seccomp-bpf/syscall_iterator.cc9
-rw-r--r--sandbox/linux/seccomp-bpf/syscall_iterator.h4
-rw-r--r--sandbox/linux/seccomp-bpf/syscall_unittest.cc2
-rw-r--r--sandbox/linux/seccomp-bpf/util.cc6
-rw-r--r--sandbox/linux/seccomp-bpf/util.h6
-rw-r--r--sandbox/linux/seccomp-bpf/verifier.cc89
-rw-r--r--sandbox/linux/seccomp-bpf/verifier.h3
-rw-r--r--sandbox/linux/tests/unit_tests.cc91
-rw-r--r--sandbox/linux/tests/unit_tests.h76
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);
};