diff options
author | Torne (Richard Coles) <torne@google.com> | 2013-03-28 15:31:22 +0000 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2013-03-28 15:31:22 +0000 |
commit | 2a99a7e74a7f215066514fe81d2bfa6639d9eddd (patch) | |
tree | 7c2d04841fcd599fd83b0f0bb1100e1c89a35bae /sandbox | |
parent | 61c449bbbb53310a8c041d8cefdd6b01a126cc7e (diff) | |
download | chromium_org-2a99a7e74a7f215066514fe81d2bfa6639d9eddd.tar.gz |
Merge from Chromium at DEPS revision r190564
This commit was generated by merge_to_master.py.
Change-Id: Icadecbce29854b8fa25fd335b2c1949b5ca5d170
Diffstat (limited to 'sandbox')
67 files changed, 6828 insertions, 1149 deletions
diff --git a/sandbox/linux/sandbox_linux.gypi b/sandbox/linux/sandbox_linux.gypi index c02cd31644..f9cef2bf6d 100644 --- a/sandbox/linux/sandbox_linux.gypi +++ b/sandbox/linux/sandbox_linux.gypi @@ -3,6 +3,33 @@ # found in the LICENSE file. { + 'variables': { + 'conditions': [ + ['OS=="linux"', { + 'compile_suid_client': 1, + }, { + 'compile_suid_client': 0, + }], + ['((OS=="linux" or OS=="android") and ' + '(target_arch=="ia32" or target_arch=="x64" or ' + 'target_arch=="arm"))', { + 'compile_seccomp_bpf': 1, + }, { + 'compile_seccomp_bpf': 0, + }], + ], + }, + 'target_defaults': { + 'target_conditions': [ + # All linux/ files will automatically be excluded on Android + # so make sure we re-include them explicitly. + ['OS == "android"', { + 'sources/': [ + ['include', '^linux/'], + ], + }], + ], + }, 'targets': [ # We have two principal targets: sandbox and sandbox_linux_unittests # All other targets are listed as dependencies. @@ -12,9 +39,14 @@ 'target_name': 'sandbox', 'type': 'none', 'dependencies': [ - 'suid_sandbox_client', + 'sandbox_services', ], 'conditions': [ + [ 'compile_suid_client==1', { + 'dependencies': [ + 'suid_sandbox_client', + ], + }], # Only compile in the seccomp mode 1 code for the flag combination # where we support it. [ 'OS=="linux" and (target_arch=="ia32" or target_arch=="x64") ' @@ -24,9 +56,7 @@ ], }], # Similarly, compile seccomp BPF when we support it - [ 'OS=="linux" and (target_arch=="ia32" or target_arch=="x64" ' - 'or target_arch=="arm")', { - 'type': 'static_library', + [ 'compile_seccomp_bpf==1', { 'dependencies': [ 'seccomp_bpf', ], @@ -34,30 +64,25 @@ ], }, { + # The main sandboxing test target. 'target_name': 'sandbox_linux_unittests', - 'type': 'executable', - 'dependencies': [ - 'sandbox', - '../testing/gtest.gyp:gtest', - ], - 'sources': [ - 'tests/main.cc', - 'tests/unit_tests.cc', - 'tests/unit_tests.h', - 'suid/client/setuid_sandbox_client_unittest.cc', + 'includes': [ + 'sandbox_linux_test_sources.gypi', ], - 'include_dirs': [ - '../..', + 'type': 'executable', + }, + { + # This target is the shared library used by Android APK (i.e. + # JNI-friendly) tests. + 'target_name': 'sandbox_linux_jni_unittests', + 'includes': [ + 'sandbox_linux_test_sources.gypi', ], + 'type': 'shared_library', 'conditions': [ - [ 'OS=="linux" and (target_arch=="ia32" or target_arch=="x64" ' - 'or target_arch=="arm")', { - 'sources': [ - 'seccomp-bpf/bpf_tests.h', - 'seccomp-bpf/codegen_unittest.cc', - 'seccomp-bpf/errorcode_unittest.cc', - 'seccomp-bpf/sandbox_bpf_unittest.cc', - 'seccomp-bpf/syscall_iterator_unittest.cc', + [ 'OS == "android" and gtest_target_type == "shared_library"', { + 'dependencies': [ + '../testing/android/native_test.gyp:native_test_native_code', ], }], ], @@ -75,15 +100,22 @@ 'seccomp-bpf/errorcode.cc', 'seccomp-bpf/errorcode.h', 'seccomp-bpf/instruction.h', + 'seccomp-bpf/linux_seccomp.h', + 'seccomp-bpf/port.h', 'seccomp-bpf/sandbox_bpf.cc', 'seccomp-bpf/sandbox_bpf.h', + 'seccomp-bpf/syscall.cc', + 'seccomp-bpf/syscall.h', 'seccomp-bpf/syscall_iterator.cc', 'seccomp-bpf/syscall_iterator.h', + 'seccomp-bpf/trap.cc', + 'seccomp-bpf/trap.h', 'seccomp-bpf/verifier.cc', 'seccomp-bpf/verifier.h', ], 'dependencies': [ '../base/base.gyp:base', + 'sandbox_services_headers', ], 'include_dirs': [ '../..', @@ -110,7 +142,37 @@ '../..', ], }, + { 'target_name': 'sandbox_services', + 'type': 'static_library', + 'sources': [ + 'services/broker_process.cc', + 'services/broker_process.h', + ], + 'dependencies': [ + '../base/base.gyp:base', + ], + 'include_dirs': [ + '..', + ], + }, + { 'target_name': 'sandbox_services_headers', + 'type': 'none', + 'sources': [ + 'services/android_arm_ucontext.h', + 'services/android_ucontext.h', + 'services/android_i386_ucontext.h', + 'services/arm_linux_syscalls.h', + 'services/linux_syscalls.h', + 'services/x86_32_linux_syscalls.h', + 'services/x86_64_linux_syscalls.h', + ], + 'include_dirs': [ + '..', + ], + }, { + # We make this its own target so that it does not interfere + # with our tests. 'target_name': 'libc_urandom_override', 'type': 'static_library', 'sources': [ @@ -140,6 +202,27 @@ '..', ], }, - + ], + 'conditions': [ + # Strategy copied from base_unittests_apk in base/base.gyp. + [ 'OS=="android" and gtest_target_type == "shared_library"', { + 'targets': [ + { + 'target_name': 'sandbox_linux_jni_unittests_apk', + 'type': 'none', + 'variables': { + 'test_suite_name': 'sandbox_linux_jni_unittests', + 'input_shlib_path': + '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)' + 'sandbox_linux_jni_unittests' + '<(SHARED_LIB_SUFFIX)', + }, + 'dependencies': [ + 'sandbox_linux_jni_unittests', + ], + 'includes': [ '../../build/apk_test.gypi' ], + } + ], + }], ], } diff --git a/sandbox/linux/sandbox_linux_test_sources.gypi b/sandbox/linux/sandbox_linux_test_sources.gypi new file mode 100644 index 0000000000..0bc0aa16db --- /dev/null +++ b/sandbox/linux/sandbox_linux_test_sources.gypi @@ -0,0 +1,38 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Tests need to be compiled in the same link unit, so we have to list them +# in a separate .gypi file. +{ + 'dependencies': [ + 'sandbox', + '../testing/gtest.gyp:gtest', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'tests/main.cc', + 'tests/unit_tests.cc', + 'tests/unit_tests.h', + 'services/broker_process_unittest.cc', + ], + 'conditions': [ + [ 'compile_suid_client==1', { + 'sources': [ + 'suid/client/setuid_sandbox_client_unittest.cc', + ], + }], + [ 'compile_seccomp_bpf==1', { + 'sources': [ + 'seccomp-bpf/bpf_tests.h', + 'seccomp-bpf/codegen_unittest.cc', + 'seccomp-bpf/errorcode_unittest.cc', + 'seccomp-bpf/sandbox_bpf_unittest.cc', + 'seccomp-bpf/syscall_iterator_unittest.cc', + 'seccomp-bpf/syscall_unittest.cc', + ], + }], + ], +} diff --git a/sandbox/linux/seccomp-bpf/Makefile b/sandbox/linux/seccomp-bpf/Makefile index a69719807a..6b35580597 100644 --- a/sandbox/linux/seccomp-bpf/Makefile +++ b/sandbox/linux/seccomp-bpf/Makefile @@ -1,8 +1,8 @@ DEF_CFLAGS = -g -O3 -Wall -Werror -Wextra -Wno-missing-field-initializers -fPIC -I. -DEF_CPPFLAGS = -D_GNU_SOURCE -DSECCOMP_BPF_STANDALONE -DSECCOMP_BPF_VALGRIND_HACKS -include valgrind/valgrind.h -iquote ../../.. +DEF_CPPFLAGS = -D_GNU_SOURCE -DSECCOMP_BPF_STANDALONE -iquote ../../.. DEF_LDFLAGS = -g -lpthread DEPFLAGS = -MMD -MF .$@.d -MODS := demo sandbox_bpf die codegen errorcode syscall_iterator util verifier +MODS := demo sandbox_bpf basicblock codegen die errorcode syscall syscall_iterator trap verifier OBJS64 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o64/') OBJS32 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o32/') ALL_OBJS = $(OBJS32) $(OBJS64) diff --git a/sandbox/linux/seccomp-bpf/bpf_tests.h b/sandbox/linux/seccomp-bpf/bpf_tests.h index 8da25f99a5..680ece601f 100644 --- a/sandbox/linux/seccomp-bpf/bpf_tests.h +++ b/sandbox/linux/seccomp-bpf/bpf_tests.h @@ -5,12 +5,29 @@ #ifndef SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTS_H__ #define SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTS_H__ +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + #include "sandbox/linux/tests/unit_tests.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" 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 +39,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 +76,30 @@ 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 sandbox; + sandbox.set_proc_fd(proc_fd); + sandbox.SetSandboxPolicy(arg->policy(), &arg->aux_); + sandbox.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); + // Call the compiler and verify the policy. That's the least we can do, + // if we don't have kernel support. + playground2::Sandbox sandbox; + sandbox.SetSandboxPolicy(arg->policy(), &arg->aux_); + playground2::Sandbox::Program *program = + sandbox.AssembleFilter(true /* force_verification */); + delete program; + sandbox::UnitTests::IgnoreThisTest(); } } diff --git a/sandbox/linux/seccomp-bpf/codegen.cc b/sandbox/linux/seccomp-bpf/codegen.cc index 8b36315cff..17b5d846dc 100644 --- a/sandbox/linux/seccomp-bpf/codegen.cc +++ b/sandbox/linux/seccomp-bpf/codegen.cc @@ -2,9 +2,36 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <stdio.h> + #include "sandbox/linux/seccomp-bpf/codegen.h" +namespace { + +// Helper function for Traverse(). +void TraverseRecursively(std::set<playground2::Instruction *> *visited, + playground2::Instruction *instruction) { + if (visited->find(instruction) == visited->end()) { + visited->insert(instruction); + switch (BPF_CLASS(instruction->code)) { + case BPF_JMP: + if (BPF_OP(instruction->code) != BPF_JA) { + TraverseRecursively(visited, instruction->jf_ptr); + } + TraverseRecursively(visited, instruction->jt_ptr); + break; + case BPF_RET: + break; + default: + TraverseRecursively(visited, instruction->next); + break; + } + } +} + +} // namespace + namespace playground2 { CodeGen::CodeGen() @@ -33,7 +60,27 @@ void CodeGen::PrintProgram(const Sandbox::Program& program) { switch (BPF_CLASS(iter->code)) { case BPF_LD: if (iter->code == BPF_LD+BPF_W+BPF_ABS) { - fprintf(stderr, "LOAD %d\n", (int)iter->k); + fprintf(stderr, "LOAD %d // ", (int)iter->k); + if (iter->k == offsetof(struct arch_seccomp_data, nr)) { + fprintf(stderr, "System call number\n"); + } else if (iter->k == offsetof(struct arch_seccomp_data, arch)) { + fprintf(stderr, "Architecture\n"); + } else if (iter->k == offsetof(struct arch_seccomp_data, + instruction_pointer)) { + fprintf(stderr, "Instruction pointer (LSB)\n"); + } else if (iter->k == offsetof(struct arch_seccomp_data, + instruction_pointer) + 4) { + fprintf(stderr, "Instruction pointer (MSB)\n"); + } else if (iter->k >= offsetof(struct arch_seccomp_data, args) && + iter->k < offsetof(struct arch_seccomp_data, args)+48 && + (iter->k-offsetof(struct arch_seccomp_data, args))%4 == 0) { + fprintf(stderr, "Argument %d (%cSB)\n", + (int)(iter->k-offsetof(struct arch_seccomp_data, args))/8, + (iter->k-offsetof(struct arch_seccomp_data, + args))%8 ? 'M' : 'L'); + } else { + fprintf(stderr, "???\n"); + } } else { fprintf(stderr, "LOAD ???\n"); } @@ -52,7 +99,31 @@ void CodeGen::PrintProgram(const Sandbox::Program& program) { } break; case BPF_RET: - fprintf(stderr, "RET 0x%x\n", iter->k); + fprintf(stderr, "RET 0x%x // ", iter->k); + if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP) { + fprintf(stderr, "Trap #%d\n", iter->k & SECCOMP_RET_DATA); + } else if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) { + fprintf(stderr, "errno = %d\n", iter->k & SECCOMP_RET_DATA); + } else if (iter->k == SECCOMP_RET_ALLOW) { + fprintf(stderr, "Allowed\n"); + } else { + fprintf(stderr, "???\n"); + } + break; + case BPF_ALU: + fprintf(stderr, BPF_OP(iter->code) == BPF_NEG + ? "A := -A\n" : "A := A %s 0x%x\n", + BPF_OP(iter->code) == BPF_ADD ? "+" : + BPF_OP(iter->code) == BPF_SUB ? "-" : + BPF_OP(iter->code) == BPF_MUL ? "*" : + BPF_OP(iter->code) == BPF_DIV ? "/" : + BPF_OP(iter->code) == BPF_MOD ? "%" : + BPF_OP(iter->code) == BPF_OR ? "|" : + BPF_OP(iter->code) == BPF_XOR ? "^" : + BPF_OP(iter->code) == BPF_AND ? "&" : + BPF_OP(iter->code) == BPF_LSH ? "<<" : + BPF_OP(iter->code) == BPF_RSH ? ">>" : "???", + (int)iter->k); break; default: fprintf(stderr, "???\n"); @@ -145,6 +216,17 @@ void CodeGen::JoinInstructions(Instruction *head, Instruction *tail) { return; } +void CodeGen::Traverse(Instruction *instruction, + void (*fnc)(Instruction *, void *), void *aux) { + std::set<Instruction *> visited; + TraverseRecursively(&visited, instruction); + for (std::set<Instruction *>::const_iterator iter = visited.begin(); + iter != visited.end(); + ++iter) { + fnc(*iter, aux); + } +} + void CodeGen::FindBranchTargets(const Instruction& instructions, BranchTargets *branch_targets) { // Follow all possible paths through the "instructions" graph and compute diff --git a/sandbox/linux/seccomp-bpf/codegen.h b/sandbox/linux/seccomp-bpf/codegen.h index b7d1d3904c..88521c2b52 100644 --- a/sandbox/linux/seccomp-bpf/codegen.h +++ b/sandbox/linux/seccomp-bpf/codegen.h @@ -77,6 +77,15 @@ class CodeGen { // or if a (conditional) jump still has an unsatisfied target. void JoinInstructions(Instruction *head, Instruction *tail); + // Traverse the graph of instructions and visit each instruction once. + // Traversal order is implementation-defined. It is acceptable to make + // changes to the graph from within the callback function. These changes + // do not affect traversal. + // The "fnc" function gets called with both the instruction and the opaque + // "aux" pointer. + void Traverse(Instruction *, void (*fnc)(Instruction *, void *aux), + void *aux); + // Compiles the graph of instructions into a BPF program that can be passed // to the kernel. Please note that this function modifies the graph in place // and must therefore only be called once per graph. diff --git a/sandbox/linux/seccomp-bpf/demo.cc b/sandbox/linux/seccomp-bpf/demo.cc index 02fd8a0140..b2622ec452 100644 --- a/sandbox/linux/seccomp-bpf/demo.cc +++ b/sandbox/linux/seccomp-bpf/demo.cc @@ -10,6 +10,7 @@ #include <netinet/udp.h> #include <pthread.h> #include <signal.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -26,12 +27,10 @@ #include <unistd.h> #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" -#include "sandbox/linux/seccomp-bpf/util.h" using playground2::arch_seccomp_data; using playground2::ErrorCode; using playground2::Sandbox; -using playground2::Util; #define ERR EPERM @@ -41,6 +40,106 @@ using playground2::Util; // actually enforce restrictions in a meaningful way: #define _exit(x) do { } while (0) +namespace { + +bool SendFds(int transport, const void *buf, size_t len, ...) { + int count = 0; + va_list ap; + va_start(ap, len); + while (va_arg(ap, int) >= 0) { + ++count; + } + va_end(ap); + if (!count) { + return false; + } + char cmsg_buf[CMSG_SPACE(count*sizeof(int))]; + memset(cmsg_buf, 0, sizeof(cmsg_buf)); + struct iovec iov[2] = { { 0 } }; + struct msghdr msg = { 0 }; + int dummy = 0; + iov[0].iov_base = &dummy; + iov[0].iov_len = sizeof(dummy); + if (buf && len > 0) { + iov[1].iov_base = const_cast<void *>(buf); + iov[1].iov_len = len; + } + msg.msg_iov = iov; + msg.msg_iovlen = (buf && len > 0) ? 2 : 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_LEN(count*sizeof(int)); + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(count*sizeof(int)); + va_start(ap, len); + for (int i = 0, fd; (fd = va_arg(ap, int)) >= 0; ++i) { + (reinterpret_cast<int *>(CMSG_DATA(cmsg)))[i] = fd; + } + return sendmsg(transport, &msg, 0) == + static_cast<ssize_t>(sizeof(dummy) + ((buf && len > 0) ? len : 0)); +} + +bool GetFds(int transport, void *buf, size_t *len, ...) { + int count = 0; + va_list ap; + va_start(ap, len); + for (int *fd; (fd = va_arg(ap, int *)) != NULL; ++count) { + *fd = -1; + } + va_end(ap); + if (!count) { + return false; + } + char cmsg_buf[CMSG_SPACE(count*sizeof(int))]; + memset(cmsg_buf, 0, sizeof(cmsg_buf)); + struct iovec iov[2] = { { 0 } }; + struct msghdr msg = { 0 }; + int err; + iov[0].iov_base = &err; + iov[0].iov_len = sizeof(int); + if (buf && len && *len > 0) { + iov[1].iov_base = buf; + iov[1].iov_len = *len; + } + msg.msg_iov = iov; + msg.msg_iovlen = (buf && len && *len > 0) ? 2 : 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_LEN(count*sizeof(int)); + ssize_t bytes = recvmsg(transport, &msg, 0); + if (len) { + *len = bytes > static_cast<int>(sizeof(int)) ? bytes - sizeof(int) : 0; + } + if (bytes != static_cast<ssize_t>(sizeof(int) + iov[1].iov_len)) { + if (bytes >= 0) { + errno = 0; + } + return false; + } + if (err) { + // "err" is the first four bytes of the payload. If these are non-zero, + // the sender on the other side of the socketpair sent us an errno value. + // We don't expect to get any file handles in this case. + errno = err; + return false; + } + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if ((msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) || + !cmsg || + cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS || + cmsg->cmsg_len != CMSG_LEN(count*sizeof(int))) { + errno = EBADF; + return false; + } + va_start(ap, len); + for (int *fd, i = 0; (fd = va_arg(ap, int *)) != NULL; ++i) { + *fd = (reinterpret_cast<int *>(CMSG_DATA(cmsg)))[i]; + } + va_end(ap); + return true; +} + // POSIX doesn't define any async-signal safe function for converting // an integer to ASCII. We'll have to define our own version. @@ -48,7 +147,7 @@ using playground2::Util; // conversion was successful or NULL otherwise. It never writes more than "sz" // bytes. Output will be truncated as needed, and a NUL character is always // appended. -static char *itoa_r(int i, char *buf, size_t sz) { +char *itoa_r(int i, char *buf, size_t sz) { // Make sure we can write at least one NUL byte. size_t n = 1; if (n > sz) { @@ -116,8 +215,7 @@ static char *itoa_r(int i, char *buf, size_t sz) { // might try to evaluate the system call in user-space, instead. // The only notable complication is that this function must be async-signal // safe. This restricts the libary functions that we can call. -static intptr_t defaultHandler(const struct arch_seccomp_data& data, - void *) { +intptr_t DefaultHandler(const struct arch_seccomp_data& data, void *) { static const char msg0[] = "Disallowed system call #"; static const char msg1[] = "\n"; char buf[sizeof(msg0) - 1 + 25 + sizeof(msg1)]; @@ -137,92 +235,102 @@ static intptr_t defaultHandler(const struct arch_seccomp_data& data, return -ERR; } -static ErrorCode evaluator(int sysno) { +ErrorCode Evaluator(Sandbox *sandbox, 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 +358,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: @@ -258,15 +365,15 @@ static ErrorCode evaluator(int sysno) { // Everything that isn't explicitly allowed is denied. default: - return Sandbox::Trap(defaultHandler, NULL); + return sandbox->Trap(DefaultHandler, NULL); } } -static void *threadFnc(void *arg) { +void *ThreadFnc(void *arg) { return arg; } -static void *sendmsgStressThreadFnc(void *arg) { +void *SendmsgStressThreadFnc(void *arg) { if (arg) { } static const int repetitions = 100; static const int kNumFds = 3; @@ -278,8 +385,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 (!SendFds(fds[0], "test", 4, fds[1], fds[1], fds[1], -1) || + !GetFds(fds[1], buf, &len, fds+2, fds+3, fds+4, NULL) || len != 4 || memcmp(buf, "test", len) || write(fds[2], "demo", 4) != 4 || @@ -298,22 +405,25 @@ static void *sendmsgStressThreadFnc(void *arg) { return NULL; } +} // namespace + 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 sandbox; + sandbox.set_proc_fd(proc_fd); + sandbox.SetSandboxPolicy(Evaluator, NULL); + sandbox.StartSandbox(); // Check that we can create threads pthread_t thr; - if (!pthread_create(&thr, NULL, threadFnc, + if (!pthread_create(&thr, NULL, ThreadFnc, reinterpret_cast<void *>(0x1234))) { void *ret; pthread_join(thr, &ret); @@ -367,8 +477,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 (!SendFds(fds[0], "test", 4, fds[1], -1) || + !GetFds(fds[1], buf, &len, fds+2, NULL) || len != 4 || memcmp(buf, "test", len) || write(fds[2], "demo", 4) != 4 || @@ -401,7 +511,7 @@ int main(int argc, char *argv[]) { pthread_t sendmsgStressThreads[kSendmsgStressNumThreads]; for (int i = 0; i < kSendmsgStressNumThreads; ++i) { if (pthread_create(sendmsgStressThreads + i, NULL, - sendmsgStressThreadFnc, NULL)) { + SendmsgStressThreadFnc, NULL)) { perror("pthread_create"); _exit(1); } diff --git a/sandbox/linux/seccomp-bpf/die.cc b/sandbox/linux/seccomp-bpf/die.cc index b141424f58..4962c4d30b 100644 --- a/sandbox/linux/seccomp-bpf/die.cc +++ b/sandbox/linux/seccomp-bpf/die.cc @@ -2,9 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <errno.h> +#include <linux/unistd.h> +#include <stdio.h> +#include <sys/prctl.h> + #include <string> #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" namespace playground2 { @@ -15,7 +21,7 @@ void Die::ExitGroup() { // Especially, since we are dealing with system call filters. Continuing // execution would be very bad in most cases where ExitGroup() gets called. // So, we'll try a few other strategies too. - syscall(__NR_exit_group, 1); + SandboxSyscall(__NR_exit_group, 1); // We have no idea what our run-time environment looks like. So, signal // handlers might or might not do the right thing. Try to reset settings @@ -23,7 +29,7 @@ void Die::ExitGroup() { // succeeded in doing so. Nonetheless, triggering a fatal signal could help // us terminate. signal(SIGSEGV, SIG_DFL); - syscall(__NR_prctl, PR_SET_DUMPABLE, (void *)0, (void *)0, (void *)0); + SandboxSyscall(__NR_prctl, PR_SET_DUMPABLE, (void *)0, (void *)0, (void *)0); if (*(volatile char *)0) { } // If there is no way for us to ask for the program to exit, the next @@ -32,7 +38,7 @@ void Die::ExitGroup() { // We in fact retry the system call inside of our loop so that it will // stand out when somebody tries to diagnose the problem by using "strace". for (;;) { - syscall(__NR_exit_group, 1); + SandboxSyscall(__NR_exit_group, 1); } } @@ -49,6 +55,16 @@ void Die::SandboxDie(const char *msg, const char *file, int line) { ExitGroup(); } +void Die::SandboxInfo(const char *msg, const char *file, int line) { + if (!suppress_info_) { + #if defined(SECCOMP_BPF_STANDALONE) + Die::LogToStderr(msg, file, line); + #else + logging::LogMessage(file, line, logging::LOG_INFO).stream() << msg; + #endif + } +} + void Die::LogToStderr(const char *msg, const char *file, int line) { if (msg) { char buf[40]; @@ -57,10 +73,11 @@ void Die::LogToStderr(const char *msg, const char *file, int line) { // No need to loop. Short write()s are unlikely and if they happen we // probably prefer them over a loop that blocks. - if (HANDLE_EINTR(write(2, s.c_str(), s.length()))) { } + if (HANDLE_EINTR(SandboxSyscall(__NR_write, 2, s.c_str(), s.length()))) { } } } -bool Die::simple_exit_ = false; +bool Die::simple_exit_ = false; +bool Die::suppress_info_ = false; } // namespace diff --git a/sandbox/linux/seccomp-bpf/die.h b/sandbox/linux/seccomp-bpf/die.h index 608afde7c4..f15f10877e 100644 --- a/sandbox/linux/seccomp-bpf/die.h +++ b/sandbox/linux/seccomp-bpf/die.h @@ -5,13 +5,19 @@ #ifndef SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ #define SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ +#include "sandbox/linux/seccomp-bpf/port.h" + + namespace playground2 { class Die { public: // This is the main API for using this file. Prints a error message and // exits with a fatal error. - #define SANDBOX_DIE(m) Die::SandboxDie(m, __FILE__, __LINE__) + #define SANDBOX_DIE(m) playground2::Die::SandboxDie(m, __FILE__, __LINE__) + + // Adds an informational message to the log file or stderr as appropriate. + #define SANDBOX_INFO(m) playground2::Die::SandboxInfo(m, __FILE__, __LINE__) // Terminate the program, even if the current sandbox policy prevents some // of the more commonly used functions used for exiting. @@ -25,6 +31,10 @@ class Die { static void SandboxDie(const char *msg, const char *file, int line) __attribute__((noreturn)); + // This method gets called by SANDBOX_INFO(). There is normally no reason + // to call it directly unless you are defining your own logging macro. + static void SandboxInfo(const char *msg, const char *file, int line); + // Writes a message to stderr. Used as a fall-back choice, if we don't have // any other way to report an error. static void LogToStderr(const char *msg, const char *file, int line); @@ -36,8 +46,13 @@ class Die { // unit tests or in the supportsSeccompSandbox() method). static void EnableSimpleExit() { simple_exit_ = true; } + // Sometimes we need to disable all informational messages (e.g. from within + // unittests). + static void SuppressInfoMessages(bool flag) { suppress_info_ = flag; } + private: static bool simple_exit_; + static bool suppress_info_; DISALLOW_IMPLICIT_CONSTRUCTORS(Die); }; diff --git a/sandbox/linux/seccomp-bpf/errorcode.cc b/sandbox/linux/seccomp-bpf/errorcode.cc index cc79cb66cc..ab89d73c3f 100644 --- a/sandbox/linux/seccomp-bpf/errorcode.cc +++ b/sandbox/linux/seccomp-bpf/errorcode.cc @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" namespace playground2 { @@ -22,10 +23,12 @@ ErrorCode::ErrorCode(int err) { } } -ErrorCode::ErrorCode(ErrorCode::TrapFnc fnc, const void *aux, uint16_t id) +ErrorCode::ErrorCode(Trap::TrapFnc fnc, const void *aux, bool safe, + uint16_t id) : error_type_(ET_TRAP), fnc_(fnc), aux_(const_cast<void *>(aux)), + safe_(safe), err_(SECCOMP_RET_TRAP + id) { } diff --git a/sandbox/linux/seccomp-bpf/errorcode.h b/sandbox/linux/seccomp-bpf/errorcode.h index 2b941eea03..61ec11013d 100644 --- a/sandbox/linux/seccomp-bpf/errorcode.h +++ b/sandbox/linux/seccomp-bpf/errorcode.h @@ -5,14 +5,17 @@ #ifndef SANDBOX_LINUX_SECCOMP_BPF_ERRORCODE_H__ #define SANDBOX_LINUX_SECCOMP_BPF_ERRORCODE_H__ +#include "sandbox/linux/seccomp-bpf/linux_seccomp.h" +#include "sandbox/linux/seccomp-bpf/trap.h" + namespace playground2 { struct arch_seccomp_data; -// This class holds all the possible values that can returned by a sandbox +// This class holds all the possible values that can be returned by a sandbox // policy. // We can either wrap a symbolic ErrorCode (i.e. ERR_XXX enum values), an -// errno value (in the range 1..4095), a pointer to a TrapFnc callback +// errno value (in the range 0..4095), a pointer to a TrapFnc callback // handling a SECCOMP_RET_TRAP trap, or a complex constraint. // All of the commonly used values are stored in the "err_" field. So, code // that is using the ErrorCode class typically operates on a single 32bit @@ -20,36 +23,85 @@ struct arch_seccomp_data; class ErrorCode { public: enum { - // Allow this system call. - ERR_ALLOWED = 0x0000, + // Allow this system call. The value of ERR_ALLOWED is pretty much + // completely arbitrary. But we want to pick it so that is is unlikely + // to be passed in accidentally, when the user intended to return an + // "errno" (see below) value instead. + ERR_ALLOWED = 0x04000000, // Deny the system call with a particular "errno" value. - ERR_MIN_ERRNO = 1, + // N.B.: It is also possible to return "0" here. That would normally + // indicate success, but it won't actually run the system call. + // This is very different from return ERR_ALLOWED. + ERR_MIN_ERRNO = 0, + // TODO(markus): Android only supports errno up to 255 + // (crbug.com/181647). ERR_MAX_ERRNO = 4095, - - // This code should never be used directly, it is used internally only. - ERR_INVALID = -1, }; - // TrapFnc is a pointer to a function that handles Seccomp traps in - // user-space. The seccomp policy can request that a trap handler gets - // installed; it does so by returning a suitable ErrorCode() from the - // syscallEvaluator. See the ErrorCode() constructor for how to pass in - // the function pointer. - // Please note that TrapFnc is executed from signal context and must be - // async-signal safe: - // http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html - typedef intptr_t (*TrapFnc)(const struct arch_seccomp_data& args, void *aux); - + // While BPF filter programs always operate on 32bit quantities, the kernel + // always sees system call arguments as 64bit values. This statement is true + // no matter whether the host system is natively operating in 32bit or 64bit. + // The BPF compiler hides the fact that BPF instructions cannot directly + // access 64bit quantities. But policies are still advised to specify whether + // a system call expects a 32bit or a 64bit quantity. enum ArgType { - TP_32BIT, TP_64BIT, + // When passed as an argument to Sandbox::Cond(), TP_32BIT requests that + // the conditional test should operate on the 32bit part of the system call + // argument. + // On 64bit architectures, this verifies that user space did not pass + // a 64bit value as an argument to the system call. If it did, that will be + // interpreted as an attempt at breaking the sandbox and results in the + // program getting terminated. + // In other words, only perform a 32bit test, if you are sure this + // particular system call would never legitimately take a 64bit + // argument. + // Implementation detail: TP_32BIT does two things. 1) it restricts the + // conditional test to operating on the LSB only, and 2) it adds code to + // the BPF filter program verifying that the MSB the kernel received from + // user space is either 0, or 0xFFFFFFFF; the latter is acceptable, iff bit + // 31 was set in the system call argument. It deals with 32bit arguments + // having been sign extended. + TP_32BIT, + + // When passed as an argument to Sandbox::Cond(), TP_64BIT requests that + // the conditional test should operate on the full 64bit argument. It is + // generally harmless to perform a 64bit test on 32bit systems, as the + // kernel will always see the top 32 bits of all arguments as zero'd out. + // This approach has the desirable property that for tests of pointer + // values, we can always use TP_64BIT no matter the host architecture. + // But of course, that also means, it is possible to write conditional + // policies that turn into no-ops on 32bit systems; this is by design. + TP_64BIT, }; enum Operation { - OP_EQUAL, OP_GREATER, OP_GREATER_EQUAL, OP_HAS_BITS, + // Test whether the system call argument is equal to the operand. + OP_EQUAL, + + // Test whether the system call argument is greater (or equal) to the + // operand. Please note that all tests always operate on unsigned + // values. You can generally emulate signed tests, if that's what you + // need. + // TODO(markus): Check whether we should automatically emulate signed + // operations. + OP_GREATER_UNSIGNED, OP_GREATER_EQUAL_UNSIGNED, + + // Tests a system call argument against a bit mask. + // The "ALL_BITS" variant performs this test: "arg & mask == mask" + // This implies that a mask of zero always results in a passing test. + // The "ANY_BITS" variant performs this test: "arg & mask != 0" + // This implies that a mask of zero always results in a failing test. + OP_HAS_ALL_BITS, OP_HAS_ANY_BITS, + + // Total number of operations. OP_NUM_OPS, }; + enum ErrorType { + ET_INVALID, ET_SIMPLE, ET_TRAP, ET_COND, + }; + // We allow the default constructor, as it makes the ErrorCode class // much easier to use. But if we ever encounter an invalid ErrorCode // when compiling a BPF filter, we deliberately generate an invalid @@ -75,6 +127,16 @@ class ErrorCode { bool LessThan(const ErrorCode& err) const; uint32_t err() const { return err_; } + ErrorType error_type() const { return error_type_; } + + bool safe() const { return safe_; } + + uint64_t value() const { return value_; } + int argno() const { return argno_; } + ArgType width() const { return width_; } + Operation op() const { return op_; } + const ErrorCode *passed() const { return passed_; } + const ErrorCode *failed() const { return failed_; } struct LessThan { bool operator()(const ErrorCode& a, const ErrorCode& b) const { @@ -85,16 +147,12 @@ class ErrorCode { private: friend class CodeGen; friend class Sandbox; - friend class Verifier; - - enum ErrorType { - ET_INVALID, ET_SIMPLE, ET_TRAP, ET_COND, - }; + friend class Trap; // If we are wrapping a callback, we must assign a unique id. This id is // how the kernel tells us which one of our different SECCOMP_RET_TRAP // cases has been triggered. - ErrorCode(TrapFnc fnc, const void *aux, uint16_t id); + ErrorCode(Trap::TrapFnc fnc, const void *aux, bool safe, uint16_t id); // Some system calls require inspection of arguments. This constructor // allows us to specify additional constraints. @@ -106,8 +164,9 @@ class ErrorCode { union { // Fields needed for SECCOMP_RET_TRAP callbacks struct { - TrapFnc fnc_; // Callback function and arg, if trap was - void *aux_; // triggered by the kernel's BPF filter. + Trap::TrapFnc fnc_; // Callback function and arg, if trap was + void *aux_; // triggered by the kernel's BPF filter. + bool safe_; // Keep sandbox active while calling fnc_() }; // Fields needed when inspecting additional arguments. diff --git a/sandbox/linux/seccomp-bpf/errorcode_unittest.cc b/sandbox/linux/seccomp-bpf/errorcode_unittest.cc index 21f889ee88..10f2132459 100644 --- a/sandbox/linux/seccomp-bpf/errorcode_unittest.cc +++ b/sandbox/linux/seccomp-bpf/errorcode_unittest.cc @@ -19,17 +19,19 @@ SANDBOX_TEST(ErrorCode, ErrnoConstructor) { ErrorCode e2(EPERM); SANDBOX_ASSERT(e2.err() == SECCOMP_RET_ERRNO + EPERM); - ErrorCode e3 = Sandbox::Trap(NULL, NULL); + Sandbox sandbox; + ErrorCode e3 = sandbox.Trap(NULL, NULL); SANDBOX_ASSERT((e3.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP); } SANDBOX_TEST(ErrorCode, Trap) { - ErrorCode e0 = Sandbox::Trap(NULL, "a"); - ErrorCode e1 = Sandbox::Trap(NULL, "b"); + Sandbox sandbox; + ErrorCode e0 = sandbox.Trap(NULL, "a"); + ErrorCode e1 = sandbox.Trap(NULL, "b"); SANDBOX_ASSERT((e0.err() & SECCOMP_RET_DATA) + 1 == (e1.err() & SECCOMP_RET_DATA)); - ErrorCode e2 = Sandbox::Trap(NULL, "a"); + ErrorCode e2 = sandbox.Trap(NULL, "a"); SANDBOX_ASSERT((e0.err() & SECCOMP_RET_DATA) == (e2.err() & SECCOMP_RET_DATA)); } @@ -44,9 +46,10 @@ SANDBOX_TEST(ErrorCode, Equals) { ErrorCode e3(EPERM); SANDBOX_ASSERT(!e1.Equals(e3)); - ErrorCode e4 = Sandbox::Trap(NULL, "a"); - ErrorCode e5 = Sandbox::Trap(NULL, "b"); - ErrorCode e6 = Sandbox::Trap(NULL, "a"); + Sandbox sandbox; + ErrorCode e4 = sandbox.Trap(NULL, "a"); + ErrorCode e5 = sandbox.Trap(NULL, "b"); + ErrorCode e6 = sandbox.Trap(NULL, "a"); SANDBOX_ASSERT(!e1.Equals(e4)); SANDBOX_ASSERT(!e3.Equals(e4)); SANDBOX_ASSERT(!e5.Equals(e4)); @@ -64,9 +67,10 @@ SANDBOX_TEST(ErrorCode, LessThan) { SANDBOX_ASSERT(!e1.LessThan(e3)); SANDBOX_ASSERT( e3.LessThan(e1)); - ErrorCode e4 = Sandbox::Trap(NULL, "a"); - ErrorCode e5 = Sandbox::Trap(NULL, "b"); - ErrorCode e6 = Sandbox::Trap(NULL, "a"); + Sandbox sandbox; + ErrorCode e4 = sandbox.Trap(NULL, "a"); + ErrorCode e5 = sandbox.Trap(NULL, "b"); + ErrorCode e6 = sandbox.Trap(NULL, "a"); SANDBOX_ASSERT(e1.LessThan(e4)); SANDBOX_ASSERT(e3.LessThan(e4)); SANDBOX_ASSERT(e4.LessThan(e5)); diff --git a/sandbox/linux/seccomp-bpf/linux_seccomp.h b/sandbox/linux/seccomp-bpf/linux_seccomp.h new file mode 100644 index 0000000000..0de0259da3 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/linux_seccomp.h @@ -0,0 +1,197 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_LINUX_SECCOMP_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_LINUX_SECCOMP_H__ + +// The Seccomp2 kernel ABI is not part of older versions of glibc. +// As we can't break compilation with these versions of the library, +// we explicitly define all missing symbols. +// If we ever decide that we can now rely on system headers, the following +// include files should be enabled: +// #include <linux/audit.h> +// #include <linux/seccomp.h> + +#include <asm/unistd.h> +#include <linux/filter.h> + +// For audit.h +#ifndef EM_ARM +#define EM_ARM 40 +#endif +#ifndef EM_386 +#define EM_386 3 +#endif +#ifndef EM_X86_64 +#define EM_X86_64 62 +#endif + +#ifndef __AUDIT_ARCH_64BIT +#define __AUDIT_ARCH_64BIT 0x80000000 +#endif +#ifndef __AUDIT_ARCH_LE +#define __AUDIT_ARCH_LE 0x40000000 +#endif +#ifndef AUDIT_ARCH_ARM +#define AUDIT_ARCH_ARM (EM_ARM|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_I386 +#define AUDIT_ARCH_I386 (EM_386|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_X86_64 +#define AUDIT_ARCH_X86_64 (EM_X86_64|__AUDIT_ARCH_64BIT|__AUDIT_ARCH_LE) +#endif + +// For prctl.h +#ifndef PR_SET_SECCOMP +#define PR_SET_SECCOMP 22 +#define PR_GET_SECCOMP 21 +#endif +#ifndef PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#define PR_GET_NO_NEW_PRIVS 39 +#endif +#ifndef IPC_64 +#define IPC_64 0x0100 +#endif + +#ifndef BPF_MOD +#define BPF_MOD 0x90 +#endif +#ifndef BPF_XOR +#define BPF_XOR 0xA0 +#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_ACTION 0xffff0000U // Masks for the return value +#define SECCOMP_RET_DATA 0x0000ffffU // sections +#else +#define SECCOMP_RET_INVALID 0x00010000U // Illegal return value +#endif + +#ifndef SYS_SECCOMP +#define SYS_SECCOMP 1 +#endif + +// Impose some reasonable maximum BPF program size. Realistically, the +// kernel probably has much lower limits. But by limiting to less than +// 30 bits, we can ease requirements on some of our data types. +#define SECCOMP_MAX_PROGRAM_SIZE (1<<30) + +#if defined(__i386__) +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL 1024u +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL +#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) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, REG_EAX) +#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, REG_EIP) +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, REG_EBX) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, REG_ECX) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, REG_EDX) +#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_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)]) +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, REG_RAX) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, REG_RAX) +#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, REG_RIP) +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, REG_RDI) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, REG_RSI) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, REG_RDX) +#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|, +// and a "ghost syscall private to the kernel", cmpxchg, +// at |__ARM_NR_BASE+0x00fff0|. +// See </arch/arm/include/asm/unistd.h> in the Linux kernel. +#define MIN_SYSCALL ((unsigned int)__NR_SYSCALL_BASE) +#define MAX_PUBLIC_SYSCALL (MIN_SYSCALL + 1024u) +#define MIN_PRIVATE_SYSCALL ((unsigned int)__ARM_NR_BASE) +#define MAX_PRIVATE_SYSCALL (MIN_PRIVATE_SYSCALL + 16u) +#define MIN_GHOST_SYSCALL ((unsigned int)__ARM_NR_BASE + 0xfff0u) +#define MAX_SYSCALL (MIN_GHOST_SYSCALL + 4u) + +#define SECCOMP_ARCH AUDIT_ARCH_ARM + +// ARM sigcontext_t is different from i386/x86_64. +// 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_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 + +#endif + +#endif // SANDBOX_LINUX_SECCOMP_BPF_LINUX_SECCOMP_H__ diff --git a/sandbox/linux/seccomp-bpf/port.h b/sandbox/linux/seccomp-bpf/port.h new file mode 100644 index 0000000000..f10b1481de --- /dev/null +++ b/sandbox/linux/seccomp-bpf/port.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Commonly used macro definitions to make the code build in different +// target environments (e.g. as part of Chrome vs. stand-alone) + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_PORT_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_PORT_H__ + +#if !defined(SECCOMP_BPF_STANDALONE) + #include "base/basictypes.h" + #include "base/logging.h" + #include "base/posix/eintr_wrapper.h" +#else + #define arraysize(x) (sizeof(x)/sizeof(*(x))) + + #define HANDLE_EINTR TEMP_FAILURE_RETRY + + #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + + #define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + + template <bool> + struct CompileAssert { + }; + + #define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] +#endif + +#endif // SANDBOX_LINUX_SECCOMP_BPF_PORT_H__ diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc index eb03995904..1dc5eae042 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -2,34 +2,69 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// Some headers on Android are missing cdefs: crbug.com/172337. +// (We can't use OS_ANDROID here since build_config.h is not included). +#if defined(ANDROID) +#include <sys/cdefs.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#ifndef SECCOMP_BPF_STANDALONE +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#endif + #include "sandbox/linux/seccomp-bpf/codegen.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" #include "sandbox/linux/seccomp-bpf/syscall_iterator.h" #include "sandbox/linux/seccomp-bpf/verifier.h" namespace { +using playground2::ErrorCode; +using playground2::Instruction; +using playground2::Sandbox; +using playground2::Trap; +using playground2::arch_seccomp_data; + +const int kExpectedExitCode = 100; + +template<class T> int popcount(T x); +template<> int popcount<unsigned int>(unsigned int x) { + return __builtin_popcount(x); +} +template<> int popcount<unsigned long>(unsigned long x) { + return __builtin_popcountl(x); +} +template<> int popcount<unsigned long long>(unsigned long long x) { + return __builtin_popcountll(x); +} + void WriteFailedStderrSetupMessage(int out_fd) { const char* error_string = strerror(errno); - static const char msg[] = "Failed to set up stderr: "; + static const char msg[] = "You have reproduced a puzzling issue.\n" + "Please, report to crbug.com/152530!\n" + "Failed to set up stderr: "; if (HANDLE_EINTR(write(out_fd, msg, sizeof(msg)-1)) > 0 && error_string && HANDLE_EINTR(write(out_fd, error_string, strlen(error_string))) > 0 && HANDLE_EINTR(write(out_fd, "\n", 1))) { } } -} // namespace - -// The kernel gives us a sandbox, we turn it into a playground :-) -// This is version 2 of the playground; version 1 was built on top of -// pre-BPF seccomp mode. -namespace playground2 { - -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 ProbeEvaluator(Sandbox *, int sysnum, void *) __attribute__((const)); +ErrorCode ProbeEvaluator(Sandbox *, int sysnum, void *) { switch (sysnum) { case __NR_getpid: // Return EPERM so that we can check that the filter actually ran. @@ -43,24 +78,20 @@ ErrorCode Sandbox::probeEvaluator(int sysnum, void *) { } } -void Sandbox::probeProcess(void) { +void ProbeProcess(void) { if (syscall(__NR_getpid) < 0 && errno == EPERM) { syscall(__NR_exit_group, static_cast<intptr_t>(kExpectedExitCode)); } } -bool Sandbox::isValidSyscallNumber(int sysnum) { - return SyscallIterator::IsValid(sysnum); -} - -ErrorCode Sandbox::allowAllEvaluator(int sysnum, void *) { - if (!isValidSyscallNumber(sysnum)) { +ErrorCode AllowAllEvaluator(Sandbox *, int sysnum, void *) { + if (!Sandbox::IsValidSyscallNumber(sysnum)) { return ErrorCode(ENOSYS); } return ErrorCode(ErrorCode::ERR_ALLOWED); } -void Sandbox::tryVsyscallProcess(void) { +void 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 @@ -70,15 +101,156 @@ void Sandbox::tryVsyscallProcess(void) { } } -bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), - EvaluateSyscall syscallEvaluator, - void *aux, - int proc_fd) { +bool IsSingleThreaded(int proc_fd) { + if (proc_fd < 0) { + // Cannot determine whether program is single-threaded. Hope for + // the best... + return true; + } + + struct stat sb; + int task = -1; + if ((task = openat(proc_fd, "self/task", O_RDONLY|O_DIRECTORY)) < 0 || + fstat(task, &sb) != 0 || + sb.st_nlink != 3 || + HANDLE_EINTR(close(task))) { + if (task >= 0) { + if (HANDLE_EINTR(close(task))) { } + } + return false; + } + return true; +} + +bool 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)); +} + +// Function that can be passed as a callback function to CodeGen::Traverse(). +// Checks whether the "insn" returns an UnsafeTrap() ErrorCode. If so, it +// sets the "bool" variable pointed to by "aux". +void CheckForUnsafeErrorCodes(Instruction *insn, void *aux) { + bool *is_unsafe = static_cast<bool *>(aux); + if (!*is_unsafe) { + if (BPF_CLASS(insn->code) == BPF_RET && + insn->k > SECCOMP_RET_TRAP && + insn->k - SECCOMP_RET_TRAP <= SECCOMP_RET_DATA) { + const ErrorCode& err = + Trap::ErrorCodeFromTrapId(insn->k & SECCOMP_RET_DATA); + if (err.error_type() != ErrorCode::ET_INVALID && !err.safe()) { + *is_unsafe = true; + } + } + } +} + +// A Trap() handler that returns an "errno" value. The value is encoded +// in the "aux" parameter. +intptr_t ReturnErrno(const struct arch_seccomp_data&, void *aux) { + // TrapFnc functions report error by following the native kernel convention + // of returning an exit code in the range of -1..-4096. They do not try to + // set errno themselves. The glibc wrapper that triggered the SIGSYS will + // ultimately do so for us. + int err = reinterpret_cast<intptr_t>(aux) & SECCOMP_RET_DATA; + return -err; +} + +// Function that can be passed as a callback function to CodeGen::Traverse(). +// Checks whether the "insn" returns an errno value from a BPF filter. If so, +// it rewrites the instruction to instead call a Trap() handler that does +// the same thing. "aux" is ignored. +void RedirectToUserspace(Instruction *insn, void *aux) { + // 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 + // any state other than the syscall arguments. + // But if we redirect all error handlers to user-space, then we can easily + // make this decision. + // The performance penalty for this extra round-trip to user-space is not + // actually that bad, as we only ever pay it for denied system calls; and a + // typical program has very few of these. + Sandbox *sandbox = static_cast<Sandbox *>(aux); + if (BPF_CLASS(insn->code) == BPF_RET && + (insn->k & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) { + insn->k = sandbox->Trap(ReturnErrno, + reinterpret_cast<void *>(insn->k & SECCOMP_RET_DATA)).err(); + } +} + +// Stackable wrapper around an Evaluators handler. Changes ErrorCodes +// returned by a system call evaluator to match the changes made by +// RedirectToUserspace(). "aux" should be pointer to wrapped system call +// evaluator. +ErrorCode RedirectToUserspaceEvalWrapper(Sandbox *sandbox, int sysnum, + void *aux) { + // We need to replicate the behavior of RedirectToUserspace(), so that our + // Verifier can still work correctly. + Sandbox::Evaluators *evaluators = + reinterpret_cast<Sandbox::Evaluators *>(aux); + const std::pair<Sandbox::EvaluateSyscall, void *>& evaluator = + *evaluators->begin(); + + ErrorCode err = evaluator.first(sandbox, sysnum, evaluator.second); + if ((err.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) { + return sandbox->Trap(ReturnErrno, + reinterpret_cast<void *>(err.err() & SECCOMP_RET_DATA)); + } + return err; +} + +intptr_t BpfFailure(const struct arch_seccomp_data&, void *aux) { + SANDBOX_DIE(static_cast<char *>(aux)); +} + +} // namespace + +// The kernel gives us a sandbox, we turn it into a playground :-) +// This is version 2 of the playground; version 1 was built on top of +// pre-BPF seccomp mode. +namespace playground2 { + +Sandbox::Sandbox() + : quiet_(false), + proc_fd_(-1), + evaluators_(new Evaluators), + conds_(new Conds) { +} + +Sandbox::~Sandbox() { + // It is generally unsafe to call any memory allocator operations or to even + // call arbitrary destructors after having installed a new policy. We just + // have no way to tell whether this policy would allow the system calls that + // the constructors can trigger. + // So, we normally destroy all of our complex state prior to starting the + // sandbox. But this won't happen, if the Sandbox object was created and + // never actually used to set up a sandbox. So, just in case, we are + // destroying any remaining state. + // The "if ()" statements are technically superfluous. But let's be explicit + // that we really don't want to run any code, when we already destroyed + // objects before setting up the sandbox. + if (evaluators_) { + delete evaluators_; + } + if (conds_) { + delete conds_; + } +} + +bool Sandbox::IsValidSyscallNumber(int sysnum) { + return SyscallIterator::IsValid(sysnum); +} + + +bool Sandbox::RunFunctionInPolicy(void (*code_in_sandbox)(), + Sandbox::EvaluateSyscall syscall_evaluator, + void *aux) { // 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]; @@ -98,7 +270,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"); } @@ -108,9 +280,14 @@ bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), // successfully turn on sandboxing. Die::EnableSimpleExit(); + errno = 0; if (HANDLE_EINTR(close(fds[0]))) { + // This call to close() has been failing in strange ways. See + // crbug.com/152530. So we only fail in debug mode now. +#if !defined(NDEBUG) WriteFailedStderrSetupMessage(fds[1]); SANDBOX_DIE(NULL); +#endif } if (HANDLE_EINTR(dup2(fds[1], 2)) != 2) { // Stderr could very well be a file descriptor to .xsession-errors, or @@ -120,25 +297,28 @@ bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), // If dup2 fails here, we will continue normally, this means that our // parent won't cause a fatal failure if something writes to stderr in // this child. +#if !defined(NDEBUG) + // In DEBUG builds, we still want to get a report. + WriteFailedStderrSetupMessage(fds[1]); + SANDBOX_DIE(NULL); +#endif } if (HANDLE_EINTR(close(fds[1]))) { + // This call to close() has been failing in strange ways. See + // crbug.com/152530. So we only fail in debug mode now. +#if !defined(NDEBUG) WriteFailedStderrSetupMessage(fds[1]); SANDBOX_DIE(NULL); +#endif } - evaluators_.clear(); - setSandboxPolicy(syscallEvaluator, aux); - setProcFd(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); + SetSandboxPolicy(syscall_evaluator, aux); + StartSandbox(); // 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); } @@ -146,7 +326,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; @@ -178,23 +358,13 @@ bool Sandbox::RunFunctionInPolicy(void (*CodeInSandbox)(), return rc; } -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 - // always support sandboxing. This feature should only ever be enabled when - // debugging. - return true; - } -#endif - +bool Sandbox::KernelSupportSeccompBPF() { return - RunFunctionInPolicy(probeProcess, Sandbox::probeEvaluator, 0, proc_fd) && - RunFunctionInPolicy(tryVsyscallProcess, Sandbox::allowAllEvaluator, 0, - proc_fd); + RunFunctionInPolicy(ProbeProcess, ProbeEvaluator, 0) && + RunFunctionInPolicy(TryVsyscallProcess, AllowAllEvaluator, 0); } -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) { @@ -204,13 +374,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- @@ -226,31 +396,40 @@ 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) + // We create our own private copy of a "Sandbox" object. This ensures that + // the object does not have any policies configured, that might interfere + // with the tests done by "KernelSupportSeccompBPF()". + Sandbox sandbox; + + // By setting "quiet_ = true" we suppress messages for expected and benign + // failures (e.g. if the current kernel lacks support for BPF filters). + sandbox.quiet_ = true; + sandbox.set_proc_fd(proc_fd); + status_ = sandbox.KernelSupportSeccompBPF() ? 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::StartSandbox() { if (status_ == STATUS_UNSUPPORTED || status_ == STATUS_UNAVAILABLE) { SANDBOX_DIE("Trying to start sandbox, even though it is known to be " "unavailable"); - } else if (status_ == STATUS_ENABLED) { - SANDBOX_DIE("Cannot start sandbox recursively. Use multiple calls to " - "setSandboxPolicy() to stack policies instead"); + } else if (!evaluators_ || !conds_) { + SANDBOX_DIE("Cannot repeatedly start sandbox. Create a separate Sandbox " + "object instead."); } if (proc_fd_ < 0) { proc_fd_ = open("/proc", O_RDONLY|O_DIRECTORY); @@ -259,7 +438,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"); } @@ -274,44 +453,17 @@ void Sandbox::startSandboxInternal(bool quiet) { } // Install the filters. - installFilter(quiet); + InstallFilter(); // We are now inside the sandbox. status_ = STATUS_ENABLED; } -bool Sandbox::isSingleThreaded(int proc_fd) { - if (proc_fd < 0) { - // Cannot determine whether program is single-threaded. Hope for - // the best... - return true; - } - - struct stat sb; - int task = -1; - if ((task = openat(proc_fd, "self/task", O_RDONLY|O_DIRECTORY)) < 0 || - fstat(task, &sb) != 0 || - sb.st_nlink != 3 || - HANDLE_EINTR(close(task))) { - if (task >= 0) { - if (HANDLE_EINTR(close(task))) { } - } - return false; - } - return true; -} - -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(this, sysnum, aux))) { SANDBOX_DIE("Policies should deny system calls that are outside the " "expected range (typically MIN_SYSCALL..MAX_SYSCALL)"); } @@ -319,41 +471,65 @@ void Sandbox::policySanityChecks(EvaluateSyscall syscallEvaluator, return; } -void Sandbox::setSandboxPolicy(EvaluateSyscall syscallEvaluator, void *aux) { - if (status_ == STATUS_ENABLED) { +void Sandbox::SetSandboxPolicy(EvaluateSyscall syscall_evaluator, void *aux) { + if (!evaluators_ || !conds_) { 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) { - // Verify that the user pushed a policy. - if (evaluators_.empty()) { - filter_failed: - SANDBOX_DIE("Failed to configure system call filters"); - } +void Sandbox::InstallFilter() { + // We want to be very careful in not imposing any requirements on the + // policies that are set with SetSandboxPolicy(). This means, as soon as + // the sandbox is active, we shouldn't be relying on libraries that could + // be making system calls. This, for example, means we should avoid + // using the heap and we should avoid using STL functions. + // Temporarily copy the contents of the "program" vector into a + // stack-allocated array; and then explicitly destroy that object. + // This makes sure we don't ex- or implicitly call new/delete after we + // installed the BPF filter program in the kernel. Depending on the + // system memory allocator that is in effect, these operators can result + // in system calls to things like munmap() or brk(). + Program *program = AssembleFilter(false /* force_verification */); - // Set new SIGSYS handler - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = &sigSys; - sa.sa_flags = SA_SIGINFO; - if (sigaction(SIGSYS, &sa, NULL) < 0) { - goto filter_failed; + struct sock_filter bpf[program->size()]; + const struct sock_fprog prog = { + static_cast<unsigned short>(program->size()), bpf }; + memcpy(bpf, &(*program)[0], sizeof(bpf)); + delete program; + + // Release memory that is no longer needed + delete evaluators_; + delete conds_; + evaluators_ = NULL; + conds_ = NULL; + + // Install BPF filter program + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + SANDBOX_DIE(quiet_ ? NULL : "Kernel refuses to enable no-new-privs"); + } else { + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { + SANDBOX_DIE(quiet_ ? NULL : "Kernel refuses to turn on BPF filters"); + } } - // Unmask SIGSYS - sigset_t mask; - if (sigemptyset(&mask) || - sigaddset(&mask, SIGSYS) || - sigprocmask(SIG_UNBLOCK, &mask, NULL)) { - goto filter_failed; + return; +} + +Sandbox::Program *Sandbox::AssembleFilter(bool force_verification) { +#if !defined(NDEBUG) + force_verification = true; +#endif + + // Verify that the user pushed a policy. + if (evaluators_->empty()) { + SANDBOX_DIE("Failed to configure system call filters"); } // We can't handle stacked evaluators, yet. We'll get there eventually // though. Hang tight. - if (evaluators_.size() != 1) { + if (evaluators_->size() != 1) { SANDBOX_DIE("Not implemented"); } @@ -367,44 +543,124 @@ 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_JMP+BPF_JEQ+BPF_K, SECCOMP_ARCH, + gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, SECCOMP_ARCH_IDX, tail = - // Grab the system call number, so that we can implement jump tables. - gen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, - offsetof(struct arch_seccomp_data, nr)), - gen->MakeInstruction(BPF_RET+BPF_K, - Kill( - "Invalid audit architecture in BPF filter").err_))); - - // On Intel architectures, verify that system call numbers are in the - // expected number range. The older i386 and x86-64 APIs clear bit 30 - // on all system calls. The newer x32 API always sets bit 30. -#if defined(__i386__) || defined(__x86_64__) - Instruction *invalidX32 = + gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, SECCOMP_ARCH, + NULL, gen->MakeInstruction(BPF_RET+BPF_K, - Kill("Illegal mixing of system call ABIs").err_); - Instruction *checkX32 = -#if defined(__x86_64__) && defined(__ILP32__) - gen->MakeInstruction(BPF_JMP+BPF_JSET+BPF_K, 0x40000000, 0, invalidX32); -#else - gen->MakeInstruction(BPF_JMP+BPF_JSET+BPF_K, 0x40000000, invalidX32, 0); -#endif - gen->JoinInstructions(tail, checkX32); - tail = checkX32; -#endif - + Kill("Invalid audit architecture in BPF filter")))); + bool has_unsafe_traps = false; { // 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- + // SECCOMP_RET_ALLOW ErrorCodes are handled in user-space. This will then + // allow us to temporarily disable sandboxing rules inside of callbacks to + // UnsafeTrap(). + gen->Traverse(jumptable, CheckForUnsafeErrorCodes, &has_unsafe_traps); + + // Grab the system call number, so that we can implement jump tables. + Instruction *load_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 + // consider looking at system call numbers. + // As support for unsafe jumps essentially defeats all the security + // measures that the sandbox provides, we print a big warning message -- + // and of course, we make sure to only ever enable this feature if it + // is actually requested by the sandbox policy. + if (has_unsafe_traps) { + if (SandboxSyscall(-1) == -1 && errno == ENOSYS) { + SANDBOX_DIE("Support for UnsafeTrap() has not yet been ported to this " + "architecture"); + } + + EvaluateSyscall evaluateSyscall = evaluators_->begin()->first; + void *aux = evaluators_->begin()->second; + if (!evaluateSyscall(this, __NR_rt_sigprocmask, aux). + Equals(ErrorCode(ErrorCode::ERR_ALLOWED)) || + !evaluateSyscall(this, __NR_rt_sigreturn, aux). + Equals(ErrorCode(ErrorCode::ERR_ALLOWED)) +#if defined(__NR_sigprocmask) + || !evaluateSyscall(this, __NR_sigprocmask, aux). + Equals(ErrorCode(ErrorCode::ERR_ALLOWED)) +#endif +#if defined(__NR_sigreturn) + || !evaluateSyscall(this, __NR_sigreturn, aux). + Equals(ErrorCode(ErrorCode::ERR_ALLOWED)) +#endif + ) { + SANDBOX_DIE("Invalid seccomp policy; if using UnsafeTrap(), you must " + "unconditionally allow sigreturn() and sigprocmask()"); + } + + if (!Trap::EnableUnsafeTrapsInSigSysHandler()) { + // We should never be able to get here, as UnsafeTrap() should never + // actually return a valid ErrorCode object unless the user set the + // CHROME_SANDBOX_DEBUGGING environment variable; and therefore, + // "has_unsafe_traps" would always be false. But better double-check + // than enabling dangerous code. + SANDBOX_DIE("We'd rather die than enable unsafe traps"); + } + gen->Traverse(jumptable, RedirectToUserspace, this); + + // Allow system calls, if they originate from our magic return address + // (which we can query by calling SandboxSyscall(-1)). + uintptr_t syscall_entry_point = + static_cast<uintptr_t>(SandboxSyscall(-1)); + uint32_t low = static_cast<uint32_t>(syscall_entry_point); +#if __SIZEOF_POINTER__ > 4 + uint32_t hi = static_cast<uint32_t>(syscall_entry_point >> 32); +#endif + + // BPF cannot do native 64bit comparisons. On 64bit architectures, we + // 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, 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, SECCOMP_IP_MSB_IDX, + gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, hi, +#endif + gen->MakeInstruction(BPF_RET+BPF_K, ErrorCode(ErrorCode::ERR_ALLOWED)), +#if __SIZEOF_POINTER__ > 4 + load_nr)), +#endif + load_nr)); + gen->JoinInstructions(tail, escape_hatch); + } else { + gen->JoinInstructions(tail, load_nr); + } + tail = load_nr; + + // On Intel architectures, verify that system call numbers are in the + // expected number range. The older i386 and x86-64 APIs clear bit 30 + // on all system calls. The newer x32 API always sets bit 30. +#if defined(__i386__) || defined(__x86_64__) + Instruction *invalidX32 = + gen->MakeInstruction(BPF_RET+BPF_K, + Kill("Illegal mixing of system call ABIs").err_); + Instruction *checkX32 = +#if defined(__x86_64__) && defined(__ILP32__) + gen->MakeInstruction(BPF_JMP+BPF_JSET+BPF_K, 0x40000000, 0, invalidX32); +#else + gen->MakeInstruction(BPF_JMP+BPF_JSET+BPF_K, 0x40000000, invalidX32, 0); +#endif + gen->JoinInstructions(tail, checkX32); + tail = checkX32; +#endif // Append jump table to our pre-amble gen->JoinInstructions(tail, jumptable); @@ -418,84 +674,67 @@ void Sandbox::installFilter(bool quiet) { // Make sure compilation resulted in BPF program that executes // correctly. Otherwise, there is an internal error in our BPF compiler. // There is really nothing the caller can do until the bug is fixed. -#ifndef NDEBUG - const char *err = NULL; - if (!Verifier::VerifyBPF(*program, evaluators_, &err)) { - SANDBOX_DIE(err); + if (force_verification) { + // Verification is expensive. We only perform this step, if we are + // compiled in debug mode, or if the caller explicitly requested + // verification. + VerifyProgram(*program, has_unsafe_traps); } -#endif - // We want to be very careful in not imposing any requirements on the - // policies that are set with setSandboxPolicy(). This means, as soon as - // the sandbox is active, we shouldn't be relying on libraries that could - // be making system calls. This, for example, means we should avoid - // using the heap and we should avoid using STL functions. - // Temporarily copy the contents of the "program" vector into a - // stack-allocated array; and then explicitly destroy that object. - // This makes sure we don't ex- or implicitly call new/delete after we - // installed the BPF filter program in the kernel. Depending on the - // system memory allocator that is in effect, these operators can result - // in system calls to things like munmap() or brk(). - struct sock_filter bpf[program->size()]; - const struct sock_fprog prog = { - static_cast<unsigned short>(program->size()), bpf }; - memcpy(bpf, &(*program)[0], sizeof(bpf)); - delete program; + return program; +} - // Release memory that is no longer needed - evaluators_.clear(); - errMap_.clear(); - -#if defined(SECCOMP_BPF_VALGRIND_HACKS) - // Valgrind is really not happy about our sandbox. Disable it when running - // in Valgrind. This feature is dangerous and should never be enabled by - // default. We protect it behind a pre-processor option. - if (!RUNNING_ON_VALGRIND) -#endif - { - // Install BPF filter program - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { - SANDBOX_DIE(quiet ? NULL : "Kernel refuses to enable no-new-privs"); - } else { - if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { - SANDBOX_DIE(quiet ? NULL : "Kernel refuses to turn on BPF filters"); - } - } - } +void Sandbox::VerifyProgram(const Program& program, bool has_unsafe_traps) { + // If we previously rewrote the BPF program so that it calls user-space + // whenever we return an "errno" value from the filter, then we have to + // wrap our system call evaluator to perform the same operation. Otherwise, + // the verifier would also report a mismatch in return codes. + Evaluators redirected_evaluators; + redirected_evaluators.push_back( + std::make_pair(RedirectToUserspaceEvalWrapper, evaluators_)); - return; + const char *err = NULL; + if (!Verifier::VerifyBPF( + this, + program, + has_unsafe_traps ? redirected_evaluators : *evaluators_, + &err)) { + CodeGen::PrintProgram(program); + SANDBOX_DIE(err); + } } -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(this, old_sysnum, aux); + ErrorCode invalid_err = evaluate_syscall(this, 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(this, 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 @@ -507,7 +746,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. @@ -517,139 +756,230 @@ 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) { - // 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_) { - // 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 - // the sigSys() handler. - // TODO: If we feel confident that our code otherwise works correctly, we - // could actually make an argument that spurious SIGSYS should - // just get silently ignored. TBD - sigsys_err: - SANDBOX_DIE("Unexpected SIGSYS received"); - } - - // Signal handlers should always preserve "errno". Otherwise, we could - // trigger really subtle bugs. - int old_errno = errno; - - // Obtain the signal context. This, most notably, gives us access to - // all CPU registers at the time of the signal. - ucontext_t *ctx = reinterpret_cast<ucontext_t *>(void_context); - - // Obtain the siginfo information that is specific to SIGSYS. Unfortunately, - // most versions of glibc don't include this information in siginfo_t. So, - // we need to explicitly copy it into a arch_sigsys structure. - struct arch_sigsys sigsys; - memcpy(&sigsys, &info->_sifields, sizeof(sigsys)); - - // Some more sanity checks. - if (sigsys.ip != reinterpret_cast<void *>(SECCOMP_IP(ctx)) || - sigsys.nr != static_cast<int>(SECCOMP_SYSCALL(ctx)) || - sigsys.arch != SECCOMP_ARCH) { - goto sigsys_err; - } - - // Copy the seccomp-specific data into a arch_seccomp_data structure. This - // is what we are showing to TrapFnc callbacks that the system call evaluator - // registered with the sandbox. - struct arch_seccomp_data data = { - sigsys.nr, - SECCOMP_ARCH, - reinterpret_cast<uint64_t>(sigsys.ip), +Instruction *Sandbox::RetExpression(CodeGen *gen, const ErrorCode& err) { + if (err.error_type_ == ErrorCode::ET_COND) { + return CondExpression(gen, err); + } else { + return gen->MakeInstruction(BPF_RET+BPF_K, err); + } +} + +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), + lsb_head, + RetExpression(gen, *cond.failed_)); + gen->JoinInstructions(msb_head, msb_tail); + } + break; + case ErrorCode::OP_HAS_ALL_BITS: + // Check the bits in the LSB half of the system call argument. Our + // OP_HAS_ALL_BITS operator passes, iff all of the bits are set. This is + // different from the kernel's BPF_JSET operation which passes, if any of + // the bits are set. + // Of course, if there is only a single set bit (or none at all), then + // things get easier. { - static_cast<uint64_t>(SECCOMP_PARM1(ctx)), - static_cast<uint64_t>(SECCOMP_PARM2(ctx)), - static_cast<uint64_t>(SECCOMP_PARM3(ctx)), - static_cast<uint64_t>(SECCOMP_PARM4(ctx)), - static_cast<uint64_t>(SECCOMP_PARM5(ctx)), - static_cast<uint64_t>(SECCOMP_PARM6(ctx)) + uint32_t lsb_bits = static_cast<uint32_t>(cond.value_); + int lsb_bit_count = popcount(lsb_bits); + if (lsb_bit_count == 0) { + // No bits are set in the LSB half. The test will always pass. + lsb_head = RetExpression(gen, *cond.passed_); + lsb_tail = NULL; + } else if (lsb_bit_count == 1) { + // Exactly one bit is set in the LSB half. We can use the BPF_JSET + // operator. + lsb_tail = gen->MakeInstruction(BPF_JMP+BPF_JSET+BPF_K, + lsb_bits, + RetExpression(gen, *cond.passed_), + RetExpression(gen, *cond.failed_)); + gen->JoinInstructions(lsb_head, lsb_tail); + } else { + // More than one bit is set in the LSB half. We need to combine + // BPF_AND and BPF_JEQ to test whether all of these bits are in fact + // set in the system call argument. + gen->JoinInstructions(lsb_head, + gen->MakeInstruction(BPF_ALU+BPF_AND+BPF_K, + lsb_bits, + lsb_tail = gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, + lsb_bits, + RetExpression(gen, *cond.passed_), + RetExpression(gen, *cond.failed_)))); + } + } + + // If we are looking at a 64bit argument, we need to also check the bits + // in the MSB half of the system call argument. + if (cond.width_ == ErrorCode::TP_64BIT) { + uint32_t msb_bits = static_cast<uint32_t>(cond.value_ >> 32); + int msb_bit_count = popcount(msb_bits); + if (msb_bit_count == 0) { + // No bits are set in the MSB half. The test will always pass. + msb_head = lsb_head; + } else if (msb_bit_count == 1) { + // Exactly one bit is set in the MSB half. We can use the BPF_JSET + // operator. + msb_tail = gen->MakeInstruction(BPF_JMP+BPF_JSET+BPF_K, + msb_bits, + lsb_head, + RetExpression(gen, *cond.failed_)); + gen->JoinInstructions(msb_head, msb_tail); + } else { + // More than one bit is set in the MSB half. We need to combine + // BPF_AND and BPF_JEQ to test whether all of these bits are in fact + // set in the system call argument. + gen->JoinInstructions(msb_head, + gen->MakeInstruction(BPF_ALU+BPF_AND+BPF_K, + msb_bits, + gen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, + msb_bits, + lsb_head, + RetExpression(gen, *cond.failed_)))); + } + } + break; + case ErrorCode::OP_HAS_ANY_BITS: + // Check the bits in the LSB half of the system call argument. Our + // OP_HAS_ANY_BITS operator passes, iff any of the bits are set. This maps + // nicely to the kernel's BPF_JSET operation. + { + uint32_t lsb_bits = static_cast<uint32_t>(cond.value_); + if (!lsb_bits) { + // No bits are set in the LSB half. The test will always fail. + lsb_head = RetExpression(gen, *cond.failed_); + lsb_tail = NULL; + } else { + lsb_tail = gen->MakeInstruction(BPF_JMP+BPF_JSET+BPF_K, + lsb_bits, + RetExpression(gen, *cond.passed_), + RetExpression(gen, *cond.failed_)); + gen->JoinInstructions(lsb_head, lsb_tail); + } } - }; - // Now call the TrapFnc callback associated with this particular instance - // of SECCOMP_RET_TRAP. - const ErrorCode& err = trapArray_[info->si_errno - 1]; - intptr_t rc = err.fnc_(data, err.aux_); + // If we are looking at a 64bit argument, we need to also check the bits + // in the MSB half of the system call argument. + if (cond.width_ == ErrorCode::TP_64BIT) { + uint32_t msb_bits = static_cast<uint32_t>(cond.value_ >> 32); + if (!msb_bits) { + // No bits are set in the MSB half. The test will always fail. + msb_head = lsb_head; + } else { + msb_tail = gen->MakeInstruction(BPF_JMP+BPF_JSET+BPF_K, + msb_bits, + RetExpression(gen, *cond.passed_), + lsb_head); + gen->JoinInstructions(msb_head, msb_tail); + } + } + break; + default: + // TODO(markus): Need to add support for OP_GREATER + SANDBOX_DIE("Not implemented"); + break; + } - // Update the CPU register that stores the return code of the system call - // that we just handled, and restore "errno" to the value that it had - // before entering the signal handler. - SECCOMP_RESULT(ctx) = static_cast<greg_t>(rc); - errno = old_errno; + // 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."); + } - return; + 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)); + } + + return msb_head; } -ErrorCode Sandbox::Trap(ErrorCode::TrapFnc fnc, const void *aux) { - // Each unique pair of TrapFnc and auxiliary data make up a distinct instance - // of a SECCOMP_RET_TRAP. - std::pair<ErrorCode::TrapFnc, const void *> key(fnc, aux); - TrapIds::const_iterator iter = trapIds_.find(key); - uint16_t id; - if (iter != trapIds_.end()) { - // We have seen this pair before. Return the same id that we assigned - // earlier. - id = iter->second; - } else { - // This is a new pair. Remember it and assign a new id. - // Please note that we have to store traps in memory that doesn't get - // deallocated when the program is shutting down. A memory leak is - // intentional, because we might otherwise not be able to execute - // system calls part way through the program shutting down - if (!traps_) { - traps_ = new Traps(); - } - if (traps_->size() >= SECCOMP_RET_DATA) { - // In practice, this is pretty much impossible to trigger, as there - // are other kernel limitations that restrict overall BPF program sizes. - SANDBOX_DIE("Too many SECCOMP_RET_TRAP callback instances"); - } - id = traps_->size() + 1; +ErrorCode Sandbox::Unexpected64bitArgument() { + return Kill("Unexpected 64bit argument detected"); +} - traps_->push_back(ErrorCode(fnc, aux, id)); - trapIds_[key] = id; +ErrorCode Sandbox::Trap(Trap::TrapFnc fnc, const void *aux) { + return Trap::MakeTrap(fnc, aux, true /* Safe Trap */); +} - // 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 - // hand, C++ guarantees that the contents of a vector is stored in a - // 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; - } +ErrorCode Sandbox::UnsafeTrap(Trap::TrapFnc fnc, const void *aux) { + return Trap::MakeTrap(fnc, aux, false /* Unsafe Trap */); +} - ErrorCode err = ErrorCode(fnc, aux, id); - return errMap_[err.err()] = err; +intptr_t Sandbox::ForwardSyscall(const struct arch_seccomp_data& args) { + return SandboxSyscall(args.nr, + static_cast<intptr_t>(args.args[0]), + static_cast<intptr_t>(args.args[1]), + static_cast<intptr_t>(args.args[2]), + static_cast<intptr_t>(args.args[3]), + static_cast<intptr_t>(args.args[4]), + static_cast<intptr_t>(args.args[5])); } -intptr_t Sandbox::bpfFailure(const struct arch_seccomp_data&, void *aux) { - SANDBOX_DIE(static_cast<char *>(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); } 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; -Sandbox::Evaluators Sandbox::evaluators_; -Sandbox::ErrMap Sandbox::errMap_; -Sandbox::Traps *Sandbox::traps_ = NULL; -Sandbox::TrapIds Sandbox::trapIds_; -ErrorCode *Sandbox::trapArray_ = NULL; -size_t Sandbox::trapArraySize_ = 0; } // namespace diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h index a50ddb38cd..3d26991607 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf.h +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -5,170 +5,22 @@ #ifndef SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H__ #define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H__ -#include <endian.h> -#include <errno.h> -#include <fcntl.h> -#include <linux/audit.h> -#include <linux/filter.h> -// #include <linux/seccomp.h> -#include <linux/unistd.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <netinet/udp.h> -#include <sched.h> -#include <signal.h> #include <stddef.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/ioctl.h> -#include <sys/ipc.h> -#include <sys/mman.h> -#include <sys/prctl.h> -#include <sys/shm.h> -#include <sys/stat.h> #include <sys/types.h> -#include <sys/uio.h> #include <sys/wait.h> -#include <time.h> -#include <unistd.h> #include <algorithm> #include <limits> #include <map> +#include <set> #include <utility> #include <vector> -#ifndef SECCOMP_BPF_STANDALONE -#include "base/basictypes.h" -#include "base/eintr_wrapper.h" -#include "base/logging.h" -#endif - -#if defined(SECCOMP_BPF_VALGRIND_HACKS) -#ifndef SECCOMP_BPF_STANDALONE -#include "base/third_party/valgrind/valgrind.h" -#endif -#endif - - -// The Seccomp2 kernel ABI is not part of older versions of glibc. -// As we can't break compilation with these versions of the library, -// we explicitly define all missing symbols. - -#ifndef PR_SET_NO_NEW_PRIVS -#define PR_SET_NO_NEW_PRIVS 38 -#define PR_GET_NO_NEW_PRIVS 39 -#endif -#ifndef IPC_64 -#define IPC_64 0x0100 -#endif -#ifndef SECCOMP_MODE_FILTER -#define SECCOMP_MODE_DISABLED 0 -#define SECCOMP_MODE_STRICT 1 -#define SECCOMP_MODE_FILTER 2 // User user-supplied filter -#define SECCOMP_RET_KILL 0x00000000U // Kill the task immediately -#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 -#endif -#define SECCOMP_DENY_ERRNO EPERM -#ifndef SYS_SECCOMP -#define SYS_SECCOMP 1 -#endif - -// Impose some reasonable maximum BPF program size. Realistically, the -// kernel probably has much lower limits. But by limiting to less than -// 30 bits, we can ease requirements on some of our data types. -#define SECCOMP_MAX_PROGRAM_SIZE (1<<30) - -#if defined(__i386__) -#define MIN_SYSCALL 0u -#define MAX_PUBLIC_SYSCALL 1024u -#define MAX_SYSCALL MAX_PUBLIC_SYSCALL -#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) -#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, REG_EAX) -#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, REG_EIP) -#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, REG_EBX) -#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, REG_ECX) -#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, REG_EDX) -#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) - -#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_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)]) -#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, REG_RAX) -#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, REG_RAX) -#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, REG_RIP) -#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, REG_RDI) -#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, REG_RSI) -#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, REG_RDX) -#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) - -#elif defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__)) -// ARM EABI includes "ARM private" system calls starting at |__ARM_NR_BASE|, -// and a "ghost syscall private to the kernel", cmpxchg, -// at |__ARM_NR_BASE+0x00fff0|. -// See </arch/arm/include/asm/unistd.h> in the Linux kernel. -#define MIN_SYSCALL ((unsigned int)__NR_SYSCALL_BASE) -#define MAX_PUBLIC_SYSCALL (MIN_SYSCALL + 1024u) -#define MIN_PRIVATE_SYSCALL ((unsigned int)__ARM_NR_BASE) -#define MAX_PRIVATE_SYSCALL (MIN_PRIVATE_SYSCALL + 16u) -#define MIN_GHOST_SYSCALL ((unsigned int)__ARM_NR_BASE + 0xfff0u) -#define MAX_SYSCALL (MIN_GHOST_SYSCALL + 4u) -// <linux/audit.h> includes <linux/elf-em.h>, which does not define EM_ARM. -// <linux/elf.h> only includes <asm/elf.h> if we're in the kernel. -# if !defined(EM_ARM) -# define EM_ARM 40 -# endif -#define SECCOMP_ARCH AUDIT_ARCH_ARM - -// ARM sigcontext_t is different from i386/x86_64. -// 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) - -#else -#error Unsupported target platform - -#endif - -#if defined(SECCOMP_BPF_STANDALONE) -#define arraysize(x) (sizeof(x)/sizeof(*(x))) -#define HANDLE_EINTR TEMP_FAILURE_RETRY -#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ - TypeName(); \ - TypeName(const TypeName&); \ - void operator=(const TypeName&) -#endif - #include "sandbox/linux/seccomp-bpf/die.h" #include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/linux/seccomp-bpf/linux_seccomp.h" +#include "sandbox/linux/seccomp-bpf/port.h" + namespace playground2 { @@ -199,56 +51,49 @@ class Sandbox { STATUS_ENABLED // The sandbox is now active }; - // TrapFnc is a pointer to a function that handles Seccomp traps in - // user-space. The seccomp policy can request that a trap handler gets - // installed; it does so by returning a suitable ErrorCode() from the - // syscallEvaluator. See the ErrorCode() constructor for how to pass in - // the function pointer. - // Please note that TrapFnc is executed from signal context and must be - // async-signal safe: - // http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html - 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. // One common use case would be to pass the "aux" pointer as an argument // to Trap() functions. - typedef ErrorCode (*EvaluateSyscall)(int sysnum, void *aux); + typedef ErrorCode (*EvaluateSyscall)(Sandbox *sb, int sysnum, void *aux); typedef std::vector<std::pair<EvaluateSyscall, void *> >Evaluators; + // A vector of BPF instructions that need to be installed as a filter + // program in the kernel. + typedef std::vector<struct sock_filter> Program; + + // Constructors and destructors. + // NOTE: Setting a policy and starting the sandbox is a one-way operation. + // The kernel does not provide any option for unloading a loaded + // sandbox. Strictly speaking, that means we should disallow calling + // the destructor, if StartSandbox() has ever been called. In practice, + // this makes it needlessly complicated to operate on "Sandbox" + // objects. So, we instead opted to allow object destruction. But it + // should be noted that during its lifetime, the object probably made + // irreversible state changes to the runtime environment. These changes + // stay in effect even after the destructor has been run. + Sandbox(); + ~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); + // eventually close it when "StartSandbox()" executes. + 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 @@ -261,83 +106,162 @@ 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); + 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 // ErrorCode object. // The "aux" field can carry a pointer to arbitrary data. See EvaluateSyscall - // for a description of how to pass data from setSandboxPolicy() to a Trap() + // for a description of how to pass data from SetSandboxPolicy() to a Trap() // handler. - static ErrorCode Trap(ErrorCode::TrapFnc fnc, const void *aux); + ErrorCode Trap(Trap::TrapFnc fnc, const void *aux); + + // Calls a user-space trap handler and disables all sandboxing for system + // calls made from this trap handler. + // This feature is available only if explicitly enabled by the user having + // set the CHROME_SANDBOX_DEBUGGING environment variable. + // Returns an ET_INVALID ErrorCode, if called when not enabled. + // NOTE: This feature, by definition, disables all security features of + // the sandbox. It should never be used in production, but it can be + // very useful to diagnose code that is incompatible with the sandbox. + // If even a single system call returns "UnsafeTrap", the security of + // entire sandbox should be considered compromised. + ErrorCode UnsafeTrap(Trap::TrapFnc fnc, const void *aux); + + // From within an UnsafeTrap() it is often useful to be able to execute + // the system call that triggered the trap. The ForwardSyscall() method + // makes this easy. It is more efficient than calling glibc's syscall() + // function, as it avoid the extra round-trip to the signal handler. And + // it automatically does the correct thing to report kernel-style error + // conditions, rather than setting errno. See the comments for TrapFnc for + // details. In other words, the return value from ForwardSyscall() is + // 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). + 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); + 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); } + // It is possible to stack multiple sandboxes by creating separate "Sandbox" + // objects and calling "StartSandbox()" on each of them. Please note, that + // this requires special care, though, as newly stacked sandboxes can never + // relax restrictions imposed by earlier sandboxes. Furthermore, installing + // a new policy requires making system calls, that might already be + // disallowed. + // Finally, stacking does add more kernel overhead than having a single + // combined policy. So, it should only be used if there are no alternatives. + void StartSandbox(); + + // Assembles a BPF filter program from the current policy. After calling this + // function, you must not call any other sandboxing function. + // Typically, AssembleFilter() is only used by unit tests and by sandbox + // internals. It should not be used by production code. + // For performance reasons, we normally only run the assembled BPF program + // through the verifier, iff the program was built in debug mode. + // But by setting "force_verification", the caller can request that the + // verifier is run unconditionally. This is useful for unittests. + Program *AssembleFilter(bool force_verification); + + // Returns the fatal ErrorCode that is used to indicate that somebody + // attempted to pass a 64bit value in a 32bit system call argument. + // This method is primarily needed for testing purposes. + ErrorCode Unexpected64bitArgument(); private: - friend class ErrorCode; friend class CodeGen; friend class SandboxUnittestHelper; - friend class Util; - friend class Verifier; - - typedef std::vector<struct sock_filter> Program; + friend class ErrorCode; struct Range { - Range(uint32_t f, uint32_t t, const ErrorCode& e) : - from(f), - to(t), - err(e) { + Range(uint32_t f, uint32_t t, const ErrorCode& e) + : from(f), + to(t), + err(e) { } uint32_t from, to; ErrorCode err; }; typedef std::vector<Range> Ranges; typedef std::map<uint32_t, ErrorCode> ErrMap; - typedef std::vector<ErrorCode> Traps; - typedef std::map<std::pair<TrapFnc, const void *>, int> 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 bool RunFunctionInPolicy(void (*function)(), - EvaluateSyscall syscallEvaluator, - 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, - void *aux); - 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 intptr_t bpfFailure(const struct arch_seccomp_data& data, void *aux); - static int getTrapId(TrapFnc fnc, const void *aux); + int proc_fd() { return proc_fd_; } + + // Creates a subprocess and runs "code_in_sandbox" inside of the specified + // policy. The caller has to make sure that "this" has not yet been + // initialized with any other policies. + bool RunFunctionInPolicy(void (*code_in_sandbox)(), + EvaluateSyscall syscall_evaluator, void *aux); + + // Performs a couple of sanity checks to verify that the kernel supports the + // features that we need for successful sandboxing. + // The caller has to make sure that "this" has not yet been initialized with + // any other policies. + bool KernelSupportSeccompBPF(); + + // Verify that the current policy passes some basic sanity checks. + void PolicySanityChecks(EvaluateSyscall syscall_evaluator, void *aux); + + // Assembles and installs a filter based on the policy that has previously + // been configured with SetSandboxPolicy(). + void InstallFilter(); + + // Verify the correctness of a compiled program by comparing it against the + // current policy. This function should only ever be called by unit tests and + // by the sandbox internals. It should not be used by production code. + void VerifyProgram(const Program& program, bool has_unsafe_traps); + + // Finds all the ranges of system calls that need to be handled. Ranges are + // sorted in ascending order of system call numbers. There are no gaps in the + // ranges. System calls with identical ErrorCodes are coalesced into a single + // range. + void FindRanges(Ranges *ranges); + + // Returns a BPF program snippet that implements a jump table for the + // given range of system call numbers. This function runs recursively. + Instruction *AssembleJumpTable(CodeGen *gen, + Ranges::const_iterator start, + Ranges::const_iterator stop); + + // Returns a BPF program snippet that makes the BPF filter program exit + // with the given ErrorCode "err". N.B. the ErrorCode may very well be a + // conditional expression; if so, this function will recursively call + // CondExpression() and possibly RetExpression() to build a complex set of + // instructions. + Instruction *RetExpression(CodeGen *gen, const ErrorCode& err); + + // Returns a BPF program that evaluates the conditional expression in + // "cond" and returns the appropriate value from the BPF filter program. + // This function recursively calls RetExpression(); it should only ever be + // called from RetExpression(). + Instruction *CondExpression(CodeGen *gen, const ErrorCode& cond); static SandboxStatus status_; - static int proc_fd_; - static Evaluators evaluators_; - static ErrMap errMap_; - static Traps *traps_; - static TrapIds trapIds_; - static ErrorCode *trapArray_; - static size_t trapArraySize_; - DISALLOW_IMPLICIT_CONSTRUCTORS(Sandbox); + + bool quiet_; + int proc_fd_; + Evaluators *evaluators_; + Conds *conds_; + + DISALLOW_COPY_AND_ASSIGN(Sandbox); }; } // namespace diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc index 8ea23d9e18..2d775f4cc7 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc @@ -2,17 +2,58 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <errno.h> +#include <pthread.h> +#include <sched.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/utsname.h> +#include <unistd.h> + +#if defined(ANDROID) +// Work-around for buggy headers in Android's NDK +#define __user +#endif +#include <linux/futex.h> + #include <ostream> +#include "base/memory/scoped_ptr.h" +#include "build/build_config.h" #include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/seccomp-bpf/trap.h" #include "sandbox/linux/seccomp-bpf/verifier.h" +#include "sandbox/linux/services/broker_process.h" +#include "sandbox/linux/services/linux_syscalls.h" #include "testing/gtest/include/gtest/gtest.h" +// Workaround for Android's prctl.h file. +#ifndef PR_GET_ENDIAN +#define PR_GET_ENDIAN 19 +#endif +#ifndef PR_CAPBSET_READ +#define PR_CAPBSET_READ 23 +#define PR_CAPBSET_DROP 24 +#endif + using namespace playground2; +using sandbox::BrokerProcess; namespace { -const int kExpectedReturnValue = 42; +const int kExpectedReturnValue = 42; +const char kSandboxDebuggingEnv[] = "CHROME_SANDBOX_DEBUGGING"; + +inline bool IsAndroid() { +#if defined(OS_ANDROID) + return true; +#else + return false; +#endif +} // This test should execute no matter whether we have kernel support. So, // we make it a TEST() instead of a BPF_TEST(). @@ -20,7 +61,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", @@ -28,11 +69,13 @@ TEST(SandboxBpf, CallSupports) { std::cout << "Seccomp BPF supported: " << (seccomp_bpf_supported ? "true." : "false.") << "\n"; + RecordProperty("PointerSize", sizeof(void*)); + std::cout << "Pointer size: " << sizeof(void*) << "\n"; } 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 @@ -48,22 +91,23 @@ intptr_t FakeGetPid(const struct arch_seccomp_data& args, void *aux) { return (*pid_ptr)++; } -ErrorCode VerboseAPITestingPolicy(int sysno, void *aux) { - if (!Sandbox::isValidSyscallNumber(sysno)) { +ErrorCode VerboseAPITestingPolicy(Sandbox *sandbox, int sysno, void *aux) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { return ErrorCode(ENOSYS); } else if (sysno == __NR_getpid) { - return Sandbox::Trap(FakeGetPid, aux); + return sandbox->Trap(FakeGetPid, aux); } else { return ErrorCode(ErrorCode::ERR_ALLOWED); } } 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(); + Sandbox sandbox; + sandbox.SetSandboxPolicy(VerboseAPITestingPolicy, &test_var); + sandbox.StartSandbox(); BPF_ASSERT(test_var == 0); BPF_ASSERT(syscall(__NR_getpid) == 0); @@ -79,8 +123,8 @@ SANDBOX_TEST(SandboxBpf, VerboseAPITesting) { // A simple blacklist test -ErrorCode BlacklistNanosleepPolicy(int sysno, void *) { - if (!Sandbox::isValidSyscallNumber(sysno)) { +ErrorCode BlacklistNanosleepPolicy(Sandbox *, int sysno, void *) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); } @@ -103,7 +147,7 @@ BPF_TEST(SandboxBpf, ApplyBasicBlacklistPolicy, BlacklistNanosleepPolicy) { // Now do a simple whitelist test -ErrorCode WhitelistGetpidPolicy(int sysno, void *) { +ErrorCode WhitelistGetpidPolicy(Sandbox *, int sysno, void *) { switch (sysno) { case __NR_getpid: case __NR_exit_group: @@ -133,15 +177,16 @@ intptr_t EnomemHandler(const struct arch_seccomp_data& args, void *aux) { return -ENOMEM; } -ErrorCode BlacklistNanosleepPolicySigsys(int sysno, void *aux) { - if (!Sandbox::isValidSyscallNumber(sysno)) { +ErrorCode BlacklistNanosleepPolicySigsys(Sandbox *sandbox, int sysno, + void *aux) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); } switch (sysno) { case __NR_nanosleep: - return Sandbox::Trap(EnomemHandler, aux); + return sandbox->Trap(EnomemHandler, aux); default: return ErrorCode(ErrorCode::ERR_ALLOWED); } @@ -164,6 +209,132 @@ BPF_TEST(SandboxBpf, BasicBlacklistWithSigsys, BPF_ASSERT(BPF_AUX == kExpectedReturnValue); } +// A simple test that verifies we can return arbitrary errno values. + +ErrorCode ErrnoTestPolicy(Sandbox *, int sysno, void *) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { + // FIXME: we should really not have to do that in a trivial policy + return ErrorCode(ENOSYS); + } + + switch (sysno) { + case __NR_dup2: + // Pretend that dup2() worked, but don't actually do anything. + return ErrorCode(0); + case __NR_setuid: +#if defined(__NR_setuid32) + case __NR_setuid32: +#endif + // Return errno = 1. + return ErrorCode(1); + case __NR_setgid: +#if defined(__NR_setgid32) + case __NR_setgid32: +#endif + // Return maximum errno value (typically 4095). + return ErrorCode(ErrorCode::ERR_MAX_ERRNO); + case __NR_uname: + // Return errno = 42; + return ErrorCode(42); + default: + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +BPF_TEST(SandboxBpf, ErrnoTest, ErrnoTestPolicy) { + // Verify that dup2() returns success, but doesn't actually run. + int fds[4]; + BPF_ASSERT(pipe(fds) == 0); + BPF_ASSERT(pipe(fds+2) == 0); + BPF_ASSERT(dup2(fds[2], fds[0]) == 0); + char buf[1] = { }; + BPF_ASSERT(write(fds[1], "\x55", 1) == 1); + BPF_ASSERT(write(fds[3], "\xAA", 1) == 1); + BPF_ASSERT(read(fds[0], buf, 1) == 1); + + // If dup2() executed, we will read \xAA, but it dup2() has been turned + // into a no-op by our policy, then we will read \x55. + BPF_ASSERT(buf[0] == '\x55'); + + // Verify that we can return the minimum and maximum errno values. + errno = 0; + BPF_ASSERT(setuid(0) == -1); + BPF_ASSERT(errno == 1); + + // On Android, errno is only supported up to 255, otherwise errno + // processing is skipped. + // We work around this (crbug.com/181647). + if (IsAndroid() && setgid(0) != -1) { + errno = 0; + BPF_ASSERT(setgid(0) == -ErrorCode::ERR_MAX_ERRNO); + BPF_ASSERT(errno == 0); + } else { + errno = 0; + BPF_ASSERT(setgid(0) == -1); + BPF_ASSERT(errno == ErrorCode::ERR_MAX_ERRNO); + } + + // Finally, test an errno in between the minimum and maximum. + errno = 0; + struct utsname uts_buf; + BPF_ASSERT(uname(&uts_buf) == -1); + BPF_ASSERT(errno == 42); +} + +// Testing the stacking of two sandboxes + +ErrorCode StackingPolicyPartOne(Sandbox *sandbox, int sysno, void *) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { + return ErrorCode(ENOSYS); + } + + switch (sysno) { + case __NR_getppid: + return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 0, + ErrorCode(ErrorCode::ERR_ALLOWED), + ErrorCode(EPERM)); + default: + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +ErrorCode StackingPolicyPartTwo(Sandbox *sandbox, int sysno, void *) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { + return ErrorCode(ENOSYS); + } + + switch (sysno) { + case __NR_getppid: + return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 0, + ErrorCode(EINVAL), + ErrorCode(ErrorCode::ERR_ALLOWED)); + default: + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +BPF_TEST(SandboxBpf, StackingPolicy, StackingPolicyPartOne) { + errno = 0; + BPF_ASSERT(syscall(__NR_getppid, 0) > 0); + BPF_ASSERT(errno == 0); + + BPF_ASSERT(syscall(__NR_getppid, 1) == -1); + BPF_ASSERT(errno == EPERM); + + // Stack a second sandbox with its own policy. Verify that we can further + // restrict filters, but we cannot relax existing filters. + Sandbox sandbox; + sandbox.SetSandboxPolicy(StackingPolicyPartTwo, NULL); + sandbox.StartSandbox(); + + errno = 0; + BPF_ASSERT(syscall(__NR_getppid, 0) == -1); + BPF_ASSERT(errno == EINVAL); + + BPF_ASSERT(syscall(__NR_getppid, 1) == -1); + BPF_ASSERT(errno == EPERM); +} + // A more complex, but synthetic policy. This tests the correctness of the BPF // program by iterating through all syscalls and checking for an errno that // depends on the syscall number. Unlike the Verifier, this exercises the BPF @@ -179,8 +350,8 @@ int SysnoToRandomErrno(int sysno) { return ((sysno & ~3) >> 2) % 29 + 1; } -ErrorCode SyntheticPolicy(int sysno, void *) { - if (!Sandbox::isValidSyscallNumber(sysno)) { +ErrorCode SyntheticPolicy(Sandbox *, int sysno, void *) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy return ErrorCode(ENOSYS); } @@ -237,8 +408,8 @@ int ArmPrivateSysnoToErrno(int sysno) { } } -ErrorCode ArmPrivatePolicy(int sysno, void *) { - if (!Sandbox::isValidSyscallNumber(sysno)) { +ErrorCode ArmPrivatePolicy(Sandbox *, int sysno, void *) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { // FIXME: we should really not have to do that in a trivial policy. return ErrorCode(ENOSYS); } @@ -264,4 +435,1288 @@ BPF_TEST(SandboxBpf, ArmPrivatePolicy, ArmPrivatePolicy) { } #endif // defined(__arm__) +intptr_t CountSyscalls(const struct arch_seccomp_data& args, void *aux) { + // Count all invocations of our callback function. + ++*reinterpret_cast<int *>(aux); + + // Verify that within the callback function all filtering is temporarily + // disabled. + BPF_ASSERT(syscall(__NR_getpid) > 1); + + // Verify that we can now call the underlying system call without causing + // infinite recursion. + return Sandbox::ForwardSyscall(args); +} + +ErrorCode GreyListedPolicy(Sandbox *sandbox, int sysno, void *aux) { + // The use of UnsafeTrap() causes us to print a warning message. This is + // generally desirable, but it results in the unittest failing, as it doesn't + // expect any messages on "stderr". So, temporarily disable messages. The + // BPF_TEST() is guaranteed to turn messages back on, after the policy + // function has completed. + setenv(kSandboxDebuggingEnv, "t", 0); + Die::SuppressInfoMessages(true); + + // Some system calls must always be allowed, if our policy wants to make + // use of UnsafeTrap() + if (sysno == __NR_rt_sigprocmask || + sysno == __NR_rt_sigreturn +#if defined(__NR_sigprocmask) + || sysno == __NR_sigprocmask +#endif +#if defined(__NR_sigreturn) + || sysno == __NR_sigreturn +#endif + ) { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } else if (sysno == __NR_getpid) { + // Disallow getpid() + return ErrorCode(EPERM); + } else if (Sandbox::IsValidSyscallNumber(sysno)) { + // Allow (and count) all other system calls. + return sandbox->UnsafeTrap(CountSyscalls, aux); + } else { + return ErrorCode(ENOSYS); + } +} + +BPF_TEST(SandboxBpf, GreyListedPolicy, + GreyListedPolicy, int /* BPF_AUX */) { + BPF_ASSERT(syscall(__NR_getpid) == -1); + BPF_ASSERT(errno == EPERM); + BPF_ASSERT(BPF_AUX == 0); + BPF_ASSERT(syscall(__NR_geteuid) == syscall(__NR_getuid)); + BPF_ASSERT(BPF_AUX == 2); + char name[17] = { }; + BPF_ASSERT(!syscall(__NR_prctl, PR_GET_NAME, name, (void *)NULL, + (void *)NULL, (void *)NULL)); + BPF_ASSERT(BPF_AUX == 3); + BPF_ASSERT(*name); +} + +SANDBOX_TEST(SandboxBpf, EnableUnsafeTrapsInSigSysHandler) { + // Disabling warning messages that could confuse our test framework. + setenv(kSandboxDebuggingEnv, "t", 0); + Die::SuppressInfoMessages(true); + + unsetenv(kSandboxDebuggingEnv); + SANDBOX_ASSERT(Trap::EnableUnsafeTrapsInSigSysHandler() == false); + setenv(kSandboxDebuggingEnv, "", 1); + SANDBOX_ASSERT(Trap::EnableUnsafeTrapsInSigSysHandler() == false); + setenv(kSandboxDebuggingEnv, "t", 1); + SANDBOX_ASSERT(Trap::EnableUnsafeTrapsInSigSysHandler() == true); +} + +intptr_t PrctlHandler(const struct arch_seccomp_data& args, void *) { + if (args.args[0] == PR_CAPBSET_DROP && + static_cast<int>(args.args[1]) == -1) { + // prctl(PR_CAPBSET_DROP, -1) is never valid. The kernel will always + // return an error. But our handler allows this call. + return 0; + } else { + return Sandbox::ForwardSyscall(args); + } +} + +ErrorCode PrctlPolicy(Sandbox *sandbox, int sysno, void *aux) { + setenv(kSandboxDebuggingEnv, "t", 0); + Die::SuppressInfoMessages(true); + + if (sysno == __NR_prctl) { + // Handle prctl() inside an UnsafeTrap() + return sandbox->UnsafeTrap(PrctlHandler, NULL); + } else if (Sandbox::IsValidSyscallNumber(sysno)) { + // Allow all other system calls. + return ErrorCode(ErrorCode::ERR_ALLOWED); + } else { + return ErrorCode(ENOSYS); + } +} + +BPF_TEST(SandboxBpf, ForwardSyscall, PrctlPolicy) { + // This call should never be allowed. But our policy will intercept it and + // let it pass successfully. + BPF_ASSERT(!prctl(PR_CAPBSET_DROP, -1, (void *)NULL, (void *)NULL, + (void *)NULL)); + + // Verify that the call will fail, if it makes it all the way to the kernel. + BPF_ASSERT(prctl(PR_CAPBSET_DROP, -2, (void *)NULL, (void *)NULL, + (void *)NULL) == -1); + + // And verify that other uses of prctl() work just fine. + char name[17] = { }; + BPF_ASSERT(!syscall(__NR_prctl, PR_GET_NAME, name, (void *)NULL, + (void *)NULL, (void *)NULL)); + BPF_ASSERT(*name); + + // Finally, verify that system calls other than prctl() are completely + // unaffected by our policy. + struct utsname uts = { }; + BPF_ASSERT(!uname(&uts)); + BPF_ASSERT(!strcmp(uts.sysname, "Linux")); +} + +intptr_t AllowRedirectedSyscall(const struct arch_seccomp_data& args, void *) { + return Sandbox::ForwardSyscall(args); +} + +ErrorCode RedirectAllSyscallsPolicy(Sandbox *sandbox, int sysno, void *aux) { + setenv(kSandboxDebuggingEnv, "t", 0); + Die::SuppressInfoMessages(true); + + // Some system calls must always be allowed, if our policy wants to make + // use of UnsafeTrap() + if (sysno == __NR_rt_sigprocmask || + sysno == __NR_rt_sigreturn +#if defined(__NR_sigprocmask) + || sysno == __NR_sigprocmask +#endif +#if defined(__NR_sigreturn) + || sysno == __NR_sigreturn +#endif + ) { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } else if (Sandbox::IsValidSyscallNumber(sysno)) { + return sandbox->UnsafeTrap(AllowRedirectedSyscall, aux); + } else { + return ErrorCode(ENOSYS); + } +} + +int bus_handler_fd_ = -1; + +void SigBusHandler(int, siginfo_t *info, void *void_context) { + BPF_ASSERT(write(bus_handler_fd_, "\x55", 1) == 1); +} + +BPF_TEST(SandboxBpf, SigBus, RedirectAllSyscallsPolicy) { + // We use the SIGBUS bit in the signal mask as a thread-local boolean + // value in the implementation of UnsafeTrap(). This is obviously a bit + // of a hack that could conceivably interfere with code that uses SIGBUS + // in more traditional ways. This test verifies that basic functionality + // of SIGBUS is not impacted, but it is certainly possibly to construe + // more complex uses of signals where our use of the SIGBUS mask is not + // 100% transparent. This is expected behavior. + int fds[2]; + BPF_ASSERT(pipe(fds) == 0); + bus_handler_fd_ = fds[1]; + struct sigaction sa = { }; + sa.sa_sigaction = SigBusHandler; + sa.sa_flags = SA_SIGINFO; + BPF_ASSERT(sigaction(SIGBUS, &sa, NULL) == 0); + raise(SIGBUS); + char c = '\000'; + BPF_ASSERT(read(fds[0], &c, 1) == 1); + BPF_ASSERT(close(fds[0]) == 0); + BPF_ASSERT(close(fds[1]) == 0); + BPF_ASSERT(c == 0x55); +} + +BPF_TEST(SandboxBpf, SigMask, RedirectAllSyscallsPolicy) { + // Signal masks are potentially tricky to handle. For instance, if we + // ever tried to update them from inside a Trap() or UnsafeTrap() handler, + // the call to sigreturn() at the end of the signal handler would undo + // all of our efforts. So, it makes sense to test that sigprocmask() + // works, even if we have a policy in place that makes use of UnsafeTrap(). + // In practice, this works because we force sigprocmask() to be handled + // entirely in the kernel. + sigset_t mask0, mask1, mask2; + + // Call sigprocmask() to verify that SIGUSR2 wasn't blocked, if we didn't + // change the mask (it shouldn't have been, as it isn't blocked by default + // in POSIX). + // + // Use SIGUSR2 because Android seems to use SIGUSR1 for some purpose. + sigemptyset(&mask0); + BPF_ASSERT(!sigprocmask(SIG_BLOCK, &mask0, &mask1)); + BPF_ASSERT(!sigismember(&mask1, SIGUSR2)); + + // Try again, and this time we verify that we can block it. This + // requires a second call to sigprocmask(). + sigaddset(&mask0, SIGUSR2); + BPF_ASSERT(!sigprocmask(SIG_BLOCK, &mask0, NULL)); + BPF_ASSERT(!sigprocmask(SIG_BLOCK, NULL, &mask2)); + BPF_ASSERT( sigismember(&mask2, SIGUSR2)); +} + +BPF_TEST(SandboxBpf, UnsafeTrapWithErrno, RedirectAllSyscallsPolicy) { + // An UnsafeTrap() (or for that matter, a Trap()) has to report error + // conditions by returning an exit code in the range -1..-4096. This + // should happen automatically if using ForwardSyscall(). If the TrapFnc() + // uses some other method to make system calls, then it is responsible + // for computing the correct return code. + // This test verifies that ForwardSyscall() does the correct thing. + + // The glibc system wrapper will ultimately set errno for us. So, from normal + // userspace, all of this should be completely transparent. + errno = 0; + BPF_ASSERT(close(-1) == -1); + BPF_ASSERT(errno == EBADF); + + // Explicitly avoid the glibc wrapper. This is not normally the way anybody + // would make system calls, but it allows us to verify that we don't + // accidentally mess with errno, when we shouldn't. + errno = 0; + struct arch_seccomp_data args = { }; + args.nr = __NR_close; + args.args[0] = -1; + BPF_ASSERT(Sandbox::ForwardSyscall(args) == -EBADF); + BPF_ASSERT(errno == 0); +} + +// Test a trap handler that makes use of a broker process to open(). + +class InitializedOpenBroker { + public: + InitializedOpenBroker() : initialized_(false) { + std::vector<std::string> allowed_files; + allowed_files.push_back("/proc/allowed"); + allowed_files.push_back("/proc/cpuinfo"); + + broker_process_.reset(new BrokerProcess(allowed_files, + std::vector<std::string>())); + BPF_ASSERT(broker_process() != NULL); + BPF_ASSERT(broker_process_->Init(NULL)); + + initialized_ = true; + } + bool initialized() { return initialized_; } + class BrokerProcess* broker_process() { return broker_process_.get(); } + private: + bool initialized_; + scoped_ptr<class BrokerProcess> broker_process_; + DISALLOW_COPY_AND_ASSIGN(InitializedOpenBroker); +}; + +intptr_t BrokerOpenTrapHandler(const struct arch_seccomp_data& args, + void *aux) { + BPF_ASSERT(aux); + BrokerProcess* broker_process = static_cast<BrokerProcess*>(aux); + switch(args.nr) { + case __NR_open: + return broker_process->Open(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); + case __NR_openat: + // We only call open() so if we arrive here, it's because glibc uses + // the openat() system call. + BPF_ASSERT(static_cast<int>(args.args[0]) == AT_FDCWD); + return broker_process->Open(reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); + default: + BPF_ASSERT(false); + return -ENOSYS; + } +} + +ErrorCode DenyOpenPolicy(Sandbox *sandbox, int sysno, void *aux) { + InitializedOpenBroker* iob = static_cast<InitializedOpenBroker*>(aux); + if (!Sandbox::IsValidSyscallNumber(sysno)) { + return ErrorCode(ENOSYS); + } + + switch (sysno) { + case __NR_open: + case __NR_openat: + // We get a InitializedOpenBroker class, but our trap handler wants + // the BrokerProcess object. + return ErrorCode(sandbox->Trap(BrokerOpenTrapHandler, + iob->broker_process())); + default: + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +// We use a InitializedOpenBroker class, so that we can run unsandboxed +// code in its constructor, which is the only way to do so in a BPF_TEST. +BPF_TEST(SandboxBpf, UseOpenBroker, DenyOpenPolicy, + InitializedOpenBroker /* BPF_AUX */) { + BPF_ASSERT(BPF_AUX.initialized()); + BrokerProcess* broker_process = BPF_AUX.broker_process(); + BPF_ASSERT(broker_process != NULL); + + // First, use the broker "manually" + BPF_ASSERT(broker_process->Open("/proc/denied", O_RDONLY) == -EPERM); + BPF_ASSERT(broker_process->Open("/proc/allowed", O_RDONLY) == -ENOENT); + + // Now use glibc's open() as an external library would. + BPF_ASSERT(open("/proc/denied", O_RDONLY) == -1); + BPF_ASSERT(errno == EPERM); + + BPF_ASSERT(open("/proc/allowed", O_RDONLY) == -1); + BPF_ASSERT(errno == ENOENT); + + // Also test glibc's openat(), some versions of libc use it transparently + // instead of open(). + BPF_ASSERT(openat(AT_FDCWD, "/proc/denied", O_RDONLY) == -1); + BPF_ASSERT(errno == EPERM); + + BPF_ASSERT(openat(AT_FDCWD, "/proc/allowed", O_RDONLY) == -1); + BPF_ASSERT(errno == ENOENT); + + + // This is also white listed and does exist. + int cpu_info_fd = open("/proc/cpuinfo", O_RDONLY); + BPF_ASSERT(cpu_info_fd >= 0); + char buf[1024]; + BPF_ASSERT(read(cpu_info_fd, buf, sizeof(buf)) > 0); +} + +// Simple test demonstrating how to use Sandbox::Cond() + +ErrorCode SimpleCondTestPolicy(Sandbox *sandbox, int sysno, void *) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { + // FIXME: we should really not have to do that in a trivial policy + return ErrorCode(ENOSYS); + } + + // We deliberately return unusual errno values upon failure, so that we + // can uniquely test for these values. In a "real" policy, you would want + // to return more traditional values. + switch (sysno) { + case __NR_open: + // Allow opening files for reading, but don't allow writing. + COMPILE_ASSERT(O_RDONLY == 0, O_RDONLY_must_be_all_zero_bits); + return sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS, + O_ACCMODE /* 0x3 */, + ErrorCode(EROFS), + ErrorCode(ErrorCode::ERR_ALLOWED)); + case __NR_prctl: + // Allow prctl(PR_SET_DUMPABLE) and prctl(PR_GET_DUMPABLE), but + // disallow everything else. + return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + PR_SET_DUMPABLE, + ErrorCode(ErrorCode::ERR_ALLOWED), + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + PR_GET_DUMPABLE, + ErrorCode(ErrorCode::ERR_ALLOWED), + ErrorCode(ENOMEM))); + default: + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +BPF_TEST(SandboxBpf, SimpleCondTest, SimpleCondTestPolicy) { + int fd; + BPF_ASSERT((fd = open("/proc/self/comm", O_RDWR)) == -1); + BPF_ASSERT(errno == EROFS); + BPF_ASSERT((fd = open("/proc/self/comm", O_RDONLY)) >= 0); + close(fd); + + int ret; + BPF_ASSERT((ret = prctl(PR_GET_DUMPABLE)) >= 0); + BPF_ASSERT(prctl(PR_SET_DUMPABLE, 1-ret) == 0); + BPF_ASSERT(prctl(PR_GET_ENDIAN, &ret) == -1); + BPF_ASSERT(errno == ENOMEM); +} + +// 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(Sandbox *sandbox, 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(sandbox, 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(Sandbox *sandbox, 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(sandbox, 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(sandbox, 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(Sandbox *sandbox, int sysno, void *aux) { + return reinterpret_cast<EqualityStressTest *>(aux)->Policy(sandbox, sysno); +} + +BPF_TEST(SandboxBpf, EqualityTests, EqualityStressTestPolicy, + EqualityStressTest /* BPF_AUX */) { + BPF_AUX.VerifyFilter(); +} + +ErrorCode EqualityArgumentWidthPolicy(Sandbox *sandbox, 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)), + // The BPF compiler and the BPF interpreter in the kernel are + // (mostly) agnostic of the host platform's word size. The compiler + // will happily generate code that tests a 64bit value, and the + // interpreter will happily perform this test. + // But unless there is a kernel bug, there is no way for us to pass + // in a 64bit quantity on a 32bit platform. The upper 32bits should + // always be zero. So, this test should always evaluate as false on + // 32bit systems. + 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); +#else + BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x55555555) == -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(Sandbox *sandbox, 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 + +ErrorCode AllBitTestPolicy(Sandbox *sandbox, int sysno, void *) { + // Test the OP_HAS_ALL_BITS conditional test operator with a couple of + // different bitmasks. We try to find bitmasks that could conceivably + // touch corner cases. + // For all of these tests, we override the uname(). We can make use with + // a single system call number, as we use the first system call argument to + // select the different bit masks that we want to test against. + 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_HAS_ALL_BITS, + 0x0, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 1, + sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ALL_BITS, + 0x1, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 2, + sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ALL_BITS, + 0x3, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 3, + sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ALL_BITS, + 0x80000000, + ErrorCode(1), ErrorCode(0)), + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 4, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ALL_BITS, + 0x0, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 5, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ALL_BITS, + 0x1, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 6, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ALL_BITS, + 0x3, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 7, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ALL_BITS, + 0x80000000, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 8, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ALL_BITS, + 0x100000000ULL, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 9, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ALL_BITS, + 0x300000000ULL, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 10, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ALL_BITS, + 0x100000001ULL, + ErrorCode(1), ErrorCode(0)), + + sandbox->Kill("Invalid test case number")))))))))))); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +// Define a macro that performs tests using our test policy. +// NOTE: Not all of the arguments in this macro are actually used! +// They are here just to serve as documentation of the conditions +// implemented in the test policy. +// Most notably, "op" and "mask" are unused by the macro. If you want +// to make changes to these values, you will have to edit the +// test policy instead. +#define BITMASK_TEST(testcase, arg, op, mask, expected_value) \ + BPF_ASSERT(SandboxSyscall(__NR_uname, (testcase), (arg)) == (expected_value)) + +// Our uname() system call returns ErrorCode(1) for success and +// ErrorCode(0) for failure. SandboxSyscall() turns this into an +// exit code of -1 or 0. +#define EXPECT_FAILURE 0 +#define EXPECT_SUCCESS -1 + +// A couple of our tests behave differently on 32bit and 64bit systems, as +// there is no way for a 32bit system call to pass in a 64bit system call +// argument "arg". +// We expect these tests to succeed on 64bit systems, but to tail on 32bit +// systems. +#define EXPT64_SUCCESS \ + (sizeof(void *) > 4 ? EXPECT_SUCCESS : EXPECT_FAILURE) + +BPF_TEST(SandboxBpf, AllBitTests, AllBitTestPolicy) { + // 32bit test: all of 0x0 (should always be true) + BITMASK_TEST( 0, 0, ALLBITS32, 0, EXPECT_SUCCESS); + BITMASK_TEST( 0, 1, ALLBITS32, 0, EXPECT_SUCCESS); + BITMASK_TEST( 0, 3, ALLBITS32, 0, EXPECT_SUCCESS); + BITMASK_TEST( 0, 0xFFFFFFFFU, ALLBITS32, 0, EXPECT_SUCCESS); + BITMASK_TEST( 0, -1LL, ALLBITS32, 0, EXPECT_SUCCESS); + + // 32bit test: all of 0x1 + BITMASK_TEST( 1, 0, ALLBITS32, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 1, 1, ALLBITS32, 0x1, EXPECT_SUCCESS); + BITMASK_TEST( 1, 2, ALLBITS32, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 1, 3, ALLBITS32, 0x1, EXPECT_SUCCESS); + + // 32bit test: all of 0x3 + BITMASK_TEST( 2, 0, ALLBITS32, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 2, 1, ALLBITS32, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 2, 2, ALLBITS32, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 2, 3, ALLBITS32, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 2, 7, ALLBITS32, 0x3, EXPECT_SUCCESS); + + // 32bit test: all of 0x80000000 + BITMASK_TEST( 3, 0, ALLBITS32, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 3, 0x40000000U, ALLBITS32, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 3, 0x80000000U, ALLBITS32, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 3, 0xC0000000U, ALLBITS32, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 3, -0x80000000LL, ALLBITS32, 0x80000000, EXPECT_SUCCESS); + + // 64bit test: all of 0x0 (should always be true) + BITMASK_TEST( 4, 0, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, 1, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, 3, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, 0xFFFFFFFFU, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, 0x100000000LL, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, 0x300000000LL, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4,0x8000000000000000LL, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, -1LL, ALLBITS64, 0, EXPECT_SUCCESS); + + // 64bit test: all of 0x1 + BITMASK_TEST( 5, 0, ALLBITS64, 1, EXPECT_FAILURE); + BITMASK_TEST( 5, 1, ALLBITS64, 1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 2, ALLBITS64, 1, EXPECT_FAILURE); + BITMASK_TEST( 5, 3, ALLBITS64, 1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 0x100000000LL, ALLBITS64, 1, EXPECT_FAILURE); + BITMASK_TEST( 5, 0x100000001LL, ALLBITS64, 1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 0x100000002LL, ALLBITS64, 1, EXPECT_FAILURE); + BITMASK_TEST( 5, 0x100000003LL, ALLBITS64, 1, EXPECT_SUCCESS); + + // 64bit test: all of 0x3 + BITMASK_TEST( 6, 0, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 1, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 2, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 3, ALLBITS64, 3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 7, ALLBITS64, 3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000000LL, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 0x100000001LL, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 0x100000002LL, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 0x100000003LL, ALLBITS64, 3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000007LL, ALLBITS64, 3, EXPECT_SUCCESS); + + // 64bit test: all of 0x80000000 + BITMASK_TEST( 7, 0, ALLBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x40000000U, ALLBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x80000000U, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0xC0000000U, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, -0x80000000LL, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0x100000000LL, ALLBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x140000000LL, ALLBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x180000000LL, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0x1C0000000LL, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, -0x180000000LL, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + + // 64bit test: all of 0x100000000 + BITMASK_TEST( 8, 0x000000000LL, ALLBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x100000000LL, ALLBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x200000000LL, ALLBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x300000000LL, ALLBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x000000001LL, ALLBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x100000001LL, ALLBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x200000001LL, ALLBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x300000001LL, ALLBITS64,0x100000000, EXPT64_SUCCESS); + + // 64bit test: all of 0x300000000 + BITMASK_TEST( 9, 0x000000000LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x100000000LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x200000000LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x300000000LL, ALLBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x700000000LL, ALLBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x000000001LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x100000001LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x200000001LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x300000001LL, ALLBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x700000001LL, ALLBITS64,0x300000000, EXPT64_SUCCESS); + + // 64bit test: all of 0x100000001 + BITMASK_TEST(10, 0x000000000LL, ALLBITS64,0x100000001, EXPECT_FAILURE); + BITMASK_TEST(10, 0x000000001LL, ALLBITS64,0x100000001, EXPECT_FAILURE); + BITMASK_TEST(10, 0x100000000LL, ALLBITS64,0x100000001, EXPECT_FAILURE); + BITMASK_TEST(10, 0x100000001LL, ALLBITS64,0x100000001, EXPT64_SUCCESS); + BITMASK_TEST(10, 0xFFFFFFFFU, ALLBITS64,0x100000001, EXPECT_FAILURE); + BITMASK_TEST(10, -1L, ALLBITS64,0x100000001, EXPT64_SUCCESS); +} + +ErrorCode AnyBitTestPolicy(Sandbox *sandbox, int sysno, void *) { + // Test the OP_HAS_ANY_BITS conditional test operator with a couple of + // different bitmasks. We try to find bitmasks that could conceivably + // touch corner cases. + // For all of these tests, we override the uname(). We can make use with + // a single system call number, as we use the first system call argument to + // select the different bit masks that we want to test against. + 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_HAS_ANY_BITS, + 0x0, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 1, + sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS, + 0x1, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 2, + sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS, + 0x3, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 3, + sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS, + 0x80000000, + ErrorCode(1), ErrorCode(0)), + + // All the following tests don't really make much sense on 32bit + // systems. They will always evaluate as false. + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 4, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ANY_BITS, + 0x0, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 5, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ANY_BITS, + 0x1, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 6, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ANY_BITS, + 0x3, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 7, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ANY_BITS, + 0x80000000, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 8, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ANY_BITS, + 0x100000000ULL, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 9, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ANY_BITS, + 0x300000000ULL, + ErrorCode(1), ErrorCode(0)), + + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 10, + sandbox->Cond(1, ErrorCode::TP_64BIT, ErrorCode::OP_HAS_ANY_BITS, + 0x100000001ULL, + ErrorCode(1), ErrorCode(0)), + + sandbox->Kill("Invalid test case number")))))))))))); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +BPF_TEST(SandboxBpf, AnyBitTests, AnyBitTestPolicy) { + // 32bit test: any of 0x0 (should always be false) + BITMASK_TEST( 0, 0, ANYBITS32, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 0, 1, ANYBITS32, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 0, 3, ANYBITS32, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 0, 0xFFFFFFFFU, ANYBITS32, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 0, -1LL, ANYBITS32, 0x0, EXPECT_FAILURE); + + // 32bit test: any of 0x1 + BITMASK_TEST( 1, 0, ANYBITS32, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 1, 1, ANYBITS32, 0x1, EXPECT_SUCCESS); + BITMASK_TEST( 1, 2, ANYBITS32, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 1, 3, ANYBITS32, 0x1, EXPECT_SUCCESS); + + // 32bit test: any of 0x3 + BITMASK_TEST( 2, 0, ANYBITS32, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 2, 1, ANYBITS32, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 2, 2, ANYBITS32, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 2, 3, ANYBITS32, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 2, 7, ANYBITS32, 0x3, EXPECT_SUCCESS); + + // 32bit test: any of 0x80000000 + BITMASK_TEST( 3, 0, ANYBITS32, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 3, 0x40000000U, ANYBITS32, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 3, 0x80000000U, ANYBITS32, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 3, 0xC0000000U, ANYBITS32, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 3, -0x80000000LL, ANYBITS32, 0x80000000, EXPECT_SUCCESS); + + // 64bit test: any of 0x0 (should always be false) + BITMASK_TEST( 4, 0, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, 1, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, 3, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, 0xFFFFFFFFU, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, 0x100000000LL, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, 0x300000000LL, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4,0x8000000000000000LL, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, -1LL, ANYBITS64, 0x0, EXPECT_FAILURE); + + // 64bit test: any of 0x1 + BITMASK_TEST( 5, 0, ANYBITS64, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 5, 1, ANYBITS64, 0x1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 2, ANYBITS64, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 5, 3, ANYBITS64, 0x1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 0x100000001LL, ANYBITS64, 0x1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 0x100000000LL, ANYBITS64, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 5, 0x100000002LL, ANYBITS64, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 5, 0x100000003LL, ANYBITS64, 0x1, EXPECT_SUCCESS); + + // 64bit test: any of 0x3 + BITMASK_TEST( 6, 0, ANYBITS64, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 6, 1, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 2, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 3, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 7, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000000LL, ANYBITS64, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 6, 0x100000001LL, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000002LL, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000003LL, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000007LL, ANYBITS64, 0x3, EXPECT_SUCCESS); + + // 64bit test: any of 0x80000000 + BITMASK_TEST( 7, 0, ANYBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x40000000U, ANYBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x80000000U, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0xC0000000U, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, -0x80000000LL, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0x100000000LL, ANYBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x140000000LL, ANYBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x180000000LL, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0x1C0000000LL, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, -0x180000000LL, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + + // 64bit test: any of 0x100000000 + BITMASK_TEST( 8, 0x000000000LL, ANYBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x100000000LL, ANYBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x200000000LL, ANYBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x300000000LL, ANYBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x000000001LL, ANYBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x100000001LL, ANYBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x200000001LL, ANYBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x300000001LL, ANYBITS64,0x100000000, EXPT64_SUCCESS); + + // 64bit test: any of 0x300000000 + BITMASK_TEST( 9, 0x000000000LL, ANYBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x100000000LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x200000000LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x300000000LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x700000000LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x000000001LL, ANYBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x100000001LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x200000001LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x300000001LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x700000001LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + + // 64bit test: any of 0x100000001 + BITMASK_TEST( 10, 0x000000000LL, ANYBITS64,0x100000001, EXPECT_FAILURE); + BITMASK_TEST( 10, 0x000000001LL, ANYBITS64,0x100000001, EXPECT_SUCCESS); + BITMASK_TEST( 10, 0x100000000LL, ANYBITS64,0x100000001, EXPT64_SUCCESS); + BITMASK_TEST( 10, 0x100000001LL, ANYBITS64,0x100000001, EXPECT_SUCCESS); + BITMASK_TEST( 10, 0xFFFFFFFFU, ANYBITS64,0x100000001, EXPECT_SUCCESS); + BITMASK_TEST( 10, -1L, ANYBITS64,0x100000001, EXPECT_SUCCESS); +} + +intptr_t PthreadTrapHandler(const struct arch_seccomp_data& args, void *aux) { + if (args.args[0] != (CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD)) { + // We expect to get called for an attempt to fork(). No need to log that + // call. But if we ever get called for anything else, we want to verbosely + // print as much information as possible. + const char *msg = (const char *)aux; + printf("Clone() was called with unexpected arguments\n" + " nr: %d\n" + " 1: 0x%llX\n" + " 2: 0x%llX\n" + " 3: 0x%llX\n" + " 4: 0x%llX\n" + " 5: 0x%llX\n" + " 6: 0x%llX\n" + "%s\n", + args.nr, + (long long)args.args[0], (long long)args.args[1], + (long long)args.args[2], (long long)args.args[3], + (long long)args.args[4], (long long)args.args[5], + msg); + } + return -EPERM; +} + +ErrorCode PthreadPolicyEquality(Sandbox *sandbox, int sysno, void *aux) { + // This policy allows creating threads with pthread_create(). But it + // doesn't allow any other uses of clone(). Most notably, it does not + // allow callers to implement fork() or vfork() by passing suitable flags + // to the clone() system call. + if (!Sandbox::IsValidSyscallNumber(sysno)) { + // FIXME: we should really not have to do that in a trivial policy + return ErrorCode(ENOSYS); + } else if (sysno == __NR_clone) { + // We have seen two different valid combinations of flags. Glibc + // uses the more modern flags, sets the TLS from the call to clone(), and + // uses futexes to monitor threads. Android's C run-time library, doesn't + // do any of this, but it sets the obsolete (and no-op) CLONE_DETACHED. + // The following policy is very strict. It only allows the exact masks + // that we have seen in known implementations. It is probably somewhat + // stricter than what we would want to do. + return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND| + CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS| + CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, + ErrorCode(ErrorCode::ERR_ALLOWED), + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND| + CLONE_THREAD|CLONE_SYSVSEM|CLONE_DETACHED, + ErrorCode(ErrorCode::ERR_ALLOWED), + sandbox->Trap(PthreadTrapHandler, aux))); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +ErrorCode PthreadPolicyBitMask(Sandbox *sandbox, int sysno, void *aux) { + // This policy allows creating threads with pthread_create(). But it + // doesn't allow any other uses of clone(). Most notably, it does not + // allow callers to implement fork() or vfork() by passing suitable flags + // to the clone() system call. + if (!Sandbox::IsValidSyscallNumber(sysno)) { + // FIXME: we should really not have to do that in a trivial policy + return ErrorCode(ENOSYS); + } else if (sysno == __NR_clone) { + // We have seen two different valid combinations of flags. Glibc + // uses the more modern flags, sets the TLS from the call to clone(), and + // uses futexes to monitor threads. Android's C run-time library, doesn't + // do any of this, but it sets the obsolete (and no-op) CLONE_DETACHED. + // The following policy allows for either combination of flags, but it + // is generally a little more conservative than strictly necessary. We + // err on the side of rather safe than sorry. + // Very noticeably though, we disallow fork() (which is often just a + // wrapper around clone()). + return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS, + ~uint32(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND| + CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS| + CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID| + CLONE_DETACHED), + sandbox->Trap(PthreadTrapHandler, + "Unexpected CLONE_XXX flag found"), + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ALL_BITS, + CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND| + CLONE_THREAD|CLONE_SYSVSEM, + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ALL_BITS, + CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, + ErrorCode(ErrorCode::ERR_ALLOWED), + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS, + CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, + sandbox->Trap(PthreadTrapHandler, + "Must set either all or none of the TLS" + " and futex bits in call to clone()"), + ErrorCode(ErrorCode::ERR_ALLOWED))), + sandbox->Trap(PthreadTrapHandler, + "Missing mandatory CLONE_XXX flags " + "when creating new thread"))); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +static void *ThreadFnc(void *arg) { + ++*reinterpret_cast<int *>(arg); + SandboxSyscall(__NR_futex, arg, FUTEX_WAKE, 1, 0, 0, 0); + return NULL; +} + +static void PthreadTest() { + // Attempt to start a joinable thread. This should succeed. + pthread_t thread; + int thread_ran = 0; + BPF_ASSERT(!pthread_create(&thread, NULL, ThreadFnc, &thread_ran)); + BPF_ASSERT(!pthread_join(thread, NULL)); + BPF_ASSERT(thread_ran); + + // Attempt to start a detached thread. This should succeed. + thread_ran = 0; + pthread_attr_t attr; + BPF_ASSERT(!pthread_attr_init(&attr)); + BPF_ASSERT(!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); + BPF_ASSERT(!pthread_create(&thread, &attr, ThreadFnc, &thread_ran)); + BPF_ASSERT(!pthread_attr_destroy(&attr)); + while (SandboxSyscall(__NR_futex, &thread_ran, FUTEX_WAIT, + 0, 0, 0, 0) == -EINTR) { + } + BPF_ASSERT(thread_ran); + + // Attempt to fork() a process using clone(). This should fail. We use the + // same flags that glibc uses when calling fork(). But we don't actually + // try calling the fork() implementation in the C run-time library, as + // run-time libraries other than glibc might call __NR_fork instead of + // __NR_clone, and that would introduce a bogus test failure. + int pid; + BPF_ASSERT(SandboxSyscall(__NR_clone, + CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, + 0, 0, &pid) == -EPERM); +} + +BPF_TEST(SandboxBpf, PthreadEquality, PthreadPolicyEquality) { + PthreadTest(); +} + +BPF_TEST(SandboxBpf, PthreadBitMask, PthreadPolicyBitMask) { + PthreadTest(); +} + } // namespace diff --git a/sandbox/linux/seccomp-bpf/syscall.cc b/sandbox/linux/seccomp-bpf/syscall.cc new file mode 100644 index 0000000000..8b09a68486 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall.cc @@ -0,0 +1,243 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <asm/unistd.h> +#include <errno.h> + +#include "sandbox/linux/seccomp-bpf/port.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" + + +namespace playground2 { + + asm( // We need to be able to tell the kernel exactly where we made a + // system call. The C++ compiler likes to sometimes clone or + // inline code, which would inadvertently end up duplicating + // the entry point. + // "gcc" can suppress code duplication with suitable function + // attributes, but "clang" doesn't have this ability. + // The "clang" developer mailing list suggested that the correct + // and portable solution is a file-scope assembly block. + // N.B. We do mark our code as a proper function so that backtraces + // work correctly. But we make absolutely no attempt to use the + // ABI's calling conventions for passing arguments. We will only + // ever be called from assembly code and thus can pick more + // suitable calling conventions. +#if defined(__i386__) + ".text\n" + ".align 16, 0x90\n" + ".type SyscallAsm, @function\n" + "SyscallAsm:.cfi_startproc\n" + // Check if "%eax" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "int $0x80". This address can be + // used as a marker that BPF code inspects. + "test %eax, %eax\n" + "jge 1f\n" + // Always, make sure that our code is position-independent, or + // address space randomization might not work on i386. This means, + // we can't use "lea", but instead have to rely on "call/pop". + "call 0f; .cfi_adjust_cfa_offset 4\n" + "0:pop %eax; .cfi_adjust_cfa_offset -4\n" + "addl $2f-0b, %eax\n" + "ret\n" + // Save register that we don't want to clobber. On i386, we need to + // save relatively aggressively, as there are a couple or registers + // that are used internally (e.g. %ebx for position-independent + // code, and %ebp for the frame pointer), and as we need to keep at + // least a few registers available for the register allocator. + "1:push %esi; .cfi_adjust_cfa_offset 4\n" + "push %edi; .cfi_adjust_cfa_offset 4\n" + "push %ebx; .cfi_adjust_cfa_offset 4\n" + "push %ebp; .cfi_adjust_cfa_offset 4\n" + // Copy entries from the array holding the arguments into the + // correct CPU registers. + "movl 0(%edi), %ebx\n" + "movl 4(%edi), %ecx\n" + "movl 8(%edi), %edx\n" + "movl 12(%edi), %esi\n" + "movl 20(%edi), %ebp\n" + "movl 16(%edi), %edi\n" + // Enter the kernel. + "int $0x80\n" + // This is our "magic" return address that the BPF filter sees. + "2:" + // Restore any clobbered registers that we didn't declare to the + // compiler. + "pop %ebp; .cfi_adjust_cfa_offset -4\n" + "pop %ebx; .cfi_adjust_cfa_offset -4\n" + "pop %edi; .cfi_adjust_cfa_offset -4\n" + "pop %esi; .cfi_adjust_cfa_offset -4\n" + "ret\n" + ".cfi_endproc\n" + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#elif defined(__x86_64__) + ".text\n" + ".align 16, 0x90\n" + ".type SyscallAsm, @function\n" + "SyscallAsm:.cfi_startproc\n" + // Check if "%rax" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "syscall". This address can be + // used as a marker that BPF code inspects. + "test %rax, %rax\n" + "jge 1f\n" + // Always make sure that our code is position-independent, or the + // linker will throw a hissy fit on x86-64. + "call 0f; .cfi_adjust_cfa_offset 8\n" + "0:pop %rax; .cfi_adjust_cfa_offset -8\n" + "addq $2f-0b, %rax\n" + "ret\n" + // We declared all clobbered registers to the compiler. On x86-64, + // there really isn't much of a problem with register pressure. So, + // we can go ahead and directly copy the entries from the arguments + // array into the appropriate CPU registers. + "1:movq 0(%r12), %rdi\n" + "movq 8(%r12), %rsi\n" + "movq 16(%r12), %rdx\n" + "movq 24(%r12), %r10\n" + "movq 32(%r12), %r8\n" + "movq 40(%r12), %r9\n" + // Enter the kernel. + "syscall\n" + // This is our "magic" return address that the BPF filter sees. + "2:ret\n" + ".cfi_endproc\n" + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#elif defined(__arm__) + // Throughout this file, we use the same mode (ARM vs. thumb) + // that the C++ compiler uses. This means, when transfering control + // from C++ to assembly code, we do not need to switch modes (e.g. + // by using the "bx" instruction). It also means that our assembly + // code should not be invoked directly from code that lives in + // other compilation units, as we don't bother implementing thumb + // interworking. That's OK, as we don't make any of the assembly + // symbols public. They are all local to this file. + ".text\n" + ".align 2\n" + ".type SyscallAsm, %function\n" +#if defined(__thumb__) + ".thumb_func\n" +#else + ".arm\n" +#endif + "SyscallAsm:.fnstart\n" + "@ args = 0, pretend = 0, frame = 8\n" + "@ frame_needed = 1, uses_anonymous_args = 0\n" +#if defined(__thumb__) + ".cfi_startproc\n" + "push {r7, lr}\n" + ".cfi_offset 14, -4\n" + ".cfi_offset 7, -8\n" + "mov r7, sp\n" + ".cfi_def_cfa_register 7\n" + ".cfi_def_cfa_offset 8\n" +#else + "stmfd sp!, {fp, lr}\n" + "add fp, sp, #4\n" +#endif + // Check if "r0" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "swi 0". This address can be + // used as a marker that BPF code inspects. + "cmp r0, #0\n" + "bge 1f\n" + "ldr r0, =2f\n" + "b 2f\n" + // We declared (almost) all clobbered registers to the compiler. On + // ARM there is no particular register pressure. So, we can go + // ahead and directly copy the entries from the arguments array + // into the appropriate CPU registers. + "1:ldr r5, [r6, #20]\n" + "ldr r4, [r6, #16]\n" + "ldr r3, [r6, #12]\n" + "ldr r2, [r6, #8]\n" + "ldr r1, [r6, #4]\n" + "mov r7, r0\n" + "ldr r0, [r6, #0]\n" + // Enter the kernel + "swi 0\n" + // Restore the frame pointer. Also restore the program counter from + // the link register; this makes us return to the caller. +#if defined(__thumb__) + "2:pop {r7, pc}\n" + ".cfi_endproc\n" +#else + "2:ldmfd sp!, {fp, pc}\n" +#endif + ".fnend\n" + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#endif + ); // asm + +intptr_t SandboxSyscall(int nr, + intptr_t p0, intptr_t p1, intptr_t p2, + intptr_t p3, intptr_t p4, intptr_t p5) { + // We rely on "intptr_t" to be the exact size as a "void *". This is + // typically true, but just in case, we add a check. The language + // specification allows platforms some leeway in cases, where + // "sizeof(void *)" is not the same as "sizeof(void (*)())". We expect + // that this would only be an issue for IA64, which we are currently not + // planning on supporting. And it is even possible that this would work + // on IA64, but for lack of actual hardware, I cannot test. + COMPILE_ASSERT(sizeof(void *) == sizeof(intptr_t), + pointer_types_and_intptr_must_be_exactly_the_same_size); + + const intptr_t args[6] = { p0, p1, p2, p3, p4, p5 }; + + // Invoke our file-scope assembly code. The constraints have been picked + // carefully to match what the rest of the assembly code expects in input, + // output, and clobbered registers. +#if defined(__i386__) + intptr_t ret = nr; + asm volatile( + "call SyscallAsm\n" + // N.B. These are not the calling conventions normally used by the ABI. + : "=a"(ret) + : "0"(ret), "D"(args) + : "esp", "memory", "ecx", "edx"); +#elif defined(__x86_64__) + intptr_t ret = nr; + { + register const intptr_t *data __asm__("r12") = args; + asm volatile( + "lea -128(%%rsp), %%rsp\n" // Avoid red zone. + "call SyscallAsm\n" + "lea 128(%%rsp), %%rsp\n" + // N.B. These are not the calling conventions normally used by the ABI. + : "=a"(ret) + : "0"(ret), "r"(data) + : "rsp", "memory", + "rcx", "rdi", "rsi", "rdx", "r8", "r9", "r10", "r11"); + } +#elif defined(__arm__) + intptr_t ret; + { + register intptr_t inout __asm__("r0") = nr; + register const intptr_t *data __asm__("r6") = args; + asm volatile( + "bl SyscallAsm\n" + // N.B. These are not the calling conventions normally used by the ABI. + : "=r"(inout) + : "0"(inout), "r"(data) + : "lr", "memory", "r1", "r2", "r3", "r4", "r5" +#if !defined(__arm__) + // In thumb mode, we cannot use "r7" as a general purpose register, as + // it is our frame pointer. We have to manually manage and preserve it. + // In ARM mode, we have a dedicated frame pointer register and "r7" is + // thus available as a general purpose register. We don't preserve it, + // but instead mark it as clobbered. + , "r7" +#endif + ); + ret = inout; + } +#else + errno = ENOSYS; + intptr_t ret = -1; +#endif + return ret; +} + +} // namespace diff --git a/sandbox/linux/seccomp-bpf/syscall.h b/sandbox/linux/seccomp-bpf/syscall.h new file mode 100644 index 0000000000..39b1bcaa4a --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall.h @@ -0,0 +1,123 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ + +#include <stdint.h> + +namespace playground2 { + +// We have to make sure that we have a single "magic" return address for +// our system calls, which we can check from within a BPF filter. This +// works by writing a little bit of asm() code that a) enters the kernel, and +// that also b) can be invoked in a way that computes this return address. +// Passing "nr" as "-1" computes the "magic" return address. Passing any +// other value invokes the appropriate system call. +intptr_t SandboxSyscall(int nr, + intptr_t p0, intptr_t p1, intptr_t p2, + intptr_t p3, intptr_t p4, intptr_t p5); + + +// System calls can take up to six parameters. Traditionally, glibc +// implements this property by using variadic argument lists. This works, but +// confuses modern tools such as valgrind, because we are nominally passing +// uninitialized data whenever we call through this function and pass less +// than the full six arguments. +// So, instead, we use C++'s template system to achieve a very similar +// effect. C++ automatically sets the unused parameters to zero for us, and +// it also does the correct type expansion (e.g. from 32bit to 64bit) where +// necessary. +// We have to use C-style cast operators as we want to be able to accept both +// integer and pointer types. +// We explicitly mark all functions as inline. This is not necessary in +// optimized builds, where the compiler automatically figures out that it +// can inline everything. But it makes stack traces of unoptimized builds +// easier to read as it hides implementation details. +#if __cplusplus >= 201103 // C++11 + +template<class T0 = intptr_t, class T1 = intptr_t, class T2 = intptr_t, + class T3 = intptr_t, class T4 = intptr_t, class T5 = intptr_t> +inline intptr_t SandboxSyscall(int nr, + T0 p0 = 0, T1 p1 = 0, T2 p2 = 0, + T3 p3 = 0, T4 p4 = 0, T5 p5 = 0) + __attribute__((always_inline)); + +template<class T0, class T1, class T2, class T3, class T4, class T5> +inline intptr_t SandboxSyscall(int nr, + T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5) { + return SandboxSyscall(nr, + (intptr_t)p0, (intptr_t)p1, (intptr_t)p2, + (intptr_t)p3, (intptr_t)p4, (intptr_t)p5); +} + +#else // Pre-C++11 + +// TODO(markus): C++11 has a much more concise and readable solution for +// expressing what we are doing here. Delete the fall-back code for older +// compilers as soon as we have fully switched to C++11 + +template<class T0, class T1, class T2, class T3, class T4, class T5> +inline intptr_t SandboxSyscall(int nr, + T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5) + __attribute__((always_inline)); +template<class T0, class T1, class T2, class T3, class T4, class T5> +inline intptr_t SandboxSyscall(int nr, + T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5) { + return SandboxSyscall(nr, + (intptr_t)p0, (intptr_t)p1, (intptr_t)p2, + (intptr_t)p3, (intptr_t)p4, (intptr_t)p5); +} + +template<class T0, class T1, class T2, class T3, class T4> +inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4) + __attribute__((always_inline)); +template<class T0, class T1, class T2, class T3, class T4> +inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4) { + return SandboxSyscall(nr, p0, p1, p2, p3, p4, 0); +} + +template<class T0, class T1, class T2, class T3> +inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2, T3 p3) + __attribute__((always_inline)); +template<class T0, class T1, class T2, class T3> +inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2, T3 p3) { + return SandboxSyscall(nr, p0, p1, p2, p3, 0, 0); +} + +template<class T0, class T1, class T2> +inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2) + __attribute__((always_inline)); +template<class T0, class T1, class T2> +inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2) { + return SandboxSyscall(nr, p0, p1, p2, 0, 0, 0); +} + +template<class T0, class T1> +inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1) + __attribute__((always_inline)); +template<class T0, class T1> +inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1) { + return SandboxSyscall(nr, p0, p1, 0, 0, 0, 0); +} + +template<class T0> +inline intptr_t SandboxSyscall(int nr, T0 p0) + __attribute__((always_inline)); +template<class T0> +inline intptr_t SandboxSyscall(int nr, T0 p0) { + return SandboxSyscall(nr, p0, 0, 0, 0, 0, 0); +} + +inline intptr_t SandboxSyscall(int nr) + __attribute__((always_inline)); +inline intptr_t SandboxSyscall(int nr) { + return SandboxSyscall(nr, 0, 0, 0, 0, 0, 0); +} + +#endif // Pre-C++11 + +} // namespace + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ diff --git a/sandbox/linux/seccomp-bpf/syscall_iterator.cc b/sandbox/linux/seccomp-bpf/syscall_iterator.cc index 583dcf6e9c..4ea979a63f 100644 --- a/sandbox/linux/seccomp-bpf/syscall_iterator.cc +++ b/sandbox/linux/seccomp-bpf/syscall_iterator.cc @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/linux_seccomp.h" +#include "sandbox/linux/seccomp-bpf/port.h" #include "sandbox/linux/seccomp-bpf/syscall_iterator.h" namespace playground2 { @@ -16,7 +17,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 +80,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 39568d8f12..e17593d844 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 new file mode 100644 index 0000000000..136deb615c --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall_unittest.cc @@ -0,0 +1,185 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <asm/unistd.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include <vector> + +#include "base/basictypes.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace playground2; + +namespace { + +// Different platforms use different symbols for the six-argument version +// of the mmap() system call. Test for the correct symbol at compile time. +#ifdef __NR_mmap2 +const int kMMapNr = __NR_mmap2; +#else +const int kMMapNr = __NR_mmap; +#endif + +TEST(Syscall, WellKnownEntryPoint) { + // Test that SandboxSyscall(-1) is handled specially. Don't do this on ARM, + // where syscall(-1) crashes with SIGILL. Not running the test is fine, as we + // are still testing ARM code in the next set of tests. +#if !defined(__arm__) + EXPECT_NE(SandboxSyscall(-1), syscall(-1)); +#endif + + // If possible, test that SandboxSyscall(-1) returns the address right after + // a kernel entry point. +#if defined(__i386__) + EXPECT_EQ(0x80CDu, ((uint16_t *)SandboxSyscall(-1))[-1]); // INT 0x80 +#elif defined(__x86_64__) + EXPECT_EQ(0x050Fu, ((uint16_t *)SandboxSyscall(-1))[-1]); // SYSCALL +#elif defined(__arm__) +#if defined(__thumb__) + EXPECT_EQ(0xDF00u, ((uint16_t *)SandboxSyscall(-1))[-1]); // SWI 0 +#else + EXPECT_EQ(0xEF000000u, ((uint32_t *)SandboxSyscall(-1))[-1]); // SVC 0 +#endif +#else + #warning Incomplete test case; need port for target platform +#endif +} + +TEST(Syscall, TrivialSyscallNoArgs) { + // Test that we can do basic system calls + EXPECT_EQ(SandboxSyscall(__NR_getpid), syscall(__NR_getpid)); +} + +TEST(Syscall, TrivialSyscallOneArg) { + int new_fd; + // Duplicate standard error and close it. + ASSERT_GE(new_fd = SandboxSyscall(__NR_dup, 2), 0); + int close_return_value = HANDLE_EINTR(SandboxSyscall(__NR_close, new_fd)); + ASSERT_EQ(close_return_value, 0); +} + +// SIGSYS trap handler that will be called on __NR_uname. +intptr_t CopySyscallArgsToAux(const struct arch_seccomp_data& args, void *aux) { + // |aux| is a pointer to our BPF_AUX. + std::vector<uint64_t>* const seen_syscall_args = + static_cast<std::vector<uint64_t>*>(aux); + BPF_ASSERT(arraysize(args.args) == 6); + seen_syscall_args->assign(args.args, args.args + arraysize(args.args)); + return -ENOMEM; +} + +ErrorCode CopyAllArgsOnUnamePolicy(Sandbox *sandbox, int sysno, void *aux) { + if (!Sandbox::IsValidSyscallNumber(sysno)) { + return ErrorCode(ENOSYS); + } + if (sysno == __NR_uname) { + return sandbox->Trap(CopySyscallArgsToAux, aux); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +// We are testing SandboxSyscall() by making use of a BPF filter that allows us +// to inspect the system call arguments that the kernel saw. +BPF_TEST(Syscall, SyntheticSixArgs, CopyAllArgsOnUnamePolicy, + std::vector<uint64_t> /* BPF_AUX */) { + const int kExpectedValue = 42; + // In this test we only pass integers to the kernel. We might want to make + // additional tests to try other types. What we will see depends on + // implementation details of kernel BPF filters and we will need to document + // the expected behavior very clearly. + int syscall_args[6]; + for (size_t i = 0; i < arraysize(syscall_args); ++i) { + syscall_args[i] = kExpectedValue + i; + } + + // We could use pretty much any system call we don't need here. uname() is + // nice because it doesn't have any dangerous side effects. + BPF_ASSERT(SandboxSyscall(__NR_uname, syscall_args[0], + syscall_args[1], + syscall_args[2], + syscall_args[3], + syscall_args[4], + syscall_args[5]) == -ENOMEM); + + // We expect the trap handler to have copied the 6 arguments. + BPF_ASSERT(BPF_AUX.size() == 6); + + // Don't loop here so that we can see which argument does cause the failure + // easily from the failing line. + // uint64_t is the type passed to our SIGSYS handler. + BPF_ASSERT(BPF_AUX[0] == static_cast<uint64_t>(syscall_args[0])); + BPF_ASSERT(BPF_AUX[1] == static_cast<uint64_t>(syscall_args[1])); + BPF_ASSERT(BPF_AUX[2] == static_cast<uint64_t>(syscall_args[2])); + BPF_ASSERT(BPF_AUX[3] == static_cast<uint64_t>(syscall_args[3])); + BPF_ASSERT(BPF_AUX[4] == static_cast<uint64_t>(syscall_args[4])); + BPF_ASSERT(BPF_AUX[5] == static_cast<uint64_t>(syscall_args[5])); +} + +TEST(Syscall, ComplexSyscallSixArgs) { + int fd; + ASSERT_LE(0, fd = SandboxSyscall(__NR_open, "/dev/null", O_RDWR, 0L)); + + // Use mmap() to allocate some read-only memory + char *addr0; + ASSERT_NE((char *)NULL, + addr0 = reinterpret_cast<char *>( + SandboxSyscall(kMMapNr, (void *)NULL, 4096, PROT_READ, + MAP_PRIVATE|MAP_ANONYMOUS, fd, 0L))); + + // Try to replace the existing mapping with a read-write mapping + char *addr1; + ASSERT_EQ(addr0, + addr1 = reinterpret_cast<char *>( + SandboxSyscall(kMMapNr, addr0, 4096L, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, + fd, 0L))); + ++*addr1; // This should not seg fault + + // Clean up + EXPECT_EQ(0, SandboxSyscall(__NR_munmap, addr1, 4096L)); + EXPECT_EQ(0, HANDLE_EINTR(SandboxSyscall(__NR_close, fd))); + + // Check that the offset argument (i.e. the sixth argument) is processed + // correctly. + ASSERT_GE(fd = SandboxSyscall(__NR_open, "/proc/self/exe", O_RDONLY, 0L), 0); + char *addr2, *addr3; + ASSERT_NE((char *)NULL, + addr2 = reinterpret_cast<char *>( + SandboxSyscall(kMMapNr, (void *)NULL, 8192L, PROT_READ, + MAP_PRIVATE, fd, 0L))); + ASSERT_NE((char *)NULL, + addr3 = reinterpret_cast<char *>( + SandboxSyscall(kMMapNr, (void *)NULL, 4096L, PROT_READ, + MAP_PRIVATE, fd, +#if defined(__NR_mmap2) + 1L +#else + 4096L +#endif + ))); + EXPECT_EQ(0, memcmp(addr2 + 4096, addr3, 4096)); + + // Just to be absolutely on the safe side, also verify that the file + // contents matches what we are getting from a read() operation. + char buf[8192]; + EXPECT_EQ(8192, SandboxSyscall(__NR_read, fd, buf, 8192L)); + EXPECT_EQ(0, memcmp(addr2, buf, 8192)); + + // Clean up + EXPECT_EQ(0, SandboxSyscall(__NR_munmap, addr2, 8192L)); + EXPECT_EQ(0, SandboxSyscall(__NR_munmap, addr3, 4096L)); + EXPECT_EQ(0, HANDLE_EINTR(SandboxSyscall(__NR_close, fd))); +} + +} // namespace diff --git a/sandbox/linux/seccomp-bpf/trap.cc b/sandbox/linux/seccomp-bpf/trap.cc new file mode 100644 index 0000000000..85d7d3688e --- /dev/null +++ b/sandbox/linux/seccomp-bpf/trap.cc @@ -0,0 +1,345 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/syscall.h> + +#ifndef SECCOMP_BPF_STANDALONE +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#endif + +#include "sandbox/linux/seccomp-bpf/codegen.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/seccomp-bpf/trap.h" + +// Android's signal.h doesn't define ucontext etc. +#if defined(OS_ANDROID) +#include "sandbox/linux/services/android_ucontext.h" +#endif + +#include <limits> + + +namespace { + +const int kCapacityIncrement = 20; + +// Unsafe traps can only be turned on, if the user explicitly allowed them +// by setting the CHROME_SANDBOX_DEBUGGING environment variable. +const char kSandboxDebuggingEnv[] = "CHROME_SANDBOX_DEBUGGING"; + +// We need to tell whether we are performing a "normal" callback, or +// whether we were called recursively from within a UnsafeTrap() callback. +// This is a little tricky to do, because we need to somehow get access to +// per-thread data from within a signal context. Normal TLS storage is not +// safely accessible at this time. We could roll our own, but that involves +// a lot of complexity. Instead, we co-opt one bit in the signal mask. +// If BUS is blocked, we assume that we have been called recursively. +// There is a possibility for collision with other code that needs to do +// this, but in practice the risks are low. +// If SIGBUS turns out to be a problem, we could instead co-opt one of the +// realtime signals. There are plenty of them. Unfortunately, there is no +// way to mark a signal as allocated. So, the potential for collision is +// possibly even worse. +bool GetIsInSigHandler(const ucontext_t *ctx) { + // Note: on Android, sigismember does not take a pointer to const. + return sigismember(const_cast<sigset_t*>(&ctx->uc_sigmask), SIGBUS); +} + +void SetIsInSigHandler() { + sigset_t mask; + if (sigemptyset(&mask) || + sigaddset(&mask, SIGBUS) || + sigprocmask(SIG_BLOCK, &mask, NULL)) { + SANDBOX_DIE("Failed to block SIGBUS"); + } +} + +} // namespace + +namespace playground2 { + +Trap::Trap() + : trap_array_(NULL), + trap_array_size_(0), + trap_array_capacity_(0), + has_unsafe_traps_(false) { + // Set new SIGSYS handler + struct sigaction sa = { }; + sa.sa_sigaction = SigSysAction; + sa.sa_flags = SA_SIGINFO | SA_NODEFER; + if (sigaction(SIGSYS, &sa, NULL) < 0) { + SANDBOX_DIE("Failed to configure SIGSYS handler"); + } + + // Unmask SIGSYS + sigset_t mask; + if (sigemptyset(&mask) || + sigaddset(&mask, SIGSYS) || + sigprocmask(SIG_UNBLOCK, &mask, NULL)) { + SANDBOX_DIE("Failed to configure SIGSYS handler"); + } +} + +Trap *Trap::GetInstance() { + // Note: This class is not thread safe. It is the caller's responsibility + // to avoid race conditions. Normally, this is a non-issue as the sandbox + // can only be initialized if there are no other threads present. + // Also, this is not a normal singleton. Once created, the global trap + // object must never be destroyed again. + if (!global_trap_) { + global_trap_ = new Trap(); + if (!global_trap_) { + SANDBOX_DIE("Failed to allocate global trap handler"); + } + } + return global_trap_; +} + +void Trap::SigSysAction(int nr, siginfo_t *info, void *void_context) { + if (!global_trap_) { + SANDBOX_DIE("This can't happen. Found no global singleton instance " + "for Trap() handling."); + } + global_trap_->SigSys(nr, info, void_context); +} + +void Trap::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) > 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 + // the sigSys() handler. + // TODO: If we feel confident that our code otherwise works correctly, we + // could actually make an argument that spurious SIGSYS should + // just get silently ignored. TBD + sigsys_err: + SANDBOX_DIE("Unexpected SIGSYS received"); + } + + // Signal handlers should always preserve "errno". Otherwise, we could + // trigger really subtle bugs. + int old_errno = errno; + + // Obtain the signal context. This, most notably, gives us access to + // all CPU registers at the time of the signal. + ucontext_t *ctx = reinterpret_cast<ucontext_t *>(void_context); + + // Obtain the siginfo information that is specific to SIGSYS. Unfortunately, + // most versions of glibc don't include this information in siginfo_t. So, + // we need to explicitly copy it into a arch_sigsys structure. + struct arch_sigsys sigsys; + memcpy(&sigsys, &info->_sifields, sizeof(sigsys)); + + // Some more sanity checks. + if (sigsys.ip != reinterpret_cast<void *>(SECCOMP_IP(ctx)) || + sigsys.nr != static_cast<int>(SECCOMP_SYSCALL(ctx)) || + sigsys.arch != SECCOMP_ARCH) { + goto sigsys_err; + } + + intptr_t rc; + if (has_unsafe_traps_ && GetIsInSigHandler(ctx)) { + errno = old_errno; + if (sigsys.nr == __NR_clone) { + SANDBOX_DIE("Cannot call clone() from an UnsafeTrap() handler"); + } + rc = SandboxSyscall(sigsys.nr, + SECCOMP_PARM1(ctx), SECCOMP_PARM2(ctx), + SECCOMP_PARM3(ctx), SECCOMP_PARM4(ctx), + SECCOMP_PARM5(ctx), SECCOMP_PARM6(ctx)); + } else { + const ErrorCode& err = trap_array_[info->si_errno - 1]; + if (!err.safe_) { + SetIsInSigHandler(); + } + + // Copy the seccomp-specific data into a arch_seccomp_data structure. This + // is what we are showing to TrapFnc callbacks that the system call + // evaluator registered with the sandbox. + struct arch_seccomp_data data = { + sigsys.nr, + SECCOMP_ARCH, + reinterpret_cast<uint64_t>(sigsys.ip), + { + static_cast<uint64_t>(SECCOMP_PARM1(ctx)), + static_cast<uint64_t>(SECCOMP_PARM2(ctx)), + static_cast<uint64_t>(SECCOMP_PARM3(ctx)), + static_cast<uint64_t>(SECCOMP_PARM4(ctx)), + static_cast<uint64_t>(SECCOMP_PARM5(ctx)), + static_cast<uint64_t>(SECCOMP_PARM6(ctx)) + } + }; + + // Now call the TrapFnc callback associated with this particular instance + // of SECCOMP_RET_TRAP. + rc = err.fnc_(data, err.aux_); + } + + // Update the CPU register that stores the return code of the system call + // that we just handled, and restore "errno" to the value that it had + // before entering the signal handler. + SECCOMP_RESULT(ctx) = static_cast<greg_t>(rc); + errno = old_errno; + + return; +} + +bool Trap::TrapKey::operator<(const TrapKey& o) const { + if (fnc != o.fnc) { + return fnc < o.fnc; + } else if (aux != o.aux) { + return aux < o.aux; + } else { + return safe < o.safe; + } +} + +ErrorCode Trap::MakeTrap(TrapFnc fnc, const void *aux, bool safe) { + return GetInstance()->MakeTrapImpl(fnc, aux, safe); +} + +ErrorCode Trap::MakeTrapImpl(TrapFnc fnc, const void *aux, bool safe) { + if (!safe && !SandboxDebuggingAllowedByUser()) { + // Unless the user set the CHROME_SANDBOX_DEBUGGING environment variable, + // we never return an ErrorCode that is marked as "unsafe". This also + // means, the BPF compiler will never emit code that allow unsafe system + // calls to by-pass the filter (because they use the magic return address + // from SandboxSyscall(-1)). + + // This SANDBOX_DIE() can optionally be removed. It won't break security, + // but it might make error messages from the BPF compiler a little harder + // to understand. Removing the SANDBOX_DIE() allows callers to easyly check + // whether unsafe traps are supported (by checking whether the returned + // ErrorCode is ET_INVALID). + SANDBOX_DIE("Cannot use unsafe traps unless CHROME_SANDBOX_DEBUGGING " + "is enabled"); + + return ErrorCode(); + } + + // 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 = trap_ids_.find(key); + + // We return unique identifiers together with SECCOMP_RET_TRAP. This allows + // us to associate trap with the appropriate handler. The kernel allows us + // identifiers in the range from 0 to SECCOMP_RET_DATA (0xFFFF). We want to + // avoid 0, as it could be confused for a trap without any specific id. + // The nice thing about sequentially numbered identifiers is that we can also + // trivially look them up from our signal handler without making any system + // calls that might be async-signal-unsafe. + // In order to do so, we store all of our traps in a C-style trap_array_. + uint16_t id; + if (iter != trap_ids_.end()) { + // We have seen this pair before. Return the same id that we assigned + // earlier. + id = iter->second; + } else { + // This is a new pair. Remember it and assign a new id. + if (trap_array_size_ >= SECCOMP_RET_DATA /* 0xFFFF */ || + trap_array_size_ >= std::numeric_limits<typeof(id)>::max()) { + // In practice, this is pretty much impossible to trigger, as there + // are other kernel limitations that restrict overall BPF program sizes. + SANDBOX_DIE("Too many SECCOMP_RET_TRAP callback instances"); + } + id = trap_array_size_ + 1; + + // Our callers ensure that there are no other threads accessing trap_array_ + // concurrently (typically this is done by ensuring that we are single- + // threaded while the sandbox is being set up). But we nonetheless are + // modifying a life data structure that could be accessed any time a + // system call is made; as system calls could be triggering SIGSYS. + // So, we have to be extra careful that we update trap_array_ atomically. + // In particular, this means we shouldn't be using realloc() to resize it. + // Instead, we allocate a new array, copy the values, and then switch the + // pointer. We only really care about the pointer being updated atomically + // and the data that is pointed to being valid, as these are the only + // values accessed from the signal handler. It is OK if trap_array_size_ + // is inconsistent with the pointer, as it is monotonously increasing. + // Also, we only care about compiler barriers, as the signal handler is + // triggered synchronously from a system call. We don't have to protect + // against issues with the memory model or with completely asynchronous + // events. + if (trap_array_size_ >= trap_array_capacity_) { + trap_array_capacity_ += kCapacityIncrement; + ErrorCode *old_trap_array = trap_array_; + ErrorCode *new_trap_array = new ErrorCode[trap_array_capacity_]; + + // Language specs are unclear on whether the compiler is allowed to move + // the "delete[]" above our preceding assignments and/or memory moves, + // iff the compiler believes that "delete[]" doesn't have any other + // global side-effects. + // We insert optimization barriers to prevent this from happening. + // The first barrier is probably not needed, but better be explicit in + // what we want to tell the compiler. + // The clang developer mailing list couldn't answer whether this is a + // legitimate worry; but they at least thought that the barrier is + // sufficient to prevent the (so far hypothetical) problem of re-ordering + // of instructions by the compiler. + memcpy(new_trap_array, trap_array_, trap_array_size_*sizeof(ErrorCode)); + asm volatile("" : "=r"(new_trap_array) : "0"(new_trap_array) : "memory"); + trap_array_ = new_trap_array; + asm volatile("" : "=r"(trap_array_) : "0"(trap_array_) : "memory"); + + delete[] old_trap_array; + } + trap_ids_[key] = id; + trap_array_[trap_array_size_] = ErrorCode(fnc, aux, safe, id); + return trap_array_[trap_array_size_++]; + } + + return ErrorCode(fnc, aux, safe, id); +} + +bool Trap::SandboxDebuggingAllowedByUser() const { + const char *debug_flag = getenv(kSandboxDebuggingEnv); + return debug_flag && *debug_flag; +} + + +bool Trap::EnableUnsafeTrapsInSigSysHandler() { + Trap *trap = GetInstance(); + if (!trap->has_unsafe_traps_) { + // Unsafe traps are a one-way fuse. Once enabled, they can never be turned + // off again. + // We only allow enabling unsafe traps, if the user explicitly set an + // appropriate environment variable. This prevents bugs that accidentally + // disable all sandboxing for all users. + if (trap->SandboxDebuggingAllowedByUser()) { + // We only ever print this message once, when we enable unsafe traps the + // first time. + SANDBOX_INFO("WARNING! Disabling sandbox for debugging purposes"); + trap->has_unsafe_traps_ = true; + } else { + SANDBOX_INFO("Cannot disable sandbox and use unsafe traps unless " + "CHROME_SANDBOX_DEBUGGING is turned on first"); + } + } + // Returns the, possibly updated, value of has_unsafe_traps_. + return trap->has_unsafe_traps_; +} + +ErrorCode Trap::ErrorCodeFromTrapId(uint16_t id) { + if (global_trap_ && id > 0 && id <= global_trap_->trap_array_size_) { + return global_trap_->trap_array_[id - 1]; + } else { + return ErrorCode(); + } +} + +Trap *Trap::global_trap_; + +} // namespace playground2 diff --git a/sandbox/linux/seccomp-bpf/trap.h b/sandbox/linux/seccomp-bpf/trap.h new file mode 100644 index 0000000000..db29757c0d --- /dev/null +++ b/sandbox/linux/seccomp-bpf/trap.h @@ -0,0 +1,120 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ + +#include <signal.h> +#include <stdint.h> + +#include <map> +#include <vector> + +#include "sandbox/linux/seccomp-bpf/port.h" + + +namespace playground2 { + +class ErrorCode; + +// The Trap class allows a BPF filter program to branch out to user space by +// raising a SIGSYS signal. +// N.B.: This class does not perform any synchronization operations. If +// modifications are made to any of the traps, it is the caller's +// responsibility to ensure that this happens in a thread-safe fashion. +// Preferably, that means that no other threads should be running at that +// time. For the purposes of our sandbox, this assertion should always be +// true. Threads are incompatible with the seccomp sandbox anyway. +class Trap { + public: + // TrapFnc is a pointer to a function that handles Seccomp traps in + // user-space. The seccomp policy can request that a trap handler gets + // installed; it does so by returning a suitable ErrorCode() from the + // syscallEvaluator. See the ErrorCode() constructor for how to pass in + // the function pointer. + // Please note that TrapFnc is executed from signal context and must be + // async-signal safe: + // http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html + // Also note that it follows the calling convention of native system calls. + // In other words, it reports an error by returning an exit code in the + // range -1..-4096. It should not set errno when reporting errors; on the + // other hand, accidentally modifying errno is harmless and the changes will + // be undone afterwards. + typedef intptr_t (*TrapFnc)(const struct arch_seccomp_data& args, void *aux); + + // Registers a new trap handler and sets up the appropriate SIGSYS handler + // as needed. + // N.B.: This makes a permanent state change. Traps cannot be unregistered, + // as that would break existing BPF filters that are still active. + static ErrorCode MakeTrap(TrapFnc fnc, const void *aux, bool safe); + + // Enables support for unsafe traps in the SIGSYS signal handler. This is a + // one-way fuse. It works in conjunction with the BPF compiler emitting code + // that unconditionally allows system calls, if they have a magic return + // address (i.e. SandboxSyscall(-1)). + // Once unsafe traps are enabled, the sandbox is essentially compromised. + // But this is still a very useful feature for debugging purposes. Use with + // care. This feature is availably only if enabled by the user (see above). + // Returns "true", if unsafe traps were turned on. + static bool EnableUnsafeTrapsInSigSysHandler(); + + // Returns the ErrorCode associate with a particular trap id. + static ErrorCode ErrorCodeFromTrapId(uint16_t id); + + private: + // The destructor is unimplemented. Don't ever attempt to destruct this + // object. It'll break subsequent system calls that trigger a SIGSYS. + ~Trap(); + + struct TrapKey { + TrapKey(TrapFnc f, const void *a, bool s) + : fnc(f), + aux(a), + safe(s) { + } + TrapFnc fnc; + const void *aux; + bool safe; + bool operator<(const TrapKey&) const; + }; + typedef std::map<TrapKey, uint16_t> TrapIds; + + // We only have a very small number of methods. We opt to make them static + // and have them internally call GetInstance(). This is a little more + // convenient than having each caller obtain short-lived reference to the + // singleton. + // It also gracefully deals with methods that should check for the singleton, + // but avoid instantiating it, if it doesn't exist yet + // (e.g. ErrorCodeFromTrapId()). + static Trap *GetInstance(); + static void SigSysAction(int nr, siginfo_t *info, void *void_context); + + void SigSys(int nr, siginfo_t *info, void *void_context); + ErrorCode MakeTrapImpl(TrapFnc fnc, const void *aux, bool safe); + bool SandboxDebuggingAllowedByUser() const; + + + + // We have a global singleton that handles all of our SIGSYS traps. This + // variable must never be deallocated after it has been set up initially, as + // there is no way to reset in-kernel BPF filters that generate SIGSYS + // events. + static Trap *global_trap_; + + TrapIds trap_ids_; // Maps from TrapKeys to numeric ids + ErrorCode *trap_array_; // Array of ErrorCodes indexed by ids + size_t trap_array_size_; // Currently used size of array + size_t trap_array_capacity_; // Currently allocated capacity of array + bool has_unsafe_traps_; // Whether unsafe traps have been enabled + + // Our constructor is private. A shared global instance is created + // automatically as needed. + // Copying and assigning is unimplemented. It doesn't make sense for a + // singleton. + DISALLOW_IMPLICIT_CONSTRUCTORS(Trap); +}; + +} // namespace playground2 + +#endif // SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ diff --git a/sandbox/linux/seccomp-bpf/util.cc b/sandbox/linux/seccomp-bpf/util.cc deleted file mode 100644 index 904a169140..0000000000 --- a/sandbox/linux/seccomp-bpf/util.cc +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include <dirent.h> -#include <errno.h> -#include <fcntl.h> -#include <stdarg.h> -#include <stdlib.h> -#include <string.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" -#include "sandbox/linux/seccomp-bpf/util.h" - -namespace playground2 { - -bool Util::sendFds(int transport, const void *buf, size_t len, ...) { - int count = 0; - va_list ap; - va_start(ap, len); - while (va_arg(ap, int) >= 0) { - ++count; - } - va_end(ap); - if (!count) { - return false; - } - char cmsg_buf[CMSG_SPACE(count*sizeof(int))]; - memset(cmsg_buf, 0, sizeof(cmsg_buf)); - struct iovec iov[2] = { { 0 } }; - struct msghdr msg = { 0 }; - int dummy = 0; - iov[0].iov_base = &dummy; - iov[0].iov_len = sizeof(dummy); - if (buf && len > 0) { - iov[1].iov_base = const_cast<void *>(buf); - iov[1].iov_len = len; - } - msg.msg_iov = iov; - msg.msg_iovlen = (buf && len > 0) ? 2 : 1; - msg.msg_control = cmsg_buf; - msg.msg_controllen = CMSG_LEN(count*sizeof(int)); - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(count*sizeof(int)); - va_start(ap, len); - for (int i = 0, fd; (fd = va_arg(ap, int)) >= 0; ++i) { - (reinterpret_cast<int *>(CMSG_DATA(cmsg)))[i] = fd; - } - return sendmsg(transport, &msg, 0) == - static_cast<ssize_t>(sizeof(dummy) + ((buf && len > 0) ? len : 0)); -} - -bool Util::getFds(int transport, void *buf, size_t *len, ...) { - int count = 0; - va_list ap; - va_start(ap, len); - for (int *fd; (fd = va_arg(ap, int *)) != NULL; ++count) { - *fd = -1; - } - va_end(ap); - if (!count) { - return false; - } - char cmsg_buf[CMSG_SPACE(count*sizeof(int))]; - memset(cmsg_buf, 0, sizeof(cmsg_buf)); - struct iovec iov[2] = { { 0 } }; - struct msghdr msg = { 0 }; - int err; - iov[0].iov_base = &err; - iov[0].iov_len = sizeof(int); - if (buf && len && *len > 0) { - iov[1].iov_base = buf; - iov[1].iov_len = *len; - } - msg.msg_iov = iov; - msg.msg_iovlen = (buf && len && *len > 0) ? 2 : 1; - msg.msg_control = cmsg_buf; - msg.msg_controllen = CMSG_LEN(count*sizeof(int)); - ssize_t bytes = recvmsg(transport, &msg, 0); - if (len) { - *len = bytes > static_cast<int>(sizeof(int)) ? bytes - sizeof(int) : 0; - } - if (bytes != static_cast<ssize_t>(sizeof(int) + iov[1].iov_len)) { - if (bytes >= 0) { - errno = 0; - } - return false; - } - if (err) { - // "err" is the first four bytes of the payload. If these are non-zero, - // the sender on the other side of the socketpair sent us an errno value. - // We don't expect to get any file handles in this case. - errno = err; - return false; - } - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); - if ((msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) || - !cmsg || - cmsg->cmsg_level != SOL_SOCKET || - cmsg->cmsg_type != SCM_RIGHTS || - cmsg->cmsg_len != CMSG_LEN(count*sizeof(int))) { - errno = EBADF; - return false; - } - va_start(ap, len); - for (int *fd, i = 0; (fd = va_arg(ap, int *)) != NULL; ++i) { - *fd = (reinterpret_cast<int *>(CMSG_DATA(cmsg)))[i]; - } - va_end(ap); - return true; -} - -void Util::closeAllBut(int fd, ...) { - int proc_fd; - int fdir; - if ((proc_fd = Sandbox::proc_fd()) < 0 || - (fdir = openat(proc_fd, "self/fd", O_RDONLY|O_DIRECTORY)) < 0) { - SANDBOX_DIE("Cannot access \"/proc/self/fd\""); - } - int dev_null = open("/dev/null", O_RDWR); - DIR *dir = fdopendir(fdir); - struct dirent de, *res; - while (!readdir_r(dir, &de, &res) && res) { - if (res->d_name[0] < '0') { - continue; - } - int i = atoi(res->d_name); - if (i >= 0 && i != dirfd(dir) && i != dev_null) { - va_list ap; - va_start(ap, fd); - for (int f = fd;; f = va_arg(ap, int)) { - if (f < 0) { - if (i <= 2) { - // Never ever close 0..2. If we cannot redirect to /dev/null, - // then we are better off leaving the standard descriptors open. - if (dev_null >= 0) { - if (HANDLE_EINTR(dup2(dev_null, i))) { - SANDBOX_DIE("Cannot dup2()"); - } - } - } else { - if (HANDLE_EINTR(close(i))) { } - } - break; - } else if (i == f) { - break; - } - } - va_end(ap); - } - } - closedir(dir); - if (dev_null >= 0) { - if (HANDLE_EINTR(close(dev_null))) { } - } - return; -} - -} // namespace diff --git a/sandbox/linux/seccomp-bpf/util.h b/sandbox/linux/seccomp-bpf/util.h deleted file mode 100644 index 3e4d41b5b8..0000000000 --- a/sandbox/linux/seccomp-bpf/util.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef SANDBOX_LINUX_SECCOMP_BPF_UTIL_H__ -#define SANDBOX_LINUX_SECCOMP_BPF_UTIL_H__ - -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, ...); -}; - -} // namespace - -#endif // SANDBOX_LINUX_SECCOMP_BPF_UTIL_H__ diff --git a/sandbox/linux/seccomp-bpf/verifier.cc b/sandbox/linux/seccomp-bpf/verifier.cc index 40a1aa20be..60c0eab20d 100644 --- a/sandbox/linux/seccomp-bpf/verifier.cc +++ b/sandbox/linux/seccomp-bpf/verifier.cc @@ -2,90 +2,218 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <string.h> + #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" #include "sandbox/linux/seccomp-bpf/syscall_iterator.h" #include "sandbox/linux/seccomp-bpf/verifier.h" -namespace playground2 { +namespace { -bool Verifier::VerifyBPF(const std::vector<struct sock_filter>& program, - const Sandbox::Evaluators& evaluators, - const char **err) { - *err = NULL; - if (evaluators.size() != 1) { - *err = "Not implemented"; - return false; +using playground2::ErrorCode; +using playground2::Sandbox; +using playground2::Verifier; +using playground2::arch_seccomp_data; + +struct State { + State(const std::vector<struct sock_filter>& p, + const struct arch_seccomp_data& d) : + program(p), + data(d), + ip(0), + accumulator(0), + acc_is_valid(false) { } - Sandbox::EvaluateSyscall evaluate_syscall = evaluators.begin()->first; - void *aux = evaluators.begin()->second; - for (SyscallIterator iter(false); !iter.Done(); ) { - uint32_t sysnum = iter.Next(); - // We ideally want to iterate over the full system call range and values - // just above and just below this range. This gives us the full result set - // of the "evaluators". - // On Intel systems, this can fail in a surprising way, as a cleared bit 30 - // indicates either i386 or x86-64; and a set bit 30 indicates x32. And - // unless we pay attention to setting this bit correctly, an early check in - // our BPF program will make us fail with a misleading error code. - struct arch_seccomp_data data = { static_cast<int>(sysnum), - static_cast<uint32_t>(SECCOMP_ARCH) }; -#if defined(__i386__) || defined(__x86_64__) -#if defined(__x86_64__) && defined(__ILP32__) - if (!(sysnum & 0x40000000u)) { - continue; + const std::vector<struct sock_filter>& program; + const struct arch_seccomp_data& data; + unsigned int ip; + uint32_t accumulator; + bool acc_is_valid; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(State); +}; + +uint32_t EvaluateErrorCode(Sandbox *sandbox, const ErrorCode& code, + const struct arch_seccomp_data& data) { + if (code.error_type() == ErrorCode::ET_SIMPLE || + code.error_type() == ErrorCode::ET_TRAP) { + return code.err(); + } else if (code.error_type() == ErrorCode::ET_COND) { + if (code.width() == ErrorCode::TP_32BIT && + (data.args[code.argno()] >> 32) && + (data.args[code.argno()] & 0xFFFFFFFF80000000ull) != + 0xFFFFFFFF80000000ull) { + return sandbox->Unexpected64bitArgument().err(); } -#else - if (sysnum & 0x40000000u) { - continue; + switch (code.op()) { + case ErrorCode::OP_EQUAL: + return EvaluateErrorCode(sandbox, + (code.width() == ErrorCode::TP_32BIT + ? uint32_t(data.args[code.argno()]) + : data.args[code.argno()]) == code.value() + ? *code.passed() + : *code.failed(), + data); + case ErrorCode::OP_HAS_ALL_BITS: + return EvaluateErrorCode(sandbox, + ((code.width() == ErrorCode::TP_32BIT + ? uint32_t(data.args[code.argno()]) + : data.args[code.argno()]) & code.value()) + == code.value() + ? *code.passed() + : *code.failed(), + data); + case ErrorCode::OP_HAS_ANY_BITS: + return EvaluateErrorCode(sandbox, + (code.width() == ErrorCode::TP_32BIT + ? uint32_t(data.args[code.argno()]) + : data.args[code.argno()]) & code.value() + ? *code.passed() + : *code.failed(), + data); + default: + return SECCOMP_RET_INVALID; } -#endif -#endif - ErrorCode code = evaluate_syscall(sysnum, aux); - uint32_t computed_ret = EvaluateBPF(program, data, err); + } else { + return SECCOMP_RET_INVALID; + } +} + +bool VerifyErrorCode(Sandbox *sandbox, + const std::vector<struct sock_filter>& program, + struct arch_seccomp_data *data, + const ErrorCode& root_code, + const ErrorCode& code, + const char **err) { + if (code.error_type() == ErrorCode::ET_SIMPLE || + code.error_type() == ErrorCode::ET_TRAP) { + uint32_t computed_ret = Verifier::EvaluateBPF(program, *data, err); if (*err) { return false; - } else if (computed_ret != code.err()) { + } else if (computed_ret != EvaluateErrorCode(sandbox, root_code, *data)) { + // For efficiency's sake, we'd much rather compare "computed_ret" + // against "code.err()". This works most of the time, but it doesn't + // always work for nested conditional expressions. The test values + // that we generate on the fly to probe expressions can trigger + // code flow decisions in multiple nodes of the decision tree, and the + // only way to compute the correct error code in that situation is by + // calling EvaluateErrorCode(). *err = "Exit code from BPF program doesn't match"; return false; } - } - return true; -} - -uint32_t Verifier::EvaluateBPF(const std::vector<struct sock_filter>& program, - const struct arch_seccomp_data& data, - const char **err) { - *err = NULL; - if (program.size() < 1 || program.size() >= SECCOMP_MAX_PROGRAM_SIZE) { - *err = "Invalid program length"; - return 0; - } - for (State state(program, data); !*err; ++state.ip) { - if (state.ip >= program.size()) { - *err = "Invalid instruction pointer in BPF program"; - break; + } else if (code.error_type() == ErrorCode::ET_COND) { + if (code.argno() < 0 || code.argno() >= 6) { + *err = "Invalid argument number in error code"; + return false; } - const struct sock_filter& insn = program[state.ip]; - switch (BPF_CLASS(insn.code)) { - case BPF_LD: - Ld(&state, insn, err); - break; - case BPF_JMP: - Jmp(&state, insn, err); + 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(sandbox, program, data, root_code, + *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(sandbox, program, data, root_code, + *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(sandbox, program, data, root_code, + 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(sandbox, program, data, root_code, + *code.failed(), err)) { + return false; + } + } break; - case BPF_RET: - return Ret(&state, insn, err); - default: - *err = "Unexpected instruction in BPF program"; + case ErrorCode::OP_HAS_ALL_BITS: + case ErrorCode::OP_HAS_ANY_BITS: + // A comprehensive test of bit values is difficult and potentially rather + // time-expensive. We avoid doing so at run-time and instead rely on the + // unittest for full testing. The test that we have here covers just the + // common cases. We test against the bitmask itself, all zeros and all + // ones. + { + // Testing "any" bits against a zero mask is always false. So, there + // are some cases, where we expect tests to take the "failed()" branch + // even though this is a test that normally should take "passed()". + const ErrorCode& passed = + (!code.value() && code.op() == ErrorCode::OP_HAS_ANY_BITS) || + + // On a 32bit system, it is impossible to pass a 64bit value as a + // system call argument. So, some additional tests always evaluate + // as false. + ((code.value() & ~uint64_t(uintptr_t(-1))) && + code.op() == ErrorCode::OP_HAS_ALL_BITS) || + (code.value() && !(code.value() & uintptr_t(-1)) && + code.op() == ErrorCode::OP_HAS_ANY_BITS) + + ? *code.failed() : *code.passed(); + + // Similary, testing for "all" bits in a zero mask is always true. So, + // some cases pass despite them normally failing. + const ErrorCode& failed = + !code.value() && code.op() == ErrorCode::OP_HAS_ALL_BITS + ? *code.passed() : *code.failed(); + + data->args[code.argno()] = code.value() & uintptr_t(-1); + if (!VerifyErrorCode(sandbox, program, data, root_code, passed, err)) { + return false; + } + data->args[code.argno()] = uintptr_t(-1); + if (!VerifyErrorCode(sandbox, program, data, root_code, passed, err)) { + return false; + } + data->args[code.argno()] = 0; + if (!VerifyErrorCode(sandbox, program, data, root_code, failed, err)) { + return false; + } + } break; + default: // TODO(markus): Need to add support for OP_GREATER + *err = "Unsupported operation in conditional error code"; + return false; } + } else { + *err = "Attempting to return invalid error code from BPF program"; + return false; } - return 0; + return true; } -void Verifier::Ld(State *state, const struct sock_filter& insn, - const char **err) { +void Ld(State *state, const struct sock_filter& insn, const char **err) { if (BPF_SIZE(insn.code) != BPF_W || BPF_MODE(insn.code) != BPF_ABS) { *err = "Invalid BPF_LD instruction"; @@ -104,8 +232,7 @@ void Verifier::Ld(State *state, const struct sock_filter& insn, return; } -void Verifier::Jmp(State *state, const struct sock_filter& insn, - const char **err) { +void Jmp(State *state, const struct sock_filter& insn, const char **err) { if (BPF_OP(insn.code) == BPF_JA) { if (state->ip + insn.k + 1 >= state->program.size() || state->ip + insn.k + 1 <= state->ip) { @@ -156,8 +283,7 @@ void Verifier::Jmp(State *state, const struct sock_filter& insn, } } -uint32_t Verifier::Ret(State *, const struct sock_filter& insn, - const char **err) { +uint32_t Ret(State *, const struct sock_filter& insn, const char **err) { if (BPF_SRC(insn.code) != BPF_K) { *err = "Invalid BPF_RET instruction"; return 0; @@ -165,4 +291,159 @@ uint32_t Verifier::Ret(State *, const struct sock_filter& insn, return insn.k; } +void Alu(State *state, const struct sock_filter& insn, const char **err) { + if (BPF_OP(insn.code) == BPF_NEG) { + state->accumulator = -state->accumulator; + return; + } else { + if (BPF_SRC(insn.code) != BPF_K) { + *err = "Unexpected source operand in arithmetic operation"; + return; + } + switch (BPF_OP(insn.code)) { + case BPF_ADD: + state->accumulator += insn.k; + break; + case BPF_SUB: + state->accumulator -= insn.k; + break; + case BPF_MUL: + state->accumulator *= insn.k; + break; + case BPF_DIV: + if (!insn.k) { + *err = "Illegal division by zero"; + break; + } + state->accumulator /= insn.k; + break; + case BPF_MOD: + if (!insn.k) { + *err = "Illegal division by zero"; + break; + } + state->accumulator %= insn.k; + break; + case BPF_OR: + state->accumulator |= insn.k; + break; + case BPF_XOR: + state->accumulator ^= insn.k; + break; + case BPF_AND: + state->accumulator &= insn.k; + break; + case BPF_LSH: + if (insn.k > 32) { + *err = "Illegal shift operation"; + break; + } + state->accumulator <<= insn.k; + break; + case BPF_RSH: + if (insn.k > 32) { + *err = "Illegal shift operation"; + break; + } + state->accumulator >>= insn.k; + break; + default: + *err = "Invalid operator in arithmetic operation"; + break; + } + } +} + +} // namespace + +namespace playground2 { + +bool Verifier::VerifyBPF(Sandbox *sandbox, + const std::vector<struct sock_filter>& program, + const Sandbox::Evaluators& evaluators, + const char **err) { + *err = NULL; + if (evaluators.size() != 1) { + *err = "Not implemented"; + return false; + } + Sandbox::EvaluateSyscall evaluate_syscall = evaluators.begin()->first; + void *aux = evaluators.begin()->second; + for (SyscallIterator iter(false); !iter.Done(); ) { + uint32_t sysnum = iter.Next(); + // We ideally want to iterate over the full system call range and values + // just above and just below this range. This gives us the full result set + // of the "evaluators". + // On Intel systems, this can fail in a surprising way, as a cleared bit 30 + // indicates either i386 or x86-64; and a set bit 30 indicates x32. And + // unless we pay attention to setting this bit correctly, an early check in + // our BPF program will make us fail with a misleading error code. + struct arch_seccomp_data data = { static_cast<int>(sysnum), + static_cast<uint32_t>(SECCOMP_ARCH) }; +#if defined(__i386__) || defined(__x86_64__) +#if defined(__x86_64__) && defined(__ILP32__) + if (!(sysnum & 0x40000000u)) { + continue; + } +#else + if (sysnum & 0x40000000u) { + continue; + } +#endif +#endif + ErrorCode code = evaluate_syscall(sandbox, sysnum, aux); + if (!VerifyErrorCode(sandbox, program, &data, code, code, err)) { + return false; + } + } + return true; +} + +uint32_t Verifier::EvaluateBPF(const std::vector<struct sock_filter>& program, + const struct arch_seccomp_data& data, + const char **err) { + *err = NULL; + if (program.size() < 1 || program.size() >= SECCOMP_MAX_PROGRAM_SIZE) { + *err = "Invalid program length"; + return 0; + } + for (State state(program, data); !*err; ++state.ip) { + if (state.ip >= program.size()) { + *err = "Invalid instruction pointer in BPF program"; + break; + } + const struct sock_filter& insn = program[state.ip]; + switch (BPF_CLASS(insn.code)) { + case BPF_LD: + Ld(&state, insn, err); + break; + case BPF_JMP: + Jmp(&state, insn, err); + break; + 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; } + case BPF_ALU: + Alu(&state, insn, err); + break; + default: + *err = "Unexpected instruction in BPF program"; + break; + } + } + return 0; +} + } // namespace diff --git a/sandbox/linux/seccomp-bpf/verifier.h b/sandbox/linux/seccomp-bpf/verifier.h index 505015e39c..3e99a0818b 100644 --- a/sandbox/linux/seccomp-bpf/verifier.h +++ b/sandbox/linux/seccomp-bpf/verifier.h @@ -10,8 +10,6 @@ #include <utility> #include <vector> -#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" - namespace playground2 { @@ -24,7 +22,8 @@ class Verifier { // set by the "evaluators". // Upon success, "err" is set to NULL. Upon failure, it contains a static // error message that does not need to be free()'d. - static bool VerifyBPF(const std::vector<struct sock_filter>& program, + static bool VerifyBPF(Sandbox *sandbox, + const std::vector<struct sock_filter>& program, const Sandbox::Evaluators& evaluators, const char **err); @@ -41,32 +40,6 @@ class Verifier { const char **err); private: - struct State { - State(const std::vector<struct sock_filter>& p, - const struct arch_seccomp_data& d) : - program(p), - data(d), - ip(0), - accumulator(0), - acc_is_valid(false) { - } - const std::vector<struct sock_filter>& program; - const struct arch_seccomp_data& data; - unsigned int ip; - uint32_t accumulator; - bool acc_is_valid; - - private: - DISALLOW_IMPLICIT_CONSTRUCTORS(State); - }; - - static void Ld (State *state, const struct sock_filter& insn, - const char **err); - static void Jmp(State *state, const struct sock_filter& insn, - const char **err); - static uint32_t Ret(State *state, const struct sock_filter& insn, - const char **err); - DISALLOW_IMPLICIT_CONSTRUCTORS(Verifier); }; diff --git a/sandbox/linux/services/android_arm_ucontext.h b/sandbox/linux/services/android_arm_ucontext.h new file mode 100644 index 0000000000..d1446c67bd --- /dev/null +++ b/sandbox/linux/services/android_arm_ucontext.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_ANDROID_ARM_UCONTEXT_H_ +#define SANDBOX_LINUX_SERVICES_ANDROID_ARM_UCONTEXT_H_ + +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +#include <asm/sigcontext.h> + +// We also need greg_t for the sandbox, include it in this header as well. +typedef unsigned long greg_t; + +//typedef unsigned long sigset_t; +typedef struct ucontext { + unsigned long uc_flags; + struct ucontext *uc_link; + stack_t uc_stack; + struct sigcontext uc_mcontext; + sigset_t uc_sigmask; + /* Allow for uc_sigmask growth. Glibc uses a 1024-bit sigset_t. */ + int __not_used[32 - (sizeof (sigset_t) / sizeof (int))]; + /* Last for extensibility. Eight byte aligned because some + coprocessors require eight byte alignment. */ + unsigned long uc_regspace[128] __attribute__((__aligned__(8))); +} ucontext_t; + +#else +#include <sys/ucontext.h> +#endif // __BIONIC_HAVE_UCONTEXT_T + +#endif // SANDBOX_LINUX_SERVICES_ANDROID_ARM_UCONTEXT_H_ diff --git a/sandbox/linux/services/android_i386_ucontext.h b/sandbox/linux/services/android_i386_ucontext.h new file mode 100644 index 0000000000..580ac70a06 --- /dev/null +++ b/sandbox/linux/services/android_i386_ucontext.h @@ -0,0 +1,79 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_ANDROID_I386_UCONTEXT_H_ +#define SANDBOX_LINUX_SERVICES_ANDROID_I386_UCONTEXT_H_ + +// We do something compatible with glibc. Hopefully, at some point Android will +// provide that for us, and __BIONIC_HAVE_UCONTEXT_T should be defined. +// This is mostly copied from breakpad (common/android/include/sys/ucontext.h), +// except we do use sigset_t for uc_sigmask instead of a custom type. + +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +#include <asm/sigcontext.h> + +/* 80-bit floating-point register */ +struct _libc_fpreg { + unsigned short significand[4]; + unsigned short exponent; +}; + +/* Simple floating-point state, see FNSTENV instruction */ +struct _libc_fpstate { + unsigned long cw; + unsigned long sw; + unsigned long tag; + unsigned long ipoff; + unsigned long cssel; + unsigned long dataoff; + unsigned long datasel; + struct _libc_fpreg _st[8]; + unsigned long status; +}; + +typedef uint32_t greg_t; + +typedef struct { + uint32_t gregs[19]; + struct _libc_fpstate* fpregs; + uint32_t oldmask; + uint32_t cr2; +} mcontext_t; + +enum { + REG_GS = 0, + REG_FS, + REG_ES, + REG_DS, + REG_EDI, + REG_ESI, + REG_EBP, + REG_ESP, + REG_EBX, + REG_EDX, + REG_ECX, + REG_EAX, + REG_TRAPNO, + REG_ERR, + REG_EIP, + REG_CS, + REG_EFL, + REG_UESP, + REG_SS, +}; + +typedef struct ucontext { + uint32_t uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + mcontext_t uc_mcontext; + sigset_t uc_sigmask; + struct _libc_fpstate __fpregs_mem; +} ucontext_t; + +#else +#include <sys/ucontext.h> +#endif // __BIONIC_HAVE_UCONTEXT_T + +#endif // SANDBOX_LINUX_SERVICES_ANDROID_I386_UCONTEXT_H_ diff --git a/sandbox/linux/services/android_ucontext.h b/sandbox/linux/services/android_ucontext.h new file mode 100644 index 0000000000..437bbab7ba --- /dev/null +++ b/sandbox/linux/services/android_ucontext.h @@ -0,0 +1,22 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_ANDROID_UCONTEXT_H_ +#define SANDBOX_LINUX_SERVICES_ANDROID_UCONTEXT_H_ + +#if defined(__ANDROID__) + +#if defined(__arm__) +#include "sandbox/linux/services/android_arm_ucontext.h" +#elif defined(__i386__) +#include "sandbox/linux/services/android_i386_ucontext.h" +#else +#error "No support for your architecture in Android header" +#endif + +#else // __ANDROID__ +#error "Android header file included on non Android." +#endif // __ANDROID__ + +#endif // SANDBOX_LINUX_SERVICES_ANDROID_UCONTEXT_H_ diff --git a/sandbox/linux/services/broker_process.cc b/sandbox/linux/services/broker_process.cc new file mode 100644 index 0000000000..ea71b8fd4d --- /dev/null +++ b/sandbox/linux/services/broker_process.cc @@ -0,0 +1,358 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/broker_process.h" + +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket_linux.h" + +namespace { + +static const int kCommandOpen = 'O'; +static const size_t kMaxMessageLength = 4096; + +// Some flags will need special treatment on the client side and are not +// supported for now. +int ForCurrentProcessFlagsMask() { + return O_CLOEXEC | O_NONBLOCK; +} + +// Check whether |requested_filename| is in |allowed_file_names|. +// See GetFileNameIfAllowedAccess() for an explaination of |file_to_open|. +// async signal safe if |file_to_open| is NULL. +// TODO(jln): assert signal safety. +bool GetFileNameInWhitelist(const std::vector<std::string>& allowed_file_names, + const std::string& requested_filename, + const char** file_to_open) { + if (file_to_open && *file_to_open) { + // Make sure that callers never pass a non-empty string. In case callers + // wrongly forget to check the return value and look at the string + // instead, this could catch bugs. + RAW_LOG(FATAL, "*file_to_open should be NULL"); + return false; + } + std::vector<std::string>::const_iterator it; + it = std::find(allowed_file_names.begin(), allowed_file_names.end(), + requested_filename); + if (it < allowed_file_names.end()) { // requested_filename was found? + if (file_to_open) + *file_to_open = it->c_str(); + return true; + } + return false; +} + +// We maintain a list of flags that have been reviewed for "sanity" and that +// we're ok to allow in the broker. +// I.e. here is where we wouldn't add O_RESET_FILE_SYSTEM. +bool IsAllowedOpenFlags(int flags) { + // First, check the access mode + const int access_mode = flags & O_ACCMODE; + if (access_mode != O_RDONLY && access_mode != O_WRONLY && + access_mode != O_RDWR) { + return false; + } + + // We only support a 2-parameters open, so we forbid O_CREAT. + if (flags & O_CREAT) { + return false; + } + + // Some flags affect the behavior of the current process. We don't support + // them and don't allow them for now. + if (flags & ForCurrentProcessFlagsMask()) { + return false; + } + + // Now check that all the flags are known to us. + const int creation_and_status_flags = flags & ~O_ACCMODE; + + const int known_flags = + O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT | + O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME | O_NOCTTY | + O_NOFOLLOW | O_NONBLOCK | O_NDELAY | O_SYNC | O_TRUNC; + + const int unknown_flags = ~known_flags; + const bool has_unknown_flags = creation_and_status_flags & unknown_flags; + return !has_unknown_flags; +} + +} // namespace + +namespace sandbox { + +BrokerProcess::BrokerProcess(const std::vector<std::string>& allowed_r_files, + const std::vector<std::string>& allowed_w_files, + bool fast_check_in_client, + bool quiet_failures_for_tests) + : initialized_(false), + is_child_(false), + fast_check_in_client_(fast_check_in_client), + quiet_failures_for_tests_(quiet_failures_for_tests), + broker_pid_(-1), + allowed_r_files_(allowed_r_files), + allowed_w_files_(allowed_w_files), + ipc_socketpair_(-1) { +} + +BrokerProcess::~BrokerProcess() { + if (initialized_ && ipc_socketpair_ != -1) { + void (HANDLE_EINTR(close(ipc_socketpair_))); + } +} + +bool BrokerProcess::Init(bool (*sandbox_callback)(void)) { + CHECK(!initialized_); + int socket_pair[2]; + // Use SOCK_SEQPACKET, because we need to preserve message boundaries + // but we also want to be notified (recvmsg should return and not block) + // when the connection has been broken (one of the processes died). + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socket_pair)) { + LOG(ERROR) << "Failed to create socketpair"; + return false; + } + + int child_pid = fork(); + if (child_pid == -1) { + (void) HANDLE_EINTR(close(socket_pair[0])); + (void) HANDLE_EINTR(close(socket_pair[1])); + return false; + } + if (child_pid) { + // We are the parent and we have just forked our broker process. + (void) HANDLE_EINTR(close(socket_pair[0])); + // We should only be able to write to the IPC channel. We'll always send + // a new file descriptor to receive the reply on. + shutdown(socket_pair[1], SHUT_RD); + ipc_socketpair_ = socket_pair[1]; + is_child_ = false; + broker_pid_ = child_pid; + initialized_ = true; + return true; + } else { + // We are the broker. + (void) HANDLE_EINTR(close(socket_pair[1])); + // We should only be able to read from this IPC channel. We will send our + // replies on a new file descriptor attached to the requests. + shutdown(socket_pair[0], SHUT_WR); + ipc_socketpair_ = socket_pair[0]; + is_child_ = true; + // Enable the sandbox if provided. + if (sandbox_callback) { + CHECK(sandbox_callback()); + } + initialized_ = true; + for (;;) { + HandleRequest(); + } + _exit(1); + } + NOTREACHED(); +} + +// This function needs to be async signal safe. +int BrokerProcess::Open(const char* pathname, int flags) const { + RAW_CHECK(initialized_); // async signal safe CHECK(). + if (!pathname) + return -EFAULT; + // There is no point in forwarding a request that we know will be denied. + // Of course, the real security check needs to be on the other side of the + // IPC. + if (fast_check_in_client_) { + if (!GetFileNameIfAllowedAccess(pathname, flags, NULL)) + return -EPERM; + } + + Pickle write_pickle; + write_pickle.WriteInt(kCommandOpen); + write_pickle.WriteString(pathname); + write_pickle.WriteInt(flags); + RAW_CHECK(write_pickle.size() <= kMaxMessageLength); + + int returned_fd = -1; + uint8_t reply_buf[kMaxMessageLength]; + // Send a request (in write_pickle) as well that will include a new + // temporary socketpair (created internally by SendRecvMsg()). + // Then read the reply on this new socketpair in reply_buf and put an + // eventual attached file descriptor in |returned_fd|. + // TODO(jln): this API needs some rewriting and documentation. + ssize_t msg_len = UnixDomainSocket::SendRecvMsg(ipc_socketpair_, + reply_buf, + sizeof(reply_buf), + &returned_fd, + write_pickle); + if (msg_len <= 0) { + if (!quiet_failures_for_tests_) + RAW_LOG(ERROR, "Could not make request to broker process"); + return -ENOMEM; + } + + Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); + PickleIterator iter(read_pickle); + int return_value = -1; + // Now deserialize the return value and eventually return the file + // descriptor. + if (read_pickle.ReadInt(&iter, &return_value)) { + if (return_value < 0) { + RAW_CHECK(returned_fd == -1); + return return_value; + } else { + // We have a real file descriptor to return. + RAW_CHECK(returned_fd >= 0); + return returned_fd; + } + } else { + RAW_LOG(ERROR, "Could not read pickle"); + return -1; + } +} + +// Handle a request on the IPC channel ipc_socketpair_. +// A request should have a file descriptor attached on which we will reply and +// that we will then close. +// A request should start with an int that will be used as the command type. +bool BrokerProcess::HandleRequest() const { + + std::vector<int> fds; + char buf[kMaxMessageLength]; + errno = 0; + const ssize_t msg_len = UnixDomainSocket::RecvMsg(ipc_socketpair_, buf, + sizeof(buf), &fds); + + if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) { + // EOF from our parent, or our parent died, we should die. + _exit(0); + } + + // The parent should send exactly one file descriptor, on which we + // will write the reply. + if (msg_len < 0 || fds.size() != 1 || fds.at(0) < 0) { + PLOG(ERROR) << "Error reading message from the client"; + return false; + } + + const int temporary_ipc = fds.at(0); + + Pickle pickle(buf, msg_len); + PickleIterator iter(pickle); + int command_type; + if (pickle.ReadInt(&iter, &command_type)) { + bool r = false; + // Go through all the possible IPC messages. + switch (command_type) { + case kCommandOpen: + // We reply on the file descriptor sent to us via the IPC channel. + r = HandleOpenRequest(temporary_ipc, pickle, iter); + (void) HANDLE_EINTR(close(temporary_ipc)); + return r; + default: + NOTREACHED(); + return false; + } + } + + LOG(ERROR) << "Error parsing IPC request"; + return false; +} + +// Handle an open request contained in |read_pickle| and send the reply +// on |reply_ipc|. +bool BrokerProcess::HandleOpenRequest(int reply_ipc, + const Pickle& read_pickle, + PickleIterator iter) const { + std::string requested_filename; + int flags = 0; + if (!read_pickle.ReadString(&iter, &requested_filename) || + !read_pickle.ReadInt(&iter, &flags)) { + return -1; + } + + Pickle write_pickle; + std::vector<int> opened_files; + + const char* file_to_open = NULL; + const bool safe_to_open_file = GetFileNameIfAllowedAccess( + requested_filename.c_str(), flags, &file_to_open); + + if (safe_to_open_file) { + CHECK(file_to_open); + // O_CLOEXEC doesn't hurt (even though we won't execve()), and this + // property won't be passed to the client. + // We may want to think about O_NONBLOCK as well. + // We're doing a 2-parameter open, so we don't support O_CREAT. It doesn't + // hurt to always pass a third argument though. + int opened_fd = open(file_to_open, flags | O_CLOEXEC, 0); + if (opened_fd < 0) { + write_pickle.WriteInt(-errno); + } else { + // Success. + opened_files.push_back(opened_fd); + write_pickle.WriteInt(0); + } + } else { + write_pickle.WriteInt(-EPERM); + } + + CHECK_LE(write_pickle.size(), kMaxMessageLength); + ssize_t sent = UnixDomainSocket::SendMsg(reply_ipc, write_pickle.data(), + write_pickle.size(), opened_files); + + // Close anything we have opened in this process. + for (std::vector<int>::iterator it = opened_files.begin(); + it < opened_files.end(); ++it) { + (void) HANDLE_EINTR(close(*it)); + } + + if (sent <= 0) { + LOG(ERROR) << "Could not send IPC reply"; + return false; + } + return true; +} + +// For paranoia, if |file_to_open| is not NULL, we will return the matching +// string from the white list. +// Async signal safe only if |file_to_open| is NULL. +// Even if an attacker managed to fool the string comparison mechanism, we +// would not open an attacker-controlled file name. +// Return true if access should be allowed, false otherwise. +bool BrokerProcess::GetFileNameIfAllowedAccess(const char* requested_filename, + int requested_flags, const char** file_to_open) const { + if (!IsAllowedOpenFlags(requested_flags)) { + return false; + } + switch (requested_flags & O_ACCMODE) { + case O_RDONLY: + return GetFileNameInWhitelist(allowed_r_files_, requested_filename, + file_to_open); + case O_WRONLY: + return GetFileNameInWhitelist(allowed_w_files_, requested_filename, + file_to_open); + case O_RDWR: + { + bool allowed_for_read_and_write = + GetFileNameInWhitelist(allowed_r_files_, requested_filename, NULL) && + GetFileNameInWhitelist(allowed_w_files_, requested_filename, + file_to_open); + return allowed_for_read_and_write; + } + default: + return false; + } +} + +} // namespace sandbox. diff --git a/sandbox/linux/services/broker_process.h b/sandbox/linux/services/broker_process.h new file mode 100644 index 0000000000..d04f703191 --- /dev/null +++ b/sandbox/linux/services/broker_process.h @@ -0,0 +1,72 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ +#define SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/pickle.h" +#include "base/process.h" + +namespace sandbox { + +// Create a new "broker" process to which we can send requests via an IPC +// channel. +// This is a low level IPC mechanism that is suitable to be called from a +// signal handler. +// A process would typically create a broker process before entering +// sandboxing. +// 1. BrokerProcess open_broker(read_whitelist, write_whitelist); +// 2. CHECK(open_broker.Init(NULL)); +// 3. Enable sandbox. +// 4. Use open_broker.Open() to open files. +class BrokerProcess { + public: + // |allowed_file_names| is a white list of files that can be opened later via + // the Open() API. + // |fast_check_in_client| and |quiet_failures_for_tests| are reserved for + // unit tests, don't use it. + explicit BrokerProcess(const std::vector<std::string>& allowed_r_files_, + const std::vector<std::string>& allowed_w_files_, + bool fast_check_in_client = true, + bool quiet_failures_for_tests = false); + ~BrokerProcess(); + // Will initialize the broker process. There should be no threads at this + // point, since we need to fork(). + // sandbox_callback is a function that should be called to enable the + // sandbox in the broker. + bool Init(bool (*sandbox_callback)(void)); + + // Can be used in place of open(). Will be async signal safe. + // The implementation only supports certain white listed flags and will + // return -EPERM on other flags. + // It's similar to the open() system call and will return -errno on errors. + int Open(const char* pathname, int flags) const; + + int broker_pid() const { return broker_pid_; } + + private: + bool HandleRequest() const; + bool HandleOpenRequest(int reply_ipc, const Pickle& read_pickle, + PickleIterator iter) const; + bool GetFileNameIfAllowedAccess(const char*, int, const char**) const; + bool initialized_; // Whether we've been through Init() yet. + bool is_child_; // Whether we're the child (broker process). + bool fast_check_in_client_; // Whether to forward a request that we know + // will be denied to the broker. + bool quiet_failures_for_tests_; // Disable certain error message when + // testing for failures. + pid_t broker_pid_; // The PID of the broker (child). + const std::vector<std::string> allowed_r_files_; // Files allowed for read. + const std::vector<std::string> allowed_w_files_; // Files allowed for write. + int ipc_socketpair_; // Our communication channel to parent or child. + DISALLOW_IMPLICIT_CONSTRUCTORS(BrokerProcess); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ diff --git a/sandbox/linux/services/broker_process_unittest.cc b/sandbox/linux/services/broker_process_unittest.cc new file mode 100644 index 0000000000..ab570351aa --- /dev/null +++ b/sandbox/linux/services/broker_process_unittest.cc @@ -0,0 +1,311 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/broker_process.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <string> +#include <vector> + +#if defined(OS_ANDROID) +#include "base/android/path_utils.h" +#endif +#include "base/files/file_path.h" +#include "base/logging.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +#if defined(OS_ANDROID) + #define DISABLE_ON_ANDROID(function) DISABLED_##function +#else + #define DISABLE_ON_ANDROID(function) function +#endif + +TEST(BrokerProcess, CreateAndDestroy) { + std::vector<std::string> read_whitelist; + read_whitelist.push_back("/proc/cpuinfo"); + + BrokerProcess* open_broker = new BrokerProcess(read_whitelist, + std::vector<std::string>()); + ASSERT_TRUE(open_broker->Init(NULL)); + pid_t broker_pid = open_broker->broker_pid(); + delete(open_broker); + + // Now we check that the broker has exited properly. + int status = 0; + ASSERT_EQ(waitpid(broker_pid, &status, 0), broker_pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), 0); +} + +TEST(BrokerProcess, TestOpenNull) { + const std::vector<std::string> empty; + BrokerProcess open_broker(empty, empty); + ASSERT_TRUE(open_broker.Init(NULL)); + + int fd = open_broker.Open(NULL, O_RDONLY); + ASSERT_EQ(fd, -EFAULT); +} + +void TestOpenFilePerms(bool fast_check_in_client) { + const char kR_WhiteListed[] = "/proc/DOESNOTEXIST1"; + const char kW_WhiteListed[] = "/proc/DOESNOTEXIST2"; + const char kRW_WhiteListed[] = "/proc/DOESNOTEXIST3"; + const char k_NotWhitelisted[] = "/proc/DOESNOTEXIST4"; + + std::vector<std::string> read_whitelist; + read_whitelist.push_back(kR_WhiteListed); + read_whitelist.push_back(kRW_WhiteListed); + + std::vector<std::string> write_whitelist; + write_whitelist.push_back(kW_WhiteListed); + write_whitelist.push_back(kRW_WhiteListed); + + BrokerProcess open_broker(read_whitelist, + write_whitelist, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(NULL)); + + int fd = -1; + fd = open_broker.Open(kR_WhiteListed, O_RDONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kR_WhiteListed, O_WRONLY); + ASSERT_EQ(fd, -EPERM); + fd = open_broker.Open(kR_WhiteListed, O_RDWR); + ASSERT_EQ(fd, -EPERM); + + fd = open_broker.Open(kW_WhiteListed, O_RDONLY); + ASSERT_EQ(fd, -EPERM); + fd = open_broker.Open(kW_WhiteListed, O_WRONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kW_WhiteListed, O_RDWR); + ASSERT_EQ(fd, -EPERM); + + fd = open_broker.Open(kRW_WhiteListed, O_RDONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kRW_WhiteListed, O_WRONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kRW_WhiteListed, O_RDWR); + ASSERT_EQ(fd, -ENOENT); + + fd = open_broker.Open(k_NotWhitelisted, O_RDONLY); + ASSERT_EQ(fd, -EPERM); + fd = open_broker.Open(k_NotWhitelisted, O_WRONLY); + ASSERT_EQ(fd, -EPERM); + fd = open_broker.Open(k_NotWhitelisted, O_RDWR); + ASSERT_EQ(fd, -EPERM); + + // We have some extra sanity check for clearly wrong values. + fd = open_broker.Open(kRW_WhiteListed, O_RDONLY|O_WRONLY|O_RDWR); + ASSERT_EQ(fd, -EPERM); + + // It makes no sense to allow O_CREAT in a 2-parameters open. Ensure this + // is denied. + fd = open_broker.Open(kRW_WhiteListed, O_RDWR|O_CREAT); + ASSERT_EQ(fd, -EPERM); +} + +// Run the same thing twice. The second time, we make sure that no security +// check is performed on the client. +TEST(BrokerProcess, OpenFilePermsWithClientCheck) { + TestOpenFilePerms(true /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenOpenFilePermsNoClientCheck) { + TestOpenFilePerms(false /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + + +void TestOpenCpuinfo(bool fast_check_in_client) { + const char kFileCpuInfo[] = "/proc/cpuinfo"; + std::vector<std::string> read_whitelist; + read_whitelist.push_back(kFileCpuInfo); + + BrokerProcess* open_broker = new BrokerProcess(read_whitelist, + std::vector<std::string>(), + fast_check_in_client); + ASSERT_TRUE(open_broker->Init(NULL)); + pid_t broker_pid = open_broker->broker_pid(); + + int fd = -1; + fd = open_broker->Open(kFileCpuInfo, O_RDWR); + ASSERT_EQ(fd, -EPERM); + + // Open cpuinfo via the broker. + int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); + ASSERT_GE(cpuinfo_fd, 0); + char buf[3]; + memset(buf, 0, sizeof(buf)); + int read_len1 = read(cpuinfo_fd, buf, sizeof(buf)); + ASSERT_GT(read_len1, 0); + + // Open cpuinfo directly. + int cpuinfo_fd2 = open(kFileCpuInfo, O_RDONLY); + ASSERT_GE(cpuinfo_fd2, 0); + char buf2[3]; + memset(buf2, 1, sizeof(buf2)); + int read_len2 = read(cpuinfo_fd2, buf2, sizeof(buf2)); + ASSERT_GT(read_len1, 0); + + // The following is not guaranteed true, but will be in practice. + ASSERT_EQ(read_len1, read_len2); + // Compare the cpuinfo as returned by the broker with the one we opened + // ourselves. + ASSERT_EQ(memcmp(buf, buf2, read_len1), 0); + + if (fd >= 0) + close(fd); + if (cpuinfo_fd >= 0) + close(cpuinfo_fd); + if (cpuinfo_fd2 >= 0) + close(cpuinfo_fd); + + delete(open_broker); + + // Now we check that the broker has exited properly. + int status = 0; + ASSERT_EQ(waitpid(broker_pid, &status, 0), broker_pid); + ASSERT_TRUE(WIFEXITED(status)); + ASSERT_EQ(WEXITSTATUS(status), 0); +} + +// Run the same thing twice. The second time, we make sure that no security +// check is performed on the client. +TEST(BrokerProcess, OpenCpuinfoWithClientCheck) { + TestOpenCpuinfo(true /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenCpuinfoNoClientCheck) { + TestOpenCpuinfo(false /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +// Disabled until we implement a mkstemp that doesn't require JNI. +TEST(BrokerProcess, DISABLE_ON_ANDROID(OpenFileRW)) { + const char basename[] = "BrokerProcessXXXXXX"; + char template_name[2048]; +#if defined(OS_ANDROID) + base::FilePath cache_directory; + ASSERT_TRUE(base::android::GetCacheDirectory(&cache_directory)); + ssize_t length = snprintf(template_name, sizeof(template_name), + "%s%s", + cache_directory.value().c_str(), basename); + ASSERT_LT(length, static_cast<ssize_t>(sizeof(template_name))); +#else + strncpy(template_name, basename, sizeof(basename) - 1); + template_name[sizeof(basename) - 1] = '\0'; +#endif + int tempfile = mkstemp(template_name); + ASSERT_GE(tempfile, 0); + char tempfile_name[2048]; + int written = snprintf(tempfile_name, sizeof(tempfile_name), + "/proc/self/fd/%d", tempfile); + ASSERT_LT(written, static_cast<int>(sizeof(tempfile_name))); + + std::vector<std::string> whitelist; + whitelist.push_back(tempfile_name); + + BrokerProcess open_broker(whitelist, whitelist); + ASSERT_TRUE(open_broker.Init(NULL)); + + int tempfile2 = -1; + tempfile2 = open_broker.Open(tempfile_name, O_RDWR); + ASSERT_GE(tempfile2, 0); + + // Write to the descriptor opened by the broker. + char test_text[] = "TESTTESTTEST"; + ssize_t len = write(tempfile2, test_text, sizeof(test_text)); + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(test_text))); + + // Read back from the original file descriptor what we wrote through + // the descriptor provided by the broker. + char buf[1024]; + len = read(tempfile, buf, sizeof(buf)); + + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(test_text))); + ASSERT_EQ(memcmp(test_text, buf, sizeof(test_text)), 0); + + // Cleanup the temporary file. + char tempfile_full_path[2048]; + // Make sure tempfile_full_path will terminate with a 0. + memset(tempfile_full_path, 0, sizeof(tempfile_full_path)); + ssize_t ret = readlink(tempfile_name, tempfile_full_path, + sizeof(tempfile_full_path)); + ASSERT_GT(ret, 0); + // Make sure we still have a trailing zero in tempfile_full_path. + ASSERT_LT(ret, static_cast<ssize_t>(sizeof(tempfile_full_path))); + ASSERT_EQ(unlink(tempfile_full_path), 0); + + ASSERT_EQ(close(tempfile), 0); + ASSERT_EQ(close(tempfile2), 0); +} + +// Sandbox test because we could get a SIGPIPE. +SANDBOX_TEST(BrokerProcess, BrokerDied) { + std::vector<std::string> read_whitelist; + read_whitelist.push_back("/proc/cpuinfo"); + + BrokerProcess open_broker(read_whitelist, + std::vector<std::string>(), + true /* fast_check_in_client */, + true /* quiet_failures_for_tests */); + SANDBOX_ASSERT(open_broker.Init(NULL)); + pid_t broker_pid = open_broker.broker_pid(); + SANDBOX_ASSERT(kill(broker_pid, SIGKILL) == 0); + + // Now we check that the broker has exited properly. + int status = 0; + SANDBOX_ASSERT(waitpid(broker_pid, &status, 0) == broker_pid); + SANDBOX_ASSERT(WIFSIGNALED(status)); + SANDBOX_ASSERT(WTERMSIG(status) == SIGKILL); + // Hopefully doing Open with a dead broker won't SIGPIPE us. + SANDBOX_ASSERT(open_broker.Open("/proc/cpuinfo", O_RDONLY) == -ENOMEM); +} + +void TestComplexFlags(bool fast_check_in_client) { + std::vector<std::string> whitelist; + whitelist.push_back("/proc/cpuinfo"); + + BrokerProcess open_broker(whitelist, + whitelist, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(NULL)); + // Test that we do the right thing for O_CLOEXEC and O_NONBLOCK. + // Presently, the right thing is to always deny them since they are not + // supported. + int fd = -1; + fd = open_broker.Open("/proc/cpuinfo", O_RDONLY); + ASSERT_GE(fd, 0); + ASSERT_EQ(close(fd), 0); + + ASSERT_EQ(open_broker.Open("/proc/cpuinfo", O_RDONLY | O_CLOEXEC), -EPERM); + ASSERT_EQ(open_broker.Open("/proc/cpuinfo", O_RDONLY | O_NONBLOCK), -EPERM); +} + +TEST(BrokerProcess, ComplexFlagsWithClientCheck) { + TestComplexFlags(true /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, ComplexFlagsNoClientCheck) { + TestComplexFlags(false /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +} // namespace sandbox diff --git a/sandbox/linux/services/libc_urandom_override.cc b/sandbox/linux/services/libc_urandom_override.cc index ee34045b3f..3f9957222f 100644 --- a/sandbox/linux/services/libc_urandom_override.cc +++ b/sandbox/linux/services/libc_urandom_override.cc @@ -10,8 +10,8 @@ #include <sys/stat.h> #include <unistd.h> -#include "base/eintr_wrapper.h" #include "base/logging.h" +#include "base/posix/eintr_wrapper.h" #include "base/rand_util.h" // Note: this file is used by the zygote and nacl_helper. diff --git a/sandbox/linux/suid/client/setuid_sandbox_client.cc b/sandbox/linux/suid/client/setuid_sandbox_client.cc index 45d700b363..7a174ef7d7 100644 --- a/sandbox/linux/suid/client/setuid_sandbox_client.cc +++ b/sandbox/linux/suid/client/setuid_sandbox_client.cc @@ -6,10 +6,10 @@ #include <sys/wait.h> #include <unistd.h> -#include "base/eintr_wrapper.h" #include "base/environment.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" #include "base/string_number_conversions.h" #include "sandbox/linux/suid/common/sandbox.h" diff --git a/sandbox/linux/suid/linux_util.c b/sandbox/linux/suid/linux_util.c index c5af0d0d1d..256468ff4e 100644 --- a/sandbox/linux/suid/linux_util.c +++ b/sandbox/linux/suid/linux_util.c @@ -5,9 +5,12 @@ // The following is duplicated from base/linux_utils.cc. // We shouldn't link against C++ code in a setuid binary. +#define _GNU_SOURCE // For O_DIRECTORY #include "linux_util.h" #include <dirent.h> +#include <errno.h> +#include <fcntl.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> @@ -23,29 +26,42 @@ static const char kSocketLinkPrefix[] = "socket:["; // socket. // inode_out: (output) set to the inode number on success // path: e.g. /proc/1234/fd/5 (must be a UNIX domain socket descriptor) -static bool ProcPathGetInode(ino_t* inode_out, const char* path) { +static bool ProcPathGetInodeAt(ino_t* inode_out, int base_dir_fd, + const char* path) { + // We also check that the path is relative. + if (!inode_out || !path || *path == '/') + return false; char buf[256]; - const ssize_t n = readlink(path, buf, sizeof(buf) - 1); - if (n == -1) + const ssize_t n = readlinkat(base_dir_fd, path, buf, sizeof(buf) - 1); + if (n < 0) return false; buf[n] = 0; if (memcmp(kSocketLinkPrefix, buf, sizeof(kSocketLinkPrefix) - 1)) return false; - char *endptr; - const unsigned long long int inode_ul = + char *endptr = NULL; + errno = 0; + const unsigned long long int inode_ull = strtoull(buf + sizeof(kSocketLinkPrefix) - 1, &endptr, 10); - if (*endptr != ']') - return false; - - if (inode_ul == ULLONG_MAX) + if (inode_ull == ULLONG_MAX || !endptr || *endptr != ']' || errno != 0) return false; - *inode_out = inode_ul; + *inode_out = inode_ull; return true; } +static DIR* opendirat(int base_dir_fd, const char* name) { + // Also check that |name| is relative. + if (base_dir_fd < 0 || !name || *name == '/') + return NULL; + int new_dir_fd = openat(base_dir_fd, name, O_RDONLY | O_DIRECTORY); + if (new_dir_fd < 0) + return NULL; + + return fdopendir(new_dir_fd); +} + bool FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode) { bool already_found = false; @@ -56,9 +72,10 @@ bool FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode) { const uid_t uid = getuid(); struct dirent* dent; while ((dent = readdir(proc))) { - char *endptr; + char *endptr = NULL; + errno = 0; const unsigned long int pid_ul = strtoul(dent->d_name, &endptr, 10); - if (pid_ul == ULONG_MAX || *endptr) + if (pid_ul == ULONG_MAX || !endptr || *endptr || errno != 0) continue; // We have this setuid code here because the zygote and its children have @@ -66,34 +83,39 @@ bool FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode) { // extra check so users cannot accidentally gain information about other // users' processes. To determine process ownership, we use the property // that if user foo owns process N, then /proc/N is owned by foo. + int proc_pid_fd = -1; { char buf[256]; struct stat statbuf; snprintf(buf, sizeof(buf), "/proc/%lu", pid_ul); - if (stat(buf, &statbuf) < 0) + proc_pid_fd = open(buf, O_RDONLY | O_DIRECTORY); + if (proc_pid_fd < 0) continue; - if (uid != statbuf.st_uid) + if (fstat(proc_pid_fd, &statbuf) < 0 || uid != statbuf.st_uid) { + close(proc_pid_fd); continue; + } } - char buf[256]; - snprintf(buf, sizeof(buf), "/proc/%lu/fd", pid_ul); - DIR* fd = opendir(buf); - if (!fd) + DIR* fd = opendirat(proc_pid_fd, "fd"); + if (!fd) { + close(proc_pid_fd); continue; + } while ((dent = readdir(fd))) { - int printed = snprintf(buf, sizeof(buf), "/proc/%lu/fd/%s", pid_ul, - dent->d_name); + char buf[256]; + int printed = snprintf(buf, sizeof(buf), "fd/%s", dent->d_name); if (printed < 0 || printed >= (int)(sizeof(buf) - 1)) { continue; } ino_t fd_inode; - if (ProcPathGetInode(&fd_inode, buf)) { + if (ProcPathGetInodeAt(&fd_inode, proc_pid_fd, buf)) { if (fd_inode == socket_inode) { if (already_found) { closedir(fd); + close(proc_pid_fd); closedir(proc); return false; } @@ -105,6 +127,7 @@ bool FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode) { } } closedir(fd); + close(proc_pid_fd); } closedir(proc); diff --git a/sandbox/linux/tests/main.cc b/sandbox/linux/tests/main.cc index 4412645321..8142545654 100644 --- a/sandbox/linux/tests/main.cc +++ b/sandbox/linux/tests/main.cc @@ -4,7 +4,6 @@ #include "testing/gtest/include/gtest/gtest.h" - int main(int argc, char *argv[]) { testing::InitGoogleTest(&argc, argv); // Always go through re-execution for death tests. diff --git a/sandbox/linux/tests/unit_tests.cc b/sandbox/linux/tests/unit_tests.cc index 105c45bc58..3d049ab80c 100644 --- a/sandbox/linux/tests/unit_tests.cc +++ b/sandbox/linux/tests/unit_tests.cc @@ -2,24 +2,98 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <fcntl.h> +#include <poll.h> +#include <signal.h> #include <stdio.h> #include <sys/resource.h> #include <sys/time.h> +#include <unistd.h> #include "base/file_util.h" +#include "base/third_party/valgrind/valgrind.h" #include "sandbox/linux/tests/unit_tests.h" +namespace { +std::string TestFailedMessage(const std::string& msg) { + return msg.empty() ? "" : "Actual test failure: " + msg; +} + +int GetSubProcessTimeoutTimeInSeconds() { + // 10s ought to be enough for anybody. + return 10; +} + +// Returns the number of threads of the current process or -1. +int CountThreads() { + struct stat task_stat; + int task_d = stat("/proc/self/task", &task_stat); + // task_stat.st_nlink should be the number of tasks + 2 (accounting for + // "." and "..". + if (task_d != 0 || task_stat.st_nlink < 3) + return -1; + return task_stat.st_nlink - 2; +} + +} // namespace + namespace sandbox { static const int kExpectedValue = 42; +static const int kIgnoreThisTest = 43; +static const int kExitWithAssertionFailure = 1; +static const int kExitForTimeout = 2; + +static void SigAlrmHandler(int) { + const char failure_message[] = "Timeout reached!\n"; + // Make sure that we never block here. + if (!fcntl(2, F_SETFL, O_NONBLOCK)) { + if (write(2, failure_message, sizeof(failure_message) - 1) < 0) { + } + } + _exit(kExitForTimeout); +} + +// Set a timeout with a handler that will automatically fail the +// test. +static void SetProcessTimeout(int time_in_seconds) { + struct sigaction act = {}; + act.sa_handler = SigAlrmHandler; + SANDBOX_ASSERT(sigemptyset(&act.sa_mask) == 0); + act.sa_flags = 0; -void UnitTests::RunTestInProcess(UnitTests::Test test, void *arg) { - // 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 - // insecure manner. + struct sigaction old_act; + SANDBOX_ASSERT(sigaction(SIGALRM, &act, &old_act) == 0); + + // We don't implemenet signal chaining, so make sure that nothing else + // is expecting to handle SIGALRM. + SANDBOX_ASSERT((old_act.sa_flags & SA_SIGINFO) == 0); + SANDBOX_ASSERT(old_act.sa_handler == SIG_DFL); + sigset_t sigalrm_set; + SANDBOX_ASSERT(sigemptyset(&sigalrm_set) == 0); + SANDBOX_ASSERT(sigaddset(&sigalrm_set, SIGALRM) == 0); + SANDBOX_ASSERT(sigprocmask(SIG_UNBLOCK, &sigalrm_set, NULL) == 0); + SANDBOX_ASSERT(alarm(time_in_seconds) == 0); // There should be no previous + // alarm. +} + +// 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 +// insecure manner. +void UnitTests::RunTestInProcess(UnitTests::Test test, void *arg, + DeathCheck death, const void *death_aux) { + if (CountThreads() != 1) { + // We need to use fork(), so we can't be multi-threaded. + // TODO(jln): change this to a fatal error once we can launch + // Android tests with --exe. + fprintf(stderr, "WARNING: running sandbox tests with multiple threads" + " is not supported and will make the tests flaky.\n"); + } int fds[2]; ASSERT_EQ(0, pipe(fds)); + // Check that our pipe is not on one of the standard file descriptor. + SANDBOX_ASSERT(fds[0] > 2 && fds[1] > 2); pid_t pid; ASSERT_LE(0, (pid = fork())); @@ -31,6 +105,12 @@ void UnitTests::RunTestInProcess(UnitTests::Test test, void *arg) { SANDBOX_ASSERT(!close(fds[0])); SANDBOX_ASSERT(!close(fds[1])); + // Don't set a timeout if running on Valgrind, since it's generally much + // slower. + if (!RUNNING_ON_VALGRIND) { + SetProcessTimeout(GetSubProcessTimeoutTimeInSeconds()); + } + // Disable core files. They are not very useful for our individual test // cases. struct rlimit no_core = { 0 }; @@ -41,24 +121,56 @@ 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))); - } while (rc > 0); - std::string details; - if (!msg.empty()) { - details = "Actual test failure: " + std::string(msg.begin(), msg.end()); + + // Make sure read() will never block as we'll use poll() to + // block with a timeout instead. + const int fcntl_ret = fcntl(fds[0], F_SETFL, O_NONBLOCK); + ASSERT_EQ(fcntl_ret, 0); + struct pollfd poll_fd = { fds[0], POLLIN | POLLRDHUP, 0 }; + + int poll_ret; + // We prefer the SIGALRM timeout to trigger in the child than this timeout + // so we double the common value here. + int poll_timeout = GetSubProcessTimeoutTimeInSeconds() * 2 * 1000; + while ((poll_ret = poll(&poll_fd, 1, poll_timeout) > 0)) { + const size_t kCapacity = 256; + const 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))); + if (rc <= 0) + break; } + ASSERT_NE(poll_ret, -1) << "poll() failed"; + ASSERT_NE(poll_ret, 0) << "Timeout while reading child state"; (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 +179,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 d6b4761c5c..78bf9bcf18 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); }; diff --git a/sandbox/sandbox.gyp b/sandbox/sandbox.gyp index c953f08aa6..b48727a28b 100644 --- a/sandbox/sandbox.gyp +++ b/sandbox/sandbox.gyp @@ -12,14 +12,13 @@ 'win/sandbox_win.gypi', ], }], - [ 'OS=="linux"', { + [ 'OS=="linux" or OS=="android"', { 'includes': [ 'linux/sandbox_linux.gypi', ], }], - [ 'OS!="win" and OS!="mac" and OS!="linux"', { - # We need a 'default' to accomodate the "sandbox" target, for instance - # on Android. + [ 'OS!="win" and OS!="mac" and OS!="linux" and OS!="android"', { + # A 'default' to accomodate the "sandbox" target. 'targets': [ { 'target_name': 'sandbox', diff --git a/sandbox/sandbox.target.mk b/sandbox/sandbox.target.linux-arm.mk index 2453f10876..05ff0094ec 100644 --- a/sandbox/sandbox.target.mk +++ b/sandbox/sandbox.target.linux-arm.mk @@ -11,7 +11,9 @@ gyp_intermediate_dir := $(call local-intermediates-dir) gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared) # Make sure our deps are built first. -GYP_TARGET_DEPENDENCIES := +GYP_TARGET_DEPENDENCIES := \ + $(call intermediates-dir-for,STATIC_LIBRARIES,sandbox_sandbox_services_gyp)/sandbox_sandbox_services_gyp.a \ + $(call intermediates-dir-for,STATIC_LIBRARIES,sandbox_seccomp_bpf_gyp)/sandbox_seccomp_bpf_gyp.a GYP_GENERATED_OUTPUTS := diff --git a/sandbox/sandbox.target.linux-x86.mk b/sandbox/sandbox.target.linux-x86.mk new file mode 100644 index 0000000000..05ff0094ec --- /dev/null +++ b/sandbox/sandbox.target.linux-x86.mk @@ -0,0 +1,40 @@ +# This file is generated by gyp; do not edit. + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := GYP +LOCAL_MODULE := sandbox_sandbox_gyp +LOCAL_MODULE_STEM := sandbox +LOCAL_MODULE_SUFFIX := .stamp +LOCAL_MODULE_TAGS := optional +gyp_intermediate_dir := $(call local-intermediates-dir) +gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared) + +# Make sure our deps are built first. +GYP_TARGET_DEPENDENCIES := \ + $(call intermediates-dir-for,STATIC_LIBRARIES,sandbox_sandbox_services_gyp)/sandbox_sandbox_services_gyp.a \ + $(call intermediates-dir-for,STATIC_LIBRARIES,sandbox_seccomp_bpf_gyp)/sandbox_seccomp_bpf_gyp.a + +GYP_GENERATED_OUTPUTS := + +# Make sure our deps and generated files are built first. +LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) $(GYP_GENERATED_OUTPUTS) + +### Rules for final target. +# Add target alias to "gyp_all_modules" target. +.PHONY: gyp_all_modules +gyp_all_modules: sandbox_sandbox_gyp + +# Alias gyp target name. +.PHONY: sandbox +sandbox: sandbox_sandbox_gyp + +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/gyp_stamp +LOCAL_UNINSTALLABLE_MODULE := true + +include $(BUILD_SYSTEM)/base_rules.mk + +$(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES) + $(hide) echo "Gyp timestamp: $@" + $(hide) mkdir -p $(dir $@) + $(hide) touch $@ diff --git a/sandbox/sandbox_services.target.linux-arm.mk b/sandbox/sandbox_services.target.linux-arm.mk new file mode 100644 index 0000000000..92e5cdaa76 --- /dev/null +++ b/sandbox/sandbox_services.target.linux-arm.mk @@ -0,0 +1,149 @@ +# This file is generated by gyp; do not edit. + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_MODULE := sandbox_sandbox_services_gyp +LOCAL_MODULE_SUFFIX := .a +LOCAL_MODULE_TAGS := optional +gyp_intermediate_dir := $(call local-intermediates-dir) +gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared) + +# Make sure our deps are built first. +GYP_TARGET_DEPENDENCIES := + +GYP_GENERATED_OUTPUTS := + +# Make sure our deps and generated files are built first. +LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) $(GYP_GENERATED_OUTPUTS) + +LOCAL_CPP_EXTENSION := .cc +LOCAL_GENERATED_SOURCES := + +GYP_COPIED_SOURCE_ORIGIN_DIRS := + +LOCAL_SRC_FILES := \ + sandbox/linux/services/broker_process.cc + + +# Flags passed to both C and C++ files. +MY_CFLAGS := \ + -fstack-protector \ + --param=ssp-buffer-size=4 \ + -Werror \ + -fno-exceptions \ + -fno-strict-aliasing \ + -Wall \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -fvisibility=hidden \ + -pipe \ + -fPIC \ + -fno-tree-sra \ + -fuse-ld=gold \ + -Wno-psabi \ + -ffunction-sections \ + -funwind-tables \ + -g \ + -fstack-protector \ + -fno-short-enums \ + -finline-limit=64 \ + -Wa,--noexecstack \ + -U_FORTIFY_SOURCE \ + -Wno-extra \ + -Wno-ignored-qualifiers \ + -Wno-type-limits \ + -Os \ + -g \ + -fomit-frame-pointer \ + -fdata-sections \ + -ffunction-sections + +MY_CFLAGS_C := + +MY_DEFS := \ + '-DUSE_SKIA' \ + '-D_FILE_OFFSET_BITS=64' \ + '-DUSE_LINUX_BREAKPAD' \ + '-DNO_TCMALLOC' \ + '-DDISABLE_NACL' \ + '-DCHROMIUM_BUILD' \ + '-DUSE_LIBJPEG_TURBO=1' \ + '-DUSE_PROPRIETARY_CODECS' \ + '-DENABLE_PEPPER_THREADING' \ + '-DENABLE_GPU=1' \ + '-DUSE_OPENSSL=1' \ + '-DENABLE_EGLIMAGE=1' \ + '-DENABLE_LANGUAGE_DETECTION=1' \ + '-D__STDC_CONSTANT_MACROS' \ + '-D__STDC_FORMAT_MACROS' \ + '-DANDROID' \ + '-D__GNU_SOURCE=1' \ + '-DUSE_STLPORT=1' \ + '-D_STLP_USE_PTR_SPECIALIZATIONS=1' \ + '-DCHROME_BUILD_ID=""' \ + '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \ + '-DWTF_USE_DYNAMIC_ANNOTATIONS=1' \ + '-D_DEBUG' + +LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) + +# Include paths placed before CFLAGS/CPPFLAGS +LOCAL_C_INCLUDES := \ + $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \ + $(LOCAL_PATH)/sandbox \ + $(LOCAL_PATH) \ + $(GYP_ABS_ANDROID_TOP_DIR)/frameworks/wilhelm/include \ + $(GYP_ABS_ANDROID_TOP_DIR)/bionic \ + $(GYP_ABS_ANDROID_TOP_DIR)/external/stlport/stlport + +LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) $(LOCAL_C_INCLUDES) + +# Flags passed to only C++ (and not C) files. +LOCAL_CPPFLAGS := \ + -fno-rtti \ + -fno-threadsafe-statics \ + -fvisibility-inlines-hidden \ + -Wsign-compare \ + -Wno-abi \ + -Wno-error=c++0x-compat \ + -Wno-non-virtual-dtor \ + -Wno-sign-promo + +### Rules for final target. + +LOCAL_LDFLAGS := \ + -Wl,-z,now \ + -Wl,-z,relro \ + -Wl,-z,noexecstack \ + -fPIC \ + -Wl,-z,relro \ + -Wl,-z,now \ + -fuse-ld=gold \ + -nostdlib \ + -Wl,--no-undefined \ + -Wl,--exclude-libs=ALL \ + -Wl,--icf=safe \ + -Wl,-O1 \ + -Wl,--as-needed \ + -Wl,--gc-sections + + +LOCAL_STATIC_LIBRARIES := + +# Enable grouping to fix circular references +LOCAL_GROUP_STATIC_LIBRARIES := true + +LOCAL_SHARED_LIBRARIES := \ + libstlport \ + libdl + +# Add target alias to "gyp_all_modules" target. +.PHONY: gyp_all_modules +gyp_all_modules: sandbox_sandbox_services_gyp + +# Alias gyp target name. +.PHONY: sandbox_services +sandbox_services: sandbox_sandbox_services_gyp + +include $(BUILD_STATIC_LIBRARY) diff --git a/sandbox/sandbox_services.target.linux-x86.mk b/sandbox/sandbox_services.target.linux-x86.mk new file mode 100644 index 0000000000..85a9124972 --- /dev/null +++ b/sandbox/sandbox_services.target.linux-x86.mk @@ -0,0 +1,146 @@ +# This file is generated by gyp; do not edit. + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_MODULE := sandbox_sandbox_services_gyp +LOCAL_MODULE_SUFFIX := .a +LOCAL_MODULE_TAGS := optional +gyp_intermediate_dir := $(call local-intermediates-dir) +gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared) + +# Make sure our deps are built first. +GYP_TARGET_DEPENDENCIES := + +GYP_GENERATED_OUTPUTS := + +# Make sure our deps and generated files are built first. +LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) $(GYP_GENERATED_OUTPUTS) + +LOCAL_CPP_EXTENSION := .cc +LOCAL_GENERATED_SOURCES := + +GYP_COPIED_SOURCE_ORIGIN_DIRS := + +LOCAL_SRC_FILES := \ + sandbox/linux/services/broker_process.cc + + +# Flags passed to both C and C++ files. +MY_CFLAGS := \ + --param=ssp-buffer-size=4 \ + -Werror \ + -fno-exceptions \ + -fno-strict-aliasing \ + -Wall \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -fvisibility=hidden \ + -pipe \ + -fPIC \ + -m32 \ + -mmmx \ + -march=pentium4 \ + -msse2 \ + -mfpmath=sse \ + -ffunction-sections \ + -funwind-tables \ + -g \ + -fno-short-enums \ + -finline-limit=64 \ + -Wa,--noexecstack \ + -U_FORTIFY_SOURCE \ + -Wno-extra \ + -Wno-ignored-qualifiers \ + -Wno-type-limits \ + -fno-stack-protector \ + -Os \ + -g \ + -fomit-frame-pointer \ + -fdata-sections \ + -ffunction-sections + +MY_CFLAGS_C := + +MY_DEFS := \ + '-DUSE_SKIA' \ + '-D_FILE_OFFSET_BITS=64' \ + '-DUSE_LINUX_BREAKPAD' \ + '-DNO_TCMALLOC' \ + '-DDISABLE_NACL' \ + '-DCHROMIUM_BUILD' \ + '-DUSE_LIBJPEG_TURBO=1' \ + '-DUSE_PROPRIETARY_CODECS' \ + '-DENABLE_PEPPER_THREADING' \ + '-DENABLE_GPU=1' \ + '-DUSE_OPENSSL=1' \ + '-DENABLE_EGLIMAGE=1' \ + '-DENABLE_LANGUAGE_DETECTION=1' \ + '-D__STDC_CONSTANT_MACROS' \ + '-D__STDC_FORMAT_MACROS' \ + '-DANDROID' \ + '-D__GNU_SOURCE=1' \ + '-DUSE_STLPORT=1' \ + '-D_STLP_USE_PTR_SPECIALIZATIONS=1' \ + '-DCHROME_BUILD_ID=""' \ + '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \ + '-DWTF_USE_DYNAMIC_ANNOTATIONS=1' \ + '-D_DEBUG' + +LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) + +# Include paths placed before CFLAGS/CPPFLAGS +LOCAL_C_INCLUDES := \ + $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \ + $(LOCAL_PATH)/sandbox \ + $(LOCAL_PATH) \ + $(GYP_ABS_ANDROID_TOP_DIR)/frameworks/wilhelm/include \ + $(GYP_ABS_ANDROID_TOP_DIR)/bionic \ + $(GYP_ABS_ANDROID_TOP_DIR)/external/stlport/stlport + +LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) $(LOCAL_C_INCLUDES) + +# Flags passed to only C++ (and not C) files. +LOCAL_CPPFLAGS := \ + -fno-rtti \ + -fno-threadsafe-statics \ + -fvisibility-inlines-hidden \ + -Wsign-compare \ + -Wno-error=c++0x-compat \ + -Wno-non-virtual-dtor \ + -Wno-sign-promo + +### Rules for final target. + +LOCAL_LDFLAGS := \ + -Wl,-z,now \ + -Wl,-z,relro \ + -Wl,-z,noexecstack \ + -fPIC \ + -m32 \ + -nostdlib \ + -Wl,--no-undefined \ + -Wl,--exclude-libs=ALL \ + -Wl,-O1 \ + -Wl,--as-needed \ + -Wl,--gc-sections + + +LOCAL_STATIC_LIBRARIES := + +# Enable grouping to fix circular references +LOCAL_GROUP_STATIC_LIBRARIES := true + +LOCAL_SHARED_LIBRARIES := \ + libstlport \ + libdl + +# Add target alias to "gyp_all_modules" target. +.PHONY: gyp_all_modules +gyp_all_modules: sandbox_sandbox_services_gyp + +# Alias gyp target name. +.PHONY: sandbox_services +sandbox_services: sandbox_sandbox_services_gyp + +include $(BUILD_STATIC_LIBRARY) diff --git a/sandbox/sandbox_services_headers.target.linux-arm.mk b/sandbox/sandbox_services_headers.target.linux-arm.mk new file mode 100644 index 0000000000..7190aa5bb2 --- /dev/null +++ b/sandbox/sandbox_services_headers.target.linux-arm.mk @@ -0,0 +1,127 @@ +# This file is generated by gyp; do not edit. + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := GYP +LOCAL_MODULE := sandbox_sandbox_services_headers_gyp +LOCAL_MODULE_STEM := sandbox_services_headers +LOCAL_MODULE_SUFFIX := .stamp +LOCAL_MODULE_TAGS := optional +gyp_intermediate_dir := $(call local-intermediates-dir) +gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared) + +# Make sure our deps are built first. +GYP_TARGET_DEPENDENCIES := + +GYP_GENERATED_OUTPUTS := + +# Make sure our deps and generated files are built first. +LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) $(GYP_GENERATED_OUTPUTS) + +LOCAL_GENERATED_SOURCES := + +GYP_COPIED_SOURCE_ORIGIN_DIRS := + +LOCAL_SRC_FILES := + + +# Flags passed to both C and C++ files. +MY_CFLAGS := \ + -fstack-protector \ + --param=ssp-buffer-size=4 \ + -Werror \ + -fno-exceptions \ + -fno-strict-aliasing \ + -Wall \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -fvisibility=hidden \ + -pipe \ + -fPIC \ + -fno-tree-sra \ + -fuse-ld=gold \ + -Wno-psabi \ + -ffunction-sections \ + -funwind-tables \ + -g \ + -fstack-protector \ + -fno-short-enums \ + -finline-limit=64 \ + -Wa,--noexecstack \ + -U_FORTIFY_SOURCE \ + -Wno-extra \ + -Wno-ignored-qualifiers \ + -Wno-type-limits \ + -Os \ + -g \ + -fomit-frame-pointer \ + -fdata-sections \ + -ffunction-sections + +MY_CFLAGS_C := + +MY_DEFS := \ + '-DUSE_SKIA' \ + '-D_FILE_OFFSET_BITS=64' \ + '-DUSE_LINUX_BREAKPAD' \ + '-DNO_TCMALLOC' \ + '-DDISABLE_NACL' \ + '-DCHROMIUM_BUILD' \ + '-DUSE_LIBJPEG_TURBO=1' \ + '-DUSE_PROPRIETARY_CODECS' \ + '-DENABLE_PEPPER_THREADING' \ + '-DENABLE_GPU=1' \ + '-DUSE_OPENSSL=1' \ + '-DENABLE_EGLIMAGE=1' \ + '-DENABLE_LANGUAGE_DETECTION=1' \ + '-D__STDC_CONSTANT_MACROS' \ + '-D__STDC_FORMAT_MACROS' \ + '-DANDROID' \ + '-D__GNU_SOURCE=1' \ + '-DUSE_STLPORT=1' \ + '-D_STLP_USE_PTR_SPECIALIZATIONS=1' \ + '-DCHROME_BUILD_ID=""' \ + '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \ + '-DWTF_USE_DYNAMIC_ANNOTATIONS=1' \ + '-D_DEBUG' + +LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) + +# Include paths placed before CFLAGS/CPPFLAGS +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/sandbox \ + $(GYP_ABS_ANDROID_TOP_DIR)/frameworks/wilhelm/include \ + $(GYP_ABS_ANDROID_TOP_DIR)/bionic \ + $(GYP_ABS_ANDROID_TOP_DIR)/external/stlport/stlport + +LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) $(LOCAL_C_INCLUDES) + +# Flags passed to only C++ (and not C) files. +LOCAL_CPPFLAGS := \ + -fno-rtti \ + -fno-threadsafe-statics \ + -fvisibility-inlines-hidden \ + -Wsign-compare \ + -Wno-abi \ + -Wno-error=c++0x-compat \ + -Wno-non-virtual-dtor \ + -Wno-sign-promo + +### Rules for final target. +# Add target alias to "gyp_all_modules" target. +.PHONY: gyp_all_modules +gyp_all_modules: sandbox_sandbox_services_headers_gyp + +# Alias gyp target name. +.PHONY: sandbox_services_headers +sandbox_services_headers: sandbox_sandbox_services_headers_gyp + +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/gyp_stamp +LOCAL_UNINSTALLABLE_MODULE := true + +include $(BUILD_SYSTEM)/base_rules.mk + +$(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES) + $(hide) echo "Gyp timestamp: $@" + $(hide) mkdir -p $(dir $@) + $(hide) touch $@ diff --git a/sandbox/sandbox_services_headers.target.linux-x86.mk b/sandbox/sandbox_services_headers.target.linux-x86.mk new file mode 100644 index 0000000000..32f132d288 --- /dev/null +++ b/sandbox/sandbox_services_headers.target.linux-x86.mk @@ -0,0 +1,127 @@ +# This file is generated by gyp; do not edit. + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := GYP +LOCAL_MODULE := sandbox_sandbox_services_headers_gyp +LOCAL_MODULE_STEM := sandbox_services_headers +LOCAL_MODULE_SUFFIX := .stamp +LOCAL_MODULE_TAGS := optional +gyp_intermediate_dir := $(call local-intermediates-dir) +gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared) + +# Make sure our deps are built first. +GYP_TARGET_DEPENDENCIES := + +GYP_GENERATED_OUTPUTS := + +# Make sure our deps and generated files are built first. +LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) $(GYP_GENERATED_OUTPUTS) + +LOCAL_GENERATED_SOURCES := + +GYP_COPIED_SOURCE_ORIGIN_DIRS := + +LOCAL_SRC_FILES := + + +# Flags passed to both C and C++ files. +MY_CFLAGS := \ + --param=ssp-buffer-size=4 \ + -Werror \ + -fno-exceptions \ + -fno-strict-aliasing \ + -Wall \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -fvisibility=hidden \ + -pipe \ + -fPIC \ + -m32 \ + -mmmx \ + -march=pentium4 \ + -msse2 \ + -mfpmath=sse \ + -ffunction-sections \ + -funwind-tables \ + -g \ + -fno-short-enums \ + -finline-limit=64 \ + -Wa,--noexecstack \ + -U_FORTIFY_SOURCE \ + -Wno-extra \ + -Wno-ignored-qualifiers \ + -Wno-type-limits \ + -fno-stack-protector \ + -Os \ + -g \ + -fomit-frame-pointer \ + -fdata-sections \ + -ffunction-sections + +MY_CFLAGS_C := + +MY_DEFS := \ + '-DUSE_SKIA' \ + '-D_FILE_OFFSET_BITS=64' \ + '-DUSE_LINUX_BREAKPAD' \ + '-DNO_TCMALLOC' \ + '-DDISABLE_NACL' \ + '-DCHROMIUM_BUILD' \ + '-DUSE_LIBJPEG_TURBO=1' \ + '-DUSE_PROPRIETARY_CODECS' \ + '-DENABLE_PEPPER_THREADING' \ + '-DENABLE_GPU=1' \ + '-DUSE_OPENSSL=1' \ + '-DENABLE_EGLIMAGE=1' \ + '-DENABLE_LANGUAGE_DETECTION=1' \ + '-D__STDC_CONSTANT_MACROS' \ + '-D__STDC_FORMAT_MACROS' \ + '-DANDROID' \ + '-D__GNU_SOURCE=1' \ + '-DUSE_STLPORT=1' \ + '-D_STLP_USE_PTR_SPECIALIZATIONS=1' \ + '-DCHROME_BUILD_ID=""' \ + '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \ + '-DWTF_USE_DYNAMIC_ANNOTATIONS=1' \ + '-D_DEBUG' + +LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) + +# Include paths placed before CFLAGS/CPPFLAGS +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/sandbox \ + $(GYP_ABS_ANDROID_TOP_DIR)/frameworks/wilhelm/include \ + $(GYP_ABS_ANDROID_TOP_DIR)/bionic \ + $(GYP_ABS_ANDROID_TOP_DIR)/external/stlport/stlport + +LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) $(LOCAL_C_INCLUDES) + +# Flags passed to only C++ (and not C) files. +LOCAL_CPPFLAGS := \ + -fno-rtti \ + -fno-threadsafe-statics \ + -fvisibility-inlines-hidden \ + -Wsign-compare \ + -Wno-error=c++0x-compat \ + -Wno-non-virtual-dtor \ + -Wno-sign-promo + +### Rules for final target. +# Add target alias to "gyp_all_modules" target. +.PHONY: gyp_all_modules +gyp_all_modules: sandbox_sandbox_services_headers_gyp + +# Alias gyp target name. +.PHONY: sandbox_services_headers +sandbox_services_headers: sandbox_sandbox_services_headers_gyp + +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/gyp_stamp +LOCAL_UNINSTALLABLE_MODULE := true + +include $(BUILD_SYSTEM)/base_rules.mk + +$(LOCAL_BUILT_MODULE): $(LOCAL_ADDITIONAL_DEPENDENCIES) + $(hide) echo "Gyp timestamp: $@" + $(hide) mkdir -p $(dir $@) + $(hide) touch $@ diff --git a/sandbox/seccomp_bpf.target.linux-arm.mk b/sandbox/seccomp_bpf.target.linux-arm.mk new file mode 100644 index 0000000000..a5fda7046f --- /dev/null +++ b/sandbox/seccomp_bpf.target.linux-arm.mk @@ -0,0 +1,157 @@ +# This file is generated by gyp; do not edit. + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_MODULE := sandbox_seccomp_bpf_gyp +LOCAL_MODULE_SUFFIX := .a +LOCAL_MODULE_TAGS := optional +gyp_intermediate_dir := $(call local-intermediates-dir) +gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared) + +# Make sure our deps are built first. +GYP_TARGET_DEPENDENCIES := \ + $(call intermediates-dir-for,GYP,sandbox_sandbox_services_headers_gyp)/sandbox_services_headers.stamp + +GYP_GENERATED_OUTPUTS := + +# Make sure our deps and generated files are built first. +LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) $(GYP_GENERATED_OUTPUTS) + +LOCAL_CPP_EXTENSION := .cc +LOCAL_GENERATED_SOURCES := + +GYP_COPIED_SOURCE_ORIGIN_DIRS := + +LOCAL_SRC_FILES := \ + sandbox/linux/seccomp-bpf/basicblock.cc \ + sandbox/linux/seccomp-bpf/codegen.cc \ + sandbox/linux/seccomp-bpf/die.cc \ + sandbox/linux/seccomp-bpf/errorcode.cc \ + sandbox/linux/seccomp-bpf/sandbox_bpf.cc \ + sandbox/linux/seccomp-bpf/syscall.cc \ + sandbox/linux/seccomp-bpf/syscall_iterator.cc \ + sandbox/linux/seccomp-bpf/trap.cc \ + sandbox/linux/seccomp-bpf/verifier.cc + + +# Flags passed to both C and C++ files. +MY_CFLAGS := \ + -fstack-protector \ + --param=ssp-buffer-size=4 \ + -Werror \ + -fno-exceptions \ + -fno-strict-aliasing \ + -Wall \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -fvisibility=hidden \ + -pipe \ + -fPIC \ + -fno-tree-sra \ + -fuse-ld=gold \ + -Wno-psabi \ + -ffunction-sections \ + -funwind-tables \ + -g \ + -fstack-protector \ + -fno-short-enums \ + -finline-limit=64 \ + -Wa,--noexecstack \ + -U_FORTIFY_SOURCE \ + -Wno-extra \ + -Wno-ignored-qualifiers \ + -Wno-type-limits \ + -Os \ + -g \ + -fomit-frame-pointer \ + -fdata-sections \ + -ffunction-sections + +MY_CFLAGS_C := + +MY_DEFS := \ + '-DUSE_SKIA' \ + '-D_FILE_OFFSET_BITS=64' \ + '-DUSE_LINUX_BREAKPAD' \ + '-DNO_TCMALLOC' \ + '-DDISABLE_NACL' \ + '-DCHROMIUM_BUILD' \ + '-DUSE_LIBJPEG_TURBO=1' \ + '-DUSE_PROPRIETARY_CODECS' \ + '-DENABLE_PEPPER_THREADING' \ + '-DENABLE_GPU=1' \ + '-DUSE_OPENSSL=1' \ + '-DENABLE_EGLIMAGE=1' \ + '-DENABLE_LANGUAGE_DETECTION=1' \ + '-D__STDC_CONSTANT_MACROS' \ + '-D__STDC_FORMAT_MACROS' \ + '-DANDROID' \ + '-D__GNU_SOURCE=1' \ + '-DUSE_STLPORT=1' \ + '-D_STLP_USE_PTR_SPECIALIZATIONS=1' \ + '-DCHROME_BUILD_ID=""' \ + '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \ + '-DWTF_USE_DYNAMIC_ANNOTATIONS=1' \ + '-D_DEBUG' + +LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) + +# Include paths placed before CFLAGS/CPPFLAGS +LOCAL_C_INCLUDES := \ + $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \ + $(LOCAL_PATH) \ + $(GYP_ABS_ANDROID_TOP_DIR)/frameworks/wilhelm/include \ + $(GYP_ABS_ANDROID_TOP_DIR)/bionic \ + $(GYP_ABS_ANDROID_TOP_DIR)/external/stlport/stlport + +LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) $(LOCAL_C_INCLUDES) + +# Flags passed to only C++ (and not C) files. +LOCAL_CPPFLAGS := \ + -fno-rtti \ + -fno-threadsafe-statics \ + -fvisibility-inlines-hidden \ + -Wsign-compare \ + -Wno-abi \ + -Wno-error=c++0x-compat \ + -Wno-non-virtual-dtor \ + -Wno-sign-promo + +### Rules for final target. + +LOCAL_LDFLAGS := \ + -Wl,-z,now \ + -Wl,-z,relro \ + -Wl,-z,noexecstack \ + -fPIC \ + -Wl,-z,relro \ + -Wl,-z,now \ + -fuse-ld=gold \ + -nostdlib \ + -Wl,--no-undefined \ + -Wl,--exclude-libs=ALL \ + -Wl,--icf=safe \ + -Wl,-O1 \ + -Wl,--as-needed \ + -Wl,--gc-sections + + +LOCAL_STATIC_LIBRARIES := + +# Enable grouping to fix circular references +LOCAL_GROUP_STATIC_LIBRARIES := true + +LOCAL_SHARED_LIBRARIES := \ + libstlport \ + libdl + +# Add target alias to "gyp_all_modules" target. +.PHONY: gyp_all_modules +gyp_all_modules: sandbox_seccomp_bpf_gyp + +# Alias gyp target name. +.PHONY: seccomp_bpf +seccomp_bpf: sandbox_seccomp_bpf_gyp + +include $(BUILD_STATIC_LIBRARY) diff --git a/sandbox/seccomp_bpf.target.linux-x86.mk b/sandbox/seccomp_bpf.target.linux-x86.mk new file mode 100644 index 0000000000..56af63de8a --- /dev/null +++ b/sandbox/seccomp_bpf.target.linux-x86.mk @@ -0,0 +1,154 @@ +# This file is generated by gyp; do not edit. + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_MODULE := sandbox_seccomp_bpf_gyp +LOCAL_MODULE_SUFFIX := .a +LOCAL_MODULE_TAGS := optional +gyp_intermediate_dir := $(call local-intermediates-dir) +gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared) + +# Make sure our deps are built first. +GYP_TARGET_DEPENDENCIES := \ + $(call intermediates-dir-for,GYP,sandbox_sandbox_services_headers_gyp)/sandbox_services_headers.stamp + +GYP_GENERATED_OUTPUTS := + +# Make sure our deps and generated files are built first. +LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) $(GYP_GENERATED_OUTPUTS) + +LOCAL_CPP_EXTENSION := .cc +LOCAL_GENERATED_SOURCES := + +GYP_COPIED_SOURCE_ORIGIN_DIRS := + +LOCAL_SRC_FILES := \ + sandbox/linux/seccomp-bpf/basicblock.cc \ + sandbox/linux/seccomp-bpf/codegen.cc \ + sandbox/linux/seccomp-bpf/die.cc \ + sandbox/linux/seccomp-bpf/errorcode.cc \ + sandbox/linux/seccomp-bpf/sandbox_bpf.cc \ + sandbox/linux/seccomp-bpf/syscall.cc \ + sandbox/linux/seccomp-bpf/syscall_iterator.cc \ + sandbox/linux/seccomp-bpf/trap.cc \ + sandbox/linux/seccomp-bpf/verifier.cc + + +# Flags passed to both C and C++ files. +MY_CFLAGS := \ + --param=ssp-buffer-size=4 \ + -Werror \ + -fno-exceptions \ + -fno-strict-aliasing \ + -Wall \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -fvisibility=hidden \ + -pipe \ + -fPIC \ + -m32 \ + -mmmx \ + -march=pentium4 \ + -msse2 \ + -mfpmath=sse \ + -ffunction-sections \ + -funwind-tables \ + -g \ + -fno-short-enums \ + -finline-limit=64 \ + -Wa,--noexecstack \ + -U_FORTIFY_SOURCE \ + -Wno-extra \ + -Wno-ignored-qualifiers \ + -Wno-type-limits \ + -fno-stack-protector \ + -Os \ + -g \ + -fomit-frame-pointer \ + -fdata-sections \ + -ffunction-sections + +MY_CFLAGS_C := + +MY_DEFS := \ + '-DUSE_SKIA' \ + '-D_FILE_OFFSET_BITS=64' \ + '-DUSE_LINUX_BREAKPAD' \ + '-DNO_TCMALLOC' \ + '-DDISABLE_NACL' \ + '-DCHROMIUM_BUILD' \ + '-DUSE_LIBJPEG_TURBO=1' \ + '-DUSE_PROPRIETARY_CODECS' \ + '-DENABLE_PEPPER_THREADING' \ + '-DENABLE_GPU=1' \ + '-DUSE_OPENSSL=1' \ + '-DENABLE_EGLIMAGE=1' \ + '-DENABLE_LANGUAGE_DETECTION=1' \ + '-D__STDC_CONSTANT_MACROS' \ + '-D__STDC_FORMAT_MACROS' \ + '-DANDROID' \ + '-D__GNU_SOURCE=1' \ + '-DUSE_STLPORT=1' \ + '-D_STLP_USE_PTR_SPECIALIZATIONS=1' \ + '-DCHROME_BUILD_ID=""' \ + '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \ + '-DWTF_USE_DYNAMIC_ANNOTATIONS=1' \ + '-D_DEBUG' + +LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) + +# Include paths placed before CFLAGS/CPPFLAGS +LOCAL_C_INCLUDES := \ + $(gyp_shared_intermediate_dir)/shim_headers/icuuc/target \ + $(LOCAL_PATH) \ + $(GYP_ABS_ANDROID_TOP_DIR)/frameworks/wilhelm/include \ + $(GYP_ABS_ANDROID_TOP_DIR)/bionic \ + $(GYP_ABS_ANDROID_TOP_DIR)/external/stlport/stlport + +LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) $(LOCAL_C_INCLUDES) + +# Flags passed to only C++ (and not C) files. +LOCAL_CPPFLAGS := \ + -fno-rtti \ + -fno-threadsafe-statics \ + -fvisibility-inlines-hidden \ + -Wsign-compare \ + -Wno-error=c++0x-compat \ + -Wno-non-virtual-dtor \ + -Wno-sign-promo + +### Rules for final target. + +LOCAL_LDFLAGS := \ + -Wl,-z,now \ + -Wl,-z,relro \ + -Wl,-z,noexecstack \ + -fPIC \ + -m32 \ + -nostdlib \ + -Wl,--no-undefined \ + -Wl,--exclude-libs=ALL \ + -Wl,-O1 \ + -Wl,--as-needed \ + -Wl,--gc-sections + + +LOCAL_STATIC_LIBRARIES := + +# Enable grouping to fix circular references +LOCAL_GROUP_STATIC_LIBRARIES := true + +LOCAL_SHARED_LIBRARIES := \ + libstlport \ + libdl + +# Add target alias to "gyp_all_modules" target. +.PHONY: gyp_all_modules +gyp_all_modules: sandbox_seccomp_bpf_gyp + +# Alias gyp target name. +.PHONY: seccomp_bpf +seccomp_bpf: sandbox_seccomp_bpf_gyp + +include $(BUILD_STATIC_LIBRARY) diff --git a/sandbox/win/sandbox_win.gypi b/sandbox/win/sandbox_win.gypi index 0b3f590028..abdc29cd50 100644 --- a/sandbox/win/sandbox_win.gypi +++ b/sandbox/win/sandbox_win.gypi @@ -6,6 +6,7 @@ 'target_defaults': { 'variables': { 'sandbox_windows_target': 0, + 'target_arch%': 'ia32', }, 'target_conditions': [ ['sandbox_windows_target==1', { @@ -133,6 +134,34 @@ 'src/window.cc', 'src/window.h', ], + 'target_conditions': [ + ['target_arch=="x64"', { + 'sources': [ + 'src/interceptors_64.cc', + 'src/interceptors_64.h', + 'src/resolver_64.cc', + 'src/service_resolver_64.cc', + 'src/Wow64_64.cc', + ], + }], + ['target_arch=="ia32"', { + 'sources': [ + 'src/resolver_32.cc', + 'src/service_resolver_32.cc', + 'src/sidestep_resolver.cc', + 'src/sidestep_resolver.h', + 'src/sidestep\ia32_modrm_map.cpp', + 'src/sidestep\ia32_opcode_map.cpp', + 'src/sidestep\mini_disassembler_types.h', + 'src/sidestep\mini_disassembler.cpp', + 'src/sidestep\mini_disassembler.h', + 'src/sidestep\preamble_patcher_with_stub.cpp', + 'src/sidestep\preamble_patcher.h', + 'src/Wow64.cc', + 'src/Wow64.h', + ], + }], + ], }], ], }, @@ -151,22 +180,6 @@ 'export_dependent_settings': [ '../base/base.gyp:base', ], - 'sources': [ - # Files that are used by the 32-bit version of Windows sandbox only. - 'src/resolver_32.cc', - 'src/service_resolver_32.cc', - 'src/sidestep_resolver.cc', - 'src/sidestep_resolver.h', - 'src/sidestep\ia32_modrm_map.cpp', - 'src/sidestep\ia32_opcode_map.cpp', - 'src/sidestep\mini_disassembler_types.h', - 'src/sidestep\mini_disassembler.cpp', - 'src/sidestep\mini_disassembler.h', - 'src/sidestep\preamble_patcher_with_stub.cpp', - 'src/sidestep\preamble_patcher.h', - 'src/Wow64.cc', - 'src/Wow64.h', - ], 'include_dirs': [ '../..', ], @@ -187,43 +200,6 @@ }, }, { - 'target_name': 'sandbox_win64', - 'type': 'static_library', - 'variables': { - 'sandbox_windows_target': 1, - }, - 'dependencies': [ - '../testing/gtest.gyp:gtest', - '../base/base.gyp:base_nacl_win64', - '../base/base.gyp:base_static_win64', - ], - 'configurations': { - 'Common_Base': { - 'msvs_target_platform': 'x64', - }, - }, - 'sources': [ - # Files that are used by the 64-bit version of Windows sandbox only. - 'src/interceptors_64.cc', - 'src/interceptors_64.h', - 'src/resolver_64.cc', - 'src/service_resolver_64.cc', - 'src/Wow64_64.cc', - ], - 'include_dirs': [ - '../..', - ], - 'direct_dependent_settings': { - 'include_dirs': [ - 'src', - '../..', - ], - }, - 'defines': [ - '<@(nacl_win64_defines)', - ] - }, - { 'target_name': 'sbox_integration_tests', 'type': 'executable', 'dependencies': [ @@ -233,6 +209,7 @@ 'sources': [ 'src/app_container_test.cc', 'src/file_policy_test.cc', + 'src/handle_inheritance_test.cc', 'src/handle_policy_test.cc', 'tests/integration_tests/integration_tests_test.cc', 'src/handle_closer_test.cc', @@ -344,4 +321,40 @@ ], }, ], + 'conditions': [ + ['OS=="win" and target_arch=="ia32"', { + 'targets': [ + { + 'target_name': 'sandbox_win64', + 'type': 'static_library', + 'variables': { + 'sandbox_windows_target': 1, + 'target_arch': 'x64', + }, + 'dependencies': [ + '../testing/gtest.gyp:gtest', + '../base/base.gyp:base_nacl_win64', + '../base/base.gyp:base_static_win64', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + 'include_dirs': [ + '../..', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'src', + '../..', + ], + }, + 'defines': [ + '<@(nacl_win64_defines)', + ] + }, + ], + }], + ], } diff --git a/sandbox/win/src/broker_services.cc b/sandbox/win/src/broker_services.cc index 65a7b2b40b..921eb4f89e 100644 --- a/sandbox/win/src/broker_services.cc +++ b/sandbox/win/src/broker_services.cc @@ -309,10 +309,11 @@ ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, HANDLE job_temp; result = policy_base->MakeJobObject(&job_temp); - base::win::ScopedHandle job(job_temp); if (SBOX_ALL_OK != result) return result; + base::win::ScopedHandle job(job_temp); + // Initialize the startup information from the policy. base::win::StartupInformation startup_info; string16 desktop = policy_base->GetAlternateDesktop(); @@ -321,6 +322,7 @@ ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, const_cast<wchar_t*>(desktop.c_str()); } + bool inherit_handles = false; if (base::win::GetVersion() >= base::win::VERSION_VISTA) { int attribute_count = 0; const AppContainerAttributes* app_container = @@ -335,6 +337,18 @@ ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, if (mitigations) ++attribute_count; + HANDLE stdout_handle = policy_base->GetStdoutHandle(); + HANDLE stderr_handle = policy_base->GetStderrHandle(); + HANDLE inherit_handle_list[2]; + int inherit_handle_count = 0; + if (stdout_handle != INVALID_HANDLE_VALUE) + inherit_handle_list[inherit_handle_count++] = stdout_handle; + // Handles in the list must be unique. + if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE) + inherit_handle_list[inherit_handle_count++] = stderr_handle; + if (inherit_handle_count) + ++attribute_count; + if (!startup_info.InitializeProcThreadAttributeList(attribute_count)) return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; @@ -351,6 +365,22 @@ ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; } } + + if (inherit_handle_count) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + inherit_handle_list, + sizeof(inherit_handle_list[0]) * inherit_handle_count)) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES; + startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE; + startup_info.startup_info()->hStdOutput = stdout_handle; + startup_info.startup_info()->hStdError = stderr_handle; + // Allowing inheritance of handles is only secure now that we + // have limited which handles will be inherited. + inherit_handles = true; + } } // Construct the thread pool here in case it is expensive. @@ -366,8 +396,8 @@ ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, job, thread_pool_); - DWORD win_result = target->Create(exe_path, command_line, startup_info, - &process_info); + DWORD win_result = target->Create(exe_path, command_line, inherit_handles, + startup_info, &process_info); if (ERROR_SUCCESS != win_result) return SpawnCleanup(target, win_result); diff --git a/sandbox/win/src/eat_resolver.cc b/sandbox/win/src/eat_resolver.cc index 8e81820090..154bfcfbdd 100644 --- a/sandbox/win/src/eat_resolver.cc +++ b/sandbox/win/src/eat_resolver.cc @@ -41,7 +41,9 @@ NTSTATUS EatResolverThunk::Setup(const void* target_module, return STATUS_BUFFER_TOO_SMALL; AutoProtectMemory memory; - memory.ChangeProtection(eat_entry_, sizeof(DWORD), PAGE_READWRITE); + ret = memory.ChangeProtection(eat_entry_, sizeof(DWORD), PAGE_READWRITE); + if (!NT_SUCCESS(ret)) + return ret; // Perform the patch. #pragma warning(push) diff --git a/sandbox/win/src/handle_inheritance_test.cc b/sandbox/win/src/handle_inheritance_test.cc new file mode 100644 index 0000000000..5523a6c64a --- /dev/null +++ b/sandbox/win/src/handle_inheritance_test.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdio.h> + +#include "base/file_util.h" +#include "base/win/windows_version.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int HandleInheritanceTests_PrintToStdout(int argc, + wchar_t** argv) { + printf("Example output to stdout\n"); + return SBOX_TEST_SUCCEEDED; +} + +TEST(HandleInheritanceTests, TestStdoutInheritance) { + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + SECURITY_ATTRIBUTES attrs = {}; + attrs.nLength = sizeof(attrs); + attrs.lpSecurityDescriptor = NULL; + attrs.bInheritHandle = TRUE; + HANDLE file_handle = CreateFile( + temp_file_name, GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, + &attrs, OPEN_EXISTING, 0, NULL); + EXPECT_NE(file_handle, INVALID_HANDLE_VALUE); + + TestRunner runner; + EXPECT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetStdoutHandle(file_handle)); + int result = runner.RunTest(L"HandleInheritanceTests_PrintToStdout"); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, result); + EXPECT_TRUE(::CloseHandle(file_handle)); + + std::string data; + EXPECT_TRUE(file_util::ReadFileToString(base::FilePath(temp_file_name), + &data)); + // Redirection uses a feature that was added in Windows Vista. + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + EXPECT_EQ("Example output to stdout\r\n", data); + } else { + EXPECT_EQ("", data); + } + + EXPECT_TRUE(::DeleteFile(temp_file_name)); +} + +} diff --git a/sandbox/win/src/policy_engine_opcodes.h b/sandbox/win/src/policy_engine_opcodes.h index f74ce31a17..306e67761c 100644 --- a/sandbox/win/src/policy_engine_opcodes.h +++ b/sandbox/win/src/policy_engine_opcodes.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef SANDBOX_SRC_POLICY_ENGINE_OPCODES_H__ -#define SANDBOX_SRC_POLICY_ENGINE_OPCODES_H__ +#ifndef SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ +#define SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ #include "sandbox/win/src/policy_engine_params.h" #include "base/basictypes.h" @@ -287,6 +287,11 @@ class OpcodeFactory { memory_bottom_ = &memory_top_[memory_size]; } + // Returns the available memory to make opcodes. + size_t memory_size() const { + return memory_bottom_ - memory_top_; + } + // Creates an OpAlwaysFalse opcode. PolicyOpcode* MakeOpAlwaysFalse(uint32 options); @@ -358,11 +363,6 @@ class OpcodeFactory { // returns the displacement with respect to start. ptrdiff_t AllocRelative(void* start, const wchar_t* str, size_t lenght); - // Returns the available memory to make opcodes. - size_t memory_size() const { - return memory_bottom_ - memory_top_; - } - // Points to the lowest currently available address of the memory // used to make the opcodes. This pointer increments as opcodes are made. char* memory_top_; @@ -377,4 +377,4 @@ class OpcodeFactory { } // namespace sandbox -#endif // SANDBOX_SRC_POLICY_ENGINE_OPCODES_H__ +#endif // SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ diff --git a/sandbox/win/src/policy_low_level.cc b/sandbox/win/src/policy_low_level.cc index 8431bc0dd9..686caa197b 100644 --- a/sandbox/win/src/policy_low_level.cc +++ b/sandbox/win/src/policy_low_level.cc @@ -136,9 +136,9 @@ PolicyRule::PolicyRule(const PolicyRule& other) { memcpy(buffer_, other.buffer_, buffer_size); char* opcode_buffer = reinterpret_cast<char*>(&buffer_->opcodes[0]); - char* buffer_end = &opcode_buffer[kRuleBufferSize + sizeof(PolicyOpcode)]; char* next_opcode = &opcode_buffer[GetOpcodeCount() * sizeof(PolicyOpcode)]; - opcode_factory_ = new OpcodeFactory(next_opcode, buffer_end - next_opcode); + opcode_factory_ = + new OpcodeFactory(next_opcode, other.opcode_factory_->memory_size()); } // This function get called from a simple state machine implemented in diff --git a/sandbox/win/src/policy_low_level_unittest.cc b/sandbox/win/src/policy_low_level_unittest.cc index 4a2cf4be39..2b5d0f7a6e 100644 --- a/sandbox/win/src/policy_low_level_unittest.cc +++ b/sandbox/win/src/policy_low_level_unittest.cc @@ -572,4 +572,46 @@ TEST(PolicyEngineTest, ThreeRulesTest) { delete [] reinterpret_cast<char*>(policy); } +TEST(PolicyEngineTest, PolicyRuleCopyConstructorTwoStrings) { + SetupNtdllImports(); + // Both pr_orig and pr_copy should allow hello.* but not *.txt files. + PolicyRule pr_orig(ASK_BROKER); + EXPECT_TRUE(pr_orig.AddStringMatch(IF, 0, L"hello.*", CASE_SENSITIVE)); + + PolicyRule pr_copy(pr_orig); + EXPECT_TRUE(pr_orig.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + EXPECT_TRUE(pr_copy.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(1, &pr_orig)); + EXPECT_TRUE(policyGen.AddRule(2, &pr_copy)); + EXPECT_TRUE(policyGen.Done()); + + wchar_t* name = NULL; + POLPARAMS_BEGIN(eval_params) + POLPARAM(name) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev_orig(policy->entry[1]); + name = L"domo.txt"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + name = L"hello.bmp"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev_orig.GetAction()); + + PolicyProcessor pol_ev_copy(policy->entry[2]); + name = L"domo.txt"; + result = pol_ev_copy.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + name = L"hello.bmp"; + result = pol_ev_copy.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev_copy.GetAction()); +} } // namespace sandbox diff --git a/sandbox/win/src/process_mitigations.cc b/sandbox/win/src/process_mitigations.cc index f5694790ff..b9148549f9 100644 --- a/sandbox/win/src/process_mitigations.cc +++ b/sandbox/win/src/process_mitigations.cc @@ -191,6 +191,8 @@ void ConvertProcessMitigationsToPolicy(MitigationFlags flags, if (version <= base::win::VERSION_VISTA) return; + // DEP and SEHOP are not valid for 64-bit Windows +#if !defined(_WIN64) if (flags & MITIGATION_DEP) { *policy_flags |= PROCESS_CREATION_MITIGATION_POLICY_DEP_ENABLE; if (!(flags & MITIGATION_DEP_NO_ATL_THUNK)) @@ -199,6 +201,7 @@ void ConvertProcessMitigationsToPolicy(MitigationFlags flags, if (flags & MITIGATION_SEHOP) *policy_flags |= PROCESS_CREATION_MITIGATION_POLICY_SEHOP_ENABLE; +#endif // Win 7 if (version < base::win::VERSION_WIN8) diff --git a/sandbox/win/src/process_mitigations_test.cc b/sandbox/win/src/process_mitigations_test.cc index 7ea670b350..69b158d6bd 100644 --- a/sandbox/win/src/process_mitigations_test.cc +++ b/sandbox/win/src/process_mitigations_test.cc @@ -140,7 +140,6 @@ TEST(ProcessMitigationsTest, CheckWin8) { SBOX_TESTS_COMMAND int CheckDep(int argc, wchar_t **argv) { -#if !defined(_WIN64) // DEP is always enabled on 64-bit. GetProcessDEPPolicyFunction get_process_dep_policy = reinterpret_cast<GetProcessDEPPolicyFunction>( ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), @@ -183,11 +182,11 @@ SBOX_TESTS_COMMAND int CheckDep(int argc, wchar_t **argv) { return SBOX_TEST_FOURTH_ERROR; } } -#endif return SBOX_TEST_SUCCEEDED; } +#if !defined(_WIN64) // DEP is always enabled on 64-bit. TEST(ProcessMitigationsTest, CheckDep) { if (!IsXPSP2OrLater() || base::win::GetVersion() > base::win::VERSION_WIN7) return; @@ -202,6 +201,7 @@ TEST(ProcessMitigationsTest, CheckDep) { SBOX_ALL_OK); EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckDep")); } +#endif } // namespace sandbox diff --git a/sandbox/win/src/sandbox.cc b/sandbox/win/src/sandbox.cc index 3344ba4dc3..9e4ab08a09 100644 --- a/sandbox/win/src/sandbox.cc +++ b/sandbox/win/src/sandbox.cc @@ -9,13 +9,6 @@ #include "sandbox/win/src/broker_services.h" #include "sandbox/win/src/target_services.h" -#if defined(_WIN64) && !defined(NACL_WIN64) -// We allow building this code for Win64 as part of NaCl to enable development -#error Sandbox code was not tested on 64-bit Windows. See \ - http://crbug.com/27218 for details and progress log. -#endif - - namespace sandbox { // The section for IPC and policy. SANDBOX_INTERCEPT HANDLE g_shared_section = NULL; diff --git a/sandbox/win/src/sandbox_policy.h b/sandbox/win/src/sandbox_policy.h index f0fc2bc093..733356a39b 100644 --- a/sandbox/win/src/sandbox_policy.h +++ b/sandbox/win/src/sandbox_policy.h @@ -187,6 +187,14 @@ class TargetPolicy { // refuse to perform the interception. virtual void SetStrictInterceptions() = 0; + // Set the handles the target process should inherit for stdout and + // stderr. The handles the caller passes must remain valid for the + // lifetime of the policy object. This only has an effect on + // Windows Vista and later versions. These methods accept pipe and + // file handles, but not console handles. + virtual ResultCode SetStdoutHandle(HANDLE handle) = 0; + virtual ResultCode SetStderrHandle(HANDLE handle) = 0; + // Adds a policy rule effective for processes spawned using this policy. // subsystem: One of the above enumerated windows subsystems. // semantics: One of the above enumerated FileSemantics. diff --git a/sandbox/win/src/sandbox_policy_base.cc b/sandbox/win/src/sandbox_policy_base.cc index 10ac6427c5..aaff0a1541 100644 --- a/sandbox/win/src/sandbox_policy_base.cc +++ b/sandbox/win/src/sandbox_policy_base.cc @@ -33,6 +33,7 @@ #include "sandbox/win/src/window.h" namespace { + // The standard windows size for one memory page. const size_t kOneMemPage = 4096; // The IPC and Policy shared memory sizes. @@ -49,6 +50,19 @@ sandbox::PolicyGlobal* MakeBrokerPolicyMemory() { policy->data_size = kTotalPolicySz - sizeof(sandbox::PolicyGlobal); return policy; } + +bool IsInheritableHandle(HANDLE handle) { + if (!handle) + return false; + if (handle == INVALID_HANDLE_VALUE) + return false; + // File handles (FILE_TYPE_DISK) and pipe handles are known to be + // inheritable. Console handles (FILE_TYPE_CHAR) are not + // inheritable via PROC_THREAD_ATTRIBUTE_HANDLE_LIST. + DWORD handle_type = GetFileType(handle); + return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE; +} + } namespace sandbox { @@ -70,6 +84,8 @@ PolicyBase::PolicyBase() use_alternate_winstation_(false), file_system_init_(false), relaxed_interceptions_(true), + stdout_handle_(INVALID_HANDLE_VALUE), + stderr_handle_(INVALID_HANDLE_VALUE), integrity_level_(INTEGRITY_LEVEL_LAST), delayed_integrity_level_(INTEGRITY_LEVEL_LAST), mitigations_(0), @@ -308,6 +324,20 @@ void PolicyBase::SetStrictInterceptions() { relaxed_interceptions_ = false; } +ResultCode PolicyBase::SetStdoutHandle(HANDLE handle) { + if (!IsInheritableHandle(handle)) + return SBOX_ERROR_BAD_PARAMS; + stdout_handle_ = handle; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetStderrHandle(HANDLE handle) { + if (!IsInheritableHandle(handle)) + return SBOX_ERROR_BAD_PARAMS; + stderr_handle_ = handle; + return SBOX_ALL_OK; +} + ResultCode PolicyBase::AddRule(SubSystem subsystem, Semantics semantics, const wchar_t* pattern) { if (NULL == policy_) { @@ -567,6 +597,14 @@ EvalResult PolicyBase::EvalPolicy(int service, return DENY_ACCESS; } +HANDLE PolicyBase::GetStdoutHandle() { + return stdout_handle_; +} + +HANDLE PolicyBase::GetStderrHandle() { + return stderr_handle_; +} + // We service IPC_PING_TAG message which is a way to test a round trip of the // IPC subsystem. We receive a integer cookie and we are expected to return the // cookie times two (or three) and the current tick count. diff --git a/sandbox/win/src/sandbox_policy_base.h b/sandbox/win/src/sandbox_policy_base.h index efac6a00c5..0bb906e240 100644 --- a/sandbox/win/src/sandbox_policy_base.h +++ b/sandbox/win/src/sandbox_policy_base.h @@ -58,6 +58,8 @@ class PolicyBase : public Dispatcher, public TargetPolicy { MitigationFlags flags) OVERRIDE; virtual MitigationFlags GetDelayedProcessMitigations() OVERRIDE; virtual void SetStrictInterceptions() OVERRIDE; + virtual ResultCode SetStdoutHandle(HANDLE handle) OVERRIDE; + virtual ResultCode SetStderrHandle(HANDLE handle) OVERRIDE; virtual ResultCode AddRule(SubSystem subsystem, Semantics semantics, const wchar_t* pattern) OVERRIDE; virtual ResultCode AddDllToUnload(const wchar_t* dll_name); @@ -90,6 +92,9 @@ class PolicyBase : public Dispatcher, public TargetPolicy { EvalResult EvalPolicy(int service, CountedParameterSetBase* params); + HANDLE GetStdoutHandle(); + HANDLE GetStderrHandle(); + private: ~PolicyBase(); @@ -123,6 +128,8 @@ class PolicyBase : public Dispatcher, public TargetPolicy { // Helps the file system policy initialization. bool file_system_init_; bool relaxed_interceptions_; + HANDLE stdout_handle_; + HANDLE stderr_handle_; IntegrityLevel integrity_level_; IntegrityLevel delayed_integrity_level_; MitigationFlags mitigations_; diff --git a/sandbox/win/src/security_level.h b/sandbox/win/src/security_level.h index 7ed1713a6f..f8e72b3ce0 100644 --- a/sandbox/win/src/security_level.h +++ b/sandbox/win/src/security_level.h @@ -5,6 +5,8 @@ #ifndef SANDBOX_SRC_SECURITY_LEVEL_H_ #define SANDBOX_SRC_SECURITY_LEVEL_H_ +#include "base/basictypes.h" + namespace sandbox { // List of all the integrity levels supported in the sandbox. This is used diff --git a/sandbox/win/src/sidestep_resolver.cc b/sandbox/win/src/sidestep_resolver.cc index 47c0f5623f..828c000a7c 100644 --- a/sandbox/win/src/sidestep_resolver.cc +++ b/sandbox/win/src/sidestep_resolver.cc @@ -49,7 +49,9 @@ NTSTATUS SidestepResolverThunk::Setup(const void* target_module, return STATUS_BUFFER_TOO_SMALL; AutoProtectMemory memory; - memory.ChangeProtection(target_, kSizeOfSidestepStub, PAGE_READWRITE); + ret = memory.ChangeProtection(target_, kSizeOfSidestepStub, PAGE_READWRITE); + if (!NT_SUCCESS(ret)) + return ret; sidestep::SideStepError rv = sidestep::PreamblePatcher::Patch( target_, reinterpret_cast<void*>(&thunk->internal_thunk), thunk_storage, diff --git a/sandbox/win/src/target_process.cc b/sandbox/win/src/target_process.cc index 5de6ee0ce9..9300ccee07 100644 --- a/sandbox/win/src/target_process.cc +++ b/sandbox/win/src/target_process.cc @@ -110,6 +110,7 @@ TargetProcess::~TargetProcess() { // object. DWORD TargetProcess::Create(const wchar_t* exe_path, const wchar_t* command_line, + bool inherit_handles, const base::win::StartupInformation& startup_info, base::win::ScopedProcessInformation* target_info) { exe_name_.reset(_wcsdup(exe_path)); @@ -137,7 +138,7 @@ DWORD TargetProcess::Create(const wchar_t* exe_path, cmd_line.get(), NULL, // No security attribute. NULL, // No thread attribute. - FALSE, // Do not inherit handles. + inherit_handles, flags, NULL, // Use the environment of the caller. NULL, // Use current directory of the caller. @@ -198,7 +199,7 @@ DWORD TargetProcess::Create(const wchar_t* exe_path, } base_address_ = GetBaseAddress(exe_path, entry_point); - sandbox_process_info_.Swap(&process_info); + sandbox_process_info_.Set(process_info.Take()); return win_result; } @@ -324,10 +325,11 @@ void TargetProcess::Terminate() { ::TerminateProcess(sandbox_process_info_.process_handle(), 0); } - TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address) { TargetProcess* target = new TargetProcess(NULL, NULL, NULL, NULL); - target->sandbox_process_info_.Receive()->hProcess = process; + PROCESS_INFORMATION process_info = {}; + process_info.hProcess = process; + target->sandbox_process_info_.Set(process_info); target->base_address_ = base_address; return target; } diff --git a/sandbox/win/src/target_process.h b/sandbox/win/src/target_process.h index be4cd2a0e1..0b72d98f23 100644 --- a/sandbox/win/src/target_process.h +++ b/sandbox/win/src/target_process.h @@ -47,6 +47,7 @@ class TargetProcess { // Creates the new target process. The process is created suspended. DWORD Create(const wchar_t* exe_path, const wchar_t* command_line, + bool inherit_handles, const base::win::StartupInformation& startup_info, base::win::ScopedProcessInformation* target_info); diff --git a/sandbox/win/tests/common/controller.cc b/sandbox/win/tests/common/controller.cc index 1de20924df..b66a6c850a 100644 --- a/sandbox/win/tests/common/controller.cc +++ b/sandbox/win/tests/common/controller.cc @@ -14,7 +14,7 @@ namespace { -static const int kDefaultTimeout = 10000; +static const int kDefaultTimeout = 60000; // Constructs a full path to a file inside the system32 folder. std::wstring MakePathToSys32(const wchar_t* name, bool is_obj_man_path) { |