summaryrefslogtreecommitdiff
path: root/sandbox
diff options
context:
space:
mode:
authorTorne (Richard Coles) <torne@google.com>2013-03-28 15:31:22 +0000
committerTorne (Richard Coles) <torne@google.com>2013-03-28 15:31:22 +0000
commit2a99a7e74a7f215066514fe81d2bfa6639d9eddd (patch)
tree7c2d04841fcd599fd83b0f0bb1100e1c89a35bae /sandbox
parent61c449bbbb53310a8c041d8cefdd6b01a126cc7e (diff)
downloadchromium_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')
-rw-r--r--sandbox/linux/sandbox_linux.gypi133
-rw-r--r--sandbox/linux/sandbox_linux_test_sources.gypi38
-rw-r--r--sandbox/linux/seccomp-bpf/Makefile4
-rw-r--r--sandbox/linux/seccomp-bpf/bpf_tests.h48
-rw-r--r--sandbox/linux/seccomp-bpf/codegen.cc86
-rw-r--r--sandbox/linux/seccomp-bpf/codegen.h9
-rw-r--r--sandbox/linux/seccomp-bpf/demo.cc256
-rw-r--r--sandbox/linux/seccomp-bpf/die.cc27
-rw-r--r--sandbox/linux/seccomp-bpf/die.h17
-rw-r--r--sandbox/linux/seccomp-bpf/errorcode.cc7
-rw-r--r--sandbox/linux/seccomp-bpf/errorcode.h115
-rw-r--r--sandbox/linux/seccomp-bpf/errorcode_unittest.cc24
-rw-r--r--sandbox/linux/seccomp-bpf/linux_seccomp.h197
-rw-r--r--sandbox/linux/seccomp-bpf/port.h36
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.cc956
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.h386
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc1495
-rw-r--r--sandbox/linux/seccomp-bpf/syscall.cc243
-rw-r--r--sandbox/linux/seccomp-bpf/syscall.h123
-rw-r--r--sandbox/linux/seccomp-bpf/syscall_iterator.cc12
-rw-r--r--sandbox/linux/seccomp-bpf/syscall_iterator.h4
-rw-r--r--sandbox/linux/seccomp-bpf/syscall_unittest.cc185
-rw-r--r--sandbox/linux/seccomp-bpf/trap.cc345
-rw-r--r--sandbox/linux/seccomp-bpf/trap.h120
-rw-r--r--sandbox/linux/seccomp-bpf/util.cc164
-rw-r--r--sandbox/linux/seccomp-bpf/util.h19
-rw-r--r--sandbox/linux/seccomp-bpf/verifier.cc415
-rw-r--r--sandbox/linux/seccomp-bpf/verifier.h31
-rw-r--r--sandbox/linux/services/android_arm_ucontext.h32
-rw-r--r--sandbox/linux/services/android_i386_ucontext.h79
-rw-r--r--sandbox/linux/services/android_ucontext.h22
-rw-r--r--sandbox/linux/services/broker_process.cc358
-rw-r--r--sandbox/linux/services/broker_process.h72
-rw-r--r--sandbox/linux/services/broker_process_unittest.cc311
-rw-r--r--sandbox/linux/services/libc_urandom_override.cc2
-rw-r--r--sandbox/linux/suid/client/setuid_sandbox_client.cc2
-rw-r--r--sandbox/linux/suid/linux_util.c65
-rw-r--r--sandbox/linux/tests/main.cc1
-rw-r--r--sandbox/linux/tests/unit_tests.cc189
-rw-r--r--sandbox/linux/tests/unit_tests.h76
-rw-r--r--sandbox/sandbox.gyp7
-rw-r--r--sandbox/sandbox.target.linux-arm.mk (renamed from sandbox/sandbox.target.mk)4
-rw-r--r--sandbox/sandbox.target.linux-x86.mk40
-rw-r--r--sandbox/sandbox_services.target.linux-arm.mk149
-rw-r--r--sandbox/sandbox_services.target.linux-x86.mk146
-rw-r--r--sandbox/sandbox_services_headers.target.linux-arm.mk127
-rw-r--r--sandbox/sandbox_services_headers.target.linux-x86.mk127
-rw-r--r--sandbox/seccomp_bpf.target.linux-arm.mk157
-rw-r--r--sandbox/seccomp_bpf.target.linux-x86.mk154
-rw-r--r--sandbox/win/sandbox_win.gypi119
-rw-r--r--sandbox/win/src/broker_services.cc36
-rw-r--r--sandbox/win/src/eat_resolver.cc4
-rw-r--r--sandbox/win/src/handle_inheritance_test.cc55
-rw-r--r--sandbox/win/src/policy_engine_opcodes.h16
-rw-r--r--sandbox/win/src/policy_low_level.cc4
-rw-r--r--sandbox/win/src/policy_low_level_unittest.cc42
-rw-r--r--sandbox/win/src/process_mitigations.cc3
-rw-r--r--sandbox/win/src/process_mitigations_test.cc4
-rw-r--r--sandbox/win/src/sandbox.cc7
-rw-r--r--sandbox/win/src/sandbox_policy.h8
-rw-r--r--sandbox/win/src/sandbox_policy_base.cc38
-rw-r--r--sandbox/win/src/sandbox_policy_base.h7
-rw-r--r--sandbox/win/src/security_level.h2
-rw-r--r--sandbox/win/src/sidestep_resolver.cc4
-rw-r--r--sandbox/win/src/target_process.cc10
-rw-r--r--sandbox/win/src/target_process.h1
-rw-r--r--sandbox/win/tests/common/controller.cc2
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) {