diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/sandbox/linux | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/sandbox/linux')
74 files changed, 4590 insertions, 1260 deletions
diff --git a/chromium/sandbox/linux/BUILD.gn b/chromium/sandbox/linux/BUILD.gn new file mode 100644 index 00000000000..0cc9be4562a --- /dev/null +++ b/chromium/sandbox/linux/BUILD.gn @@ -0,0 +1,314 @@ +# Copyright 2014 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. + +import("//build/config/features.gni") + +declare_args() { + compile_suid_client = is_linux + + compile_credentials = is_linux + + compile_seccomp_bpf_demo = + (is_linux && (cpu_arch == "x86" || cpu_arch == "x64")) +} + +# We have two principal targets: sandbox and sandbox_linux_unittests +# All other targets are listed as dependencies. +# There is one notable exception: for historical reasons, chrome_sandbox is +# the setuid sandbox and is its own target. + +group("sandbox") { + deps = [ + ":sandbox_services", + ] + + if (compile_suid_client) { + deps += [ ":suid_sandbox_client" ] + } + if (use_seccomp_bpf) { + deps += [ + ":seccomp_bpf", + ":seccomp_bpf_helpers", + ] + } +} + +source_set("sandbox_linux_test_utils") { + sources = [ + "tests/sandbox_test_runner.cc", + "tests/sandbox_test_runner.h", + "tests/sandbox_test_runner_function_pointer.cc", + "tests/sandbox_test_runner_function_pointer.h", + "tests/test_utils.cc", + "tests/test_utils.h", + "tests/unit_tests.cc", + "tests/unit_tests.h", + ] + + deps = [ + "//testing/gtest", + ] + + if (use_seccomp_bpf) { + sources += [ + "seccomp-bpf/bpf_tester_compatibility_delegate.h", + "seccomp-bpf/bpf_tests.h", + "seccomp-bpf/sandbox_bpf_test_runner.cc", + "seccomp-bpf/sandbox_bpf_test_runner.h", + ] + deps += [ + ":seccomp_bpf", + ] + } +} + +# The main sandboxing test target. +test("sandbox_linux_unittests") { + sources = [ + "tests/main.cc", + "tests/unit_tests_unittest.cc", + "services/broker_process_unittest.cc", + "services/scoped_process_unittest.cc", + "services/thread_helpers_unittests.cc", + "services/yama_unittests.cc", + ] + + deps = [ + ":sandbox", + ":sandbox_linux_test_utils", + "//base", + "//base/test:test_support", + "//testing/gtest", + ] + + if (compile_suid_client) { + sources += [ + "suid/client/setuid_sandbox_client_unittest.cc", + ] + } + if (use_seccomp_bpf) { + sources += [ + "seccomp-bpf-helpers/baseline_policy_unittest.cc", + "seccomp-bpf/bpf_tests_unittest.cc", + "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", + ] + } + if (compile_credentials) { + sources += [ + "services/credentials_unittest.cc", + "services/unix_domain_socket_unittest.cc", + ] + } +} + +# TODO(GYP) Android version of this test. +# { +# # 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 == "android"", { +# "dependencies": [ +# "../testing/android/native_test.gyp:native_test_native_code", +# ], +# }], +# ], +# }, + +component("seccomp_bpf") { + sources = [ + "seccomp-bpf/basicblock.cc", + "seccomp-bpf/basicblock.h", + "seccomp-bpf/codegen.cc", + "seccomp-bpf/codegen.h", + "seccomp-bpf/die.cc", + "seccomp-bpf/die.h", + "seccomp-bpf/errorcode.cc", + "seccomp-bpf/errorcode.h", + "seccomp-bpf/instruction.h", + "seccomp-bpf/linux_seccomp.h", + "seccomp-bpf/sandbox_bpf.cc", + "seccomp-bpf/sandbox_bpf.h", + "seccomp-bpf/sandbox_bpf_compatibility_policy.h", + "seccomp-bpf/sandbox_bpf_policy.cc", + "seccomp-bpf/sandbox_bpf_policy.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", + ] + defines = [ "SANDBOX_IMPLEMENTATION" ] + + deps = [ + ":sandbox_services_headers", + "//base", + ] +} + +component("seccomp_bpf_helpers") { + sources = [ + "seccomp-bpf-helpers/baseline_policy.cc", + "seccomp-bpf-helpers/baseline_policy.h", + "seccomp-bpf-helpers/sigsys_handlers.cc", + "seccomp-bpf-helpers/sigsys_handlers.h", + "seccomp-bpf-helpers/syscall_parameters_restrictions.cc", + "seccomp-bpf-helpers/syscall_parameters_restrictions.h", + "seccomp-bpf-helpers/syscall_sets.cc", + "seccomp-bpf-helpers/syscall_sets.h", + ] + defines = [ "SANDBOX_IMPLEMENTATION" ] + + deps = [ + "//base", + ":seccomp_bpf", + ] +} + +if (compile_seccomp_bpf_demo) { + # A demonstration program for the seccomp-bpf sandbox. + executable("seccomp_bpf_demo") { + sources = [ + "seccomp-bpf/demo.cc", + ] + deps = [ + ":seccomp_bpf", + ] + } +} + +# The setuid sandbox for Linux. +executable("chrome_sandbox") { + sources = [ + "suid/common/sandbox.h", + "suid/common/suid_unsafe_environment_variables.h", + "suid/linux_util.c", + "suid/linux_util.h", + "suid/process_util.h", + "suid/process_util_linux.c", + "suid/sandbox.c", + ] + + cflags = [ + # For ULLONG_MAX + "-std=gnu99", + # These files have a suspicious comparison. + # TODO fix this and re-enable this warning. + "-Wno-sign-compare", + ] +} + +component("sandbox_services") { + sources = [ + "services/broker_process.cc", + "services/broker_process.h", + "services/init_process_reaper.cc", + "services/init_process_reaper.h", + "services/scoped_process.cc", + "services/scoped_process.h", + "services/thread_helpers.cc", + "services/thread_helpers.h", + "services/yama.h", + "services/yama.cc", + ] + + defines = [ "SANDBOX_IMPLEMENTATION" ] + + if (compile_credentials) { + sources += [ + "services/credentials.cc", + "services/credentials.h", + ] + # For capabilities.cc. + configs += [ "//build/config/linux:libcap" ] + } + + deps = [ + "//base", + ] +} + +source_set("sandbox_services_headers") { + sources = [ + "services/android_arm_ucontext.h", + "services/android_futex.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", + ] +} + +# We make this its own target so that it does not interfere with our tests. +source_set("libc_urandom_override") { + sources = [ + "services/libc_urandom_override.cc", + "services/libc_urandom_override.h", + ] + deps = [ + "//base", + ] +} + +component("suid_sandbox_client") { + sources = [ + "suid/common/sandbox.h", + "suid/common/suid_unsafe_environment_variables.h", + "suid/client/setuid_sandbox_client.cc", + "suid/client/setuid_sandbox_client.h", + ] + defines = [ "SANDBOX_IMPLEMENTATION" ] + + deps = [ + ":sandbox_services", + "//base", + ] +} + +if (is_android) { + # TODO(GYP) enable this. Needs an android_strip wrapper python script. + #action("sandbox_linux_unittests_stripped") { + # script = "android_stip.py" + # + # in_file = "$root_out_dir/sandbox_linux_unittests" + # + # out_file = "$root_out_dir/sandbox_linux_unittests_stripped" + # outputs = [ out_file ] + # + # args = [ + # rebase_path(in_file, root_build_dir), + # "-o", rebase_path(out_file, root_build_dir), + # ] + # + # deps = [ + # ":sandbox_linux_unittests", + # ] + #} + + # TODO(GYP) convert this. + # { + # 'target_name': 'sandbox_linux_jni_unittests_apk', + # 'type': 'none', + # 'variables': { + # 'test_suite_name': 'sandbox_linux_jni_unittests', + # }, + # 'dependencies': [ + # 'sandbox_linux_jni_unittests', + # ], + # 'includes': [ '../../build/apk_test.gypi' ], + # } +} diff --git a/chromium/sandbox/linux/DEPS b/chromium/sandbox/linux/DEPS new file mode 100644 index 00000000000..39128593449 --- /dev/null +++ b/chromium/sandbox/linux/DEPS @@ -0,0 +1,25 @@ +include_rules = [ + # First, exclude everything. + # Exclude a few dependencies that are included in the root DEPS and that we + # don't need. + # Sadly, there is no way to exclude all root DEPS since the root has no name. + "-ipc", + "-library_loaders", + "-third_party", + "-url", + # Make sure that each subdirectory has to declare its dependencies in + # sandbox/ explicitly. + "-sandbox/linux", + + # Second, add what we want to allow. + # Anything included from sandbox/linux must be declared after this line or in + # a more specific DEPS file. + # base/, build/ and testing/ are already included in the global DEPS file, + # but be explicit. + "+base", + "+build", + "+testing", + "+sandbox/sandbox_export.h", + # Everyone can use tests/ + "+sandbox/linux/tests", +] diff --git a/chromium/sandbox/linux/OWNERS b/chromium/sandbox/linux/OWNERS new file mode 100644 index 00000000000..35643d1565f --- /dev/null +++ b/chromium/sandbox/linux/OWNERS @@ -0,0 +1,3 @@ +cevans@chromium.org +jln@chromium.org +jorgelo@chromium.org diff --git a/chromium/sandbox/linux/sandbox_linux.gypi b/chromium/sandbox/linux/sandbox_linux.gypi index 0e211f6c320..9ddcf0c874c 100644 --- a/chromium/sandbox/linux/sandbox_linux.gypi +++ b/chromium/sandbox/linux/sandbox_linux.gypi @@ -12,13 +12,6 @@ 'compile_suid_client': 0, 'compile_credentials': 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, - }], ['OS=="linux" and (target_arch=="ia32" or target_arch=="x64")', { 'compile_seccomp_bpf_demo': 1, }, { @@ -40,8 +33,8 @@ 'targets': [ # We have two principal targets: sandbox and sandbox_linux_unittests # All other targets are listed as dependencies. - # FIXME(jln): for historial reasons, sandbox_linux is the setuid sandbox - # and is its own target. + # There is one notable exception: for historical reasons, chrome_sandbox is + # the setuid sandbox and is its own target. { 'target_name': 'sandbox', 'type': 'none', @@ -55,7 +48,7 @@ ], }], # Compile seccomp BPF when we support it. - [ 'compile_seccomp_bpf==1', { + [ 'use_seccomp_bpf==1', { 'dependencies': [ 'seccomp_bpf', 'seccomp_bpf_helpers', @@ -64,6 +57,39 @@ ], }, { + 'target_name': 'sandbox_linux_test_utils', + 'type': 'static_library', + 'dependencies': [ + '../testing/gtest.gyp:gtest', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'tests/sandbox_test_runner.cc', + 'tests/sandbox_test_runner.h', + 'tests/sandbox_test_runner_function_pointer.cc', + 'tests/sandbox_test_runner_function_pointer.h', + 'tests/test_utils.cc', + 'tests/test_utils.h', + 'tests/unit_tests.cc', + 'tests/unit_tests.h', + ], + 'conditions': [ + [ 'use_seccomp_bpf==1', { + 'sources': [ + 'seccomp-bpf/bpf_tester_compatibility_delegate.h', + 'seccomp-bpf/bpf_tests.h', + 'seccomp-bpf/sandbox_bpf_test_runner.cc', + 'seccomp-bpf/sandbox_bpf_test_runner.h', + ], + 'dependencies': [ + 'seccomp_bpf', + ] + }], + ], + }, + { # The main sandboxing test target. 'target_name': 'sandbox_linux_unittests', 'includes': [ @@ -80,21 +106,16 @@ ], 'type': 'shared_library', 'conditions': [ - [ 'OS == "android" and gtest_target_type == "shared_library"', { + [ 'OS == "android"', { 'dependencies': [ '../testing/android/native_test.gyp:native_test_native_code', ], - 'ldflags!': [ - # Remove warnings about text relocations, to prevent build - # failure. - '-Wl,--warn-shared-textrel' - ], }], ], }, { 'target_name': 'seccomp_bpf', - 'type': 'static_library', + 'type': '<(component)', 'sources': [ 'seccomp-bpf/basicblock.cc', 'seccomp-bpf/basicblock.h', @@ -108,6 +129,8 @@ 'seccomp-bpf/linux_seccomp.h', 'seccomp-bpf/sandbox_bpf.cc', 'seccomp-bpf/sandbox_bpf.h', + 'seccomp-bpf/sandbox_bpf_compatibility_policy.h', + 'seccomp-bpf/sandbox_bpf_policy.cc', 'seccomp-bpf/sandbox_bpf_policy.h', 'seccomp-bpf/syscall.cc', 'seccomp-bpf/syscall.h', @@ -122,13 +145,16 @@ '../base/base.gyp:base', 'sandbox_services_headers', ], + 'defines': [ + 'SANDBOX_IMPLEMENTATION', + ], 'include_dirs': [ '../..', ], }, { 'target_name': 'seccomp_bpf_helpers', - 'type': 'static_library', + 'type': '<(component)', 'sources': [ 'seccomp-bpf-helpers/baseline_policy.cc', 'seccomp-bpf-helpers/baseline_policy.h', @@ -140,6 +166,11 @@ 'seccomp-bpf-helpers/syscall_sets.h', ], 'dependencies': [ + '../base/base.gyp:base', + 'seccomp_bpf', + ], + 'defines': [ + 'SANDBOX_IMPLEMENTATION', ], 'include_dirs': [ '../..', @@ -185,20 +216,34 @@ 'include_dirs': [ '../..', ], + # Do not use any sanitizer tools with this binary. http://crbug.com/382766 + 'cflags/': [ + ['exclude', '-fsanitize'], + ], + 'ldflags/': [ + ['exclude', '-fsanitize'], + ], }, { 'target_name': 'sandbox_services', - 'type': 'static_library', + 'type': '<(component)', 'sources': [ 'services/broker_process.cc', 'services/broker_process.h', 'services/init_process_reaper.cc', 'services/init_process_reaper.h', + 'services/scoped_process.cc', + 'services/scoped_process.h', 'services/thread_helpers.cc', 'services/thread_helpers.h', + 'services/yama.h', + 'services/yama.cc', ], 'dependencies': [ '../base/base.gyp:base', ], + 'defines': [ + 'SANDBOX_IMPLEMENTATION', + ], 'conditions': [ ['compile_credentials==1', { 'sources': [ @@ -219,6 +264,7 @@ 'type': 'none', 'sources': [ 'services/android_arm_ucontext.h', + 'services/android_futex.h', 'services/android_ucontext.h', 'services/android_i386_ucontext.h', 'services/arm_linux_syscalls.h', @@ -248,13 +294,16 @@ }, { 'target_name': 'suid_sandbox_client', - 'type': 'static_library', + 'type': '<(component)', 'sources': [ 'suid/common/sandbox.h', 'suid/common/suid_unsafe_environment_variables.h', 'suid/client/setuid_sandbox_client.cc', 'suid/client/setuid_sandbox_client.h', ], + 'defines': [ + 'SANDBOX_IMPLEMENTATION', + ], 'dependencies': [ '../base/base.gyp:base', 'sandbox_services', @@ -265,18 +314,28 @@ }, ], 'conditions': [ - # Strategy copied from base_unittests_apk in base/base.gyp. - [ 'OS=="android" and gtest_target_type == "shared_library"', { + [ 'OS=="android"', { + 'targets': [ + { + 'target_name': 'sandbox_linux_unittests_stripped', + 'type': 'none', + 'dependencies': [ 'sandbox_linux_unittests' ], + 'actions': [{ + 'action_name': 'strip sandbox_linux_unittests', + 'inputs': [ '<(PRODUCT_DIR)/sandbox_linux_unittests' ], + 'outputs': [ '<(PRODUCT_DIR)/sandbox_linux_unittests_stripped' ], + 'action': [ '<(android_strip)', '<@(_inputs)', '-o', '<@(_outputs)' ], + }], + } + ], + }], + [ 'OS=="android"', { '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', @@ -285,5 +344,23 @@ } ], }], + ['test_isolation_mode != "noop"', { + 'targets': [ + { + 'target_name': 'sandbox_linux_unittests_run', + 'type': 'none', + 'dependencies': [ + 'sandbox_linux_unittests', + ], + 'includes': [ + '../../build/isolate.gypi', + '../sandbox_linux_unittests.isolate', + ], + 'sources': [ + '../sandbox_linux_unittests.isolate', + ], + }, + ], + }], ], } diff --git a/chromium/sandbox/linux/sandbox_linux_test_sources.gypi b/chromium/sandbox/linux/sandbox_linux_test_sources.gypi index a6a916fee45..bf414712132 100644 --- a/chromium/sandbox/linux/sandbox_linux_test_sources.gypi +++ b/chromium/sandbox/linux/sandbox_linux_test_sources.gypi @@ -7,7 +7,9 @@ { 'dependencies': [ 'sandbox', + 'sandbox_linux_test_utils', '../base/base.gyp:base', + '../base/base.gyp:test_support_base', '../testing/gtest.gyp:gtest', ], 'include_dirs': [ @@ -15,10 +17,11 @@ ], 'sources': [ 'tests/main.cc', - 'tests/unit_tests.cc', - 'tests/unit_tests.h', + 'tests/unit_tests_unittest.cc', 'services/broker_process_unittest.cc', + 'services/scoped_process_unittest.cc', 'services/thread_helpers_unittests.cc', + 'services/yama_unittests.cc', ], 'conditions': [ [ 'compile_suid_client==1', { @@ -26,9 +29,10 @@ 'suid/client/setuid_sandbox_client_unittest.cc', ], }], - [ 'compile_seccomp_bpf==1', { + [ 'use_seccomp_bpf==1', { 'sources': [ - 'seccomp-bpf/bpf_tests.h', + 'seccomp-bpf-helpers/baseline_policy_unittest.cc', + 'seccomp-bpf/bpf_tests_unittest.cc', 'seccomp-bpf/codegen_unittest.cc', 'seccomp-bpf/errorcode_unittest.cc', 'seccomp-bpf/sandbox_bpf_unittest.cc', @@ -39,6 +43,7 @@ [ 'compile_credentials==1', { 'sources': [ 'services/credentials_unittest.cc', + 'services/unix_domain_socket_unittest.cc', ], }], ], diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/DEPS b/chromium/sandbox/linux/seccomp-bpf-helpers/DEPS new file mode 100644 index 00000000000..e8000d3b32c --- /dev/null +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+sandbox/linux/services", + "+sandbox/linux/seccomp-bpf", +] diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc index d0e53e39bc8..a9fb1044778 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc @@ -6,8 +6,10 @@ #include <errno.h> #include <sys/mman.h> -#include <sys/types.h> #include <sys/socket.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> #include "base/logging.h" #include "build/build_config.h" @@ -30,19 +32,17 @@ bool IsBaselinePolicyAllowed(int sysno) { SyscallSets::IsAllowedBasicScheduler(sysno) || SyscallSets::IsAllowedEpoll(sysno) || SyscallSets::IsAllowedFileSystemAccessViaFd(sysno) || + SyscallSets::IsAllowedFutex(sysno) || SyscallSets::IsAllowedGeneralIo(sysno) || SyscallSets::IsAllowedGetOrModifySocket(sysno) || SyscallSets::IsAllowedGettime(sysno) || - SyscallSets::IsAllowedPrctl(sysno) || SyscallSets::IsAllowedProcessStartOrDeath(sysno) || SyscallSets::IsAllowedSignalHandling(sysno) || - SyscallSets::IsFutex(sysno) || SyscallSets::IsGetSimpleId(sysno) || SyscallSets::IsKernelInternalApi(sysno) || #if defined(__arm__) SyscallSets::IsArmPrivate(sysno) || #endif - SyscallSets::IsKill(sysno) || SyscallSets::IsAllowedOperationOnFd(sysno); } @@ -63,14 +63,15 @@ bool IsBaselinePolicyWatched(int sysno) { SyscallSets::IsInotify(sysno) || SyscallSets::IsKernelModule(sysno) || SyscallSets::IsKeyManagement(sysno) || + SyscallSets::IsKill(sysno) || SyscallSets::IsMessageQueue(sysno) || SyscallSets::IsMisc(sysno) || #if defined(__x86_64__) SyscallSets::IsNetworkSocketInformation(sysno) || #endif SyscallSets::IsNuma(sysno) || + SyscallSets::IsPrctl(sysno) || SyscallSets::IsProcessGroupOrSession(sysno) || - SyscallSets::IsProcessPrivilegeChange(sysno) || #if defined(__i386__) SyscallSets::IsSocketCall(sysno) || #endif @@ -81,22 +82,48 @@ bool IsBaselinePolicyWatched(int sysno) { } // |fs_denied_errno| is the errno return for denied filesystem access. -ErrorCode EvaluateSyscallImpl(int fs_denied_errno, SandboxBPF* sandbox, +ErrorCode EvaluateSyscallImpl(int fs_denied_errno, + pid_t current_pid, + SandboxBPF* sandbox, int sysno) { +#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \ + defined(MEMORY_SANITIZER) + // TCGETS is required by the sanitizers on failure. + if (sysno == __NR_ioctl) { + return RestrictIoctl(sandbox); + } + + if (sysno == __NR_sched_getaffinity) { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } + + if (sysno == __NR_sigaltstack) { + // Required for better stack overflow detection in ASan. Disallowed in + // non-ASan builds. + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +#endif // defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || + // defined(MEMORY_SANITIZER) + if (IsBaselinePolicyAllowed(sysno)) { return ErrorCode(ErrorCode::ERR_ALLOWED); } -#if defined(__x86_64__) || defined(__arm__) - if (sysno == __NR_socketpair) { - // Only allow AF_UNIX, PF_UNIX. Crash if anything else is seen. - COMPILE_ASSERT(AF_UNIX == PF_UNIX, af_unix_pf_unix_different); - return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, AF_UNIX, - ErrorCode(ErrorCode::ERR_ALLOWED), - sandbox->Trap(CrashSIGSYS_Handler, NULL)); + if (sysno == __NR_clone) { + return RestrictCloneToThreadsAndEPERMFork(sandbox); } + + if (sysno == __NR_fcntl) + return RestrictFcntlCommands(sandbox); + +#if defined(__i386__) || defined(__arm__) + if (sysno == __NR_fcntl64) + return RestrictFcntlCommands(sandbox); #endif + if (sysno == __NR_futex) + return RestrictFutex(sandbox); + if (sysno == __NR_madvise) { // Only allow MADV_DONTNEED (aka MADV_FREE). return sandbox->Cond(2, ErrorCode::TP_32BIT, @@ -118,14 +145,23 @@ ErrorCode EvaluateSyscallImpl(int fs_denied_errno, SandboxBPF* sandbox, if (sysno == __NR_mprotect) return RestrictMprotectFlags(sandbox); - if (sysno == __NR_fcntl) - return RestrictFcntlCommands(sandbox); + if (sysno == __NR_prctl) + return sandbox::RestrictPrctl(sandbox); -#if defined(__i386__) || defined(__arm__) - if (sysno == __NR_fcntl64) - return RestrictFcntlCommands(sandbox); +#if defined(__x86_64__) || defined(__arm__) + if (sysno == __NR_socketpair) { + // Only allow AF_UNIX, PF_UNIX. Crash if anything else is seen. + COMPILE_ASSERT(AF_UNIX == PF_UNIX, af_unix_pf_unix_different); + return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, AF_UNIX, + ErrorCode(ErrorCode::ERR_ALLOWED), + sandbox->Trap(CrashSIGSYS_Handler, NULL)); + } #endif + if (SyscallSets::IsKill(sysno)) { + return RestrictKillTarget(current_pid, sandbox, sysno); + } + if (SyscallSets::IsFileSystem(sysno) || SyscallSets::IsCurrentDirectory(sysno)) { return ErrorCode(fs_denied_errno); @@ -137,7 +173,8 @@ ErrorCode EvaluateSyscallImpl(int fs_denied_errno, SandboxBPF* sandbox, if (SyscallSets::IsUmask(sysno) || SyscallSets::IsDeniedFileSystemAccessViaFd(sysno) || - SyscallSets::IsDeniedGetOrModifySocket(sysno)) { + SyscallSets::IsDeniedGetOrModifySocket(sysno) || + SyscallSets::IsProcessPrivilegeChange(sysno)) { return ErrorCode(EPERM); } @@ -151,6 +188,7 @@ ErrorCode EvaluateSyscallImpl(int fs_denied_errno, SandboxBPF* sandbox, // be denied gracefully right away. return sandbox->Trap(CrashSIGSYS_Handler, NULL); } + // In any other case crash the program with our SIGSYS handler. return sandbox->Trap(CrashSIGSYS_Handler, NULL); } @@ -160,16 +198,24 @@ ErrorCode EvaluateSyscallImpl(int fs_denied_errno, SandboxBPF* sandbox, // Unfortunately C++03 doesn't allow delegated constructors. // Call other constructor when C++11 lands. BaselinePolicy::BaselinePolicy() - : fs_denied_errno_(EPERM) {} + : fs_denied_errno_(EPERM), current_pid_(syscall(__NR_getpid)) {} BaselinePolicy::BaselinePolicy(int fs_denied_errno) - : fs_denied_errno_(fs_denied_errno) {} + : fs_denied_errno_(fs_denied_errno), current_pid_(syscall(__NR_getpid)) {} -BaselinePolicy::~BaselinePolicy() {} +BaselinePolicy::~BaselinePolicy() { + // Make sure that this policy is created, used and destroyed by a single + // process. + DCHECK_EQ(syscall(__NR_getpid), current_pid_); +} ErrorCode BaselinePolicy::EvaluateSyscall(SandboxBPF* sandbox, int sysno) const { - return EvaluateSyscallImpl(fs_denied_errno_, sandbox, sysno); + // Make sure that this policy is used in the creating process. + if (1 == sysno) { + DCHECK_EQ(syscall(__NR_getpid), current_pid_); + } + return EvaluateSyscallImpl(fs_denied_errno_, current_pid_, sandbox, sysno); } } // namespace sandbox. diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h index 1dfd137fa3d..edf4c77b3c6 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h @@ -7,6 +7,7 @@ #include "sandbox/linux/seccomp-bpf/errorcode.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -17,12 +18,14 @@ class SandboxBPFPolicy; // that reduces the Linux kernel's attack surface. Given its nature, it doesn't // have a clear semantics and is mostly "implementation-defined". // -// This returns an object that implements the SandboxBPFPolicy interface with -// a "baseline" policy within Chromium. +// This class implements the SandboxBPFPolicy interface with a "baseline" +// policy for us within Chromium. // The "baseline" policy is somewhat arbitrary. All Chromium policies are an // alteration of it, and it represents a reasonable common ground to run most // code in a sandboxed environment. -class BaselinePolicy : public SandboxBPFPolicy { +// A baseline policy is only valid for the process for which this object was +// instantiated (so do not fork() and use it in a child). +class SANDBOX_EXPORT BaselinePolicy : public SandboxBPFPolicy { public: BaselinePolicy(); // |fs_denied_errno| is the errno returned when a filesystem access system @@ -35,6 +38,7 @@ class BaselinePolicy : public SandboxBPFPolicy { private: int fs_denied_errno_; + pid_t current_pid_; DISALLOW_COPY_AND_ASSIGN(BaselinePolicy); }; diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc new file mode 100644 index 00000000000..2fa0e93c1f0 --- /dev/null +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc @@ -0,0 +1,288 @@ +// Copyright 2014 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/seccomp-bpf-helpers/baseline_policy.h" + +#include <errno.h> +#include <linux/futex.h> +#include <sched.h> +#include <signal.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/thread.h" +#include "build/build_config.h" +#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" +#include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/services/android_futex.h" +#include "sandbox/linux/services/linux_syscalls.h" +#include "sandbox/linux/services/thread_helpers.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +namespace { + +// |pid| is the return value of a fork()-like call. This +// makes sure that if fork() succeeded the child exits +// and the parent waits for it. +void HandlePostForkReturn(pid_t pid) { + const int kChildExitCode = 1; + if (pid > 0) { + int status = 0; + PCHECK(pid == HANDLE_EINTR(waitpid(pid, &status, 0))); + CHECK(WIFEXITED(status)); + CHECK_EQ(kChildExitCode, WEXITSTATUS(status)); + } else if (pid == 0) { + _exit(kChildExitCode); + } +} + +// Check that HandlePostForkReturn works. +TEST(BaselinePolicy, HandlePostForkReturn) { + pid_t pid = fork(); + HandlePostForkReturn(pid); +} + +// This also tests that read(), write() and fstat() are allowed. +void TestPipeOrSocketPair(base::ScopedFD read_end, base::ScopedFD write_end) { + BPF_ASSERT_LE(0, read_end.get()); + BPF_ASSERT_LE(0, write_end.get()); + struct stat stat_buf; + int sys_ret = fstat(read_end.get(), &stat_buf); + BPF_ASSERT_EQ(0, sys_ret); + BPF_ASSERT(S_ISFIFO(stat_buf.st_mode) || S_ISSOCK(stat_buf.st_mode)); + + const ssize_t kTestTransferSize = 4; + static const char kTestString[kTestTransferSize] = {'T', 'E', 'S', 'T'}; + ssize_t transfered = 0; + + transfered = + HANDLE_EINTR(write(write_end.get(), kTestString, kTestTransferSize)); + BPF_ASSERT_EQ(kTestTransferSize, transfered); + char read_buf[kTestTransferSize + 1] = {0}; + transfered = HANDLE_EINTR(read(read_end.get(), read_buf, sizeof(read_buf))); + BPF_ASSERT_EQ(kTestTransferSize, transfered); + BPF_ASSERT_EQ(0, memcmp(kTestString, read_buf, kTestTransferSize)); +} + +// Test that a few easy-to-test system calls are allowed. +BPF_TEST_C(BaselinePolicy, BaselinePolicyBasicAllowed, BaselinePolicy) { + BPF_ASSERT_EQ(0, sched_yield()); + + int pipefd[2]; + int sys_ret = pipe(pipefd); + BPF_ASSERT_EQ(0, sys_ret); + TestPipeOrSocketPair(base::ScopedFD(pipefd[0]), base::ScopedFD(pipefd[1])); + + BPF_ASSERT_LE(1, getpid()); + BPF_ASSERT_LE(0, getuid()); +} + +BPF_TEST_C(BaselinePolicy, FchmodErrno, BaselinePolicy) { + int ret = fchmod(-1, 07777); + BPF_ASSERT_EQ(-1, ret); + // Without the sandbox, this would EBADF instead. + BPF_ASSERT_EQ(EPERM, errno); +} + +BPF_TEST_C(BaselinePolicy, ForkErrno, BaselinePolicy) { + errno = 0; + pid_t pid = fork(); + const int fork_errno = errno; + HandlePostForkReturn(pid); + + BPF_ASSERT_EQ(-1, pid); + BPF_ASSERT_EQ(EPERM, fork_errno); +} + +pid_t ForkX86Glibc() { + return syscall(__NR_clone, CLONE_PARENT_SETTID | SIGCHLD); +} + +BPF_TEST_C(BaselinePolicy, ForkX86Eperm, BaselinePolicy) { + errno = 0; + pid_t pid = ForkX86Glibc(); + const int fork_errno = errno; + HandlePostForkReturn(pid); + + BPF_ASSERT_EQ(-1, pid); + BPF_ASSERT_EQ(EPERM, fork_errno); +} + +pid_t ForkARMGlibc() { + return syscall(__NR_clone, + CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD); +} + +BPF_TEST_C(BaselinePolicy, ForkArmEperm, BaselinePolicy) { + errno = 0; + pid_t pid = ForkARMGlibc(); + const int fork_errno = errno; + HandlePostForkReturn(pid); + + BPF_ASSERT_EQ(-1, pid); + BPF_ASSERT_EQ(EPERM, fork_errno); +} + +BPF_TEST_C(BaselinePolicy, CreateThread, BaselinePolicy) { + base::Thread thread("sandbox_tests"); + BPF_ASSERT(thread.Start()); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + DisallowedCloneFlagCrashes, + DEATH_MESSAGE(GetCloneErrorMessageContentForTests()), + BaselinePolicy) { + pid_t pid = syscall(__NR_clone, CLONE_THREAD | SIGCHLD); + HandlePostForkReturn(pid); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + DisallowedKillCrashes, + DEATH_MESSAGE(GetKillErrorMessageContentForTests()), + BaselinePolicy) { + BPF_ASSERT_NE(1, getpid()); + kill(1, 0); + _exit(1); +} + +BPF_TEST_C(BaselinePolicy, CanKillSelf, BaselinePolicy) { + int sys_ret = kill(getpid(), 0); + BPF_ASSERT_EQ(0, sys_ret); +} + +BPF_TEST_C(BaselinePolicy, Socketpair, BaselinePolicy) { + int sv[2]; + int sys_ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, sv); + BPF_ASSERT_EQ(0, sys_ret); + TestPipeOrSocketPair(base::ScopedFD(sv[0]), base::ScopedFD(sv[1])); + + sys_ret = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sv); + BPF_ASSERT_EQ(0, sys_ret); + TestPipeOrSocketPair(base::ScopedFD(sv[0]), base::ScopedFD(sv[1])); +} + +// Not all architectures can restrict the domain for socketpair(). +#if defined(__x86_64__) || defined(__arm__) +BPF_DEATH_TEST_C(BaselinePolicy, + SocketpairWrongDomain, + DEATH_MESSAGE(GetErrorMessageContentForTests()), + BaselinePolicy) { + int sv[2]; + ignore_result(socketpair(AF_INET, SOCK_STREAM, 0, sv)); + _exit(1); +} +#endif // defined(__x86_64__) || defined(__arm__) + +BPF_TEST_C(BaselinePolicy, EPERM_open, BaselinePolicy) { + errno = 0; + int sys_ret = open("/proc/cpuinfo", O_RDONLY); + BPF_ASSERT_EQ(-1, sys_ret); + BPF_ASSERT_EQ(EPERM, errno); +} + +BPF_TEST_C(BaselinePolicy, EPERM_access, BaselinePolicy) { + errno = 0; + int sys_ret = access("/proc/cpuinfo", R_OK); + BPF_ASSERT_EQ(-1, sys_ret); + BPF_ASSERT_EQ(EPERM, errno); +} + +BPF_TEST_C(BaselinePolicy, EPERM_getcwd, BaselinePolicy) { + errno = 0; + char buf[1024]; + char* cwd = getcwd(buf, sizeof(buf)); + BPF_ASSERT_EQ(NULL, cwd); + BPF_ASSERT_EQ(EPERM, errno); +} + +// A failing test using this macro could be problematic since we perform +// system calls by passing "0" as every argument. +// The kernel could SIGSEGV the process or the system call itself could reboot +// the machine. Some thoughts have been given when hand-picking the system +// calls below to limit any potential side effects outside of the current +// process. +#define TEST_BASELINE_SIGSYS(sysno) \ + BPF_DEATH_TEST_C(BaselinePolicy, \ + SIGSYS_##sysno, \ + DEATH_MESSAGE(GetErrorMessageContentForTests()), \ + BaselinePolicy) { \ + syscall(sysno, 0, 0, 0, 0, 0, 0); \ + _exit(1); \ + } + +TEST_BASELINE_SIGSYS(__NR_syslog); +TEST_BASELINE_SIGSYS(__NR_sched_setaffinity); +TEST_BASELINE_SIGSYS(__NR_timer_create); +TEST_BASELINE_SIGSYS(__NR_io_cancel); +TEST_BASELINE_SIGSYS(__NR_ptrace); +TEST_BASELINE_SIGSYS(__NR_eventfd); +TEST_BASELINE_SIGSYS(__NR_fgetxattr); +TEST_BASELINE_SIGSYS(__NR_fanotify_init); +TEST_BASELINE_SIGSYS(__NR_swapon); +TEST_BASELINE_SIGSYS(__NR_chroot); +TEST_BASELINE_SIGSYS(__NR_acct); +TEST_BASELINE_SIGSYS(__NR_sysinfo); +TEST_BASELINE_SIGSYS(__NR_inotify_init); +TEST_BASELINE_SIGSYS(__NR_init_module); +TEST_BASELINE_SIGSYS(__NR_keyctl); +TEST_BASELINE_SIGSYS(__NR_mq_open); +TEST_BASELINE_SIGSYS(__NR_vserver); +TEST_BASELINE_SIGSYS(__NR_getcpu); +TEST_BASELINE_SIGSYS(__NR_setpgid); +TEST_BASELINE_SIGSYS(__NR_getitimer); + +#if !defined(OS_ANDROID) +BPF_DEATH_TEST_C(BaselinePolicy, + FutexWithRequeuePriorityInheritence, + DEATH_MESSAGE(GetFutexErrorMessageContentForTests()), + BaselinePolicy) { + syscall(__NR_futex, NULL, FUTEX_CMP_REQUEUE_PI, 0, NULL, NULL, 0); + _exit(1); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + FutexWithRequeuePriorityInheritencePrivate, + DEATH_MESSAGE(GetFutexErrorMessageContentForTests()), + BaselinePolicy) { + syscall(__NR_futex, NULL, FUTEX_CMP_REQUEUE_PI_PRIVATE, 0, NULL, NULL, 0); + _exit(1); +} +#endif // !defined(OS_ANDROID) + +BPF_TEST_C(BaselinePolicy, PrctlDumpable, BaselinePolicy) { + const int is_dumpable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0); + BPF_ASSERT(is_dumpable == 1 || is_dumpable == 0); + const int prctl_ret = prctl(PR_SET_DUMPABLE, is_dumpable, 0, 0, 0, 0); + BPF_ASSERT_EQ(0, prctl_ret); +} + +// Workaround incomplete Android headers. +#if !defined(PR_CAPBSET_READ) +#define PR_CAPBSET_READ 23 +#endif + +BPF_DEATH_TEST_C(BaselinePolicy, + PrctlSigsys, + DEATH_MESSAGE(GetPrctlErrorMessageContentForTests()), + BaselinePolicy) { + prctl(PR_CAPBSET_READ, 0, 0, 0, 0); + _exit(1); +} + +} // namespace + +} // namespace sandbox diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc index 6ff71257526..57dc24e8f88 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc @@ -9,11 +9,17 @@ #include <unistd.h> #include "base/basictypes.h" -#include "base/logging.h" #include "base/posix/eintr_wrapper.h" #include "build/build_config.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#define SECCOMP_MESSAGE_COMMON_CONTENT "seccomp-bpf failure" +#define SECCOMP_MESSAGE_CLONE_CONTENT "clone() failure" +#define SECCOMP_MESSAGE_PRCTL_CONTENT "prctl() failure" +#define SECCOMP_MESSAGE_IOCTL_CONTENT "ioctl() failure" +#define SECCOMP_MESSAGE_KILL_CONTENT "(tg)kill() failure" +#define SECCOMP_MESSAGE_FUTEX_CONTENT "futex() failure" + namespace { inline bool IsArchitectureX86_64() { @@ -55,7 +61,7 @@ void PrintSyscallError(uint32_t sysno) { sysno_base10[i] = '0' + mod; } static const char kSeccompErrorPrefix[] = - __FILE__":**CRASHING**:seccomp-bpf failure in syscall "; + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_COMMON_CONTENT " in syscall "; static const char kSeccompErrorPostfix[] = "\n"; WriteToStdErr(kSeccompErrorPrefix, sizeof(kSeccompErrorPrefix) - 1); WriteToStdErr(sysno_base10, sizeof(sysno_base10)); @@ -95,11 +101,11 @@ intptr_t CrashSIGSYS_Handler(const struct arch_seccomp_data& args, void* aux) { // TODO(jln): refactor the reporting functions. intptr_t SIGSYSCloneFailure(const struct arch_seccomp_data& args, void* aux) { + static const char kSeccompCloneError[] = + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_CLONE_CONTENT "\n"; + WriteToStdErr(kSeccompCloneError, sizeof(kSeccompCloneError) - 1); // "flags" is the first argument in the kernel's clone(). // Mark as volatile to be able to find the value on the stack in a minidump. -#if !defined(NDEBUG) - RAW_LOG(ERROR, __FILE__":**CRASHING**:clone() failure\n"); -#endif volatile uint64_t clone_flags = args.args[0]; volatile char* addr; if (IsArchitectureX86_64()) { @@ -115,10 +121,10 @@ intptr_t SIGSYSCloneFailure(const struct arch_seccomp_data& args, void* aux) { intptr_t SIGSYSPrctlFailure(const struct arch_seccomp_data& args, void* /* aux */) { + static const char kSeccompPrctlError[] = + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_PRCTL_CONTENT "\n"; + WriteToStdErr(kSeccompPrctlError, sizeof(kSeccompPrctlError) - 1); // Mark as volatile to be able to find the value on the stack in a minidump. -#if !defined(NDEBUG) - RAW_LOG(ERROR, __FILE__":**CRASHING**:prctl() failure\n"); -#endif volatile uint64_t option = args.args[0]; volatile char* addr = reinterpret_cast<volatile char*>(option & 0xFFF); @@ -129,10 +135,10 @@ intptr_t SIGSYSPrctlFailure(const struct arch_seccomp_data& args, intptr_t SIGSYSIoctlFailure(const struct arch_seccomp_data& args, void* /* aux */) { + static const char kSeccompIoctlError[] = + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_IOCTL_CONTENT "\n"; + WriteToStdErr(kSeccompIoctlError, sizeof(kSeccompIoctlError) - 1); // Make "request" volatile so that we can see it on the stack in a minidump. -#if !defined(NDEBUG) - RAW_LOG(ERROR, __FILE__":**CRASHING**:ioctl() failure\n"); -#endif volatile uint64_t request = args.args[1]; volatile char* addr = reinterpret_cast<volatile char*>(request & 0xFFFF); *addr = '\0'; @@ -143,4 +149,56 @@ intptr_t SIGSYSIoctlFailure(const struct arch_seccomp_data& args, _exit(1); } +intptr_t SIGSYSKillFailure(const struct arch_seccomp_data& args, + void* /* aux */) { + static const char kSeccompKillError[] = + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_KILL_CONTENT "\n"; + WriteToStdErr(kSeccompKillError, sizeof(kSeccompKillError) - 1); + // Make "request" volatile so that we can see it on the stack in a minidump. + volatile uint64_t pid = args.args[0]; + volatile char* addr = reinterpret_cast<volatile char*>(pid & 0xFFF); + *addr = '\0'; + // Hit the NULL page if this fails. + addr = reinterpret_cast<volatile char*>(pid & 0xFFF); + *addr = '\0'; + for (;;) + _exit(1); +} + +intptr_t SIGSYSFutexFailure(const struct arch_seccomp_data& args, + void* /* aux */) { + static const char kSeccompFutexError[] = + __FILE__ ":**CRASHING**:" SECCOMP_MESSAGE_FUTEX_CONTENT "\n"; + WriteToStdErr(kSeccompFutexError, sizeof(kSeccompFutexError) - 1); + volatile int futex_op = args.args[1]; + volatile char* addr = reinterpret_cast<volatile char*>(futex_op & 0xFFF); + *addr = '\0'; + for (;;) + _exit(1); +} + +const char* GetErrorMessageContentForTests() { + return SECCOMP_MESSAGE_COMMON_CONTENT; +} + +const char* GetCloneErrorMessageContentForTests() { + return SECCOMP_MESSAGE_CLONE_CONTENT; +} + +const char* GetPrctlErrorMessageContentForTests() { + return SECCOMP_MESSAGE_PRCTL_CONTENT; +} + +const char* GetIoctlErrorMessageContentForTests() { + return SECCOMP_MESSAGE_IOCTL_CONTENT; +} + +const char* GetKillErrorMessageContentForTests() { + return SECCOMP_MESSAGE_KILL_CONTENT; +} + +const char* GetFutexErrorMessageContentForTests() { + return SECCOMP_MESSAGE_FUTEX_CONTENT; +} + } // namespace sandbox. diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h index 3bf5c16db1f..280afa7a69f 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "build/build_config.h" +#include "sandbox/sandbox_export.h" // The handlers are suitable for use in Trap() error codes. They are // guaranteed to be async-signal safe. @@ -19,20 +20,41 @@ struct arch_seccomp_data; // This handler will crash the currently running process. The crashing address // will be the number of the current system call, extracted from |args|. // This handler will also print to stderr the number of the crashing syscall. -intptr_t CrashSIGSYS_Handler(const struct arch_seccomp_data& args, void* aux); +SANDBOX_EXPORT intptr_t + CrashSIGSYS_Handler(const struct arch_seccomp_data& args, void* aux); // The following three handlers are suitable to report failures with the // clone(), prctl() and ioctl() system calls respectively. // The crashing address will be (clone_flags & 0xFFFFFF), where clone_flags is // the clone(2) argument, extracted from |args|. -intptr_t SIGSYSCloneFailure(const struct arch_seccomp_data& args, void* aux); +SANDBOX_EXPORT intptr_t + SIGSYSCloneFailure(const struct arch_seccomp_data& args, void* aux); // The crashing address will be (option & 0xFFF), where option is the prctl(2) // argument. -intptr_t SIGSYSPrctlFailure(const struct arch_seccomp_data& args, void* aux); +SANDBOX_EXPORT intptr_t + SIGSYSPrctlFailure(const struct arch_seccomp_data& args, void* aux); // The crashing address will be request & 0xFFFF, where request is the ioctl(2) // argument. -intptr_t SIGSYSIoctlFailure(const struct arch_seccomp_data& args, void* aux); +SANDBOX_EXPORT intptr_t + SIGSYSIoctlFailure(const struct arch_seccomp_data& args, void* aux); +// The crashing address will be (pid & 0xFFF), where pid is the first +// argument (and can be a tid). +SANDBOX_EXPORT intptr_t + SIGSYSKillFailure(const struct arch_seccomp_data& args, void* aux); +// The crashing address will be (op & 0xFFF), where op is the second +// argument. +SANDBOX_EXPORT intptr_t + SIGSYSFutexFailure(const struct arch_seccomp_data& args, void* aux); + +// Following four functions return substrings of error messages used +// in the above four functions. They are useful in death tests. +SANDBOX_EXPORT const char* GetErrorMessageContentForTests(); +SANDBOX_EXPORT const char* GetCloneErrorMessageContentForTests(); +SANDBOX_EXPORT const char* GetPrctlErrorMessageContentForTests(); +SANDBOX_EXPORT const char* GetIoctlErrorMessageContentForTests(); +SANDBOX_EXPORT const char* GetKillErrorMessageContentForTests(); +SANDBOX_EXPORT const char* GetFutexErrorMessageContentForTests(); } // namespace sandbox. diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc index 9b417ce221f..16c37a06975 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc @@ -7,6 +7,7 @@ #include <errno.h> #include <fcntl.h> #include <fcntl.h> +#include <linux/futex.h> #include <linux/net.h> #include <sched.h> #include <signal.h> @@ -19,9 +20,12 @@ #include "base/basictypes.h" #include "base/logging.h" +#include "base/macros.h" +#include "build/build_config.h" #include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" #include "sandbox/linux/seccomp-bpf/linux_seccomp.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/services/android_futex.h" #if defined(OS_ANDROID) #if !defined(F_DUPFD_CLOEXEC) @@ -35,24 +39,24 @@ namespace { -inline bool RunningOnASAN() { -#if defined(ADDRESS_SANITIZER) +inline bool IsArchitectureX86_64() { +#if defined(__x86_64__) return true; #else return false; #endif } -inline bool IsArchitectureX86_64() { -#if defined(__x86_64__) +inline bool IsArchitectureI386() { +#if defined(__i386__) return true; #else return false; #endif } -inline bool IsArchitectureI386() { -#if defined(__i386__) +inline bool IsAndroid() { +#if defined(OS_ANDROID) return true; #else return false; @@ -63,24 +67,40 @@ inline bool IsArchitectureI386() { namespace sandbox { +// Allow Glibc's and Android pthread creation flags, crash on any other +// thread creation attempts and EPERM attempts to use neither +// CLONE_VM, nor CLONE_THREAD, which includes all fork() implementations. ErrorCode RestrictCloneToThreadsAndEPERMFork(SandboxBPF* sandbox) { - // Glibc's pthread. - if (!RunningOnASAN()) { + if (!IsAndroid()) { + const uint64_t kGlibcPthreadFlags = + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | + CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | + CLONE_CHILD_CLEARTID; + 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, + kGlibcPthreadFlags, ErrorCode(ErrorCode::ERR_ALLOWED), - sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, - CLONE_PARENT_SETTID | SIGCHLD, - ErrorCode(EPERM), - // ARM - sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, - CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, - ErrorCode(EPERM), - sandbox->Trap(SIGSYSCloneFailure, NULL)))); + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS, + CLONE_VM | CLONE_THREAD, + sandbox->Trap(SIGSYSCloneFailure, NULL), + ErrorCode(EPERM))); } else { - return ErrorCode(ErrorCode::ERR_ALLOWED); + const uint64_t kAndroidCloneMask = CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | + CLONE_SYSVSEM; + const uint64_t kObsoleteAndroidCloneMask = + kAndroidCloneMask | CLONE_DETACHED; + + return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + kAndroidCloneMask, + ErrorCode(ErrorCode::ERR_ALLOWED), + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + kObsoleteAndroidCloneMask, + ErrorCode(ErrorCode::ERR_ALLOWED), + sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS, + CLONE_VM | CLONE_THREAD, + sandbox->Trap(SIGSYSCloneFailure, NULL), + ErrorCode(EPERM)))); } } @@ -211,4 +231,46 @@ ErrorCode RestrictSocketcallCommand(SandboxBPF* sandbox) { } #endif +ErrorCode RestrictKillTarget(pid_t target_pid, SandboxBPF* sandbox, int sysno) { + switch (sysno) { + case __NR_kill: + case __NR_tgkill: + return sandbox->Cond(0, + ErrorCode::TP_32BIT, + ErrorCode::OP_EQUAL, + target_pid, + ErrorCode(ErrorCode::ERR_ALLOWED), + sandbox->Trap(SIGSYSKillFailure, NULL)); + case __NR_tkill: + return sandbox->Trap(SIGSYSKillFailure, NULL); + default: + NOTREACHED(); + return sandbox->Trap(CrashSIGSYS_Handler, NULL); + } +} + +ErrorCode RestrictFutex(SandboxBPF* sandbox) { + // In futex.c, the kernel does "int cmd = op & FUTEX_CMD_MASK;". We need to + // make sure that the combination below will cover every way to get + // FUTEX_CMP_REQUEUE_PI. + const int kBannedFutexBits = + ~(FUTEX_CMD_MASK | FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME); + COMPILE_ASSERT(0 == kBannedFutexBits, + need_to_explicitly_blacklist_more_bits); + + return sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + FUTEX_CMP_REQUEUE_PI, + sandbox->Trap(SIGSYSFutexFailure, NULL), + sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + FUTEX_CMP_REQUEUE_PI_PRIVATE, + sandbox->Trap(SIGSYSFutexFailure, NULL), + sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + FUTEX_CMP_REQUEUE_PI | FUTEX_CLOCK_REALTIME, + sandbox->Trap(SIGSYSFutexFailure, NULL), + sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, + FUTEX_CMP_REQUEUE_PI_PRIVATE | FUTEX_CLOCK_REALTIME, + sandbox->Trap(SIGSYSFutexFailure, NULL), + ErrorCode(ErrorCode::ERR_ALLOWED))))); +} + } // namespace sandbox. diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h index 65b7c472198..bc5a1c0e76a 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h @@ -5,11 +5,14 @@ #ifndef SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_PARAMETERS_RESTRICTIONS_H_ #define SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_PARAMETERS_RESTRICTIONS_H_ +#include <unistd.h> + #include "build/build_config.h" +#include "sandbox/sandbox_export.h" // These are helpers to build seccomp-bpf policies, i.e. policies for a // sandbox that reduces the Linux kernel's attack surface. They return an -// ErrorCode suitable to restrict certain system call parameters. +// SANDBOX_EXPORT ErrorCode suitable to restrict certain system call parameters. namespace sandbox { @@ -20,39 +23,48 @@ class SandboxBPF; // Reject fork(2) attempts with EPERM. // Don't restrict on ASAN. // Crash if anything else is attempted. -ErrorCode RestrictCloneToThreadsAndEPERMFork(SandboxBPF* sandbox); +SANDBOX_EXPORT ErrorCode + RestrictCloneToThreadsAndEPERMFork(SandboxBPF* sandbox); // Allow PR_SET_NAME, PR_SET_DUMPABLE, PR_GET_DUMPABLE. // Crash if anything else is attempted. -ErrorCode RestrictPrctl(SandboxBPF* sandbox); +SANDBOX_EXPORT ErrorCode RestrictPrctl(SandboxBPF* sandbox); // Allow TCGETS and FIONREAD. // Crash if anything else is attempted. -ErrorCode RestrictIoctl(SandboxBPF* sandbox); +SANDBOX_EXPORT ErrorCode RestrictIoctl(SandboxBPF* sandbox); // Restrict the flags argument in mmap(2). // Only allow: MAP_SHARED | MAP_PRIVATE | MAP_ANONYMOUS | // MAP_STACK | MAP_NORESERVE | MAP_FIXED | MAP_DENYWRITE. // Crash if any other flag is used. -ErrorCode RestrictMmapFlags(SandboxBPF* sandbox); +SANDBOX_EXPORT ErrorCode RestrictMmapFlags(SandboxBPF* sandbox); // Restrict the prot argument in mprotect(2). // Only allow: PROT_READ | PROT_WRITE | PROT_EXEC. -ErrorCode RestrictMprotectFlags(SandboxBPF* sandbox); +SANDBOX_EXPORT ErrorCode RestrictMprotectFlags(SandboxBPF* sandbox); // Restrict fcntl(2) cmd argument to: // We allow F_GETFL, F_SETFL, F_GETFD, F_SETFD, F_DUPFD, F_DUPFD_CLOEXEC, // F_SETLK, F_SETLKW and F_GETLK. // Also, in F_SETFL, restrict the allowed flags to: O_ACCMODE | O_APPEND | // O_NONBLOCK | O_SYNC | O_LARGEFILE | O_CLOEXEC | O_NOATIME. -ErrorCode RestrictFcntlCommands(SandboxBPF* sandbox); +SANDBOX_EXPORT ErrorCode RestrictFcntlCommands(SandboxBPF* sandbox); #if defined(__i386__) // Restrict socketcall(2) to only allow socketpair(2), send(2), recv(2), // sendto(2), recvfrom(2), shutdown(2), sendmsg(2) and recvmsg(2). -ErrorCode RestrictSocketcallCommand(SandboxBPF* sandbox); +SANDBOX_EXPORT ErrorCode RestrictSocketcallCommand(SandboxBPF* sandbox); #endif +// Restrict |sysno| (which must be kill, tkill or tgkill) by allowing tgkill or +// kill iff the first parameter is |target_pid|, crashing otherwise or if +// |sysno| is tkill. +ErrorCode RestrictKillTarget(pid_t target_pid, SandboxBPF* sandbox, int sysno); + +// Crash if FUTEX_CMP_REQUEUE_PI is used in the second argument of futex(2). +ErrorCode RestrictFutex(SandboxBPF* sandbox); + } // namespace sandbox. #endif // SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_PARAMETERS_RESTRICTIONS_H_ diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc index 032f6c3c472..e3db231502d 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc @@ -14,12 +14,11 @@ namespace sandbox { // The implicitly defined sets form a partition of the sets of // system calls. -// TODO(jln) we need to restrict the first parameter! bool SyscallSets::IsKill(int sysno) { switch (sysno) { case __NR_kill: - case __NR_tkill: case __NR_tgkill: + case __NR_tkill: // Deprecated. return true; default: return false; @@ -351,7 +350,6 @@ bool SyscallSets::IsKernelInternalApi(int sysno) { // This should be thought through in conjunction with IsFutex(). bool SyscallSets::IsAllowedProcessStartOrDeath(int sysno) { switch (sysno) { - case __NR_clone: // TODO(jln): restrict flags. case __NR_exit: case __NR_exit_group: case __NR_wait4: @@ -360,6 +358,7 @@ bool SyscallSets::IsAllowedProcessStartOrDeath(int sysno) { case __NR_waitpid: #endif return true; + case __NR_clone: // Should be parameter-restricted. case __NR_setns: // Privileged. case __NR_fork: #if defined(__i386__) || defined(__x86_64__) @@ -375,12 +374,12 @@ bool SyscallSets::IsAllowedProcessStartOrDeath(int sysno) { } // It's difficult to restrict those, but there is attack surface here. -bool SyscallSets::IsFutex(int sysno) { +bool SyscallSets::IsAllowedFutex(int sysno) { switch (sysno) { - case __NR_futex: case __NR_get_robust_list: case __NR_set_robust_list: return true; + case __NR_futex: default: return false; } @@ -547,14 +546,14 @@ bool SyscallSets::IsAllowedGeneralIo(int sysno) { } } -bool SyscallSets::IsAllowedPrctl(int sysno) { +bool SyscallSets::IsPrctl(int sysno) { switch (sysno) { - case __NR_prctl: - return true; - default: #if defined(__x86_64__) case __NR_arch_prctl: #endif + case __NR_prctl: + return true; + default: return false; } } diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h index d2cf1a17801..c1e412e9034 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "build/build_config.h" +#include "sandbox/sandbox_export.h" // These are helpers to build seccomp-bpf policies, i.e. policies for a // sandbox that reduces the Linux kernel's attack surface. Given their @@ -15,7 +16,7 @@ namespace sandbox { -class SyscallSets { +class SANDBOX_EXPORT SyscallSets { public: static bool IsKill(int sysno); static bool IsAllowedGettime(int sysno); @@ -36,7 +37,7 @@ class SyscallSets { // This should be thought through in conjunction with IsFutex(). static bool IsAllowedProcessStartOrDeath(int sysno); // It's difficult to restrict those, but there is attack surface here. - static bool IsFutex(int sysno); + static bool IsAllowedFutex(int sysno); static bool IsAllowedEpoll(int sysno); static bool IsAllowedGetOrModifySocket(int sysno); static bool IsDeniedGetOrModifySocket(int sysno); @@ -52,7 +53,7 @@ class SyscallSets { static bool IsAllowedAddressSpaceAccess(int sysno); static bool IsAllowedGeneralIo(int sysno); - static bool IsAllowedPrctl(int sysno); + static bool IsPrctl(int sysno); static bool IsAllowedBasicScheduler(int sysno); static bool IsAdminOperation(int sysno); static bool IsKernelModule(int sysno); diff --git a/chromium/sandbox/linux/seccomp-bpf/DEPS b/chromium/sandbox/linux/seccomp-bpf/DEPS new file mode 100644 index 00000000000..15b2b36a3ef --- /dev/null +++ b/chromium/sandbox/linux/seccomp-bpf/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+sandbox/linux/services" +] diff --git a/chromium/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h b/chromium/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h new file mode 100644 index 00000000000..8890f798749 --- /dev/null +++ b/chromium/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h @@ -0,0 +1,65 @@ +// Copyright 2014 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_BPF_TESTER_COMPATIBILITY_DELEGATE_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTER_COMPATIBILITY_DELEGATE_H_ + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf_compatibility_policy.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h" +#include "sandbox/linux/tests/sandbox_test_runner.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +// This templated class allows building a BPFTesterDelegate from a +// deprecated-style BPF policy (that is a SyscallEvaluator function pointer, +// instead of a SandboxBPFPolicy class), specified in |policy_function| and a +// function pointer to a test in |test_function|. +// This allows both the policy and the test function to take a pointer to an +// object of type "Aux" as a parameter. This is used to implement the BPF_TEST +// macro and should generally not be used directly. +template <class Aux> +class BPFTesterCompatibilityDelegate : public BPFTesterDelegate { + public: + typedef Aux AuxType; + BPFTesterCompatibilityDelegate( + void (*test_function)(AuxType*), + typename CompatibilityPolicy<AuxType>::SyscallEvaluator policy_function) + : aux_(), + test_function_(test_function), + policy_function_(policy_function) {} + + virtual ~BPFTesterCompatibilityDelegate() {} + + virtual scoped_ptr<SandboxBPFPolicy> GetSandboxBPFPolicy() OVERRIDE { + // The current method is guaranteed to only run in the child process + // running the test. In this process, the current object is guaranteed + // to live forever. So it's ok to pass aux_pointer_for_policy_ to + // the policy, which could in turn pass it to the kernel via Trap(). + return scoped_ptr<SandboxBPFPolicy>( + new CompatibilityPolicy<AuxType>(policy_function_, &aux_)); + } + + virtual void RunTestFunction() OVERRIDE { + // Run the actual test. + // The current object is guaranteed to live forever in the child process + // where this will run. + test_function_(&aux_); + } + + private: + AuxType aux_; + void (*test_function_)(AuxType*); + typename CompatibilityPolicy<AuxType>::SyscallEvaluator policy_function_; + DISALLOW_COPY_AND_ASSIGN(BPFTesterCompatibilityDelegate); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTER_COMPATIBILITY_DELEGATE_H_ diff --git a/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h b/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h index 7095c23b8c1..da92de80b79 100644 --- a/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h +++ b/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h @@ -5,110 +5,118 @@ #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 "base/basictypes.h" #include "build/build_config.h" +#include "sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.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. -// A BPF_DEATH_TEST is always disabled under ThreadSanitizer, see -// crbug.com/243968. -#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, DISABLE_ON_TSAN(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_C() is a special version of SANDBOX_TEST(). It runs a test function +// in a sub-process, under a seccomp-bpf policy specified in +// |bpf_policy_class_name| without failing on configurations that are allowed +// to not support seccomp-bpf in their kernels. +// This is the preferred format for new BPF tests. |bpf_policy_class_name| is a +// class name (which will be default-constructed) that implements the +// SandboxBPFPolicy interface. +// The test function's body can simply follow. Test functions should use +// the BPF_ASSERT macros defined below, not GTEST's macros. The use of +// CHECK* macros is supported but less robust. +#define BPF_TEST_C(test_case_name, test_name, bpf_policy_class_name) \ + BPF_DEATH_TEST_C( \ + test_case_name, test_name, DEATH_SUCCESS(), bpf_policy_class_name) + +// Identical to BPF_TEST_C but allows to specify the nature of death. +#define BPF_DEATH_TEST_C( \ + test_case_name, test_name, death, bpf_policy_class_name) \ + void BPF_TEST_C_##test_name(); \ + TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \ + sandbox::SandboxBPFTestRunner bpf_test_runner( \ + new sandbox::BPFTesterSimpleDelegate<bpf_policy_class_name>( \ + BPF_TEST_C_##test_name)); \ + sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, death); \ + } \ + void BPF_TEST_C_##test_name() + +// This form of BPF_TEST is a little verbose and should be reserved for complex +// tests where a lot of control is required. +// |bpf_tester_delegate_class| must be a classname implementing the +// BPFTesterDelegate interface. +#define BPF_TEST_D(test_case_name, test_name, bpf_tester_delegate_class) \ + BPF_DEATH_TEST_D( \ + test_case_name, test_name, DEATH_SUCCESS(), bpf_tester_delegate_class) + +// Identical to BPF_TEST_D but allows to specify the nature of death. +#define BPF_DEATH_TEST_D( \ + test_case_name, test_name, death, bpf_tester_delegate_class) \ + TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \ + sandbox::SandboxBPFTestRunner bpf_test_runner( \ + new bpf_tester_delegate_class()); \ + sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, death); \ + } -// 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 -// inside our tests, as we don't need or even want all the error handling that -// LOG(FATAL) would do. +// Assertions are handled exactly the same as with a normal SANDBOX_TEST() +#define BPF_ASSERT SANDBOX_ASSERT +#define BPF_ASSERT_EQ(x, y) BPF_ASSERT((x) == (y)) +#define BPF_ASSERT_NE(x, y) BPF_ASSERT((x) != (y)) +#define BPF_ASSERT_LT(x, y) BPF_ASSERT((x) < (y)) +#define BPF_ASSERT_GT(x, y) BPF_ASSERT((x) > (y)) +#define BPF_ASSERT_LE(x, y) BPF_ASSERT((x) <= (y)) +#define BPF_ASSERT_GE(x, y) BPF_ASSERT((x) >= (y)) + +// This form of BPF_TEST is now discouraged (but still allowed) in favor of +// BPF_TEST_D and BPF_TEST_C. +// The |policy| parameter should be a SyscallEvaluator function pointer +// (which is now a deprecated way of expressing policies). // BPF_TEST() takes a C++ data type as an optional fourth parameter. If // present, this sets up a variable that can be accessed as "BPF_AUX". This // variable will be passed as an argument to the "policy" function. Policies // would typically use it as an argument to SandboxBPF::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...) \ +// communicate data between the BPF_TEST() and a Trap() function. The life-time +// of this object is the same as the life-time of the process running under the +// seccomp-bpf policy. +// The type specified in |aux| and the last parameter of the policy function +// must be compatible. |aux| must not be void. +#define BPF_TEST(test_case_name, test_name, policy, 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 - -// The "Aux" type is optional. We use an "empty" type by default, so that if -// the caller doesn't provide any type, all the BPF_AUX related data compiles -// to nothing. -template <class Aux = int[0]> -class BPFTests : public UnitTests { +// 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::BPFTesterCompatibilityDelegate<aux>::AuxType* BPF_AUX); \ + TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \ + sandbox::SandboxBPFTestRunner bpf_test_runner( \ + new sandbox::BPFTesterCompatibilityDelegate<aux>(BPF_TEST_##test_name, \ + policy)); \ + sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, death); \ + } \ + void BPF_TEST_##test_name( \ + sandbox::BPFTesterCompatibilityDelegate<aux>::AuxType* BPF_AUX) + +// This class takes a simple function pointer as a constructor parameter and a +// class name as a template parameter to implement the BPFTesterDelegate +// interface which can be used to build BPF unittests with +// the SandboxBPFTestRunner class. +template <class PolicyClass> +class BPFTesterSimpleDelegate : public BPFTesterDelegate { public: - typedef Aux AuxType; - - class TestArgs { - public: - TestArgs(void (*t)(AuxType&), sandbox::SandboxBPF::EvaluateSyscall p) - : test_(t), policy_(p), aux_() {} - - void (*test() const)(AuxType&) { return test_; } - sandbox::SandboxBPF::EvaluateSyscall policy() const { return policy_; } + explicit BPFTesterSimpleDelegate(void (*test_function)(void)) + : test_function_(test_function) {} + virtual ~BPFTesterSimpleDelegate() {} - private: - friend class BPFTests; - - void (*test_)(AuxType&); - sandbox::SandboxBPF::EvaluateSyscall policy_; - AuxType aux_; - }; - - static void TestWrapper(void* void_arg) { - TestArgs* arg = reinterpret_cast<TestArgs*>(void_arg); - sandbox::Die::EnableSimpleExit(); - if (sandbox::SandboxBPF::SupportsSeccompSandbox(-1) == - sandbox::SandboxBPF::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(sandbox::SandboxBPF::SupportsSeccompSandbox(proc_fd) == - sandbox::SandboxBPF::STATUS_AVAILABLE); - - // Initialize and then start the sandbox with our custom policy - sandbox::SandboxBPF sandbox; - sandbox.set_proc_fd(proc_fd); - sandbox.SetSandboxPolicyDeprecated(arg->policy(), &arg->aux_); - sandbox.SandboxBPF::StartSandbox(); - - arg->test()(arg->aux_); - } else { - printf("This BPF test is not fully running in this configuration!\n"); - // Android, ARM and Valgrind are the three only configurations where we - // accept not having kernel BPF support. - // TODO(jln): remote ARM from this list when possible (crbug.com/243478). - if (!IsAndroid() && !IsRunningOnValgrind() && !IsArchitectureArm()) { - const bool seccomp_bpf_is_supported = false; - BPF_ASSERT(seccomp_bpf_is_supported); - } - // Call the compiler and verify the policy. That's the least we can do, - // if we don't have kernel support. - sandbox::SandboxBPF sandbox; - sandbox.SetSandboxPolicyDeprecated(arg->policy(), &arg->aux_); - sandbox::SandboxBPF::Program* program = - sandbox.AssembleFilter(true /* force_verification */); - delete program; - sandbox::UnitTests::IgnoreThisTest(); - } + virtual scoped_ptr<SandboxBPFPolicy> GetSandboxBPFPolicy() OVERRIDE { + return scoped_ptr<SandboxBPFPolicy>(new PolicyClass()); + } + virtual void RunTestFunction() OVERRIDE { + DCHECK(test_function_); + test_function_(); } private: - DISALLOW_IMPLICIT_CONSTRUCTORS(BPFTests); + void (*test_function_)(void); + DISALLOW_COPY_AND_ASSIGN(BPFTesterSimpleDelegate); }; } // namespace sandbox diff --git a/chromium/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc new file mode 100644 index 00000000000..bd18412bee6 --- /dev/null +++ b/chromium/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc @@ -0,0 +1,139 @@ +// Copyright 2014 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/seccomp-bpf/bpf_tests.h" + +#include <errno.h> +#include <sys/ptrace.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "build/build_config.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/services/linux_syscalls.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +class FourtyTwo { + public: + static const int kMagicValue = 42; + FourtyTwo() : value_(kMagicValue) {} + int value() { return value_; } + + private: + int value_; + DISALLOW_COPY_AND_ASSIGN(FourtyTwo); +}; + +ErrorCode EmptyPolicyTakesClass(SandboxBPF* sandbox, + int sysno, + FourtyTwo* fourty_two) { + // |aux| should point to an instance of FourtyTwo. + BPF_ASSERT(fourty_two); + BPF_ASSERT(FourtyTwo::kMagicValue == fourty_two->value()); + if (!SandboxBPF::IsValidSyscallNumber(sysno)) { + return ErrorCode(ENOSYS); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +BPF_TEST(BPFTest, + BPFAUXPointsToClass, + EmptyPolicyTakesClass, + FourtyTwo /* *BPF_AUX */) { + // BPF_AUX should point to an instance of FourtyTwo. + BPF_ASSERT(BPF_AUX); + BPF_ASSERT(FourtyTwo::kMagicValue == BPF_AUX->value()); +} + +void DummyTestFunction(FourtyTwo *fourty_two) { +} + +TEST(BPFTest, BPFTesterCompatibilityDelegateLeakTest) { + // Don't do anything, simply gives dynamic tools an opportunity to detect + // leaks. + { + BPFTesterCompatibilityDelegate<FourtyTwo> simple_delegate( + DummyTestFunction, EmptyPolicyTakesClass); + } + { + // Test polymorphism. + scoped_ptr<BPFTesterDelegate> simple_delegate( + new BPFTesterCompatibilityDelegate<FourtyTwo>(DummyTestFunction, + EmptyPolicyTakesClass)); + } +} + +class EnosysPtracePolicy : public SandboxBPFPolicy { + public: + EnosysPtracePolicy() { + my_pid_ = syscall(__NR_getpid); + } + virtual ~EnosysPtracePolicy() { + // Policies should be able to bind with the process on which they are + // created. They should never be created in a parent process. + BPF_ASSERT_EQ(my_pid_, syscall(__NR_getpid)); + } + + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler, + int system_call_number) const OVERRIDE { + if (!SandboxBPF::IsValidSyscallNumber(system_call_number)) { + return ErrorCode(ENOSYS); + } else if (system_call_number == __NR_ptrace) { + // The EvaluateSyscall function should run in the process that created + // the current object. + BPF_ASSERT_EQ(my_pid_, syscall(__NR_getpid)); + return ErrorCode(ENOSYS); + } else { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } + } + + private: + pid_t my_pid_; + DISALLOW_COPY_AND_ASSIGN(EnosysPtracePolicy); +}; + +class BasicBPFTesterDelegate : public BPFTesterDelegate { + public: + BasicBPFTesterDelegate() {} + virtual ~BasicBPFTesterDelegate() {} + + virtual scoped_ptr<SandboxBPFPolicy> GetSandboxBPFPolicy() OVERRIDE { + return scoped_ptr<SandboxBPFPolicy>(new EnosysPtracePolicy()); + } + virtual void RunTestFunction() OVERRIDE { + errno = 0; + int ret = ptrace(PTRACE_TRACEME, -1, NULL, NULL); + BPF_ASSERT(-1 == ret); + BPF_ASSERT(ENOSYS == errno); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicBPFTesterDelegate); +}; + +// This is the most powerful and complex way to create a BPF test, but it +// requires a full class definition (BasicBPFTesterDelegate). +BPF_TEST_D(BPFTest, BPFTestWithDelegateClass, BasicBPFTesterDelegate); + +// This is the simplest form of BPF tests. +BPF_TEST_C(BPFTest, BPFTestWithInlineTest, EnosysPtracePolicy) { + errno = 0; + int ret = ptrace(PTRACE_TRACEME, -1, NULL, NULL); + BPF_ASSERT(-1 == ret); + BPF_ASSERT(ENOSYS == errno); +} + +} // namespace + +} // namespace sandbox diff --git a/chromium/sandbox/linux/seccomp-bpf/codegen.cc b/chromium/sandbox/linux/seccomp-bpf/codegen.cc index 8fb1701179e..c90bffcad30 100644 --- a/chromium/sandbox/linux/seccomp-bpf/codegen.cc +++ b/chromium/sandbox/linux/seccomp-bpf/codegen.cc @@ -4,6 +4,7 @@ #include <stdio.h> +#include "base/logging.h" #include "sandbox/linux/seccomp-bpf/codegen.h" namespace { @@ -105,6 +106,8 @@ void CodeGen::PrintProgram(const SandboxBPF::Program& program) { 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_ACTION) == SECCOMP_RET_TRACE) { + fprintf(stderr, "Trace #%d\n", iter->k & SECCOMP_RET_DATA); } else if (iter->k == SECCOMP_RET_ALLOW) { fprintf(stderr, "Allowed\n"); } else { @@ -432,67 +435,84 @@ static int PointerCompare(const BasicBlock* block1, // We compare the sequence of instructions in both basic blocks. const Instructions& insns1 = block1->instructions; const Instructions& insns2 = block2->instructions; + // Basic blocks should never be empty. + CHECK(!insns1.empty()); + CHECK(!insns2.empty()); + Instructions::const_iterator iter1 = insns1.begin(); Instructions::const_iterator iter2 = insns2.begin(); for (;; ++iter1, ++iter2) { // If we have reached the end of the sequence of instructions in one or // both basic blocks, we know the relative ordering between the two blocks // and can return. - if (iter1 == insns1.end()) { - return iter2 == insns2.end() ? 0 : -1; - } else if (iter2 == insns2.end()) { - return 1; + if (iter1 == insns1.end() || iter2 == insns2.end()) { + if (iter1 != insns1.end()) { + return 1; + } + if (iter2 != insns2.end()) { + return -1; + } + + // If the two blocks are the same length (and have elementwise-equal code + // and k fields) and their last instructions are neither a JMP nor a RET + // (which is the only way we can reach this point), then we must compare + // their successors. + Instruction* const insns1_last = insns1.back(); + Instruction* const insns2_last = insns2.back(); + CHECK(BPF_CLASS(insns1_last->code) != BPF_JMP && + BPF_CLASS(insns1_last->code) != BPF_RET); + + // Non jumping instructions will always have a valid next instruction. + CHECK(insns1_last->next); + CHECK(insns2_last->next); + return PointerCompare(blocks.find(insns1_last->next)->second, + blocks.find(insns2_last->next)->second, + blocks); } // Compare the individual fields for both instructions. const Instruction& insn1 = **iter1; const Instruction& insn2 = **iter2; - if (insn1.code == insn2.code) { - if (insn1.k == insn2.k) { - // Only conditional jump instructions use the jt_ptr and jf_ptr - // fields. - if (BPF_CLASS(insn1.code) == BPF_JMP) { - if (BPF_OP(insn1.code) != BPF_JA) { - // Recursively compare the "true" and "false" branches. - // A well-formed BPF program can't have any cycles, so we know - // that our recursive algorithm will ultimately terminate. - // In the unlikely event that the programmer made a mistake and - // went out of the way to give us a cyclic program, we will crash - // with a stack overflow. We are OK with that. - int c = PointerCompare(blocks.find(insn1.jt_ptr)->second, - blocks.find(insn2.jt_ptr)->second, - blocks); - if (c == 0) { - c = PointerCompare(blocks.find(insn1.jf_ptr)->second, - blocks.find(insn2.jf_ptr)->second, - blocks); - if (c == 0) { - continue; - } else { - return c; - } - } else { - return c; - } - } else { - int c = PointerCompare(blocks.find(insn1.jt_ptr)->second, - blocks.find(insn2.jt_ptr)->second, - blocks); - if (c == 0) { - continue; - } else { - return c; - } - } - } else { - continue; - } - } else { - return insn1.k - insn2.k; - } - } else { + if (insn1.code != insn2.code) { return insn1.code - insn2.code; } + if (insn1.k != insn2.k) { + return insn1.k - insn2.k; + } + + // Sanity check: If we're looking at a JMP or RET instruction, by definition + // it should be the last instruction of the basic block. + if (BPF_CLASS(insn1.code) == BPF_JMP || BPF_CLASS(insn1.code) == BPF_RET) { + CHECK_EQ(insns1.back(), &insn1); + CHECK_EQ(insns2.back(), &insn2); + } + + // RET instructions terminate execution, and only JMP instructions use the + // jt_ptr and jf_ptr fields. Anything else can continue to the next + // instruction in the basic block. + if (BPF_CLASS(insn1.code) == BPF_RET) { + return 0; + } else if (BPF_CLASS(insn1.code) != BPF_JMP) { + continue; + } + + // Recursively compare the "true" and "false" branches. + // A well-formed BPF program can't have any cycles, so we know + // that our recursive algorithm will ultimately terminate. + // In the unlikely event that the programmer made a mistake and + // went out of the way to give us a cyclic program, we will crash + // with a stack overflow. We are OK with that. + if (BPF_OP(insn1.code) != BPF_JA) { + int c = PointerCompare(blocks.find(insn1.jf_ptr)->second, + blocks.find(insn2.jf_ptr)->second, + blocks); + if (c != 0) { + return c; + } + } + return PointerCompare(blocks.find(insn1.jt_ptr)->second, + blocks.find(insn2.jt_ptr)->second, + blocks); } } diff --git a/chromium/sandbox/linux/seccomp-bpf/codegen.h b/chromium/sandbox/linux/seccomp-bpf/codegen.h index 2745e51194e..1c4cd46a5a9 100644 --- a/chromium/sandbox/linux/seccomp-bpf/codegen.h +++ b/chromium/sandbox/linux/seccomp-bpf/codegen.h @@ -12,6 +12,7 @@ #include "sandbox/linux/seccomp-bpf/basicblock.h" #include "sandbox/linux/seccomp-bpf/instruction.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -52,7 +53,7 @@ typedef std::map<const BasicBlock*, int> IncomingBranches; // static_cast<unsigned short>(program->size()), &program[0] }; // prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog); // -class CodeGen { +class SANDBOX_EXPORT CodeGen { public: CodeGen(); ~CodeGen(); diff --git a/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc index 0539a0d4337..52fc24c6927 100644 --- a/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc +++ b/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc @@ -24,47 +24,44 @@ class SandboxUnittestHelper : public SandboxBPF { class CodeGenUnittestHelper : public CodeGen { public: void FindBranchTargets(const Instruction& instructions, - BranchTargets *branch_targets) { + BranchTargets* branch_targets) { CodeGen::FindBranchTargets(instructions, branch_targets); } - BasicBlock *CutGraphIntoBasicBlocks(Instruction *insns, + BasicBlock* CutGraphIntoBasicBlocks(Instruction* insns, const BranchTargets& branch_targets, - TargetsToBlocks *blocks) { + TargetsToBlocks* blocks) { return CodeGen::CutGraphIntoBasicBlocks(insns, branch_targets, blocks); } - void MergeTails(TargetsToBlocks *blocks) { - CodeGen::MergeTails(blocks); - } + void MergeTails(TargetsToBlocks* blocks) { CodeGen::MergeTails(blocks); } }; -enum { NO_FLAGS = 0x0000, - HAS_MERGEABLE_TAILS = 0x0001, -}; +enum { NO_FLAGS = 0x0000, HAS_MERGEABLE_TAILS = 0x0001, }; -Instruction *SampleProgramOneInstruction(CodeGen *codegen, int *flags) { +Instruction* SampleProgramOneInstruction(CodeGen* codegen, int* flags) { // Create the most basic valid BPF program: // RET ERR_ALLOWED *flags = NO_FLAGS; - return codegen->MakeInstruction(BPF_RET+BPF_K, + return codegen->MakeInstruction(BPF_RET + BPF_K, ErrorCode(ErrorCode::ERR_ALLOWED)); } -Instruction *SampleProgramSimpleBranch(CodeGen *codegen, int *flags) { +Instruction* SampleProgramSimpleBranch(CodeGen* codegen, int* flags) { // Create a program with a single branch: // JUMP if eq 42 then $0 else $1 // 0: RET EPERM // 1: RET ERR_ALLOWED *flags = NO_FLAGS; - return codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42, - codegen->MakeInstruction(BPF_RET+BPF_K, - ErrorCode(EPERM)), - codegen->MakeInstruction(BPF_RET+BPF_K, - ErrorCode(ErrorCode::ERR_ALLOWED))); + return codegen->MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, + 42, + codegen->MakeInstruction(BPF_RET + BPF_K, ErrorCode(EPERM)), + codegen->MakeInstruction(BPF_RET + BPF_K, + ErrorCode(ErrorCode::ERR_ALLOWED))); } -Instruction *SampleProgramAtypicalBranch(CodeGen *codegen, int *flags) { +Instruction* SampleProgramAtypicalBranch(CodeGen* codegen, int* flags) { // Create a program with a single branch: // JUMP if eq 42 then $0 else $0 // 0: RET ERR_ALLOWED @@ -74,13 +71,12 @@ Instruction *SampleProgramAtypicalBranch(CodeGen *codegen, int *flags) { // This needs to be reflected in our choice of "flags". *flags = NO_FLAGS; - Instruction *ret = - codegen->MakeInstruction(BPF_RET+BPF_K, - ErrorCode(ErrorCode::ERR_ALLOWED)); - return codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42, ret, ret); + Instruction* ret = codegen->MakeInstruction( + BPF_RET + BPF_K, ErrorCode(ErrorCode::ERR_ALLOWED)); + return codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, ret, ret); } -Instruction *SampleProgramComplex(CodeGen *codegen, int *flags) { +Instruction* SampleProgramComplex(CodeGen* codegen, int* flags) { // Creates a basic BPF program that we'll use to test some of the code: // JUMP if eq 42 the $0 else $1 (insn6) // 0: LD 23 (insn5) @@ -92,31 +88,33 @@ Instruction *SampleProgramComplex(CodeGen *codegen, int *flags) { // RET ErrorCode(42) (insn3+) *flags = HAS_MERGEABLE_TAILS; - Instruction *insn0 = codegen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, 42); + Instruction* insn0 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 42); SANDBOX_ASSERT(insn0); - SANDBOX_ASSERT(insn0->code == BPF_LD+BPF_W+BPF_ABS); + SANDBOX_ASSERT(insn0->code == BPF_LD + BPF_W + BPF_ABS); SANDBOX_ASSERT(insn0->k == 42); SANDBOX_ASSERT(insn0->next == NULL); - Instruction *insn1 = codegen->MakeInstruction(BPF_JMP+BPF_JA, 0, insn0); + Instruction* insn1 = codegen->MakeInstruction(BPF_JMP + BPF_JA, 0, insn0); SANDBOX_ASSERT(insn1); - SANDBOX_ASSERT(insn1->code == BPF_JMP+BPF_JA); + SANDBOX_ASSERT(insn1->code == BPF_JMP + BPF_JA); SANDBOX_ASSERT(insn1->jt_ptr == insn0); - Instruction *insn2 = codegen->MakeInstruction(BPF_RET+BPF_K, ErrorCode(42)); + Instruction* insn2 = codegen->MakeInstruction(BPF_RET + BPF_K, ErrorCode(42)); SANDBOX_ASSERT(insn2); - SANDBOX_ASSERT(insn2->code == BPF_RET+BPF_K); + SANDBOX_ASSERT(insn2->code == BPF_RET + BPF_K); SANDBOX_ASSERT(insn2->next == NULL); // We explicitly duplicate instructions so that MergeTails() can coalesce // them later. - Instruction *insn3 = codegen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, 42, - codegen->MakeInstruction(BPF_RET+BPF_K, ErrorCode(42))); + Instruction* insn3 = codegen->MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + 42, + codegen->MakeInstruction(BPF_RET + BPF_K, ErrorCode(42))); - Instruction *insn4 = codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42, - insn1, insn3); + Instruction* insn4 = + codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, insn1, insn3); SANDBOX_ASSERT(insn4); - SANDBOX_ASSERT(insn4->code == BPF_JMP+BPF_JEQ+BPF_K); + SANDBOX_ASSERT(insn4->code == BPF_JMP + BPF_JEQ + BPF_K); SANDBOX_ASSERT(insn4->k == 42); SANDBOX_ASSERT(insn4->jt_ptr == insn1); SANDBOX_ASSERT(insn4->jf_ptr == insn3); @@ -124,10 +122,10 @@ Instruction *SampleProgramComplex(CodeGen *codegen, int *flags) { codegen->JoinInstructions(insn0, insn2); SANDBOX_ASSERT(insn0->next == insn2); - Instruction *insn5 = codegen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, - 23, insn4); + Instruction* insn5 = + codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 23, insn4); SANDBOX_ASSERT(insn5); - SANDBOX_ASSERT(insn5->code == BPF_LD+BPF_W+BPF_ABS); + SANDBOX_ASSERT(insn5->code == BPF_LD + BPF_W + BPF_ABS); SANDBOX_ASSERT(insn5->k == 23); SANDBOX_ASSERT(insn5->next == insn4); @@ -137,18 +135,113 @@ Instruction *SampleProgramComplex(CodeGen *codegen, int *flags) { // This also gives us a diamond-shaped pattern in our graph, which stresses // another aspect of the topo-sort algorithm (namely, the ability to // correctly count the incoming branches for subtrees that are not disjunct). - Instruction *insn6 = codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42, - insn5, insn4); + Instruction* insn6 = + codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, insn5, insn4); return insn6; } -void ForAllPrograms(void (*test)(CodeGenUnittestHelper *, Instruction *, int)){ - Instruction *(*function_table[])(CodeGen *codegen, int *flags) = { +Instruction* SampleProgramConfusingTails(CodeGen* codegen, int* flags) { + // This simple program demonstrates https://crbug.com/351103/ + // The two "LOAD 0" instructions are blocks of their own. MergeTails() could + // be tempted to merge them since they are the same. However, they are + // not mergeable because they fall-through to non semantically equivalent + // blocks. + // Without the fix for this bug, this program should trigger the check in + // CompileAndCompare: the serialized graphs from the program and its compiled + // version will differ. + // + // 0) LOAD 1 // ??? + // 1) if A == 0x1; then JMP 2 else JMP 3 + // 2) LOAD 0 // System call number + // 3) if A == 0x2; then JMP 4 else JMP 5 + // 4) LOAD 0 // System call number + // 5) if A == 0x1; then JMP 6 else JMP 7 + // 6) RET 0x50000 // errno = 0 + // 7) RET 0x50001 // errno = 1 + *flags = NO_FLAGS; + + Instruction* i7 = codegen->MakeInstruction(BPF_RET, ErrorCode(1)); + Instruction* i6 = codegen->MakeInstruction(BPF_RET, ErrorCode(0)); + Instruction* i5 = + codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i6, i7); + Instruction* i4 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i5); + Instruction* i3 = + codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5); + Instruction* i2 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i3); + Instruction* i1 = + codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3); + Instruction* i0 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1); + + return i0; +} + +Instruction* SampleProgramConfusingTailsBasic(CodeGen* codegen, int* flags) { + // Without the fix for https://crbug.com/351103/, (see + // SampleProgramConfusingTails()), this would generate a cyclic graph and + // crash as the two "LOAD 0" instructions would get merged. + // + // 0) LOAD 1 // ??? + // 1) if A == 0x1; then JMP 2 else JMP 3 + // 2) LOAD 0 // System call number + // 3) if A == 0x2; then JMP 4 else JMP 5 + // 4) LOAD 0 // System call number + // 5) RET 0x50001 // errno = 1 + *flags = NO_FLAGS; + + Instruction* i5 = codegen->MakeInstruction(BPF_RET, ErrorCode(1)); + Instruction* i4 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i5); + Instruction* i3 = + codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5); + Instruction* i2 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i3); + Instruction* i1 = + codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3); + Instruction* i0 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1); + + return i0; +} + +Instruction* SampleProgramConfusingTailsMergeable(CodeGen* codegen, + int* flags) { + // This is similar to SampleProgramConfusingTails(), except that + // instructions 2 and 4 are now RET instructions. + // In PointerCompare(), this exercises the path where two blocks are of the + // same length and identical and the last instruction is a JMP or RET, so the + // following blocks don't need to be looked at and the blocks are mergeable. + // + // 0) LOAD 1 // ??? + // 1) if A == 0x1; then JMP 2 else JMP 3 + // 2) RET 0x5002a // errno = 42 + // 3) if A == 0x2; then JMP 4 else JMP 5 + // 4) RET 0x5002a // errno = 42 + // 5) if A == 0x1; then JMP 6 else JMP 7 + // 6) RET 0x50000 // errno = 0 + // 7) RET 0x50001 // errno = 1 + *flags = HAS_MERGEABLE_TAILS; + + Instruction* i7 = codegen->MakeInstruction(BPF_RET, ErrorCode(1)); + Instruction* i6 = codegen->MakeInstruction(BPF_RET, ErrorCode(0)); + Instruction* i5 = + codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i6, i7); + Instruction* i4 = codegen->MakeInstruction(BPF_RET, ErrorCode(42)); + Instruction* i3 = + codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5); + Instruction* i2 = codegen->MakeInstruction(BPF_RET, ErrorCode(42)); + Instruction* i1 = + codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3); + Instruction* i0 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1); + + return i0; +} +void ForAllPrograms(void (*test)(CodeGenUnittestHelper*, Instruction*, int)) { + Instruction* (*function_table[])(CodeGen* codegen, int* flags) = { SampleProgramOneInstruction, SampleProgramSimpleBranch, SampleProgramAtypicalBranch, SampleProgramComplex, + SampleProgramConfusingTails, + SampleProgramConfusingTailsBasic, + SampleProgramConfusingTailsMergeable, }; for (size_t i = 0; i < arraysize(function_table); ++i) { @@ -159,8 +252,8 @@ void ForAllPrograms(void (*test)(CodeGenUnittestHelper *, Instruction *, int)){ } } -void MakeInstruction(CodeGenUnittestHelper *codegen, - Instruction *program, int) { +void MakeInstruction(CodeGenUnittestHelper* codegen, + Instruction* program, int) { // Nothing to do here } @@ -168,7 +261,7 @@ SANDBOX_TEST(CodeGen, MakeInstruction) { ForAllPrograms(MakeInstruction); } -void FindBranchTargets(CodeGenUnittestHelper *codegen, Instruction *prg, int) { +void FindBranchTargets(CodeGenUnittestHelper* codegen, Instruction* prg, int) { BranchTargets branch_targets; codegen->FindBranchTargets(*prg, &branch_targets); @@ -178,11 +271,11 @@ void FindBranchTargets(CodeGenUnittestHelper *codegen, Instruction *prg, int) { // targets of BPF_JMP instructions are represented in the "branch_targets". // At the same time, compute a set of both the branch targets and all the // instructions in the program. - std::vector<Instruction *> stack; - std::set<Instruction *> all_instructions; - std::set<Instruction *> target_instructions; + std::vector<Instruction*> stack; + std::set<Instruction*> all_instructions; + std::set<Instruction*> target_instructions; BranchTargets::const_iterator end = branch_targets.end(); - for (Instruction *insn = prg;;) { + for (Instruction* insn = prg;;) { all_instructions.insert(insn); if (BPF_CLASS(insn->code) == BPF_JMP) { target_instructions.insert(insn->jt_ptr); @@ -215,8 +308,10 @@ void FindBranchTargets(CodeGenUnittestHelper *codegen, Instruction *prg, int) { // "branch_targets" that FindBranchTargets() computed for us. Instructions non_target_instructions(all_instructions.size() - target_instructions.size()); - set_difference(all_instructions.begin(), all_instructions.end(), - target_instructions.begin(), target_instructions.end(), + set_difference(all_instructions.begin(), + all_instructions.end(), + target_instructions.begin(), + target_instructions.end(), non_target_instructions.begin()); for (Instructions::const_iterator iter = non_target_instructions.begin(); iter != non_target_instructions.end(); @@ -225,20 +320,19 @@ void FindBranchTargets(CodeGenUnittestHelper *codegen, Instruction *prg, int) { } } -SANDBOX_TEST(CodeGen, FindBranchTargets) { - ForAllPrograms(FindBranchTargets); -} +SANDBOX_TEST(CodeGen, FindBranchTargets) { ForAllPrograms(FindBranchTargets); } -void CutGraphIntoBasicBlocks(CodeGenUnittestHelper *codegen, - Instruction *prg, int) { +void CutGraphIntoBasicBlocks(CodeGenUnittestHelper* codegen, + Instruction* prg, + int) { BranchTargets branch_targets; codegen->FindBranchTargets(*prg, &branch_targets); TargetsToBlocks all_blocks; - BasicBlock *first_block = - codegen->CutGraphIntoBasicBlocks(prg, branch_targets, &all_blocks); + BasicBlock* first_block = + codegen->CutGraphIntoBasicBlocks(prg, branch_targets, &all_blocks); SANDBOX_ASSERT(first_block != NULL); SANDBOX_ASSERT(first_block->instructions.size() > 0); - Instruction *first_insn = first_block->instructions[0]; + Instruction* first_insn = first_block->instructions[0]; // Basic blocks are supposed to start with a branch target and end with // either a jump or a return instruction. It can also end, if the next @@ -247,13 +341,13 @@ void CutGraphIntoBasicBlocks(CodeGenUnittestHelper *codegen, for (TargetsToBlocks::const_iterator bb_iter = all_blocks.begin(); bb_iter != all_blocks.end(); ++bb_iter) { - BasicBlock *bb = bb_iter->second; + BasicBlock* bb = bb_iter->second; SANDBOX_ASSERT(bb != NULL); SANDBOX_ASSERT(bb->instructions.size() > 0); - Instruction *insn = bb->instructions[0]; + Instruction* insn = bb->instructions[0]; SANDBOX_ASSERT(insn == first_insn || branch_targets.find(insn) != branch_targets.end()); - for (Instructions::const_iterator insn_iter = bb->instructions.begin();;){ + for (Instructions::const_iterator insn_iter = bb->instructions.begin();;) { insn = *insn_iter; if (++insn_iter != bb->instructions.end()) { SANDBOX_ASSERT(BPF_CLASS(insn->code) != BPF_JMP); @@ -261,8 +355,7 @@ void CutGraphIntoBasicBlocks(CodeGenUnittestHelper *codegen, } else { SANDBOX_ASSERT(BPF_CLASS(insn->code) == BPF_JMP || BPF_CLASS(insn->code) == BPF_RET || - branch_targets.find(insn->next) != - branch_targets.end()); + branch_targets.find(insn->next) != branch_targets.end()); break; } SANDBOX_ASSERT(branch_targets.find(*insn_iter) == branch_targets.end()); @@ -274,13 +367,12 @@ SANDBOX_TEST(CodeGen, CutGraphIntoBasicBlocks) { ForAllPrograms(CutGraphIntoBasicBlocks); } -void MergeTails(CodeGenUnittestHelper *codegen, Instruction *prg, - int flags) { +void MergeTails(CodeGenUnittestHelper* codegen, Instruction* prg, int flags) { BranchTargets branch_targets; codegen->FindBranchTargets(*prg, &branch_targets); TargetsToBlocks all_blocks; - BasicBlock *first_block = - codegen->CutGraphIntoBasicBlocks(prg, branch_targets, &all_blocks); + BasicBlock* first_block = + codegen->CutGraphIntoBasicBlocks(prg, branch_targets, &all_blocks); // The shape of our graph and thus the function of our program should // still be unchanged after we run MergeTails(). We verify this by @@ -294,8 +386,8 @@ void MergeTails(CodeGenUnittestHelper *codegen, Instruction *prg, // our graph. for (int i = 0;;) { // Traverse the entire program in depth-first order. - std::vector<BasicBlock *> stack; - for (BasicBlock *bb = first_block;;) { + std::vector<BasicBlock*> stack; + for (BasicBlock* bb = first_block;;) { // Serialize the instructions in this basic block. In general, we only // need to serialize "code" and "k"; except for a BPF_JA instruction // where "k" isn't set. @@ -303,23 +395,23 @@ void MergeTails(CodeGenUnittestHelper *codegen, Instruction *prg, for (Instructions::const_iterator iter = bb->instructions.begin(); iter != bb->instructions.end(); ++iter) { - graph[i].append(reinterpret_cast<char *>(&(*iter)->code), + graph[i].append(reinterpret_cast<char*>(&(*iter)->code), sizeof((*iter)->code)); if (BPF_CLASS((*iter)->code) != BPF_JMP || BPF_OP((*iter)->code) != BPF_JA) { - graph[i].append(reinterpret_cast<char *>(&(*iter)->k), + graph[i].append(reinterpret_cast<char*>(&(*iter)->k), sizeof((*iter)->k)); } } // Also serialize the addresses the basic blocks as we encounter them. // This will change as basic blocks are coalesed by MergeTails(). - edges[i].append(reinterpret_cast<char *>(&bb), sizeof(bb)); + edges[i].append(reinterpret_cast<char*>(&bb), sizeof(bb)); // Depth-first traversal of the graph. We only ever need to look at the // very last instruction in the basic block, as that is the only one that // can change code flow. - Instruction *insn = bb->instructions.back(); + Instruction* insn = bb->instructions.back(); if (BPF_CLASS(insn->code) == BPF_JMP) { // For jump instructions, we need to remember the "false" branch while // traversing the "true" branch. This is not necessary for BPF_JA which @@ -359,7 +451,7 @@ SANDBOX_TEST(CodeGen, MergeTails) { ForAllPrograms(MergeTails); } -void CompileAndCompare(CodeGenUnittestHelper *codegen, Instruction *prg, int) { +void CompileAndCompare(CodeGenUnittestHelper* codegen, Instruction* prg, int) { // TopoSortBasicBlocks() has internal checks that cause it to fail, if it // detects a problem. Typically, if anything goes wrong, this looks to the // TopoSort algorithm as if there had been cycles in the input data. @@ -375,7 +467,7 @@ void CompileAndCompare(CodeGenUnittestHelper *codegen, Instruction *prg, int) { // before calling Compile(). std::string source; Instructions source_stack; - for (const Instruction *insn = prg, *next; insn; insn = next) { + for (const Instruction* insn = prg, *next; insn; insn = next) { if (BPF_CLASS(insn->code) == BPF_JMP) { if (BPF_OP(insn->code) == BPF_JA) { // Do not serialize BPF_JA instructions (see above). @@ -398,10 +490,9 @@ void CompileAndCompare(CodeGenUnittestHelper *codegen, Instruction *prg, int) { // Only serialize "code" and "k". That's all the information we need to // compare. The rest of the information is encoded in the order of // instructions. - source.append(reinterpret_cast<const char *>(&insn->code), + source.append(reinterpret_cast<const char*>(&insn->code), sizeof(insn->code)); - source.append(reinterpret_cast<const char *>(&insn->k), - sizeof(insn->k)); + source.append(reinterpret_cast<const char*>(&insn->k), sizeof(insn->k)); } // Compile the program @@ -434,8 +525,8 @@ void CompileAndCompare(CodeGenUnittestHelper *codegen, Instruction *prg, int) { ++idx; } // Serialize the same information that we serialized before compilation. - assembly.append(reinterpret_cast<char *>(&insn.code), sizeof(insn.code)); - assembly.append(reinterpret_cast<char *>(&insn.k), sizeof(insn.k)); + assembly.append(reinterpret_cast<char*>(&insn.code), sizeof(insn.code)); + assembly.append(reinterpret_cast<char*>(&insn.k), sizeof(insn.k)); } SANDBOX_ASSERT(source == assembly); } diff --git a/chromium/sandbox/linux/seccomp-bpf/demo.cc b/chromium/sandbox/linux/seccomp-bpf/demo.cc index 14180181a6e..d9fd3423eee 100644 --- a/chromium/sandbox/linux/seccomp-bpf/demo.cc +++ b/chromium/sandbox/linux/seccomp-bpf/demo.cc @@ -26,12 +26,15 @@ #include <time.h> #include <unistd.h> +#include "base/macros.h" #include "base/posix/eintr_wrapper.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h" #include "sandbox/linux/services/linux_syscalls.h" using sandbox::ErrorCode; using sandbox::SandboxBPF; +using sandbox::SandboxBPFPolicy; using sandbox::arch_seccomp_data; #define ERR EPERM @@ -237,7 +240,17 @@ intptr_t DefaultHandler(const struct arch_seccomp_data& data, void *) { return -ERR; } -ErrorCode Evaluator(SandboxBPF* sandbox, int sysno, void *) { +class DemoPolicy : public SandboxBPFPolicy { + public: + DemoPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(DemoPolicy); +}; + +ErrorCode DemoPolicy::EvaluateSyscall(SandboxBPF* sandbox, int sysno) const { switch (sysno) { #if defined(__NR_accept) case __NR_accept: case __NR_accept4: @@ -420,8 +433,11 @@ int main(int argc, char *argv[]) { } SandboxBPF sandbox; sandbox.set_proc_fd(proc_fd); - sandbox.SetSandboxPolicyDeprecated(Evaluator, NULL); - sandbox.StartSandbox(); + sandbox.SetSandboxPolicy(new DemoPolicy()); + if (!sandbox.StartSandbox(SandboxBPF::PROCESS_SINGLE_THREADED)) { + fprintf(stderr, "StartSandbox() failed"); + _exit(1); + } // Check that we can create threads pthread_t thr; diff --git a/chromium/sandbox/linux/seccomp-bpf/die.cc b/chromium/sandbox/linux/seccomp-bpf/die.cc index 533e2e9c353..e5bc7c93847 100644 --- a/chromium/sandbox/linux/seccomp-bpf/die.cc +++ b/chromium/sandbox/linux/seccomp-bpf/die.cc @@ -22,7 +22,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. - SandboxSyscall(__NR_exit_group, 1); + Syscall::Call(__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 @@ -30,7 +30,7 @@ void Die::ExitGroup() { // succeeded in doing so. Nonetheless, triggering a fatal signal could help // us terminate. signal(SIGSEGV, SIG_DFL); - SandboxSyscall(__NR_prctl, PR_SET_DUMPABLE, (void*)0, (void*)0, (void*)0); + Syscall::Call(__NR_prctl, PR_SET_DUMPABLE, (void*)0, (void*)0, (void*)0); if (*(volatile char*)0) { } @@ -40,7 +40,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 (;;) { - SandboxSyscall(__NR_exit_group, 1); + Syscall::Call(__NR_exit_group, 1); } } @@ -75,7 +75,7 @@ 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. ignore_result( - HANDLE_EINTR(SandboxSyscall(__NR_write, 2, s.c_str(), s.length()))); + HANDLE_EINTR(Syscall::Call(__NR_write, 2, s.c_str(), s.length()))); } } diff --git a/chromium/sandbox/linux/seccomp-bpf/die.h b/chromium/sandbox/linux/seccomp-bpf/die.h index 5dcfda0f2b4..3ac31cc6c41 100644 --- a/chromium/sandbox/linux/seccomp-bpf/die.h +++ b/chromium/sandbox/linux/seccomp-bpf/die.h @@ -6,6 +6,7 @@ #define SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ #include "base/basictypes.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -20,7 +21,7 @@ namespace sandbox { // Adds an informational message to the log file or stderr as appropriate. #define SANDBOX_INFO(m) sandbox::Die::SandboxInfo(m, __FILE__, __LINE__) -class Die { +class SANDBOX_EXPORT Die { public: // Terminate the program, even if the current sandbox policy prevents some // of the more commonly used functions used for exiting. diff --git a/chromium/sandbox/linux/seccomp-bpf/errorcode.cc b/chromium/sandbox/linux/seccomp-bpf/errorcode.cc index 64848528202..5a45e4c8cf3 100644 --- a/chromium/sandbox/linux/seccomp-bpf/errorcode.cc +++ b/chromium/sandbox/linux/seccomp-bpf/errorcode.cc @@ -18,6 +18,11 @@ ErrorCode::ErrorCode(int err) { error_type_ = ET_SIMPLE; break; default: + if ((err & ~SECCOMP_RET_DATA) == ERR_TRACE) { + err_ = SECCOMP_RET_TRACE + (err & SECCOMP_RET_DATA); + error_type_ = ET_SIMPLE; + break; + } SANDBOX_DIE("Invalid use of ErrorCode object"); } } diff --git a/chromium/sandbox/linux/seccomp-bpf/errorcode.h b/chromium/sandbox/linux/seccomp-bpf/errorcode.h index 625d123513f..2e513818d81 100644 --- a/chromium/sandbox/linux/seccomp-bpf/errorcode.h +++ b/chromium/sandbox/linux/seccomp-bpf/errorcode.h @@ -7,6 +7,7 @@ #include "sandbox/linux/seccomp-bpf/linux_seccomp.h" #include "sandbox/linux/seccomp-bpf/trap.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -20,7 +21,7 @@ struct arch_seccomp_data; // 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 // field. -class ErrorCode { +class SANDBOX_EXPORT ErrorCode { public: enum { // Allow this system call. The value of ERR_ALLOWED is pretty much @@ -29,6 +30,12 @@ class ErrorCode { // "errno" (see below) value instead. ERR_ALLOWED = 0x04000000, + // If the progress is being ptraced with PTRACE_O_TRACESECCOMP, then the + // tracer will be notified of a PTRACE_EVENT_SECCOMP and allowed to change + // or skip the system call. The lower 16 bits of err will be available to + // the tracer via PTRACE_GETEVENTMSG. + ERR_TRACE = 0x08000000, + // Deny the system call with a particular "errno" value. // N.B.: It is also possible to return "0" here. That would normally // indicate success, but it won't actually run the system call. diff --git a/chromium/sandbox/linux/seccomp-bpf/errorcode_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/errorcode_unittest.cc index ef04a5f61f8..f3b77483355 100644 --- a/chromium/sandbox/linux/seccomp-bpf/errorcode_unittest.cc +++ b/chromium/sandbox/linux/seccomp-bpf/errorcode_unittest.cc @@ -24,6 +24,17 @@ SANDBOX_TEST(ErrorCode, ErrnoConstructor) { SandboxBPF sandbox; ErrorCode e3 = sandbox.Trap(NULL, NULL); SANDBOX_ASSERT((e3.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP); + + uint16_t data = 0xdead; + ErrorCode e4(ErrorCode::ERR_TRACE + data); + SANDBOX_ASSERT(e4.err() == SECCOMP_RET_TRACE + data); +} + +SANDBOX_DEATH_TEST(ErrorCode, + InvalidSeccompRetTrace, + DEATH_MESSAGE("Invalid use of ErrorCode object")) { + // Should die if the trace data does not fit in 16 bits. + ErrorCode e(ErrorCode::ERR_TRACE + (1 << 16)); } SANDBOX_TEST(ErrorCode, Trap) { diff --git a/chromium/sandbox/linux/seccomp-bpf/linux_seccomp.h b/chromium/sandbox/linux/seccomp-bpf/linux_seccomp.h index 0de0259da39..270e11c1f8f 100644 --- a/chromium/sandbox/linux/seccomp-bpf/linux_seccomp.h +++ b/chromium/sandbox/linux/seccomp-bpf/linux_seccomp.h @@ -16,6 +16,14 @@ #include <asm/unistd.h> #include <linux/filter.h> +#include <sys/cdefs.h> +// Old Bionic versions do not have sys/user.h. The if can be removed once we no +// longer need to support these old Bionic versions. +// All x86_64 builds use a new enough bionic to have sys/user.h. +#if !defined(__BIONIC__) || defined(__x86_64__) +#include <sys/user.h> +#endif + // For audit.h #ifndef EM_ARM #define EM_ARM 40 @@ -124,6 +132,44 @@ #define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ 8*(nr) + 0) + +#if defined(__BIONIC__) +// Old Bionic versions don't have sys/user.h, so we just define regs_struct +// directly. This can be removed once we no longer need to support these old +// Bionic versions. +struct regs_struct { + long int ebx; + long int ecx; + long int edx; + long int esi; + long int edi; + long int ebp; + long int eax; + long int xds; + long int xes; + long int xfs; + long int xgs; + long int orig_eax; + long int eip; + long int xcs; + long int eflags; + long int esp; + long int xss; +}; +#else +typedef user_regs_struct regs_struct; +#endif + +#define SECCOMP_PT_RESULT(_regs) (_regs).eax +#define SECCOMP_PT_SYSCALL(_regs) (_regs).orig_eax +#define SECCOMP_PT_IP(_regs) (_regs).eip +#define SECCOMP_PT_PARM1(_regs) (_regs).ebx +#define SECCOMP_PT_PARM2(_regs) (_regs).ecx +#define SECCOMP_PT_PARM3(_regs) (_regs).edx +#define SECCOMP_PT_PARM4(_regs) (_regs).esi +#define SECCOMP_PT_PARM5(_regs) (_regs).edi +#define SECCOMP_PT_PARM6(_regs) (_regs).ebp + #elif defined(__x86_64__) #define MIN_SYSCALL 0u #define MAX_PUBLIC_SYSCALL 1024u @@ -151,6 +197,17 @@ #define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ 8*(nr) + 0) +typedef user_regs_struct regs_struct; +#define SECCOMP_PT_RESULT(_regs) (_regs).rax +#define SECCOMP_PT_SYSCALL(_regs) (_regs).orig_rax +#define SECCOMP_PT_IP(_regs) (_regs).rip +#define SECCOMP_PT_PARM1(_regs) (_regs).rdi +#define SECCOMP_PT_PARM2(_regs) (_regs).rsi +#define SECCOMP_PT_PARM3(_regs) (_regs).rdx +#define SECCOMP_PT_PARM4(_regs) (_regs).r10 +#define SECCOMP_PT_PARM5(_regs) (_regs).r8 +#define SECCOMP_PT_PARM6(_regs) (_regs).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, @@ -189,6 +246,46 @@ #define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ 8*(nr) + 0) +#if defined(__BIONIC__) +// Old Bionic versions don't have sys/user.h, so we just define regs_struct +// directly. This can be removed once we no longer need to support these old +// Bionic versions. +struct regs_struct { + unsigned long uregs[18]; +}; +#else +typedef user_regs regs_struct; +#endif + +#define REG_cpsr uregs[16] +#define REG_pc uregs[15] +#define REG_lr uregs[14] +#define REG_sp uregs[13] +#define REG_ip uregs[12] +#define REG_fp uregs[11] +#define REG_r10 uregs[10] +#define REG_r9 uregs[9] +#define REG_r8 uregs[8] +#define REG_r7 uregs[7] +#define REG_r6 uregs[6] +#define REG_r5 uregs[5] +#define REG_r4 uregs[4] +#define REG_r3 uregs[3] +#define REG_r2 uregs[2] +#define REG_r1 uregs[1] +#define REG_r0 uregs[0] +#define REG_ORIG_r0 uregs[17] + +#define SECCOMP_PT_RESULT(_regs) (_regs).REG_r0 +#define SECCOMP_PT_SYSCALL(_regs) (_regs).REG_r7 +#define SECCOMP_PT_IP(_regs) (_regs).REG_pc +#define SECCOMP_PT_PARM1(_regs) (_regs).REG_r0 +#define SECCOMP_PT_PARM2(_regs) (_regs).REG_r1 +#define SECCOMP_PT_PARM3(_regs) (_regs).REG_r2 +#define SECCOMP_PT_PARM4(_regs) (_regs).REG_r3 +#define SECCOMP_PT_PARM5(_regs) (_regs).REG_r4 +#define SECCOMP_PT_PARM6(_regs) (_regs).REG_r5 + #else #error Unsupported target platform diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc index 6b2327e5452..6ecbca99a5f 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -22,6 +22,7 @@ #include "base/compiler_specific.h" #include "base/logging.h" +#include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/posix/eintr_wrapper.h" #include "sandbox/linux/seccomp-bpf/codegen.h" @@ -56,20 +57,26 @@ void WriteFailedStderrSetupMessage(int out_fd) { // We define a really simple sandbox policy. It is just good enough for us // to tell that the sandbox has actually been activated. -ErrorCode ProbeEvaluator(SandboxBPF*, int sysnum, void*) __attribute__((const)); -ErrorCode ProbeEvaluator(SandboxBPF*, int sysnum, void*) { - switch (sysnum) { - case __NR_getpid: - // Return EPERM so that we can check that the filter actually ran. - return ErrorCode(EPERM); - case __NR_exit_group: - // Allow exit() with a non-default return code. - return ErrorCode(ErrorCode::ERR_ALLOWED); - default: - // Make everything else fail in an easily recognizable way. - return ErrorCode(EINVAL); +class ProbePolicy : public SandboxBPFPolicy { + public: + ProbePolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysnum) const OVERRIDE { + switch (sysnum) { + case __NR_getpid: + // Return EPERM so that we can check that the filter actually ran. + return ErrorCode(EPERM); + case __NR_exit_group: + // Allow exit() with a non-default return code. + return ErrorCode(ErrorCode::ERR_ALLOWED); + default: + // Make everything else fail in an easily recognizable way. + return ErrorCode(EINVAL); + } } -} + + private: + DISALLOW_COPY_AND_ASSIGN(ProbePolicy); +}; void ProbeProcess(void) { if (syscall(__NR_getpid) < 0 && errno == EPERM) { @@ -77,12 +84,17 @@ void ProbeProcess(void) { } } -ErrorCode AllowAllEvaluator(SandboxBPF*, int sysnum, void*) { - if (!SandboxBPF::IsValidSyscallNumber(sysnum)) { - return ErrorCode(ENOSYS); +class AllowAllPolicy : public SandboxBPFPolicy { + public: + AllowAllPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysnum) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysnum)); + return ErrorCode(ErrorCode::ERR_ALLOWED); } - return ErrorCode(ErrorCode::ERR_ALLOWED); -} + + private: + DISALLOW_COPY_AND_ASSIGN(AllowAllPolicy); +}; void TryVsyscallProcess(void) { time_t current_time; @@ -187,13 +199,21 @@ class RedirectToUserSpacePolicyWrapper : public SandboxBPFPolicy { ErrorCode err = wrapped_policy_->EvaluateSyscall(sandbox_compiler, system_call_number); if ((err.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) { - return sandbox_compiler->Trap( - ReturnErrno, reinterpret_cast<void*>(err.err() & SECCOMP_RET_DATA)); + return ReturnErrnoViaTrap(sandbox_compiler, err.err() & SECCOMP_RET_DATA); } return err; } + virtual ErrorCode InvalidSyscall( + SandboxBPF* sandbox_compiler) const OVERRIDE { + return ReturnErrnoViaTrap(sandbox_compiler, ENOSYS); + } + private: + ErrorCode ReturnErrnoViaTrap(SandboxBPF* sandbox_compiler, int err) const { + return sandbox_compiler->Trap(ReturnErrno, reinterpret_cast<void*>(err)); + } + const SandboxBPFPolicy* wrapped_policy_; DISALLOW_COPY_AND_ASSIGN(RedirectToUserSpacePolicyWrapper); }; @@ -202,25 +222,6 @@ intptr_t BPFFailure(const struct arch_seccomp_data&, void* aux) { SANDBOX_DIE(static_cast<char*>(aux)); } -// This class allows compatibility with the old, deprecated SetSandboxPolicy. -class CompatibilityPolicy : public SandboxBPFPolicy { - public: - CompatibilityPolicy(SandboxBPF::EvaluateSyscall syscall_evaluator, void* aux) - : syscall_evaluator_(syscall_evaluator), aux_(aux) { - DCHECK(syscall_evaluator_); - } - - virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler, - int system_call_number) const OVERRIDE { - return syscall_evaluator_(sandbox_compiler, system_call_number, aux_); - } - - private: - SandboxBPF::EvaluateSyscall syscall_evaluator_; - void* aux_; - DISALLOW_COPY_AND_ASSIGN(CompatibilityPolicy); -}; - } // namespace SandboxBPF::SandboxBPF() @@ -251,8 +252,7 @@ bool SandboxBPF::IsValidSyscallNumber(int sysnum) { } bool SandboxBPF::RunFunctionInPolicy(void (*code_in_sandbox)(), - EvaluateSyscall syscall_evaluator, - void* aux) { + scoped_ptr<SandboxBPFPolicy> policy) { // Block all signals before forking a child process. This prevents an // attacker from manipulating our test by sending us an unexpected signal. sigset_t old_mask, new_mask; @@ -322,8 +322,10 @@ bool SandboxBPF::RunFunctionInPolicy(void (*code_in_sandbox)(), #endif } - SetSandboxPolicyDeprecated(syscall_evaluator, aux); - StartSandbox(); + SetSandboxPolicy(policy.release()); + if (!StartSandbox(PROCESS_SINGLE_THREADED)) { + SANDBOX_DIE(NULL); + } // Run our code in the sandbox. code_in_sandbox(); @@ -369,8 +371,11 @@ bool SandboxBPF::RunFunctionInPolicy(void (*code_in_sandbox)(), } bool SandboxBPF::KernelSupportSeccompBPF() { - return RunFunctionInPolicy(ProbeProcess, ProbeEvaluator, 0) && - RunFunctionInPolicy(TryVsyscallProcess, AllowAllEvaluator, 0); + return RunFunctionInPolicy(ProbeProcess, + scoped_ptr<SandboxBPFPolicy>(new ProbePolicy())) && + RunFunctionInPolicy( + TryVsyscallProcess, + scoped_ptr<SandboxBPFPolicy>(new AllowAllPolicy())); } SandboxBPF::SandboxStatus SandboxBPF::SupportsSeccompSandbox(int proc_fd) { @@ -430,15 +435,20 @@ SandboxBPF::SandboxStatus SandboxBPF::SupportsSeccompSandbox(int proc_fd) { void SandboxBPF::set_proc_fd(int proc_fd) { proc_fd_ = proc_fd; } -void SandboxBPF::StartSandbox() { +bool SandboxBPF::StartSandbox(SandboxThreadState thread_state) { + CHECK(thread_state == PROCESS_SINGLE_THREADED || + thread_state == PROCESS_MULTI_THREADED); + if (status_ == STATUS_UNSUPPORTED || status_ == STATUS_UNAVAILABLE) { SANDBOX_DIE( "Trying to start sandbox, even though it is known to be " "unavailable"); + return false; } else if (sandbox_has_started_ || !conds_) { SANDBOX_DIE( "Cannot repeatedly start sandbox. Create a separate Sandbox " "object instead."); + return false; } if (proc_fd_ < 0) { proc_fd_ = open("/proc", O_RDONLY | O_DIRECTORY); @@ -447,8 +457,10 @@ void SandboxBPF::StartSandbox() { // 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 (thread_state == PROCESS_SINGLE_THREADED && !IsSingleThreaded(proc_fd_)) { SANDBOX_DIE("Cannot start sandbox, if process is already multi-threaded"); + return false; } // We no longer need access to any files in /proc. We want to do this @@ -457,38 +469,27 @@ void SandboxBPF::StartSandbox() { if (proc_fd_ >= 0) { if (IGNORE_EINTR(close(proc_fd_))) { SANDBOX_DIE("Failed to close file descriptor for /proc"); + return false; } proc_fd_ = -1; } // Install the filters. - InstallFilter(); + InstallFilter(thread_state); // We are now inside the sandbox. status_ = STATUS_ENABLED; + + return true; } void SandboxBPF::PolicySanityChecks(SandboxBPFPolicy* policy) { - for (SyscallIterator iter(true); !iter.Done();) { - uint32_t sysnum = iter.Next(); - if (!IsDenied(policy->EvaluateSyscall(this, sysnum))) { - SANDBOX_DIE( - "Policies should deny system calls that are outside the " - "expected range (typically MIN_SYSCALL..MAX_SYSCALL)"); - } + if (!IsDenied(policy->InvalidSyscall(this))) { + SANDBOX_DIE("Policies should deny invalid system calls."); } return; } -// Deprecated API, supported with a wrapper to the new API. -void SandboxBPF::SetSandboxPolicyDeprecated(EvaluateSyscall syscall_evaluator, - void* aux) { - if (sandbox_has_started_ || !conds_) { - SANDBOX_DIE("Cannot change policy after sandbox has started"); - } - SetSandboxPolicy(new CompatibilityPolicy(syscall_evaluator, aux)); -} - // Don't take a scoped_ptr here, polymorphism make their use awkward. void SandboxBPF::SetSandboxPolicy(SandboxBPFPolicy* policy) { DCHECK(!policy_); @@ -499,7 +500,7 @@ void SandboxBPF::SetSandboxPolicy(SandboxBPFPolicy* policy) { policy_.reset(policy); } -void SandboxBPF::InstallFilter() { +void SandboxBPF::InstallFilter(SandboxThreadState thread_state) { // 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 @@ -535,9 +536,23 @@ void SandboxBPF::InstallFilter() { } } - sandbox_has_started_ = true; + // TODO(rsesek): Always try to engage the sandbox with the + // PROCESS_MULTI_THREADED path first, and if that fails, assert that the + // process IsSingleThreaded() or SANDBOX_DIE. + + if (thread_state == PROCESS_MULTI_THREADED) { + // TODO(rsesek): Move these to a more reasonable place once the kernel + // patch has landed upstream and these values are formalized. + #define PR_SECCOMP_EXT 41 + #define SECCOMP_EXT_ACT 1 + #define SECCOMP_EXT_ACT_TSYNC 1 + if (prctl(PR_SECCOMP_EXT, SECCOMP_EXT_ACT, SECCOMP_EXT_ACT_TSYNC, 0, 0)) { + SANDBOX_DIE(quiet_ ? NULL : "Kernel refuses to synchronize threadgroup " + "BPF filters."); + } + } - return; + sandbox_has_started_ = true; } SandboxBPF::Program* SandboxBPF::AssembleFilter(bool force_verification) { @@ -598,7 +613,7 @@ SandboxBPF::Program* SandboxBPF::AssembleFilter(bool force_verification) { // 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) { + if (Syscall::Call(-1) == -1 && errno == ENOSYS) { SANDBOX_DIE( "Support for UnsafeTrap() has not yet been ported to this " "architecture"); @@ -635,9 +650,8 @@ SandboxBPF::Program* SandboxBPF::AssembleFilter(bool force_verification) { 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)); + // (which we can query by calling Syscall::Call(-1)). + uintptr_t syscall_entry_point = static_cast<uintptr_t>(Syscall::Call(-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); @@ -737,20 +751,18 @@ void SandboxBPF::FindRanges(Ranges* ranges) { // 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. + const ErrorCode invalid_err = policy_->InvalidSyscall(this); uint32_t old_sysnum = 0; - ErrorCode old_err = policy_->EvaluateSyscall(this, old_sysnum); - ErrorCode invalid_err = policy_->EvaluateSyscall(this, MIN_SYSCALL - 1); + ErrorCode old_err = IsValidSyscallNumber(old_sysnum) + ? policy_->EvaluateSyscall(this, old_sysnum) + : invalid_err; for (SyscallIterator iter(false); !iter.Done();) { uint32_t sysnum = iter.Next(); - ErrorCode err = policy_->EvaluateSyscall(this, static_cast<int>(sysnum)); - 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"); - } + ErrorCode err = + IsValidSyscallNumber(sysnum) + ? policy_->EvaluateSyscall(this, static_cast<int>(sysnum)) + : invalid_err; if (!err.Equals(old_err) || iter.Done()) { ranges->push_back(Range(old_sysnum, sysnum - 1, old_err)); old_sysnum = sysnum; @@ -990,13 +1002,13 @@ ErrorCode SandboxBPF::UnsafeTrap(Trap::TrapFnc fnc, const void* aux) { } intptr_t SandboxBPF::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])); + return Syscall::Call(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])); } ErrorCode SandboxBPF::Cond(int argno, diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h index d626e4c74ca..32fe2a7d748 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -16,13 +16,16 @@ #include <utility> #include <vector> +#include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "sandbox/linux/seccomp-bpf/die.h" #include "sandbox/linux/seccomp-bpf/errorcode.h" #include "sandbox/linux/seccomp-bpf/linux_seccomp.h" +#include "sandbox/sandbox_export.h" namespace sandbox { +// This must match the kernel's seccomp_data structure. struct arch_seccomp_data { int nr; uint32_t arch; @@ -41,7 +44,7 @@ class SandboxBPFPolicy; class SandboxUnittestHelper; struct Instruction; -class SandboxBPF { +class SANDBOX_EXPORT SandboxBPF { public: enum SandboxStatus { STATUS_UNKNOWN, // Status prior to calling supportsSeccompSandbox() @@ -51,15 +54,18 @@ class SandboxBPF { STATUS_ENABLED // The sandbox is now active }; - // When calling setSandboxPolicy(), the caller can provide an arbitrary - // pointer in |aux|. 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)(SandboxBPF* sandbox_compiler, - int system_call_number, - void* aux); - typedef std::vector<std::pair<EvaluateSyscall, void*> > Evaluators; + // Depending on the level of kernel support, seccomp-bpf may require the + // process to be single-threaded in order to enable it. When calling + // StartSandbox(), the program should indicate whether or not the sandbox + // should try and engage with multi-thread support. + enum SandboxThreadState { + PROCESS_INVALID, + PROCESS_SINGLE_THREADED, // The program is currently single-threaded. + // Note: PROCESS_MULTI_THREADED requires experimental kernel support that + // has not been contributed to upstream Linux. + PROCESS_MULTI_THREADED, // The program may be multi-threaded. + }; + // A vector of BPF instructions that need to be installed as a filter // program in the kernel. typedef std::vector<struct sock_filter> Program; @@ -96,20 +102,6 @@ class SandboxBPF { // 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 - // by returning ERR_ALLOWED; it can deny the system call unconditionally by - // returning an appropriate "errno" value; or it can request inspection - // of system call argument(s) by returning a suitable ErrorCode. - // The "aux" parameter can be used to pass optional data to the system call - // evaluator. There are different possible uses for this data, but one of the - // use cases would be for the policy to then forward this pointer to a Trap() - // 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. - // DEPRECATED: use the policy interface below. - void SetSandboxPolicyDeprecated(EvaluateSyscall syscallEvaluator, void* aux); - // Set the BPF policy as |policy|. Ownership of |policy| is transfered here // to the sandbox object. void SetSandboxPolicy(SandboxBPFPolicy* policy); @@ -167,6 +159,8 @@ class SandboxBPF { // 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. + // The calling process must specify its current SandboxThreadState, as a way + // to tell the sandbox which type of kernel support it should engage. // 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 @@ -175,7 +169,7 @@ class SandboxBPF { // 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(); + bool StartSandbox(SandboxThreadState thread_state) WARN_UNUSED_RESULT; // Assembles a BPF filter program from the current policy. After calling this // function, you must not call any other sandboxing function. @@ -214,8 +208,7 @@ class SandboxBPF { // 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); + scoped_ptr<SandboxBPFPolicy> policy); // Performs a couple of sanity checks to verify that the kernel supports the // features that we need for successful sandboxing. @@ -228,7 +221,7 @@ class SandboxBPF { // Assembles and installs a filter based on the policy that has previously // been configured with SetSandboxPolicy(). - void InstallFilter(); + void InstallFilter(SandboxThreadState thread_state); // 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 diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_compatibility_policy.h b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_compatibility_policy.h new file mode 100644 index 00000000000..d4b8ab8b15a --- /dev/null +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_compatibility_policy.h @@ -0,0 +1,43 @@ +// Copyright 2014 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_SANDBOX_BPF_COMPATIBILITY_POLICY_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_COMPATIBILITY_POLICY_H_ + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/macros.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h" + +namespace sandbox { + +// This class allows compatibility with the old, deprecated +// policies that were designed for SetSandboxPolicyDeprecated(). +template <class AuxType> +class CompatibilityPolicy : public SandboxBPFPolicy { + public: + typedef ErrorCode (*SyscallEvaluator)(SandboxBPF* sandbox_compiler, + int system_call_number, + AuxType* aux); + CompatibilityPolicy(SyscallEvaluator syscall_evaluator, AuxType* aux) + : syscall_evaluator_(syscall_evaluator), aux_(aux) {} + + virtual ~CompatibilityPolicy() {} + + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler, + int system_call_number) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(system_call_number)); + return syscall_evaluator_(sandbox_compiler, system_call_number, aux_); + } + + private: + SyscallEvaluator syscall_evaluator_; + AuxType* aux_; + DISALLOW_COPY_AND_ASSIGN(CompatibilityPolicy); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_COMPATIBILITY_POLICY_H_ diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.cc new file mode 100644 index 00000000000..962c8036f8a --- /dev/null +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.cc @@ -0,0 +1,17 @@ +// Copyright 2014 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/seccomp-bpf/sandbox_bpf_policy.h" + +#include <errno.h> + +#include "sandbox/linux/seccomp-bpf/errorcode.h" + +namespace sandbox { + +ErrorCode SandboxBPFPolicy::InvalidSyscall(SandboxBPF* sandbox_compiler) const { + return ErrorCode(ENOSYS); +} + +} // namespace sandbox diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h index 1ac5daba5d9..fc6fdf6fe4f 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h @@ -6,6 +6,7 @@ #define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_POLICY_H_ #include "base/basictypes.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -13,7 +14,7 @@ class ErrorCode; class SandboxBPF; // This is the interface to implement to define a BPF sandbox policy. -class SandboxBPFPolicy { +class SANDBOX_EXPORT SandboxBPFPolicy { public: SandboxBPFPolicy() {} virtual ~SandboxBPFPolicy() {} @@ -23,9 +24,14 @@ class SandboxBPFPolicy { // it can deny the system call unconditionally by returning an appropriate // "errno" value; or it can request inspection of system call argument(s) by // returning a suitable ErrorCode. + // Will only be called for valid system call numbers. virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler, int system_call_number) const = 0; + // The InvalidSyscall method specifies the behavior used for invalid + // system calls. The default implementation is to return ENOSYS. + virtual ErrorCode InvalidSyscall(SandboxBPF* sandbox_compiler) const; + private: DISALLOW_COPY_AND_ASSIGN(SandboxBPFPolicy); }; diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc new file mode 100644 index 00000000000..ff659ab7f80 --- /dev/null +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc @@ -0,0 +1,76 @@ +// Copyright 2014 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/seccomp-bpf/sandbox_bpf_test_runner.h" + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +SandboxBPFTestRunner::SandboxBPFTestRunner( + BPFTesterDelegate* bpf_tester_delegate) + : bpf_tester_delegate_(bpf_tester_delegate) { +} + +SandboxBPFTestRunner::~SandboxBPFTestRunner() { +} + +void SandboxBPFTestRunner::Run() { + DCHECK(bpf_tester_delegate_); + sandbox::Die::EnableSimpleExit(); + + scoped_ptr<SandboxBPFPolicy> policy = + bpf_tester_delegate_->GetSandboxBPFPolicy(); + + if (sandbox::SandboxBPF::SupportsSeccompSandbox(-1) == + sandbox::SandboxBPF::STATUS_AVAILABLE) { + // Ensure the the sandbox is actually available at this time + int proc_fd; + SANDBOX_ASSERT((proc_fd = open("/proc", O_RDONLY | O_DIRECTORY)) >= 0); + SANDBOX_ASSERT(sandbox::SandboxBPF::SupportsSeccompSandbox(proc_fd) == + sandbox::SandboxBPF::STATUS_AVAILABLE); + + // Initialize and then start the sandbox with our custom policy + sandbox::SandboxBPF sandbox; + sandbox.set_proc_fd(proc_fd); + sandbox.SetSandboxPolicy(policy.release()); + SANDBOX_ASSERT( + sandbox.StartSandbox(sandbox::SandboxBPF::PROCESS_SINGLE_THREADED)); + + // Run the actual test. + bpf_tester_delegate_->RunTestFunction(); + } else { + printf("This BPF test is not fully running in this configuration!\n"); + // Android and Valgrind are the only configurations where we accept not + // having kernel BPF support. + if (!IsAndroid() && !IsRunningOnValgrind()) { + const bool seccomp_bpf_is_supported = false; + SANDBOX_ASSERT(seccomp_bpf_is_supported); + } + // Call the compiler and verify the policy. That's the least we can do, + // if we don't have kernel support. + sandbox::SandboxBPF sandbox; + sandbox.SetSandboxPolicy(policy.release()); + sandbox::SandboxBPF::Program* program = + sandbox.AssembleFilter(true /* force_verification */); + delete program; + sandbox::UnitTests::IgnoreThisTest(); + } +} + +bool SandboxBPFTestRunner::ShouldCheckForLeaks() const { + // LSAN requires being able to use ptrace() and other system calls that could + // be denied. + return false; +} + +} // namespace sandbox diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h new file mode 100644 index 00000000000..77210330414 --- /dev/null +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h @@ -0,0 +1,59 @@ +// Copyright 2014 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_SANDBOX_BPF_TEST_RUNNER_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_TEST_RUNNER_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h" +#include "sandbox/linux/tests/sandbox_test_runner.h" + +namespace sandbox { + +// To create a SandboxBPFTestRunner object, one needs to implement this +// interface and pass an instance to the SandboxBPFTestRunner constructor. +// In the child process running the test, the BPFTesterDelegate object is +// guaranteed to not be destroyed until the child process terminates. +class BPFTesterDelegate { + public: + BPFTesterDelegate() {} + virtual ~BPFTesterDelegate() {} + + // This will instanciate a policy suitable for the test we want to run. It is + // guaranteed to only be called from the child process that will run the + // test. + virtual scoped_ptr<SandboxBPFPolicy> GetSandboxBPFPolicy() = 0; + // This will be called from a child process with the BPF sandbox turned on. + virtual void RunTestFunction() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(BPFTesterDelegate); +}; + +// This class implements the SandboxTestRunner interface and Run() will +// initialize a seccomp-bpf sandbox (specified by |bpf_tester_delegate|) and +// run a test function (via |bpf_tester_delegate|) if the current kernel +// configuration allows it. If it can not run the test under seccomp-bpf, +// Run() will still compile the policy which should allow to get some coverage +// under tools such as Valgrind. +class SandboxBPFTestRunner : public SandboxTestRunner { + public: + // This constructor takes ownership of the |bpf_tester_delegate| object. + // (It doesn't take a scoped_ptr since they make polymorphism verbose). + explicit SandboxBPFTestRunner(BPFTesterDelegate* bpf_tester_delegate); + virtual ~SandboxBPFTestRunner(); + + virtual void Run() OVERRIDE; + + virtual bool ShouldCheckForLeaks() const OVERRIDE; + + private: + scoped_ptr<BPFTesterDelegate> bpf_tester_delegate_; + DISALLOW_COPY_AND_ASSIGN(SandboxBPFTestRunner); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_TEST_RUNNER_H_ diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc index 988e29544b0..06ba2090c91 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc @@ -5,7 +5,9 @@ #include <errno.h> #include <pthread.h> #include <sched.h> +#include <signal.h> #include <sys/prctl.h> +#include <sys/ptrace.h> #include <sys/syscall.h> #include <sys/time.h> #include <sys/types.h> @@ -20,7 +22,11 @@ #include <ostream> +#include "base/bind.h" +#include "base/logging.h" +#include "base/macros.h" #include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" #include "build/build_config.h" #include "sandbox/linux/seccomp-bpf/bpf_tests.h" #include "sandbox/linux/seccomp-bpf/syscall.h" @@ -49,7 +55,7 @@ const char kSandboxDebuggingEnv[] = "CHROME_SANDBOX_DEBUGGING"; // This test should execute no matter whether we have kernel support. So, // we make it a TEST() instead of a BPF_TEST(). -TEST(SandboxBPF, CallSupports) { +TEST(SandboxBPF, DISABLE_ON_TSAN(CallSupports)) { // We check that we don't crash, but it's ok if the kernel doesn't // support it. bool seccomp_bpf_supported = @@ -64,7 +70,7 @@ TEST(SandboxBPF, CallSupports) { std::cout << "Pointer size: " << sizeof(void*) << "\n"; } -SANDBOX_TEST(SandboxBPF, CallSupportsTwice) { +SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(CallSupportsTwice)) { SandboxBPF::SupportsSeccompSandbox(-1); SandboxBPF::SupportsSeccompSandbox(-1); } @@ -76,59 +82,67 @@ SANDBOX_TEST(SandboxBPF, CallSupportsTwice) { // setting up the sandbox. But it wouldn't hurt to have at least one test // that explicitly walks through all these steps. -intptr_t FakeGetPid(const struct arch_seccomp_data& args, void* aux) { +intptr_t IncreaseCounter(const struct arch_seccomp_data& args, void* aux) { BPF_ASSERT(aux); - pid_t* pid_ptr = static_cast<pid_t*>(aux); - return (*pid_ptr)++; + int* counter = static_cast<int*>(aux); + return (*counter)++; } -ErrorCode VerboseAPITestingPolicy(SandboxBPF* sandbox, int sysno, void* aux) { - if (!SandboxBPF::IsValidSyscallNumber(sysno)) { - return ErrorCode(ENOSYS); - } else if (sysno == __NR_getpid) { - return sandbox->Trap(FakeGetPid, aux); - } else { +class VerboseAPITestingPolicy : public SandboxBPFPolicy { + public: + VerboseAPITestingPolicy(int* counter_ptr) : counter_ptr_(counter_ptr) {} + + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno == __NR_uname) { + return sandbox->Trap(IncreaseCounter, counter_ptr_); + } return ErrorCode(ErrorCode::ERR_ALLOWED); } -} + + private: + int* counter_ptr_; + DISALLOW_COPY_AND_ASSIGN(VerboseAPITestingPolicy); +}; SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(VerboseAPITesting)) { if (SandboxBPF::SupportsSeccompSandbox(-1) == sandbox::SandboxBPF::STATUS_AVAILABLE) { - pid_t test_var = 0; + static int counter = 0; + SandboxBPF sandbox; - sandbox.SetSandboxPolicyDeprecated(VerboseAPITestingPolicy, &test_var); - sandbox.StartSandbox(); - - BPF_ASSERT(test_var == 0); - BPF_ASSERT(syscall(__NR_getpid) == 0); - BPF_ASSERT(test_var == 1); - BPF_ASSERT(syscall(__NR_getpid) == 1); - BPF_ASSERT(test_var == 2); - - // N.B.: Any future call to getpid() would corrupt the stack. - // This is OK. The SANDBOX_TEST() macro is guaranteed to - // only ever call _exit() after the test completes. + sandbox.SetSandboxPolicy(new VerboseAPITestingPolicy(&counter)); + BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::PROCESS_SINGLE_THREADED)); + + BPF_ASSERT_EQ(0, counter); + BPF_ASSERT_EQ(0, syscall(__NR_uname, 0)); + BPF_ASSERT_EQ(1, counter); + BPF_ASSERT_EQ(1, syscall(__NR_uname, 0)); + BPF_ASSERT_EQ(2, counter); } } // A simple blacklist test -ErrorCode BlacklistNanosleepPolicy(SandboxBPF*, int sysno, void*) { - if (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy - return ErrorCode(ENOSYS); +class BlacklistNanosleepPolicy : public SandboxBPFPolicy { + public: + BlacklistNanosleepPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysno) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + switch (sysno) { + case __NR_nanosleep: + return ErrorCode(EACCES); + default: + return ErrorCode(ErrorCode::ERR_ALLOWED); + } } - switch (sysno) { - case __NR_nanosleep: - return ErrorCode(EACCES); - default: - return ErrorCode(ErrorCode::ERR_ALLOWED); - } -} + private: + DISALLOW_COPY_AND_ASSIGN(BlacklistNanosleepPolicy); +}; -BPF_TEST(SandboxBPF, ApplyBasicBlacklistPolicy, BlacklistNanosleepPolicy) { +BPF_TEST_C(SandboxBPF, ApplyBasicBlacklistPolicy, BlacklistNanosleepPolicy) { // nanosleep() should be denied const struct timespec ts = {0, 0}; errno = 0; @@ -138,17 +152,25 @@ BPF_TEST(SandboxBPF, ApplyBasicBlacklistPolicy, BlacklistNanosleepPolicy) { // Now do a simple whitelist test -ErrorCode WhitelistGetpidPolicy(SandboxBPF*, int sysno, void*) { - switch (sysno) { - case __NR_getpid: - case __NR_exit_group: - return ErrorCode(ErrorCode::ERR_ALLOWED); - default: - return ErrorCode(ENOMEM); +class WhitelistGetpidPolicy : public SandboxBPFPolicy { + public: + WhitelistGetpidPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysno) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + switch (sysno) { + case __NR_getpid: + case __NR_exit_group: + return ErrorCode(ErrorCode::ERR_ALLOWED); + default: + return ErrorCode(ENOMEM); + } } -} -BPF_TEST(SandboxBPF, ApplyBasicWhitelistPolicy, WhitelistGetpidPolicy) { + private: + DISALLOW_COPY_AND_ASSIGN(WhitelistGetpidPolicy); +}; + +BPF_TEST_C(SandboxBPF, ApplyBasicWhitelistPolicy, WhitelistGetpidPolicy) { // getpid() should be allowed errno = 0; BPF_ASSERT(syscall(__NR_getpid) > 0); @@ -160,7 +182,6 @@ BPF_TEST(SandboxBPF, ApplyBasicWhitelistPolicy, WhitelistGetpidPolicy) { } // A simple blacklist policy, with a SIGSYS handler - intptr_t EnomemHandler(const struct arch_seccomp_data& args, void* aux) { // We also check that the auxiliary data is correct SANDBOX_ASSERT(aux); @@ -170,12 +191,8 @@ intptr_t EnomemHandler(const struct arch_seccomp_data& args, void* aux) { ErrorCode BlacklistNanosleepPolicySigsys(SandboxBPF* sandbox, int sysno, - void* aux) { - if (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy - return ErrorCode(ENOSYS); - } - + int* aux) { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); switch (sysno) { case __NR_nanosleep: return sandbox->Trap(EnomemHandler, aux); @@ -187,32 +204,41 @@ ErrorCode BlacklistNanosleepPolicySigsys(SandboxBPF* sandbox, BPF_TEST(SandboxBPF, BasicBlacklistWithSigsys, BlacklistNanosleepPolicySigsys, - int /* BPF_AUX */) { + int /* (*BPF_AUX) */) { // getpid() should work properly errno = 0; BPF_ASSERT(syscall(__NR_getpid) > 0); BPF_ASSERT(errno == 0); // Our Auxiliary Data, should be reset by the signal handler - BPF_AUX = -1; + *BPF_AUX = -1; const struct timespec ts = {0, 0}; BPF_ASSERT(syscall(__NR_nanosleep, &ts, NULL) == -1); BPF_ASSERT(errno == ENOMEM); // We expect the signal handler to modify AuxData - BPF_ASSERT(BPF_AUX == kExpectedReturnValue); + BPF_ASSERT(*BPF_AUX == kExpectedReturnValue); } // A simple test that verifies we can return arbitrary errno values. -ErrorCode ErrnoTestPolicy(SandboxBPF*, int sysno, void*) { - if (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy - return ErrorCode(ENOSYS); - } +class ErrnoTestPolicy : public SandboxBPFPolicy { + public: + ErrnoTestPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysno) const OVERRIDE; + private: + DISALLOW_COPY_AND_ASSIGN(ErrnoTestPolicy); +}; + +ErrorCode ErrnoTestPolicy::EvaluateSyscall(SandboxBPF*, int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); switch (sysno) { +#if defined(ANDROID) + case __NR_dup3: // dup2 is a wrapper of dup3 in android +#else case __NR_dup2: +#endif // Pretend that dup2() worked, but don't actually do anything. return ErrorCode(0); case __NR_setuid: @@ -235,7 +261,7 @@ ErrorCode ErrnoTestPolicy(SandboxBPF*, int sysno, void*) { } } -BPF_TEST(SandboxBPF, ErrnoTest, ErrnoTestPolicy) { +BPF_TEST_C(SandboxBPF, ErrnoTest, ErrnoTestPolicy) { // Verify that dup2() returns success, but doesn't actually run. int fds[4]; BPF_ASSERT(pipe(fds) == 0); @@ -277,43 +303,53 @@ BPF_TEST(SandboxBPF, ErrnoTest, ErrnoTestPolicy) { // Testing the stacking of two sandboxes -ErrorCode StackingPolicyPartOne(SandboxBPF* sandbox, int sysno, void*) { - if (!SandboxBPF::IsValidSyscallNumber(sysno)) { - return ErrorCode(ENOSYS); +class StackingPolicyPartOne : public SandboxBPFPolicy { + public: + StackingPolicyPartOne() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + 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); + } } - 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); - } -} + private: + DISALLOW_COPY_AND_ASSIGN(StackingPolicyPartOne); +}; -ErrorCode StackingPolicyPartTwo(SandboxBPF* sandbox, int sysno, void*) { - if (!SandboxBPF::IsValidSyscallNumber(sysno)) { - return ErrorCode(ENOSYS); +class StackingPolicyPartTwo : public SandboxBPFPolicy { + public: + StackingPolicyPartTwo() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + 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); + } } - 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); - } -} + private: + DISALLOW_COPY_AND_ASSIGN(StackingPolicyPartTwo); +}; -BPF_TEST(SandboxBPF, StackingPolicy, StackingPolicyPartOne) { +BPF_TEST_C(SandboxBPF, StackingPolicy, StackingPolicyPartOne) { errno = 0; BPF_ASSERT(syscall(__NR_getppid, 0) > 0); BPF_ASSERT(errno == 0); @@ -324,8 +360,8 @@ BPF_TEST(SandboxBPF, StackingPolicy, StackingPolicyPartOne) { // Stack a second sandbox with its own policy. Verify that we can further // restrict filters, but we cannot relax existing filters. SandboxBPF sandbox; - sandbox.SetSandboxPolicyDeprecated(StackingPolicyPartTwo, NULL); - sandbox.StartSandbox(); + sandbox.SetSandboxPolicy(new StackingPolicyPartTwo()); + BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::PROCESS_SINGLE_THREADED)); errno = 0; BPF_ASSERT(syscall(__NR_getppid, 0) == -1); @@ -350,29 +386,24 @@ int SysnoToRandomErrno(int sysno) { return ((sysno & ~3) >> 2) % 29 + 1; } -ErrorCode SyntheticPolicy(SandboxBPF*, int sysno, void*) { - if (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy - return ErrorCode(ENOSYS); - } - -// TODO(jorgelo): remove this once the new code generator lands. -#if defined(__arm__) - if (sysno > static_cast<int>(MAX_PUBLIC_SYSCALL)) { - return ErrorCode(ENOSYS); - } -#endif - - if (sysno == __NR_exit_group || sysno == __NR_write) { - // exit_group() is special, we really need it to work. - // write() is needed for BPF_ASSERT() to report a useful error message. - return ErrorCode(ErrorCode::ERR_ALLOWED); - } else { +class SyntheticPolicy : public SandboxBPFPolicy { + public: + SyntheticPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysno) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno == __NR_exit_group || sysno == __NR_write) { + // exit_group() is special, we really need it to work. + // write() is needed for BPF_ASSERT() to report a useful error message. + return ErrorCode(ErrorCode::ERR_ALLOWED); + } return ErrorCode(SysnoToRandomErrno(sysno)); } -} -BPF_TEST(SandboxBPF, SyntheticPolicy, SyntheticPolicy) { + private: + DISALLOW_COPY_AND_ASSIGN(SyntheticPolicy); +}; + +BPF_TEST_C(SandboxBPF, SyntheticPolicy, SyntheticPolicy) { // Ensure that that kExpectedReturnValue + syscallnumber + 1 does not int // overflow. BPF_ASSERT(std::numeric_limits<int>::max() - kExpectedReturnValue - 1 >= @@ -406,23 +437,25 @@ int ArmPrivateSysnoToErrno(int sysno) { } } -ErrorCode ArmPrivatePolicy(SandboxBPF*, int sysno, void*) { - if (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy. - return ErrorCode(ENOSYS); - } - - // Start from |__ARM_NR_set_tls + 1| so as not to mess with actual - // ARM private system calls. - if (sysno >= static_cast<int>(__ARM_NR_set_tls + 1) && - sysno <= static_cast<int>(MAX_PRIVATE_SYSCALL)) { - return ErrorCode(ArmPrivateSysnoToErrno(sysno)); - } else { +class ArmPrivatePolicy : public SandboxBPFPolicy { + public: + ArmPrivatePolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysno) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + // Start from |__ARM_NR_set_tls + 1| so as not to mess with actual + // ARM private system calls. + if (sysno >= static_cast<int>(__ARM_NR_set_tls + 1) && + sysno <= static_cast<int>(MAX_PRIVATE_SYSCALL)) { + return ErrorCode(ArmPrivateSysnoToErrno(sysno)); + } return ErrorCode(ErrorCode::ERR_ALLOWED); } -} -BPF_TEST(SandboxBPF, ArmPrivatePolicy, ArmPrivatePolicy) { + private: + DISALLOW_COPY_AND_ASSIGN(ArmPrivatePolicy); +}; + +BPF_TEST_C(SandboxBPF, ArmPrivatePolicy, ArmPrivatePolicy) { for (int syscall_number = static_cast<int>(__ARM_NR_set_tls + 1); syscall_number <= static_cast<int>(MAX_PRIVATE_SYSCALL); ++syscall_number) { @@ -446,7 +479,7 @@ intptr_t CountSyscalls(const struct arch_seccomp_data& args, void* aux) { return SandboxBPF::ForwardSyscall(args); } -ErrorCode GreyListedPolicy(SandboxBPF* sandbox, int sysno, void* aux) { +ErrorCode GreyListedPolicy(SandboxBPF* sandbox, int sysno, int* 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 @@ -479,12 +512,12 @@ ErrorCode GreyListedPolicy(SandboxBPF* sandbox, int sysno, void* aux) { } } -BPF_TEST(SandboxBPF, GreyListedPolicy, GreyListedPolicy, int /* BPF_AUX */) { +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(*BPF_AUX == 0); BPF_ASSERT(syscall(__NR_geteuid) == syscall(__NR_getuid)); - BPF_ASSERT(BPF_AUX == 2); + BPF_ASSERT(*BPF_AUX == 2); char name[17] = {}; BPF_ASSERT(!syscall(__NR_prctl, PR_GET_NAME, @@ -492,7 +525,7 @@ BPF_TEST(SandboxBPF, GreyListedPolicy, GreyListedPolicy, int /* BPF_AUX */) { (void*)NULL, (void*)NULL, (void*)NULL)); - BPF_ASSERT(BPF_AUX == 3); + BPF_ASSERT(*BPF_AUX == 3); BPF_ASSERT(*name); } @@ -519,22 +552,29 @@ intptr_t PrctlHandler(const struct arch_seccomp_data& args, void*) { } } -ErrorCode PrctlPolicy(SandboxBPF* sandbox, int sysno, void* aux) { - setenv(kSandboxDebuggingEnv, "t", 0); - Die::SuppressInfoMessages(true); +class PrctlPolicy : public SandboxBPFPolicy { + public: + PrctlPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + setenv(kSandboxDebuggingEnv, "t", 0); + Die::SuppressInfoMessages(true); + + if (sysno == __NR_prctl) { + // Handle prctl() inside an UnsafeTrap() + return sandbox->UnsafeTrap(PrctlHandler, NULL); + } - if (sysno == __NR_prctl) { - // Handle prctl() inside an UnsafeTrap() - return sandbox->UnsafeTrap(PrctlHandler, NULL); - } else if (SandboxBPF::IsValidSyscallNumber(sysno)) { // Allow all other system calls. return ErrorCode(ErrorCode::ERR_ALLOWED); - } else { - return ErrorCode(ENOSYS); } -} -BPF_TEST(SandboxBPF, ForwardSyscall, PrctlPolicy) { + private: + DISALLOW_COPY_AND_ASSIGN(PrctlPolicy); +}; + +BPF_TEST_C(SandboxBPF, ForwardSyscall, PrctlPolicy) { // This call should never be allowed. But our policy will intercept it and // let it pass successfully. BPF_ASSERT( @@ -565,7 +605,19 @@ intptr_t AllowRedirectedSyscall(const struct arch_seccomp_data& args, void*) { return SandboxBPF::ForwardSyscall(args); } -ErrorCode RedirectAllSyscallsPolicy(SandboxBPF* sandbox, int sysno, void* aux) { +class RedirectAllSyscallsPolicy : public SandboxBPFPolicy { + public: + RedirectAllSyscallsPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(RedirectAllSyscallsPolicy); +}; + +ErrorCode RedirectAllSyscallsPolicy::EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); setenv(kSandboxDebuggingEnv, "t", 0); Die::SuppressInfoMessages(true); @@ -582,11 +634,8 @@ ErrorCode RedirectAllSyscallsPolicy(SandboxBPF* sandbox, int sysno, void* aux) { #endif ) { return ErrorCode(ErrorCode::ERR_ALLOWED); - } else if (SandboxBPF::IsValidSyscallNumber(sysno)) { - return sandbox->UnsafeTrap(AllowRedirectedSyscall, aux); - } else { - return ErrorCode(ENOSYS); } + return sandbox->UnsafeTrap(AllowRedirectedSyscall, NULL); } int bus_handler_fd_ = -1; @@ -595,7 +644,7 @@ void SigBusHandler(int, siginfo_t* info, void* void_context) { BPF_ASSERT(write(bus_handler_fd_, "\x55", 1) == 1); } -BPF_TEST(SandboxBPF, SigBus, RedirectAllSyscallsPolicy) { +BPF_TEST_C(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 @@ -618,7 +667,7 @@ BPF_TEST(SandboxBPF, SigBus, RedirectAllSyscallsPolicy) { BPF_ASSERT(c == 0x55); } -BPF_TEST(SandboxBPF, SigMask, RedirectAllSyscallsPolicy) { +BPF_TEST_C(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 @@ -645,7 +694,7 @@ BPF_TEST(SandboxBPF, SigMask, RedirectAllSyscallsPolicy) { BPF_ASSERT(sigismember(&mask2, SIGUSR2)); } -BPF_TEST(SandboxBPF, UnsafeTrapWithErrno, RedirectAllSyscallsPolicy) { +BPF_TEST_C(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() @@ -670,6 +719,8 @@ BPF_TEST(SandboxBPF, UnsafeTrapWithErrno, RedirectAllSyscallsPolicy) { BPF_ASSERT(errno == 0); } +bool NoOpCallback() { return true; } + // Test a trap handler that makes use of a broker process to open(). class InitializedOpenBroker { @@ -682,7 +733,7 @@ class InitializedOpenBroker { broker_process_.reset( new BrokerProcess(EPERM, allowed_files, std::vector<std::string>())); BPF_ASSERT(broker_process() != NULL); - BPF_ASSERT(broker_process_->Init(NULL)); + BPF_ASSERT(broker_process_->Init(base::Bind(&NoOpCallback))); initialized_ = true; } @@ -700,9 +751,15 @@ intptr_t BrokerOpenTrapHandler(const struct arch_seccomp_data& args, BPF_ASSERT(aux); BrokerProcess* broker_process = static_cast<BrokerProcess*>(aux); switch (args.nr) { +#if defined(ANDROID) + case __NR_faccessat: // access is a wrapper of faccessat in android + return broker_process->Access(reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); +#else case __NR_access: return broker_process->Access(reinterpret_cast<const char*>(args.args[0]), static_cast<int>(args.args[1])); +#endif case __NR_open: return broker_process->Open(reinterpret_cast<const char*>(args.args[0]), static_cast<int>(args.args[1])); @@ -718,14 +775,19 @@ intptr_t BrokerOpenTrapHandler(const struct arch_seccomp_data& args, } } -ErrorCode DenyOpenPolicy(SandboxBPF* sandbox, int sysno, void* aux) { - InitializedOpenBroker* iob = static_cast<InitializedOpenBroker*>(aux); +ErrorCode DenyOpenPolicy(SandboxBPF* sandbox, + int sysno, + InitializedOpenBroker* iob) { if (!SandboxBPF::IsValidSyscallNumber(sysno)) { return ErrorCode(ENOSYS); } switch (sysno) { +#if defined(ANDROID) + case __NR_faccessat: +#else case __NR_access: +#endif case __NR_open: case __NR_openat: // We get a InitializedOpenBroker class, but our trap handler wants @@ -742,9 +804,9 @@ ErrorCode DenyOpenPolicy(SandboxBPF* sandbox, int sysno, void* aux) { BPF_TEST(SandboxBPF, UseOpenBroker, DenyOpenPolicy, - InitializedOpenBroker /* BPF_AUX */) { - BPF_ASSERT(BPF_AUX.initialized()); - BrokerProcess* broker_process = BPF_AUX.broker_process(); + 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" @@ -786,16 +848,35 @@ BPF_TEST(SandboxBPF, // Simple test demonstrating how to use SandboxBPF::Cond() -ErrorCode SimpleCondTestPolicy(SandboxBPF* sandbox, int sysno, void*) { - if (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy - return ErrorCode(ENOSYS); - } +class SimpleCondTestPolicy : public SandboxBPFPolicy { + public: + SimpleCondTestPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleCondTestPolicy); +}; + +ErrorCode SimpleCondTestPolicy::EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); // 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) { +#if defined(ANDROID) + case __NR_openat: // open is a wrapper of openat in android + // 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(2, + ErrorCode::TP_32BIT, + ErrorCode::OP_HAS_ANY_BITS, + O_ACCMODE /* 0x3 */, + ErrorCode(EROFS), + ErrorCode(ErrorCode::ERR_ALLOWED)); +#else 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); @@ -805,6 +886,7 @@ ErrorCode SimpleCondTestPolicy(SandboxBPF* sandbox, int sysno, void*) { O_ACCMODE /* 0x3 */, ErrorCode(EROFS), ErrorCode(ErrorCode::ERR_ALLOWED)); +#endif case __NR_prctl: // Allow prctl(PR_SET_DUMPABLE) and prctl(PR_GET_DUMPABLE), but // disallow everything else. @@ -824,7 +906,7 @@ ErrorCode SimpleCondTestPolicy(SandboxBPF* sandbox, int sysno, void*) { } } -BPF_TEST(SandboxBPF, SimpleCondTest, SimpleCondTestPolicy) { +BPF_TEST_C(SandboxBPF, SimpleCondTest, SimpleCondTestPolicy) { int fd; BPF_ASSERT((fd = open("/proc/self/comm", O_RDWR)) == -1); BPF_ASSERT(errno == EROFS); @@ -1122,7 +1204,7 @@ class EqualityStressTest { // based on the system call number and the parameters that we decided // to pass in. Verify that this condition holds true. BPF_ASSERT( - SandboxSyscall( + Syscall::Call( sysno, args[0], args[1], args[2], args[3], args[4], args[5]) == -err); } @@ -1139,22 +1221,34 @@ class EqualityStressTest { static const int kMaxArgs = 6; }; -ErrorCode EqualityStressTestPolicy(SandboxBPF* sandbox, int sysno, void* aux) { - return reinterpret_cast<EqualityStressTest*>(aux)->Policy(sandbox, sysno); +ErrorCode EqualityStressTestPolicy(SandboxBPF* sandbox, + int sysno, + EqualityStressTest* aux) { + DCHECK(aux); + return aux->Policy(sandbox, sysno); } BPF_TEST(SandboxBPF, EqualityTests, EqualityStressTestPolicy, - EqualityStressTest /* BPF_AUX */) { - BPF_AUX.VerifyFilter(); + EqualityStressTest /* (*BPF_AUX) */) { + BPF_AUX->VerifyFilter(); } -ErrorCode EqualityArgumentWidthPolicy(SandboxBPF* sandbox, int sysno, void*) { - if (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy - return ErrorCode(ENOSYS); - } else if (sysno == __NR_uname) { +class EqualityArgumentWidthPolicy : public SandboxBPFPolicy { + public: + EqualityArgumentWidthPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(EqualityArgumentWidthPolicy); +}; + +ErrorCode EqualityArgumentWidthPolicy::EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno == __NR_uname) { return sandbox->Cond( 0, ErrorCode::TP_32BIT, @@ -1180,24 +1274,23 @@ ErrorCode EqualityArgumentWidthPolicy(SandboxBPF* sandbox, int sysno, void*) { 0x55555555AAAAAAAAULL, ErrorCode(1), ErrorCode(2))); - } else { - return ErrorCode(ErrorCode::ERR_ALLOWED); } + 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); +BPF_TEST_C(SandboxBPF, EqualityArgumentWidth, EqualityArgumentWidthPolicy) { + BPF_ASSERT(Syscall::Call(__NR_uname, 0, 0x55555555) == -1); + BPF_ASSERT(Syscall::Call(__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); + BPF_ASSERT(Syscall::Call(__NR_uname, 1, 0x55555555AAAAAAAAULL) == -1); + BPF_ASSERT(Syscall::Call(__NR_uname, 1, 0x5555555500000000ULL) == -2); + BPF_ASSERT(Syscall::Call(__NR_uname, 1, 0x5555555511111111ULL) == -2); + BPF_ASSERT(Syscall::Call(__NR_uname, 1, 0x11111111AAAAAAAAULL) == -2); #else - BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x55555555) == -2); + BPF_ASSERT(Syscall::Call(__NR_uname, 1, 0x55555555) == -2); #endif } @@ -1205,62 +1298,74 @@ BPF_TEST(SandboxBPF, EqualityArgumentWidth, EqualityArgumentWidthPolicy) { // 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); +BPF_DEATH_TEST_C(SandboxBPF, + EqualityArgumentUnallowed64bit, + DEATH_MESSAGE("Unexpected 64bit argument detected"), + EqualityArgumentWidthPolicy) { + Syscall::Call(__NR_uname, 0, 0x5555555555555555ULL); } #endif -ErrorCode EqualityWithNegativeArgumentsPolicy(SandboxBPF* sandbox, - int sysno, - void*) { - if (!SandboxBPF::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 { +class EqualityWithNegativeArgumentsPolicy : public SandboxBPFPolicy { + public: + EqualityWithNegativeArgumentsPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno == __NR_uname) { + return sandbox->Cond(0, + ErrorCode::TP_32BIT, + ErrorCode::OP_EQUAL, + 0xFFFFFFFF, + ErrorCode(1), + ErrorCode(2)); + } 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); + private: + DISALLOW_COPY_AND_ASSIGN(EqualityWithNegativeArgumentsPolicy); +}; + +BPF_TEST_C(SandboxBPF, + EqualityWithNegativeArguments, + EqualityWithNegativeArgumentsPolicy) { + BPF_ASSERT(Syscall::Call(__NR_uname, 0xFFFFFFFF) == -1); + BPF_ASSERT(Syscall::Call(__NR_uname, -1) == -1); + BPF_ASSERT(Syscall::Call(__NR_uname, -1LL) == -1); } #if __SIZEOF_POINTER__ > 4 -BPF_DEATH_TEST(SandboxBPF, - EqualityWithNegative64bitArguments, - DEATH_MESSAGE("Unexpected 64bit argument detected"), - EqualityWithNegativeArgumentsPolicy) { +BPF_DEATH_TEST_C(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); + BPF_ASSERT(Syscall::Call(__NR_uname, 0xFFFFFFFF00000000LL) == -1); } #endif -ErrorCode AllBitTestPolicy(SandboxBPF* sandbox, int sysno, void *) { +class AllBitTestPolicy : public SandboxBPFPolicy { + public: + AllBitTestPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(AllBitTestPolicy); +}; + +ErrorCode AllBitTestPolicy::EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); // 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 (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy - return ErrorCode(ENOSYS); - } else if (sysno == __NR_uname) { + 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, @@ -1316,9 +1421,8 @@ ErrorCode AllBitTestPolicy(SandboxBPF* sandbox, int sysno, void *) { ErrorCode(1), ErrorCode(0)), sandbox->Kill("Invalid test case number")))))))))))); - } else { - return ErrorCode(ErrorCode::ERR_ALLOWED); } + return ErrorCode(ErrorCode::ERR_ALLOWED); } // Define a macro that performs tests using our test policy. @@ -1329,10 +1433,10 @@ ErrorCode AllBitTestPolicy(SandboxBPF* sandbox, int sysno, void *) { // 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)) + BPF_ASSERT(Syscall::Call(__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 +// ErrorCode(0) for failure. Syscall::Call() turns this into an // exit code of -1 or 0. #define EXPECT_FAILURE 0 #define EXPECT_SUCCESS -1 @@ -1343,7 +1447,7 @@ ErrorCode AllBitTestPolicy(SandboxBPF* sandbox, int sysno, void *) { // 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) { +BPF_TEST_C(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); @@ -1446,17 +1550,26 @@ BPF_TEST(SandboxBPF, AllBitTests, AllBitTestPolicy) { BITMASK_TEST(10, -1L, ALLBITS64,0x100000001, EXPT64_SUCCESS); } -ErrorCode AnyBitTestPolicy(SandboxBPF* sandbox, int sysno, void*) { +class AnyBitTestPolicy : public SandboxBPFPolicy { + public: + AnyBitTestPolicy() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(AnyBitTestPolicy); +}; + +ErrorCode AnyBitTestPolicy::EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); // 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 (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy - return ErrorCode(ENOSYS); - } else if (sysno == __NR_uname) { + 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, @@ -1515,12 +1628,11 @@ ErrorCode AnyBitTestPolicy(SandboxBPF* sandbox, int sysno, void*) { ErrorCode(1), ErrorCode(0)), sandbox->Kill("Invalid test case number")))))))))))); - } else { - return ErrorCode(ErrorCode::ERR_ALLOWED); } + return ErrorCode(ErrorCode::ERR_ALLOWED); } -BPF_TEST(SandboxBPF, AnyBitTests, AnyBitTestPolicy) { +BPF_TEST_C(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); @@ -1650,15 +1762,25 @@ intptr_t PthreadTrapHandler(const struct arch_seccomp_data& args, void* aux) { } return -EPERM; } -ErrorCode PthreadPolicyEquality(SandboxBPF* sandbox, int sysno, void* aux) { + +class PthreadPolicyEquality : public SandboxBPFPolicy { + public: + PthreadPolicyEquality() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(PthreadPolicyEquality); +}; + +ErrorCode PthreadPolicyEquality::EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); // 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 (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy - return ErrorCode(ENOSYS); - } else if (sysno == __NR_clone) { + 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 @@ -1685,20 +1807,28 @@ ErrorCode PthreadPolicyEquality(SandboxBPF* sandbox, int sysno, void* aux) { kBaseAndroidCloneMask, ErrorCode(ErrorCode::ERR_ALLOWED), sandbox->Trap(PthreadTrapHandler, "Unknown mask")))); - } else { - return ErrorCode(ErrorCode::ERR_ALLOWED); } + return ErrorCode(ErrorCode::ERR_ALLOWED); } -ErrorCode PthreadPolicyBitMask(SandboxBPF* sandbox, int sysno, void* aux) { +class PthreadPolicyBitMask : public SandboxBPFPolicy { + public: + PthreadPolicyBitMask() {} + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(PthreadPolicyBitMask); +}; + +ErrorCode PthreadPolicyBitMask::EvaluateSyscall(SandboxBPF* sandbox, + int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); // 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 (!SandboxBPF::IsValidSyscallNumber(sysno)) { - // FIXME: we should really not have to do that in a trivial policy - return ErrorCode(ENOSYS); - } else if (sysno == __NR_clone) { + 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 @@ -1730,14 +1860,13 @@ ErrorCode PthreadPolicyBitMask(SandboxBPF* sandbox, int sysno, void* aux) { sandbox->Trap(PthreadTrapHandler, "Missing mandatory CLONE_XXX flags " "when creating new thread"))); - } else { - return ErrorCode(ErrorCode::ERR_ALLOWED); } + return ErrorCode(ErrorCode::ERR_ALLOWED); } static void* ThreadFnc(void* arg) { ++*reinterpret_cast<int*>(arg); - SandboxSyscall(__NR_futex, arg, FUTEX_WAKE, 1, 0, 0, 0); + Syscall::Call(__NR_futex, arg, FUTEX_WAKE, 1, 0, 0, 0); return NULL; } @@ -1756,7 +1885,7 @@ static void PthreadTest() { 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) == + while (Syscall::Call(__NR_futex, &thread_ran, FUTEX_WAIT, 0, 0, 0, 0) == -EINTR) { } BPF_ASSERT(thread_ran); @@ -1767,16 +1896,168 @@ static void PthreadTest() { // 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_ASSERT(Syscall::Call(__NR_clone, + CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | SIGCHLD, + 0, + 0, + &pid) == -EPERM); } -BPF_TEST(SandboxBPF, PthreadEquality, PthreadPolicyEquality) { PthreadTest(); } +BPF_TEST_C(SandboxBPF, PthreadEquality, PthreadPolicyEquality) { + PthreadTest(); +} + +BPF_TEST_C(SandboxBPF, PthreadBitMask, PthreadPolicyBitMask) { + PthreadTest(); +} + +// libc might not define these even though the kernel supports it. +#ifndef PTRACE_O_TRACESECCOMP +#define PTRACE_O_TRACESECCOMP 0x00000080 +#endif + +#ifdef PTRACE_EVENT_SECCOMP +#define IS_SECCOMP_EVENT(status) ((status >> 16) == PTRACE_EVENT_SECCOMP) +#else +// When Debian/Ubuntu backported seccomp-bpf support into earlier kernels, they +// changed the value of PTRACE_EVENT_SECCOMP from 7 to 8, since 7 was taken by +// PTRACE_EVENT_STOP (upstream chose to renumber PTRACE_EVENT_STOP to 128). If +// PTRACE_EVENT_SECCOMP isn't defined, we have no choice but to consider both +// values here. +#define IS_SECCOMP_EVENT(status) ((status >> 16) == 7 || (status >> 16) == 8) +#endif -BPF_TEST(SandboxBPF, PthreadBitMask, PthreadPolicyBitMask) { PthreadTest(); } +#if defined(__arm__) +#ifndef PTRACE_SET_SYSCALL +#define PTRACE_SET_SYSCALL 23 +#endif +#endif + +// Changes the syscall to run for a child being sandboxed using seccomp-bpf with +// PTRACE_O_TRACESECCOMP. Should only be called when the child is stopped on +// PTRACE_EVENT_SECCOMP. +// +// regs should contain the current set of registers of the child, obtained using +// PTRACE_GETREGS. +// +// Depending on the architecture, this may modify regs, so the caller is +// responsible for committing these changes using PTRACE_SETREGS. +long SetSyscall(pid_t pid, regs_struct* regs, int syscall_number) { +#if defined(__arm__) + // On ARM, the syscall is changed using PTRACE_SET_SYSCALL. We cannot use the + // libc ptrace call as the request parameter is an enum, and + // PTRACE_SET_SYSCALL may not be in the enum. + return syscall(__NR_ptrace, PTRACE_SET_SYSCALL, pid, NULL, syscall_number); +#endif + + SECCOMP_PT_SYSCALL(*regs) = syscall_number; + return 0; +} + +const uint16_t kTraceData = 0xcc; + +class TraceAllPolicy : public SandboxBPFPolicy { + public: + TraceAllPolicy() {} + virtual ~TraceAllPolicy() {} + + virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler, + int system_call_number) const OVERRIDE { + return ErrorCode(ErrorCode::ERR_TRACE + kTraceData); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TraceAllPolicy); +}; + +SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(SeccompRetTrace)) { + if (SandboxBPF::SupportsSeccompSandbox(-1) != + sandbox::SandboxBPF::STATUS_AVAILABLE) { + return; + } + +#if defined(__arm__) + printf("This test is currently disabled on ARM due to a kernel bug."); + return; +#endif + + pid_t pid = fork(); + BPF_ASSERT_NE(-1, pid); + if (pid == 0) { + pid_t my_pid = getpid(); + BPF_ASSERT_NE(-1, ptrace(PTRACE_TRACEME, -1, NULL, NULL)); + BPF_ASSERT_EQ(0, raise(SIGSTOP)); + SandboxBPF sandbox; + sandbox.SetSandboxPolicy(new TraceAllPolicy); + BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::PROCESS_SINGLE_THREADED)); + + // getpid is allowed. + BPF_ASSERT_EQ(my_pid, syscall(__NR_getpid)); + + // write to stdout is skipped and returns a fake value. + BPF_ASSERT_EQ(kExpectedReturnValue, + syscall(__NR_write, STDOUT_FILENO, "A", 1)); + + // kill is rewritten to exit(kExpectedReturnValue). + syscall(__NR_kill, my_pid, SIGKILL); + + // Should not be reached. + BPF_ASSERT(false); + } + + int status; + BPF_ASSERT(HANDLE_EINTR(waitpid(pid, &status, WUNTRACED)) != -1); + BPF_ASSERT(WIFSTOPPED(status)); + + BPF_ASSERT_NE(-1, ptrace(PTRACE_SETOPTIONS, pid, NULL, + reinterpret_cast<void*>(PTRACE_O_TRACESECCOMP))); + BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL)); + while (true) { + BPF_ASSERT(HANDLE_EINTR(waitpid(pid, &status, 0)) != -1); + if (WIFEXITED(status) || WIFSIGNALED(status)) { + BPF_ASSERT(WIFEXITED(status)); + BPF_ASSERT_EQ(kExpectedReturnValue, WEXITSTATUS(status)); + break; + } + + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP || + !IS_SECCOMP_EVENT(status)) { + BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL)); + continue; + } + + unsigned long data; + BPF_ASSERT_NE(-1, ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data)); + BPF_ASSERT_EQ(kTraceData, data); + + regs_struct regs; + BPF_ASSERT_NE(-1, ptrace(PTRACE_GETREGS, pid, NULL, ®s)); + switch (SECCOMP_PT_SYSCALL(regs)) { + case __NR_write: + // Skip writes to stdout, make it return kExpectedReturnValue. Allow + // writes to stderr so that BPF_ASSERT messages show up. + if (SECCOMP_PT_PARM1(regs) == STDOUT_FILENO) { + BPF_ASSERT_NE(-1, SetSyscall(pid, ®s, -1)); + SECCOMP_PT_RESULT(regs) = kExpectedReturnValue; + BPF_ASSERT_NE(-1, ptrace(PTRACE_SETREGS, pid, NULL, ®s)); + } + break; + + case __NR_kill: + // Rewrite to exit(kExpectedReturnValue). + BPF_ASSERT_NE(-1, SetSyscall(pid, ®s, __NR_exit)); + SECCOMP_PT_PARM1(regs) = kExpectedReturnValue; + BPF_ASSERT_NE(-1, ptrace(PTRACE_SETREGS, pid, NULL, ®s)); + break; + + default: + // Allow all other syscalls. + break; + } + + BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL)); + } +} } // namespace diff --git a/chromium/sandbox/linux/seccomp-bpf/syscall.cc b/chromium/sandbox/linux/seccomp-bpf/syscall.cc index acf207dc14c..64c0b8eb9b4 100644 --- a/chromium/sandbox/linux/seccomp-bpf/syscall.cc +++ b/chromium/sandbox/linux/seccomp-bpf/syscall.cc @@ -11,169 +11,177 @@ namespace sandbox { - 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. +namespace { + +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" + ".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" + ".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" + // 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" + ".thumb_func\n" #else - ".arm\n" + ".arm\n" #endif - "SyscallAsm:.fnstart\n" - "@ args = 0, pretend = 0, frame = 8\n" - "@ frame_needed = 1, uses_anonymous_args = 0\n" + "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" + ".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" + "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. + // 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" + "adr 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" + "2:pop {r7, pc}\n" + ".cfi_endproc\n" #else - "2:ldmfd sp!, {fp, pc}\n" + "2:ldmfd sp!, {fp, pc}\n" #endif - ".fnend\n" - "9:.size SyscallAsm, 9b-SyscallAsm\n" + ".fnend\n" + "9:.size SyscallAsm, 9b-SyscallAsm\n" #endif - ); // asm + ); // asm + +} // namespace -intptr_t SandboxSyscall(int nr, - intptr_t p0, intptr_t p1, intptr_t p2, - intptr_t p3, intptr_t p4, intptr_t p5) { +intptr_t Syscall::Call(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 @@ -181,61 +189,78 @@ intptr_t SandboxSyscall(int nr, // 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), + 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 }; + 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. +// 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) - : "cc", "esp", "memory", "ecx", "edx"); + "call SyscallAsm\n" + // N.B. These are not the calling conventions normally used by the ABI. + : "=a"(ret) + : "0"(ret), "D"(args) + : "cc", "esp", "memory", "ecx", "edx"); #elif defined(__x86_64__) intptr_t ret = nr; { - register const intptr_t *data __asm__("r12") = args; + 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) - : "cc", "rsp", "memory", - "rcx", "rdi", "rsi", "rdx", "r8", "r9", "r10", "r11"); + "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) + : "cc", + "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; + 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) - : "cc", "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 - ); + "bl SyscallAsm\n" + // N.B. These are not the calling conventions normally used by the ABI. + : "=r"(inout) + : "0"(inout), "r"(data) + : "cc", + "lr", + "memory", + "r1", + "r2", + "r3", + "r4", + "r5" +#if !defined(__thumb__) + // 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 // !defined(__thumb__) + ); ret = inout; } #else - errno = ENOSYS; - intptr_t ret = -1; +#error "Unimplemented architecture" #endif return ret; } diff --git a/chromium/sandbox/linux/seccomp-bpf/syscall.h b/chromium/sandbox/linux/seccomp-bpf/syscall.h index 0b51380e88b..57970a35b04 100644 --- a/chromium/sandbox/linux/seccomp-bpf/syscall.h +++ b/chromium/sandbox/linux/seccomp-bpf/syscall.h @@ -7,131 +7,82 @@ #include <stdint.h> -namespace sandbox { - -// 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 +#include "base/macros.h" +#include "sandbox/sandbox_export.h" -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); -} +namespace sandbox { -#endif // Pre-C++11 +// This purely static class can be used to perform system calls with some +// low-level control. +class SANDBOX_EXPORT Syscall { + public: + // This performs system call |nr| with the arguments p0 to p5 from a constant + // userland address, which is for instance observable by seccomp-bpf filters. + // The constant userland address from which these system calls are made will + // be returned if |nr| is passed as -1. + // On error, this function will return a value between -1 and -4095 which + // should be interpreted as -errno. + static intptr_t Call(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. + template <class T0, class T1, class T2, class T3, class T4, class T5> + static inline intptr_t + Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5) { + return Call(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> + static inline intptr_t Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4) { + return Call(nr, p0, p1, p2, p3, p4, 0); + } + + template <class T0, class T1, class T2, class T3> + static inline intptr_t Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3) { + return Call(nr, p0, p1, p2, p3, 0, 0); + } + + template <class T0, class T1, class T2> + static inline intptr_t Call(int nr, T0 p0, T1 p1, T2 p2) { + return Call(nr, p0, p1, p2, 0, 0, 0); + } + + template <class T0, class T1> + static inline intptr_t Call(int nr, T0 p0, T1 p1) { + return Call(nr, p0, p1, 0, 0, 0, 0); + } + + template <class T0> + static inline intptr_t Call(int nr, T0 p0) { + return Call(nr, p0, 0, 0, 0, 0, 0); + } + + static inline intptr_t Call(int nr) { return Call(nr, 0, 0, 0, 0, 0, 0); } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Syscall); +}; } // namespace sandbox diff --git a/chromium/sandbox/linux/seccomp-bpf/syscall_iterator.h b/chromium/sandbox/linux/seccomp-bpf/syscall_iterator.h index 3b56ea3144d..7842b2a85b2 100644 --- a/chromium/sandbox/linux/seccomp-bpf/syscall_iterator.h +++ b/chromium/sandbox/linux/seccomp-bpf/syscall_iterator.h @@ -8,6 +8,7 @@ #include <stdint.h> #include "base/basictypes.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -31,7 +32,7 @@ namespace sandbox { // } // // TODO(markus): Make this a classic C++ iterator. -class SyscallIterator { +class SANDBOX_EXPORT SyscallIterator { public: explicit SyscallIterator(bool invalid_only) : invalid_only_(invalid_only), done_(false), num_(0) {} diff --git a/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc index 60db69bcd6b..80b5079bae0 100644 --- a/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc +++ b/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc @@ -12,6 +12,7 @@ #include "base/basictypes.h" #include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" #include "sandbox/linux/seccomp-bpf/bpf_tests.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" #include "sandbox/linux/seccomp-bpf/syscall.h" @@ -31,24 +32,25 @@ const int kMMapNr = __NR_mmap; #endif TEST(Syscall, WellKnownEntryPoint) { -// Test that SandboxSyscall(-1) is handled specially. Don't do this on ARM, +// Test that Syscall::Call(-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)); + EXPECT_NE(Syscall::Call(-1), syscall(-1)); #endif -// If possible, test that SandboxSyscall(-1) returns the address right after +// If possible, test that Syscall::Call(-1) returns the address right +// after // a kernel entry point. #if defined(__i386__) - EXPECT_EQ(0x80CDu, ((uint16_t*)SandboxSyscall(-1))[-1]); // INT 0x80 + EXPECT_EQ(0x80CDu, ((uint16_t*)Syscall::Call(-1))[-1]); // INT 0x80 #elif defined(__x86_64__) - EXPECT_EQ(0x050Fu, ((uint16_t*)SandboxSyscall(-1))[-1]); // SYSCALL + EXPECT_EQ(0x050Fu, ((uint16_t*)Syscall::Call(-1))[-1]); // SYSCALL #elif defined(__arm__) #if defined(__thumb__) - EXPECT_EQ(0xDF00u, ((uint16_t*)SandboxSyscall(-1))[-1]); // SWI 0 + EXPECT_EQ(0xDF00u, ((uint16_t*)Syscall::Call(-1))[-1]); // SWI 0 #else - EXPECT_EQ(0xEF000000u, ((uint32_t*)SandboxSyscall(-1))[-1]); // SVC 0 + EXPECT_EQ(0xEF000000u, ((uint32_t*)Syscall::Call(-1))[-1]); // SVC 0 #endif #else #warning Incomplete test case; need port for target platform @@ -57,20 +59,28 @@ TEST(Syscall, WellKnownEntryPoint) { TEST(Syscall, TrivialSyscallNoArgs) { // Test that we can do basic system calls - EXPECT_EQ(SandboxSyscall(__NR_getpid), syscall(__NR_getpid)); + EXPECT_EQ(Syscall::Call(__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 = IGNORE_EINTR(SandboxSyscall(__NR_close, new_fd)); + ASSERT_GE(new_fd = Syscall::Call(__NR_dup, 2), 0); + int close_return_value = IGNORE_EINTR(Syscall::Call(__NR_close, new_fd)); ASSERT_EQ(close_return_value, 0); } +TEST(Syscall, TrivialFailingSyscall) { + errno = -42; + int ret = Syscall::Call(__NR_dup, -1); + ASSERT_EQ(-EBADF, ret); + // Verify that Syscall::Call does not touch errno. + ASSERT_EQ(-42, errno); +} + // 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. + // |aux| is our BPF_AUX pointer. std::vector<uint64_t>* const seen_syscall_args = static_cast<std::vector<uint64_t>*>(aux); BPF_ASSERT(arraysize(args.args) == 6); @@ -78,7 +88,9 @@ intptr_t CopySyscallArgsToAux(const struct arch_seccomp_data& args, void* aux) { return -ENOMEM; } -ErrorCode CopyAllArgsOnUnamePolicy(SandboxBPF* sandbox, int sysno, void* aux) { +ErrorCode CopyAllArgsOnUnamePolicy(SandboxBPF* sandbox, + int sysno, + std::vector<uint64_t>* aux) { if (!SandboxBPF::IsValidSyscallNumber(sysno)) { return ErrorCode(ENOSYS); } @@ -89,12 +101,13 @@ ErrorCode CopyAllArgsOnUnamePolicy(SandboxBPF* sandbox, int sysno, void* aux) { } } -// We are testing SandboxSyscall() by making use of a BPF filter that allows us +// We are testing Syscall::Call() 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 */) { + 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 @@ -107,93 +120,93 @@ BPF_TEST(Syscall, // 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); + BPF_ASSERT(Syscall::Call(__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); + 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])); + 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)); + ASSERT_LE(0, fd = Syscall::Call(__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))); + ASSERT_NE( + (char*)NULL, + addr0 = reinterpret_cast<char*>(Syscall::Call(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))); + Syscall::Call(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, IGNORE_EINTR(SandboxSyscall(__NR_close, fd))); + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr1, 4096L)); + EXPECT_EQ(0, IGNORE_EINTR(Syscall::Call(__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); + ASSERT_GE(fd = Syscall::Call(__NR_open, "/proc/self/exe", O_RDONLY, 0L), 0); char* addr2, *addr3; ASSERT_NE((char*)NULL, - addr2 = reinterpret_cast<char*>(SandboxSyscall( + addr2 = reinterpret_cast<char*>(Syscall::Call( 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, + addr3 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, + (void*)NULL, + 4096L, + PROT_READ, + MAP_PRIVATE, + fd, #if defined(__NR_mmap2) - 1L + 1L #else - 4096L + 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(8192, Syscall::Call(__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, IGNORE_EINTR(SandboxSyscall(__NR_close, fd))); + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr2, 8192L)); + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr3, 4096L)); + EXPECT_EQ(0, IGNORE_EINTR(Syscall::Call(__NR_close, fd))); } } // namespace diff --git a/chromium/sandbox/linux/seccomp-bpf/trap.cc b/chromium/sandbox/linux/seccomp-bpf/trap.cc index 553a9043bfb..4c42111c2b0 100644 --- a/chromium/sandbox/linux/seccomp-bpf/trap.cc +++ b/chromium/sandbox/linux/seccomp-bpf/trap.cc @@ -82,8 +82,11 @@ Trap::Trap() } if (!IsDefaultSignalAction(old_sa)) { - // TODO(jln): make this FATAL, at least in DEBUG mode. - LOG(ERROR) << "Existing signal handler when trying to install SIGSYS"; + static const char kExistingSIGSYSMsg[] = + "Existing signal handler when trying to install SIGSYS. SIGSYS needs " + "to be reserved for seccomp-bpf."; + DLOG(FATAL) << kExistingSIGSYSMsg; + LOG(ERROR) << kExistingSIGSYSMsg; } // Unmask SIGSYS @@ -165,13 +168,13 @@ void Trap::SigSys(int nr, siginfo_t* info, void* void_context) { if (sigsys.nr == __NR_clone) { RAW_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)); + rc = Syscall::Call(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_) { @@ -224,7 +227,7 @@ ErrorCode Trap::MakeTrapImpl(TrapFnc fnc, const void* aux, bool safe) { // 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)). + // from Syscall::Call(-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 diff --git a/chromium/sandbox/linux/seccomp-bpf/trap.h b/chromium/sandbox/linux/seccomp-bpf/trap.h index 334a30d965a..adc6d7f5e22 100644 --- a/chromium/sandbox/linux/seccomp-bpf/trap.h +++ b/chromium/sandbox/linux/seccomp-bpf/trap.h @@ -12,6 +12,7 @@ #include <vector> #include "base/basictypes.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -25,7 +26,7 @@ class ErrorCode; // 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 { +class SANDBOX_EXPORT 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 @@ -62,10 +63,6 @@ class Trap { 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; @@ -75,6 +72,14 @@ class Trap { }; typedef std::map<TrapKey, uint16_t> TrapIds; + // Our constructor is private. A shared global instance is created + // automatically as needed. + Trap(); + + // The destructor is unimplemented. Don't ever attempt to destruct this + // object. It'll break subsequent system calls that trigger a SIGSYS. + ~Trap(); + // 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 @@ -104,11 +109,9 @@ class Trap { 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); + DISALLOW_COPY_AND_ASSIGN(Trap); }; } // namespace sandbox diff --git a/chromium/sandbox/linux/seccomp-bpf/verifier.cc b/chromium/sandbox/linux/seccomp-bpf/verifier.cc index 1292504decc..0863556e080 100644 --- a/chromium/sandbox/linux/seccomp-bpf/verifier.cc +++ b/chromium/sandbox/linux/seccomp-bpf/verifier.cc @@ -387,7 +387,9 @@ bool Verifier::VerifyBPF(SandboxBPF* sandbox, } #endif #endif - ErrorCode code = policy.EvaluateSyscall(sandbox, sysnum); + ErrorCode code = iter.IsValid(sysnum) + ? policy.EvaluateSyscall(sandbox, sysnum) + : policy.InvalidSyscall(sandbox); if (!VerifyErrorCode(sandbox, program, &data, code, code, err)) { return false; } @@ -421,10 +423,10 @@ uint32_t Verifier::EvaluateBPF(const std::vector<struct sock_filter>& program, switch (r & SECCOMP_RET_ACTION) { case SECCOMP_RET_TRAP: case SECCOMP_RET_ERRNO: + case SECCOMP_RET_TRACE: 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"; diff --git a/chromium/sandbox/linux/services/android_futex.h b/chromium/sandbox/linux/services/android_futex.h new file mode 100644 index 00000000000..032b02481a1 --- /dev/null +++ b/chromium/sandbox/linux/services/android_futex.h @@ -0,0 +1,28 @@ +// Copyright 2014 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_FUTEX_H_ +#define SANDBOX_LINUX_SERVICES_ANDROID_FUTEX_H_ + +#if !defined(FUTEX_PRIVATE_FLAG) +#define FUTEX_PRIVATE_FLAG 128 +#endif + +#if !defined(FUTEX_CLOCK_REALTIME) +#define FUTEX_CLOCK_REALTIME 256 +#endif + +#if !defined(FUTEX_CMP_REQUEUE_PI) +#define FUTEX_CMP_REQUEUE_PI 12 +#endif + +#if !defined(FUTEX_CMP_REQUEUE_PI_PRIVATE) +#define FUTEX_CMP_REQUEUE_PI_PRIVATE (FUTEX_CMP_REQUEUE_PI | FUTEX_PRIVATE_FLAG) +#endif + +#if !defined(FUTEX_CMD_MASK) +#define FUTEX_CMD_MASK ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME) +#endif + +#endif // SANDBOX_LINUX_SERVICES_ANDROID_FUTEX_H_ diff --git a/chromium/sandbox/linux/services/android_ucontext.h b/chromium/sandbox/linux/services/android_ucontext.h index 437bbab7bab..caabaf5b1b3 100644 --- a/chromium/sandbox/linux/services/android_ucontext.h +++ b/chromium/sandbox/linux/services/android_ucontext.h @@ -11,6 +11,8 @@ #include "sandbox/linux/services/android_arm_ucontext.h" #elif defined(__i386__) #include "sandbox/linux/services/android_i386_ucontext.h" +#elif defined(__x86_64__) +#include "sandbox/linux/services/android_x86_64_ucontext.h" #else #error "No support for your architecture in Android header" #endif diff --git a/chromium/sandbox/linux/services/android_x86_64_ucontext.h b/chromium/sandbox/linux/services/android_x86_64_ucontext.h new file mode 100644 index 00000000000..ef328e55d6f --- /dev/null +++ b/chromium/sandbox/linux/services/android_x86_64_ucontext.h @@ -0,0 +1,88 @@ +// Copyright 2014 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_X86_64_UCONTEXT_H_ +#define SANDBOX_LINUX_SERVICES_ANDROID_X86_64_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. +// Spec: +// http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/libc-ddefs.html#AEN5668 + +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +#include <asm/sigcontext.h> + +struct _libc_fpxreg { + unsigned short significand[4]; + unsigned short exponent; + unsigned short padding[3]; +}; + +struct _libc_xmmreg { + uint32_t element[4]; +}; + +struct _libc_fpstate { + uint16_t cwd; + uint16_t swd; + uint16_t twd; + uint16_t fop; + uint64_t rip; + uint64_t rdp; + uint32_t mxcsr; + uint32_t mxcsr_mask; + struct _libc_fpxreg _st[8]; + struct _libc_xmmreg _xmm[16]; + uint32_t padding[24]; +}; + +typedef uint64_t greg_t; + +typedef struct { + greg_t gregs[23]; + struct _libc_fpstate* fpregs; + unsigned long __reserved1[8]; +} mcontext_t; + +enum { + REG_R8 = 0, + REG_R9, + REG_R10, + REG_R11, + REG_R12, + REG_R13, + REG_R14, + REG_R15, + REG_RDI, + REG_RSI, + REG_RBP, + REG_RBX, + REG_RDX, + REG_RAX, + REG_RCX, + REG_RSP, + REG_RIP, + REG_EFL, + REG_CSGSFS, + REG_ERR, + REG_TRAPNO, + REG_OLDMASK, + REG_CR2, + NGREG, +}; + +typedef struct ucontext { + unsigned long 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_X86_64_UCONTEXT_H_ diff --git a/chromium/sandbox/linux/services/broker_process.cc b/chromium/sandbox/linux/services/broker_process.cc index 438e9726374..ef916f223a8 100644 --- a/chromium/sandbox/linux/services/broker_process.cc +++ b/chromium/sandbox/linux/services/broker_process.cc @@ -5,10 +5,12 @@ #include "sandbox/linux/services/broker_process.h" #include <fcntl.h> +#include <signal.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/syscall.h> #include <sys/types.h> +#include <sys/wait.h> #include <unistd.h> #include <algorithm> @@ -16,12 +18,16 @@ #include <vector> #include "base/basictypes.h" +#include "base/callback.h" #include "base/compiler_specific.h" +#include "base/files/scoped_file.h" #include "base/logging.h" +#include "base/memory/scoped_vector.h" #include "base/pickle.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/unix_domain_socket_linux.h" #include "base/process/process_metrics.h" +#include "base/third_party/valgrind/valgrind.h" #include "build/build_config.h" #include "sandbox/linux/services/linux_syscalls.h" @@ -31,6 +37,22 @@ namespace { +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + +// A little open(2) wrapper to handle some oddities for us. In the general case +// make a direct system call since we want to keep in control of the broker +// process' system calls profile to be able to loosely sandbox it. +int sys_open(const char* pathname, int flags) { + // Always pass a defined |mode| in case flags mistakenly contains O_CREAT. + const int mode = 0; + if (IsRunningOnValgrind()) { + // Valgrind does not support AT_FDCWD, just use libc's open() in this case. + return open(pathname, flags, mode); + } else { + return syscall(__NR_openat, AT_FDCWD, pathname, flags, mode); + } +} + static const size_t kMaxMessageLength = 4096; // Some flags are local to the current process and cannot be sent over a Unix @@ -132,11 +154,19 @@ BrokerProcess::BrokerProcess(int denied_errno, BrokerProcess::~BrokerProcess() { if (initialized_ && ipc_socketpair_ != -1) { - close(ipc_socketpair_); + // Closing the socket should be enough to notify the child to die, + // unless it has been duplicated. + PCHECK(0 == IGNORE_EINTR(close(ipc_socketpair_))); + PCHECK(0 == kill(broker_pid_, SIGKILL)); + siginfo_t process_info; + // Reap the child. + int ret = HANDLE_EINTR(waitid(P_PID, broker_pid_, &process_info, WEXITED)); + PCHECK(0 == ret); } } -bool BrokerProcess::Init(bool (*sandbox_callback)(void)) { +bool BrokerProcess::Init( + const base::Callback<bool(void)>& broker_process_init_callback) { CHECK(!initialized_); int socket_pair[2]; // Use SOCK_SEQPACKET, because we need to preserve message boundaries @@ -147,7 +177,9 @@ bool BrokerProcess::Init(bool (*sandbox_callback)(void)) { return false; } +#if !defined(THREAD_SANITIZER) DCHECK_EQ(1, base::GetNumberOfThreads(base::GetCurrentProcessHandle())); +#endif int child_pid = fork(); if (child_pid == -1) { close(socket_pair[0]); @@ -173,10 +205,7 @@ bool BrokerProcess::Init(bool (*sandbox_callback)(void)) { 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()); - } + CHECK(broker_process_init_callback.Run()); initialized_ = true; for (;;) { HandleRequest(); @@ -292,8 +321,7 @@ int BrokerProcess::PathAndFlagsSyscall(enum IPCCommands syscall_type, // 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; + ScopedVector<base::ScopedFD> fds; char buf[kMaxMessageLength]; errno = 0; const ssize_t msg_len = UnixDomainSocket::RecvMsg(ipc_socketpair_, buf, @@ -306,12 +334,13 @@ bool BrokerProcess::HandleRequest() const { // 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) { + // TODO(mdempsky): ScopedVector doesn't have 'at()', only 'operator[]'. + if (msg_len < 0 || fds.size() != 1 || fds[0]->get() < 0) { PLOG(ERROR) << "Error reading message from the client"; return false; } - const int temporary_ipc = fds.at(0); + base::ScopedFD temporary_ipc(fds[0]->Pass()); Pickle pickle(buf, msg_len); PickleIterator iter(pickle); @@ -324,15 +353,13 @@ bool BrokerProcess::HandleRequest() const { case kCommandOpen: // We reply on the file descriptor sent to us via the IPC channel. r = HandleRemoteCommand(static_cast<IPCCommands>(command_type), - temporary_ipc, pickle, iter); + temporary_ipc.get(), pickle, iter); break; default: NOTREACHED(); r = false; break; } - int ret = IGNORE_EINTR(close(temporary_ipc)); - DCHECK(!ret) << "Could not close temporary IPC channel"; return r; } @@ -375,7 +402,7 @@ bool BrokerProcess::HandleRemoteCommand(IPCCommands command_type, int reply_ipc, // Close anything we have opened in this process. for (std::vector<int>::iterator it = opened_files.begin(); - it < opened_files.end(); ++it) { + it != opened_files.end(); ++it) { int ret = IGNORE_EINTR(close(*it)); DCHECK(!ret) << "Could not close file descriptor"; } @@ -423,9 +450,7 @@ void BrokerProcess::OpenFileForIPC(const std::string& requested_filename, if (safe_to_open_file) { CHECK(file_to_open); - // 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 = syscall(__NR_open, file_to_open, flags, 0); + int opened_fd = sys_open(file_to_open, flags); if (opened_fd < 0) { write_pickle->WriteInt(-errno); } else { diff --git a/chromium/sandbox/linux/services/broker_process.h b/chromium/sandbox/linux/services/broker_process.h index 6b13b33046d..ddde42ebfc1 100644 --- a/chromium/sandbox/linux/services/broker_process.h +++ b/chromium/sandbox/linux/services/broker_process.h @@ -9,8 +9,10 @@ #include <vector> #include "base/basictypes.h" +#include "base/callback_forward.h" #include "base/pickle.h" #include "base/process/process.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -24,7 +26,7 @@ namespace sandbox { // 2. CHECK(open_broker.Init(NULL)); // 3. Enable sandbox. // 4. Use open_broker.Open() to open files. -class BrokerProcess { +class SANDBOX_EXPORT BrokerProcess { public: // |denied_errno| is the error code returned when methods such as Open() // or Access() are invoked on a file which is not in the whitelist. EACCESS @@ -42,9 +44,9 @@ class BrokerProcess { ~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)); + // broker_process_init_callback will be called in the new broker process, + // after fork() returns. + bool Init(const base::Callback<bool(void)>& broker_process_init_callback); // Can be used in place of access(). Will be async signal safe. // X_OK will always return an error in practice since the broker process @@ -95,6 +97,8 @@ class BrokerProcess { 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); + + friend class BrokerProcessTestHelper; }; } // namespace sandbox diff --git a/chromium/sandbox/linux/services/broker_process_unittest.cc b/chromium/sandbox/linux/services/broker_process_unittest.cc index f163ef99718..a1f38df3ae9 100644 --- a/chromium/sandbox/linux/services/broker_process_unittest.cc +++ b/chromium/sandbox/linux/services/broker_process_unittest.cc @@ -6,26 +6,37 @@ #include <errno.h> #include <fcntl.h> +#include <sys/resource.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> +#include <algorithm> #include <string> #include <vector> #include "base/basictypes.h" +#include "base/bind.h" #include "base/file_util.h" +#include "base/files/scoped_file.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "sandbox/linux/tests/test_utils.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" -using file_util::ScopedFD; - namespace sandbox { +class BrokerProcessTestHelper { + public: + static int get_ipc_socketpair(const BrokerProcess* broker) { + return broker->ipc_socketpair_; + } +}; + namespace { // Creates and open a temporary file on creation and closes @@ -60,13 +71,9 @@ class ScopedTemporaryFile { DISALLOW_COPY_AND_ASSIGN(ScopedTemporaryFile); }; -} // namespace +bool NoOpCallback() { return true; } -#if defined(OS_ANDROID) - #define DISABLE_ON_ANDROID(function) DISABLED_##function -#else - #define DISABLE_ON_ANDROID(function) function -#endif +} // namespace TEST(BrokerProcess, CreateAndDestroy) { std::vector<std::string> read_whitelist; @@ -74,21 +81,18 @@ TEST(BrokerProcess, CreateAndDestroy) { scoped_ptr<BrokerProcess> open_broker( new BrokerProcess(EPERM, read_whitelist, std::vector<std::string>())); - ASSERT_TRUE(open_broker->Init(NULL)); - pid_t broker_pid = open_broker->broker_pid(); + ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); // Destroy the broker and check it has exited properly. open_broker.reset(); - int status = 0; - ASSERT_EQ(waitpid(broker_pid, &status, 0), broker_pid); - ASSERT_TRUE(WIFEXITED(status)); - ASSERT_EQ(WEXITSTATUS(status), 0); + ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); } TEST(BrokerProcess, TestOpenAccessNull) { const std::vector<std::string> empty; BrokerProcess open_broker(EPERM, empty, empty); - ASSERT_TRUE(open_broker.Init(NULL)); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); int fd = open_broker.Open(NULL, O_RDONLY); ASSERT_EQ(fd, -EFAULT); @@ -119,7 +123,7 @@ void TestOpenFilePerms(bool fast_check_in_client, int denied_errno) { read_whitelist, write_whitelist, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(NULL)); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); int fd = -1; fd = open_broker.Open(kR_WhiteListed, O_RDONLY); @@ -270,12 +274,11 @@ void TestOpenCpuinfo(bool fast_check_in_client) { scoped_ptr<BrokerProcess> open_broker(new BrokerProcess( EPERM, read_whitelist, std::vector<std::string>(), fast_check_in_client)); - ASSERT_TRUE(open_broker->Init(NULL)); - pid_t broker_pid = open_broker->broker_pid(); + ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); int fd = -1; fd = open_broker->Open(kFileCpuInfo, O_RDWR); - ScopedFD fd_closer(&fd); + base::ScopedFD fd_closer(fd); ASSERT_EQ(fd, -EPERM); // Check we can read /proc/cpuinfo. @@ -287,7 +290,7 @@ void TestOpenCpuinfo(bool fast_check_in_client) { // Open cpuinfo via the broker. int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); - ScopedFD cpuinfo_fd_closer(&cpuinfo_fd); + base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); ASSERT_GE(cpuinfo_fd, 0); char buf[3]; memset(buf, 0, sizeof(buf)); @@ -296,7 +299,7 @@ void TestOpenCpuinfo(bool fast_check_in_client) { // Open cpuinfo directly. int cpuinfo_fd2 = open(kFileCpuInfo, O_RDONLY); - ScopedFD cpuinfo_fd2_closer(&cpuinfo_fd2); + base::ScopedFD cpuinfo_fd2_closer(cpuinfo_fd2); ASSERT_GE(cpuinfo_fd2, 0); char buf2[3]; memset(buf2, 1, sizeof(buf2)); @@ -309,13 +312,9 @@ void TestOpenCpuinfo(bool fast_check_in_client) { // ourselves. ASSERT_EQ(memcmp(buf, buf2, read_len1), 0); + ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); open_broker.reset(); - - // 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); + ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); } // Run the same thing twice. The second time, we make sure that no security @@ -340,7 +339,7 @@ TEST(BrokerProcess, OpenFileRW) { whitelist.push_back(tempfile_name); BrokerProcess open_broker(EPERM, whitelist, whitelist); - ASSERT_TRUE(open_broker.Init(NULL)); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); // Check we can access that file with read or write. int can_access = open_broker.Access(tempfile_name, R_OK | W_OK); @@ -377,15 +376,19 @@ SANDBOX_TEST(BrokerProcess, BrokerDied) { 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(open_broker.Init(base::Bind(&NoOpCallback))); + const 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); + // Now we check that the broker has been signaled, but do not reap it. + siginfo_t process_info; + SANDBOX_ASSERT(HANDLE_EINTR(waitid( + P_PID, broker_pid, &process_info, WEXITED | WNOWAIT)) == + 0); + SANDBOX_ASSERT(broker_pid == process_info.si_pid); + SANDBOX_ASSERT(CLD_KILLED == process_info.si_code); + SANDBOX_ASSERT(SIGKILL == process_info.si_status); + // Check that doing Open with a dead broker won't SIGPIPE us. SANDBOX_ASSERT(open_broker.Open("/proc/cpuinfo", O_RDONLY) == -ENOMEM); SANDBOX_ASSERT(open_broker.Access("/proc/cpuinfo", O_RDONLY) == -ENOMEM); @@ -400,7 +403,7 @@ void TestOpenComplexFlags(bool fast_check_in_client) { whitelist, whitelist, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(NULL)); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); // Test that we do the right thing for O_CLOEXEC and O_NONBLOCK. int fd = -1; int ret = 0; @@ -441,4 +444,60 @@ TEST(BrokerProcess, OpenComplexFlagsNoClientCheck) { // expected. } +// We need to allow noise because the broker will log when it receives our +// bogus IPCs. +SANDBOX_TEST_ALLOW_NOISE(BrokerProcess, RecvMsgDescriptorLeak) { + // Find the four lowest available file descriptors. + int available_fds[4]; + SANDBOX_ASSERT(0 == pipe(available_fds)); + SANDBOX_ASSERT(0 == pipe(available_fds + 2)); + + // Save one FD to send to the broker later, and close the others. + base::ScopedFD message_fd(available_fds[0]); + for (size_t i = 1; i < arraysize(available_fds); i++) { + SANDBOX_ASSERT(0 == IGNORE_EINTR(close(available_fds[i]))); + } + + // Lower our file descriptor limit to just allow three more file descriptors + // to be allocated. (N.B., RLIMIT_NOFILE doesn't limit the number of file + // descriptors a process can have: it only limits the highest value that can + // be assigned to newly-created descriptors allocated by the process.) + const rlim_t fd_limit = + 1 + *std::max_element(available_fds, + available_fds + arraysize(available_fds)); + + // Valgrind doesn't allow changing the hard descriptor limit, so we only + // change the soft descriptor limit here. + struct rlimit rlim; + SANDBOX_ASSERT(0 == getrlimit(RLIMIT_NOFILE, &rlim)); + SANDBOX_ASSERT(fd_limit <= rlim.rlim_cur); + rlim.rlim_cur = fd_limit; + SANDBOX_ASSERT(0 == setrlimit(RLIMIT_NOFILE, &rlim)); + + static const char kCpuInfo[] = "/proc/cpuinfo"; + std::vector<std::string> read_whitelist; + read_whitelist.push_back(kCpuInfo); + + BrokerProcess open_broker(EPERM, read_whitelist, std::vector<std::string>()); + SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback))); + + const int ipc_fd = BrokerProcessTestHelper::get_ipc_socketpair(&open_broker); + SANDBOX_ASSERT(ipc_fd >= 0); + + static const char kBogus[] = "not a pickle"; + std::vector<int> fds; + fds.push_back(message_fd.get()); + + // The broker process should only have a couple spare file descriptors + // available, but for good measure we send it fd_limit bogus IPCs anyway. + for (rlim_t i = 0; i < fd_limit; ++i) { + SANDBOX_ASSERT( + UnixDomainSocket::SendMsg(ipc_fd, kBogus, sizeof(kBogus), fds)); + } + + const int fd = open_broker.Open(kCpuInfo, O_RDONLY); + SANDBOX_ASSERT(fd >= 0); + SANDBOX_ASSERT(0 == IGNORE_EINTR(close(fd))); +} + } // namespace sandbox diff --git a/chromium/sandbox/linux/services/credentials.cc b/chromium/sandbox/linux/services/credentials.cc index 4f041dc2887..96702b1700a 100644 --- a/chromium/sandbox/linux/services/credentials.cc +++ b/chromium/sandbox/linux/services/credentials.cc @@ -7,21 +7,28 @@ #include <dirent.h> #include <errno.h> #include <fcntl.h> +#include <signal.h> #include <stdio.h> #include <sys/capability.h> #include <sys/stat.h> +#include <sys/syscall.h> #include <sys/types.h> +#include <sys/wait.h> #include <unistd.h> #include "base/basictypes.h" #include "base/bind.h" #include "base/logging.h" +#include "base/posix/eintr_wrapper.h" #include "base/strings/string_number_conversions.h" #include "base/template_util.h" +#include "base/third_party/valgrind/valgrind.h" #include "base/threading/thread.h" namespace { +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + struct CapFreeDeleter { inline void operator()(cap_t cap) const { int ret = cap_free(cap); @@ -49,7 +56,7 @@ struct FILECloser { } }; -// Don't use ScopedFILE in base/file_util.h since it doesn't check fclose(). +// Don't use ScopedFILE in base since it doesn't check fclose(). // TODO(jln): fix base/. typedef scoped_ptr<FILE, FILECloser> ScopedFILE; @@ -146,6 +153,16 @@ bool ChrootToSafeEmptyDir() { return is_chrooted; } +// CHECK() that an attempt to move to a new user namespace raised an expected +// errno. +void CheckCloneNewUserErrno(int error) { + // EPERM can happen if already in a chroot. EUSERS if too many nested + // namespaces are used. EINVAL for kernels that don't support the feature. + // Valgrind will ENOSYS unshare(). + PCHECK(error == EPERM || error == EUSERS || error == EINVAL || + error == ENOSYS); +} + } // namespace. namespace sandbox { @@ -156,6 +173,35 @@ Credentials::Credentials() { Credentials::~Credentials() { } +int Credentials::CountOpenFds(int proc_fd) { + DCHECK_LE(0, proc_fd); + int proc_self_fd = openat(proc_fd, "self/fd", O_DIRECTORY | O_RDONLY); + PCHECK(0 <= proc_self_fd); + + // Ownership of proc_self_fd is transferred here, it must not be closed + // or modified afterwards except via dir. + ScopedDIR dir(fdopendir(proc_self_fd)); + CHECK(dir); + + int count = 0; + struct dirent e; + struct dirent* de; + while (!readdir_r(dir.get(), &e, &de) && de) { + if (strcmp(e.d_name, ".") == 0 || strcmp(e.d_name, "..") == 0) { + continue; + } + + int fd_num; + CHECK(base::StringToInt(e.d_name, &fd_num)); + if (fd_num == proc_fd || fd_num == proc_self_fd) { + continue; + } + + ++count; + } + return count; +} + bool Credentials::HasOpenDirectory(int proc_fd) { int proc_self_fd = -1; if (proc_fd >= 0) { @@ -175,7 +221,7 @@ bool Credentials::HasOpenDirectory(int proc_fd) { return false; } } - CHECK_GE(proc_self_fd, 0); + PCHECK(0 <= proc_self_fd); // Ownership of proc_self_fd is transferred here, it must not be closed // or modified afterwards except via dir. @@ -231,6 +277,37 @@ scoped_ptr<std::string> Credentials::GetCurrentCapString() const { return scoped_ptr<std::string> (new std::string(cap_text.get())); } +// static +bool Credentials::SupportsNewUserNS() { + // Valgrind will let clone(2) pass-through, but doesn't support unshare(), + // so always consider UserNS unsupported there. + if (IsRunningOnValgrind()) { + return false; + } + + // This is roughly a fork(). + const pid_t pid = syscall(__NR_clone, CLONE_NEWUSER | SIGCHLD, 0, 0, 0); + + if (pid == -1) { + CheckCloneNewUserErrno(errno); + return false; + } + + // The parent process could have had threads. In the child, these threads + // have disappeared. Make sure to not do anything in the child, as this is a + // fragile execution environment. + if (pid == 0) { + _exit(0); + } + + // Always reap the child. + siginfo_t infop; + PCHECK(0 == HANDLE_EINTR(waitid(P_PID, pid, &infop, WEXITED))); + + // clone(2) succeeded, we can use CLONE_NEWUSER. + return true; +} + bool Credentials::MoveToNewUserNS() { uid_t uid; gid_t gid; @@ -241,16 +318,14 @@ bool Credentials::MoveToNewUserNS() { return false; } int ret = unshare(CLONE_NEWUSER); - // EPERM can happen if already in a chroot. EUSERS if too many nested - // namespaces are used. EINVAL for kernels that don't support the feature. - // Valgrind will ENOSYS unshare(). - PCHECK(!ret || errno == EPERM || errno == EUSERS || errno == EINVAL || - errno == ENOSYS); if (ret) { + const int unshare_errno = errno; VLOG(1) << "Looks like unprivileged CLONE_NEWUSER may not be available " << "on this kernel."; + CheckCloneNewUserErrno(unshare_errno); return false; } + // The current {r,e,s}{u,g}id is now an overflow id (c.f. // /proc/sys/kernel/overflowuid). Setup the uid and gid maps. DCHECK(GetRESIds(NULL, NULL)); diff --git a/chromium/sandbox/linux/services/credentials.h b/chromium/sandbox/linux/services/credentials.h index c23db930df2..99f8f322dff 100644 --- a/chromium/sandbox/linux/services/credentials.h +++ b/chromium/sandbox/linux/services/credentials.h @@ -15,17 +15,23 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" +#include "sandbox/sandbox_export.h" namespace sandbox { // This class should be used to manipulate the current process' credentials. // It is currently a stub used to manipulate POSIX.1e capabilities as // implemented by the Linux kernel. -class Credentials { +class SANDBOX_EXPORT Credentials { public: Credentials(); ~Credentials(); + // Returns the number of file descriptors in the current process's FD + // table, excluding |proc_fd|, which should be a file descriptor for + // /proc. + int CountOpenFds(int proc_fd); + // Checks whether the current process has any directory file descriptor open. // Directory file descriptors are "capabilities" that would let a process use // system calls such as openat() to bypass restrictions such as @@ -51,6 +57,13 @@ class Credentials { // debugging and tests. scoped_ptr<std::string> GetCurrentCapString() const; + // Returns whether the kernel supports CLONE_NEWUSER and whether it would be + // possible to immediately move to a new user namespace. There is no point + // in using this method right before calling MoveToNewUserNS(), simply call + // MoveToNewUserNS() immediately. This method is only useful to test kernel + // support ahead of time. + static bool SupportsNewUserNS(); + // Move the current process to a new "user namespace" as supported by Linux // 3.8+ (CLONE_NEWUSER). // The uid map will be set-up so that the perceived uid and gid will not diff --git a/chromium/sandbox/linux/services/credentials_unittest.cc b/chromium/sandbox/linux/services/credentials_unittest.cc index 9160bf7a1ca..12ef32006ab 100644 --- a/chromium/sandbox/linux/services/credentials_unittest.cc +++ b/chromium/sandbox/linux/services/credentials_unittest.cc @@ -12,13 +12,12 @@ #include <unistd.h> #include "base/file_util.h" +#include "base/files/scoped_file.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" -using file_util::ScopedFD; - namespace sandbox { namespace { @@ -58,6 +57,18 @@ TEST(Credentials, CreateAndDestroy) { scoped_ptr<Credentials> cred2(new Credentials); } +TEST(Credentials, CountOpenFds) { + base::ScopedFD proc_fd(open("/proc", O_RDONLY | O_DIRECTORY)); + ASSERT_TRUE(proc_fd.is_valid()); + Credentials creds; + int fd_count = creds.CountOpenFds(proc_fd.get()); + int fd = open("/dev/null", O_RDONLY); + ASSERT_LE(0, fd); + EXPECT_EQ(fd_count + 1, creds.CountOpenFds(proc_fd.get())); + ASSERT_EQ(0, IGNORE_EINTR(close(fd))); + EXPECT_EQ(fd_count, creds.CountOpenFds(proc_fd.get())); +} + TEST(Credentials, HasOpenDirectory) { Credentials creds; // No open directory should exist at startup. @@ -65,7 +76,7 @@ TEST(Credentials, HasOpenDirectory) { { // Have a "/dev" file descriptor around. int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); - ScopedFD dev_fd_closer(&dev_fd); + base::ScopedFD dev_fd_closer(dev_fd); EXPECT_TRUE(creds.HasOpenDirectory(-1)); } EXPECT_FALSE(creds.HasOpenDirectory(-1)); @@ -75,7 +86,7 @@ TEST(Credentials, HasOpenDirectoryWithFD) { Credentials creds; int proc_fd = open("/proc", O_RDONLY | O_DIRECTORY); - ScopedFD proc_fd_closer(&proc_fd); + base::ScopedFD proc_fd_closer(proc_fd); ASSERT_LE(0, proc_fd); // Don't pass |proc_fd|, an open directory (proc_fd) should @@ -87,7 +98,7 @@ TEST(Credentials, HasOpenDirectoryWithFD) { { // Have a "/dev" file descriptor around. int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); - ScopedFD dev_fd_closer(&dev_fd); + base::ScopedFD dev_fd_closer(dev_fd); EXPECT_TRUE(creds.HasOpenDirectory(proc_fd)); } @@ -112,11 +123,12 @@ SANDBOX_TEST(Credentials, GetCurrentCapString) { SANDBOX_TEST(Credentials, MoveToNewUserNS) { Credentials creds; creds.DropAllCapabilities(); - bool userns_supported = creds.MoveToNewUserNS(); - fprintf(stdout, "Unprivileged CLONE_NEWUSER supported: %s\n", - userns_supported ? "true." : "false."); + bool moved_to_new_ns = creds.MoveToNewUserNS(); + fprintf(stdout, + "Unprivileged CLONE_NEWUSER supported: %s\n", + moved_to_new_ns ? "true." : "false."); fflush(stdout); - if (!userns_supported) { + if (!moved_to_new_ns) { fprintf(stdout, "This kernel does not support unprivileged namespaces. " "USERNS tests will succeed without running.\n"); fflush(stdout); @@ -127,6 +139,14 @@ SANDBOX_TEST(Credentials, MoveToNewUserNS) { CHECK(!creds.HasAnyCapability()); } +SANDBOX_TEST(Credentials, SupportsUserNS) { + Credentials creds; + creds.DropAllCapabilities(); + bool user_ns_supported = Credentials::SupportsNewUserNS(); + bool moved_to_new_ns = creds.MoveToNewUserNS(); + CHECK_EQ(user_ns_supported, moved_to_new_ns); +} + SANDBOX_TEST(Credentials, UidIsPreserved) { Credentials creds; creds.DropAllCapabilities(); @@ -182,7 +202,7 @@ TEST(Credentials, CanDetectRoot) { ASSERT_TRUE(WorkingDirectoryIsRoot()); } -SANDBOX_TEST(Credentials, DropFileSystemAccessIsSafe) { +SANDBOX_TEST(Credentials, DISABLE_ON_LSAN(DropFileSystemAccessIsSafe)) { Credentials creds; CHECK(creds.DropAllCapabilities()); // Probably missing kernel support. @@ -197,7 +217,7 @@ SANDBOX_TEST(Credentials, DropFileSystemAccessIsSafe) { // Check that after dropping filesystem access and dropping privileges // it is not possible to regain capabilities. -SANDBOX_TEST(Credentials, CannotRegainPrivileges) { +SANDBOX_TEST(Credentials, DISABLE_ON_LSAN(CannotRegainPrivileges)) { Credentials creds; CHECK(creds.DropAllCapabilities()); // Probably missing kernel support. @@ -207,6 +227,7 @@ SANDBOX_TEST(Credentials, CannotRegainPrivileges) { // The kernel should now prevent us from regaining capabilities because we // are in a chroot. + CHECK(!Credentials::SupportsNewUserNS()); CHECK(!creds.MoveToNewUserNS()); } diff --git a/chromium/sandbox/linux/services/init_process_reaper.h b/chromium/sandbox/linux/services/init_process_reaper.h index 531d18cba11..840f6fcda7b 100644 --- a/chromium/sandbox/linux/services/init_process_reaper.h +++ b/chromium/sandbox/linux/services/init_process_reaper.h @@ -6,6 +6,7 @@ #define SANDBOX_LINUX_SERVICES_INIT_PROCESS_REAPER_H_ #include "base/callback_forward.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -16,7 +17,8 @@ namespace sandbox { // immediately after fork(). // Since this function calls fork(), it's very important that the caller has // only one thread running. -bool CreateInitProcessReaper(base::Closure* post_fork_parent_callback); +SANDBOX_EXPORT bool CreateInitProcessReaper( + base::Closure* post_fork_parent_callback); } // namespace sandbox. diff --git a/chromium/sandbox/linux/services/libc_urandom_override.cc b/chromium/sandbox/linux/services/libc_urandom_override.cc index c5c49bac8ca..33bb25d6b17 100644 --- a/chromium/sandbox/linux/services/libc_urandom_override.cc +++ b/chromium/sandbox/linux/services/libc_urandom_override.cc @@ -25,9 +25,9 @@ namespace sandbox { static bool g_override_urandom = false; // TODO(sergeyu): Currently InitLibcUrandomOverrides() doesn't work properly -// under ASAN - it crashes content_unittests. Make sure it works properly and -// enable it here. http://crbug.com/123263 -#if !defined(ADDRESS_SANITIZER) +// under ASan or MSan - it crashes content_unittests. Make sure it works +// properly and enable it here. http://crbug.com/123263 +#if !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) static void InitLibcFileIOFunctions(); static pthread_once_t g_libc_file_io_funcs_guard = PTHREAD_ONCE_INIT; #endif @@ -37,13 +37,13 @@ void InitLibcUrandomOverrides() { base::GetUrandomFD(); g_override_urandom = true; -#if !defined(ADDRESS_SANITIZER) +#if !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) CHECK_EQ(0, pthread_once(&g_libc_file_io_funcs_guard, InitLibcFileIOFunctions)); #endif } -#if !defined(ADDRESS_SANITIZER) +#if !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) static const char kUrandomDevPath[] = "/dev/urandom"; @@ -231,6 +231,6 @@ int stat64_override(const char *path, struct stat64 *buf) { #endif // HAVE_XSTAT -#endif // !defined(ADDRESS_SANITIZER) +#endif // !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) } // namespace content diff --git a/chromium/sandbox/linux/services/scoped_process.cc b/chromium/sandbox/linux/services/scoped_process.cc new file mode 100644 index 00000000000..fd42a2a6e17 --- /dev/null +++ b/chromium/sandbox/linux/services/scoped_process.cc @@ -0,0 +1,119 @@ +// Copyright 2014 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/scoped_process.h" + +#include <fcntl.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "sandbox/linux/services/thread_helpers.h" + +namespace sandbox { + +namespace { + +const char kSynchronisationChar[] = "D"; + +void WaitForever() { + while(true) { + pause(); + } +} + +} // namespace + +ScopedProcess::ScopedProcess(const base::Closure& child_callback) + : child_process_id_(-1), process_id_(getpid()) { + PCHECK(0 == pipe(pipe_fds_)); +#if !defined(THREAD_SANITIZER) + // Make sure that we can safely fork(). + CHECK(ThreadHelpers::IsSingleThreaded(-1)); +#endif + child_process_id_ = fork(); + PCHECK(0 <= child_process_id_); + + if (0 == child_process_id_) { + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[0]))); + pipe_fds_[0] = -1; + child_callback.Run(); + // Notify the parent that the closure has run. + CHECK_EQ(1, HANDLE_EINTR(write(pipe_fds_[1], kSynchronisationChar, 1))); + WaitForever(); + NOTREACHED(); + _exit(1); + } + + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[1]))); + pipe_fds_[1] = -1; +} + +ScopedProcess::~ScopedProcess() { + CHECK(IsOriginalProcess()); + if (child_process_id_ >= 0) { + PCHECK(0 == kill(child_process_id_, SIGKILL)); + siginfo_t process_info; + + PCHECK(0 == HANDLE_EINTR( + waitid(P_PID, child_process_id_, &process_info, WEXITED))); + } + if (pipe_fds_[0] >= 0) { + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[0]))); + } + if (pipe_fds_[1] >= 0) { + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[1]))); + } +} + +int ScopedProcess::WaitForExit(bool* got_signaled) { + DCHECK(got_signaled); + CHECK(IsOriginalProcess()); + siginfo_t process_info; + // WNOWAIT to make sure that the destructor can wait on the child. + int ret = HANDLE_EINTR( + waitid(P_PID, child_process_id_, &process_info, WEXITED | WNOWAIT)); + PCHECK(0 == ret) << "Did something else wait on the child?"; + + if (process_info.si_code == CLD_EXITED) { + *got_signaled = false; + } else if (process_info.si_code == CLD_KILLED || + process_info.si_code == CLD_DUMPED) { + *got_signaled = true; + } else { + CHECK(false) << "ScopedProcess needs to be extended for si_code " + << process_info.si_code; + } + return process_info.si_status; +} + +bool ScopedProcess::WaitForClosureToRun() { + char c = 0; + int ret = HANDLE_EINTR(read(pipe_fds_[0], &c, 1)); + PCHECK(ret >= 0); + if (0 == ret) + return false; + + CHECK_EQ(c, kSynchronisationChar[0]); + return true; +} + +// It would be problematic if after a fork(), another process would start using +// this object. +// This method allows to assert it is not happening. +bool ScopedProcess::IsOriginalProcess() { + // Make a direct syscall to bypass glibc caching of PIDs. + int pid = syscall(__NR_getpid); + return pid == process_id_; +} + +} // namespace sandbox diff --git a/chromium/sandbox/linux/services/scoped_process.h b/chromium/sandbox/linux/services/scoped_process.h new file mode 100644 index 00000000000..a28131ee67d --- /dev/null +++ b/chromium/sandbox/linux/services/scoped_process.h @@ -0,0 +1,55 @@ +// Copyright 2014 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_SCOPED_PROCESS_H_ +#define SANDBOX_LINUX_SERVICES_SCOPED_PROCESS_H_ + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/process/process_handle.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// fork() a child process that will run a Closure. +// After the Closure has run, the child will pause forever. If this object +// is detroyed, the child will be destroyed, even if the closure did not +// finish running. It's ok to signal the child from outside of this class to +// destroy it. +// This class cannot be instanciated from a multi-threaded process, as it needs +// to fork(). +class SANDBOX_EXPORT ScopedProcess { + public: + // A new process will be created and |child_callback| will run in the child + // process. This callback is allowed to terminate the process or to simply + // return. If the callback returns, the process will wait forever. + explicit ScopedProcess(const base::Closure& child_callback); + ~ScopedProcess(); + + // Wait for the process to exit. + // |got_signaled| tells how to interpret the return value: either as an exit + // code, or as a signal number. + // When this returns, the process will still not have been reaped and will + // survive as a zombie for the lifetime of this object. This method can be + // called multiple times. + int WaitForExit(bool* got_signaled); + + // Wait for the |child_callback| passed at construction to run. Return false + // if |child_callback| did not finish running and we know it never will (for + // instance the child crashed or used _exit()). + bool WaitForClosureToRun(); + base::ProcessId GetPid() { return child_process_id_; } + + private: + bool IsOriginalProcess(); + + base::ProcessId child_process_id_; + base::ProcessId process_id_; + int pipe_fds_[2]; + DISALLOW_COPY_AND_ASSIGN(ScopedProcess); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_SCOPED_PROCESS_H_ diff --git a/chromium/sandbox/linux/services/scoped_process_unittest.cc b/chromium/sandbox/linux/services/scoped_process_unittest.cc new file mode 100644 index 00000000000..382c7fb2cb5 --- /dev/null +++ b/chromium/sandbox/linux/services/scoped_process_unittest.cc @@ -0,0 +1,130 @@ +// Copyright 2014 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 <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "sandbox/linux/services/scoped_process.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +void DoExit() { _exit(0); } + +void ExitWithCode(int exit_code) { _exit(exit_code); } + +void RaiseAndExit(int signal) { + PCHECK(0 == raise(signal)); + _exit(0); +} + +void DoNothing() {} + +TEST(ScopedProcess, ScopedProcessNormalExit) { + const int kCustomExitCode = 12; + ScopedProcess process(base::Bind(&ExitWithCode, kCustomExitCode)); + bool got_signaled = true; + int exit_code = process.WaitForExit(&got_signaled); + EXPECT_FALSE(got_signaled); + EXPECT_EQ(kCustomExitCode, exit_code); + + // Verify that WaitForExit() can be called multiple times on the same + // process. + bool got_signaled2 = true; + int exit_code2 = process.WaitForExit(&got_signaled2); + EXPECT_FALSE(got_signaled2); + EXPECT_EQ(kCustomExitCode, exit_code2); +} + +// Disable this test on Android, SIGABRT is funky there. +TEST(ScopedProcess, DISABLE_ON_ANDROID(ScopedProcessAbort)) { + PCHECK(SIG_ERR != signal(SIGABRT, SIG_DFL)); + ScopedProcess process(base::Bind(&RaiseAndExit, SIGABRT)); + bool got_signaled = false; + int exit_code = process.WaitForExit(&got_signaled); + EXPECT_TRUE(got_signaled); + EXPECT_EQ(SIGABRT, exit_code); +} + +TEST(ScopedProcess, ScopedProcessSignaled) { + ScopedProcess process(base::Bind(&DoNothing)); + bool got_signaled = false; + ASSERT_EQ(0, kill(process.GetPid(), SIGKILL)); + int exit_code = process.WaitForExit(&got_signaled); + EXPECT_TRUE(got_signaled); + EXPECT_EQ(SIGKILL, exit_code); +} + +TEST(ScopedProcess, DiesForReal) { + int pipe_fds[2]; + ASSERT_EQ(0, pipe(pipe_fds)); + base::ScopedFD read_end_closer(pipe_fds[0]); + base::ScopedFD write_end_closer(pipe_fds[1]); + + { ScopedProcess process(base::Bind(&DoExit)); } + + // Close writing end of the pipe. + write_end_closer.reset(); + pipe_fds[1] = -1; + + ASSERT_EQ(0, fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK)); + char c; + // If the child process is dead for real, there will be no writing end + // for this pipe left and read will EOF instead of returning EWOULDBLOCK. + ASSERT_EQ(0, read(pipe_fds[0], &c, 1)); +} + +TEST(ScopedProcess, SynchronizationBasic) { + ScopedProcess process1(base::Bind(&DoNothing)); + EXPECT_TRUE(process1.WaitForClosureToRun()); + + ScopedProcess process2(base::Bind(&DoExit)); + // The closure didn't finish running normally. This case is simple enough + // that process.WaitForClosureToRun() should return false, even though the + // API does not guarantees that it will return at all. + EXPECT_FALSE(process2.WaitForClosureToRun()); +} + +void SleepInMsAndWriteOneByte(int time_to_sleep, int fd) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(time_to_sleep)); + CHECK(1 == write(fd, "1", 1)); +} + +TEST(ScopedProcess, SynchronizationWorks) { + int pipe_fds[2]; + ASSERT_EQ(0, pipe(pipe_fds)); + base::ScopedFD read_end_closer(pipe_fds[0]); + base::ScopedFD write_end_closer(pipe_fds[1]); + + // Start a process with a closure that takes a little bit to run. + ScopedProcess process( + base::Bind(&SleepInMsAndWriteOneByte, 100, pipe_fds[1])); + EXPECT_TRUE(process.WaitForClosureToRun()); + + // Verify that the closure did, indeed, run. + ASSERT_EQ(0, fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK)); + char c = 0; + EXPECT_EQ(1, read(pipe_fds[0], &c, 1)); + EXPECT_EQ('1', c); +} + +} // namespace + +} // namespace sandbox diff --git a/chromium/sandbox/linux/services/thread_helpers.cc b/chromium/sandbox/linux/services/thread_helpers.cc index e0794f84a44..e820449cb70 100644 --- a/chromium/sandbox/linux/services/thread_helpers.cc +++ b/chromium/sandbox/linux/services/thread_helpers.cc @@ -5,6 +5,7 @@ #include "sandbox/linux/services/thread_helpers.h" #include <errno.h> +#include <fcntl.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> @@ -21,7 +22,9 @@ namespace sandbox { -bool ThreadHelpers::IsSingleThreaded(int proc_self_task) { +namespace { + +bool IsSingleThreadedImpl(int proc_self_task) { CHECK_LE(0, proc_self_task); struct stat task_stat; int fstat_ret = fstat(proc_self_task, &task_stat); @@ -35,6 +38,21 @@ bool ThreadHelpers::IsSingleThreaded(int proc_self_task) { return task_stat.st_nlink == 3; } +} // namespace + +bool ThreadHelpers::IsSingleThreaded(int proc_self_task) { + DCHECK_LE(-1, proc_self_task); + if (-1 == proc_self_task) { + const int task_fd = open("/proc/self/task/", O_RDONLY | O_DIRECTORY); + PCHECK(0 <= task_fd); + const bool result = IsSingleThreadedImpl(task_fd); + PCHECK(0 == IGNORE_EINTR(close(task_fd))); + return result; + } else { + return IsSingleThreadedImpl(proc_self_task); + } +} + bool ThreadHelpers::StopThreadAndWatchProcFS(int proc_self_task, base::Thread* thread) { DCHECK_LE(0, proc_self_task); diff --git a/chromium/sandbox/linux/services/thread_helpers.h b/chromium/sandbox/linux/services/thread_helpers.h index 651e5d9c501..377742a539a 100644 --- a/chromium/sandbox/linux/services/thread_helpers.h +++ b/chromium/sandbox/linux/services/thread_helpers.h @@ -6,16 +6,19 @@ #define SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_ #include "base/basictypes.h" +#include "sandbox/sandbox_export.h" namespace base { class Thread; } namespace sandbox { -class ThreadHelpers { +class SANDBOX_EXPORT ThreadHelpers { public: // Check whether the current process is single threaded. |proc_self_tasks| - // should be a file descriptor to /proc/self/task/ and remains owned by the - // caller. + // can be a file descriptor to /proc/self/task/ and remains owned by the + // caller or -1. + // If |proc_self_tasks| is -1, this method will open /proc/self/task/ and + // crash if it cannot. static bool IsSingleThreaded(int proc_self_task); // Stop |thread| and ensure that it does not have an entry in @@ -28,6 +31,6 @@ class ThreadHelpers { DISALLOW_IMPLICIT_CONSTRUCTORS(ThreadHelpers); }; -} // namespace content +} // namespace sandbox #endif // SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_ diff --git a/chromium/sandbox/linux/services/thread_helpers_unittests.cc b/chromium/sandbox/linux/services/thread_helpers_unittests.cc index 725a62ef766..a36fd2975f8 100644 --- a/chromium/sandbox/linux/services/thread_helpers_unittests.cc +++ b/chromium/sandbox/linux/services/thread_helpers_unittests.cc @@ -17,6 +17,7 @@ #include "base/process/process_metrics.h" #include "base/threading/platform_thread.h" #include "base/threading/thread.h" +#include "build/build_config.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" @@ -50,16 +51,31 @@ class ScopedProcSelfTask { DISALLOW_COPY_AND_ASSIGN(ScopedProcSelfTask); }; -TEST(ThreadHelpers, IsSingleThreadedBasic) { +#if defined(THREAD_SANITIZER) +// These tests fail under ThreadSanitizer, see http://crbug.com/342305 +#define MAYBE_IsSingleThreadedBasic DISABLED_IsSingleThreadedBasic +#define MAYBE_IsSingleThreadedIterated DISABLED_IsSingleThreadedIterated +#define MAYBE_IsSingleThreadedStartAndStop DISABLED_IsSingleThreadedStartAndStop +#else +#define MAYBE_IsSingleThreadedBasic IsSingleThreadedBasic +#define MAYBE_IsSingleThreadedIterated IsSingleThreadedIterated +#define MAYBE_IsSingleThreadedStartAndStop IsSingleThreadedStartAndStop +#endif + +TEST(ThreadHelpers, MAYBE_IsSingleThreadedBasic) { ScopedProcSelfTask task; ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd())); + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(-1)); base::Thread thread("sandbox_tests"); ASSERT_TRUE(thread.Start()); ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(task.fd())); + ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(-1)); + // Explicitly stop the thread here to not pollute the next test. + ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(task.fd(), &thread)); } -TEST(ThreadHelpers, IsSingleThreadedIterated) { +TEST(ThreadHelpers, MAYBE_IsSingleThreadedIterated) { ScopedProcSelfTask task; ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd())); @@ -68,10 +84,12 @@ TEST(ThreadHelpers, IsSingleThreadedIterated) { base::Thread thread("sandbox_tests"); ASSERT_TRUE(thread.Start()); ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(task.fd())); + // Explicitly stop the thread here to not pollute the next test. + ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(task.fd(), &thread)); } } -TEST(ThreadHelpers, IsSingleThreadedStartAndStop) { +TEST(ThreadHelpers, MAYBE_IsSingleThreadedStartAndStop) { ScopedProcSelfTask task; ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd())); diff --git a/chromium/sandbox/linux/services/unix_domain_socket_unittest.cc b/chromium/sandbox/linux/services/unix_domain_socket_unittest.cc new file mode 100644 index 00000000000..17208a829dd --- /dev/null +++ b/chromium/sandbox/linux/services/unix_domain_socket_unittest.cc @@ -0,0 +1,267 @@ +// Copyright 2014 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 <sched.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <vector> + +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "base/process/process_handle.h" +#include "sandbox/linux/tests/unit_tests.h" + +// Additional tests for base's UnixDomainSocket to make sure it behaves +// correctly in the presence of sandboxing functionality (e.g., receiving +// PIDs across namespaces). + +namespace sandbox { + +namespace { + +const char kHello[] = "hello"; + +// If the calling process isn't root, then try using unshare(CLONE_NEWUSER) +// to fake it. +void FakeRoot() { + // If we're already root, then allow test to proceed. + if (geteuid() == 0) + return; + + // Otherwise hope the kernel supports unprivileged namespaces. + if (unshare(CLONE_NEWUSER) == 0) + return; + + printf("Permission to use CLONE_NEWPID missing; skipping test.\n"); + UnitTests::IgnoreThisTest(); +} + +void WaitForExit(pid_t pid) { + int status; + CHECK_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0))); + CHECK(WIFEXITED(status)); + CHECK_EQ(0, WEXITSTATUS(status)); +} + +base::ProcessId GetParentProcessId(base::ProcessId pid) { + // base::GetParentProcessId() is defined as taking a ProcessHandle instead of + // a ProcessId, even though it's a POSIX-only function and IDs and Handles + // are both simply pid_t on POSIX... :/ + base::ProcessHandle handle; + CHECK(base::OpenProcessHandle(pid, &handle)); + base::ProcessId ret = base::GetParentProcessId(pid); + base::CloseProcessHandle(handle); + return ret; +} + +// SendHello sends a "hello" to socket fd, and then blocks until the recipient +// acknowledges it by calling RecvHello. +void SendHello(int fd) { + int pipe_fds[2]; + CHECK_EQ(0, pipe(pipe_fds)); + base::ScopedFD read_pipe(pipe_fds[0]); + base::ScopedFD write_pipe(pipe_fds[1]); + + std::vector<int> send_fds; + send_fds.push_back(write_pipe.get()); + CHECK(UnixDomainSocket::SendMsg(fd, kHello, sizeof(kHello), send_fds)); + + write_pipe.reset(); + + // Block until receiver closes their end of the pipe. + char ch; + CHECK_EQ(0, HANDLE_EINTR(read(read_pipe.get(), &ch, 1))); +} + +// RecvHello receives and acknowledges a "hello" on socket fd, and returns the +// process ID of the sender in sender_pid. Optionally, write_pipe can be used +// to return a file descriptor, and the acknowledgement will be delayed until +// the descriptor is closed. +// (Implementation details: SendHello allocates a new pipe, sends us the writing +// end alongside the "hello" message, and then blocks until we close the writing +// end of the pipe.) +void RecvHello(int fd, + base::ProcessId* sender_pid, + base::ScopedFD* write_pipe = NULL) { + // Extra receiving buffer space to make sure we really received only + // sizeof(kHello) bytes and it wasn't just truncated to fit the buffer. + char buf[sizeof(kHello) + 1]; + ScopedVector<base::ScopedFD> message_fds; + ssize_t n = UnixDomainSocket::RecvMsgWithPid( + fd, buf, sizeof(buf), &message_fds, sender_pid); + CHECK_EQ(sizeof(kHello), static_cast<size_t>(n)); + CHECK_EQ(0, memcmp(buf, kHello, sizeof(kHello))); + CHECK_EQ(1U, message_fds.size()); + if (write_pipe) + write_pipe->swap(*message_fds[0]); +} + +// Check that receiving PIDs works across a fork(). +SANDBOX_TEST(UnixDomainSocketTest, Fork) { + int fds[2]; + CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + base::ScopedFD recv_sock(fds[0]); + base::ScopedFD send_sock(fds[1]); + + CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = fork(); + CHECK_NE(-1, pid); + if (pid == 0) { + // Child process. + recv_sock.reset(); + SendHello(send_sock.get()); + _exit(0); + } + + // Parent process. + send_sock.reset(); + + base::ProcessId sender_pid; + RecvHello(recv_sock.get(), &sender_pid); + CHECK_EQ(pid, sender_pid); + + WaitForExit(pid); +} + +// Similar to Fork above, but forking the child into a new pid namespace. +SANDBOX_TEST(UnixDomainSocketTest, Namespace) { + FakeRoot(); + + int fds[2]; + CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + base::ScopedFD recv_sock(fds[0]); + base::ScopedFD send_sock(fds[1]); + + CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); + CHECK_NE(-1, pid); + if (pid == 0) { + // Child process. + recv_sock.reset(); + + // Check that we think we're pid 1 in our new namespace. + CHECK_EQ(1, syscall(__NR_getpid)); + + SendHello(send_sock.get()); + _exit(0); + } + + // Parent process. + send_sock.reset(); + + base::ProcessId sender_pid; + RecvHello(recv_sock.get(), &sender_pid); + CHECK_EQ(pid, sender_pid); + + WaitForExit(pid); +} + +// Again similar to Fork, but now with nested PID namespaces. +SANDBOX_TEST(UnixDomainSocketTest, DoubleNamespace) { + FakeRoot(); + + int fds[2]; + CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + base::ScopedFD recv_sock(fds[0]); + base::ScopedFD send_sock(fds[1]); + + CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); + CHECK_NE(-1, pid); + if (pid == 0) { + // Child process. + recv_sock.reset(); + + const pid_t pid2 = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); + CHECK_NE(-1, pid2); + + if (pid2 != 0) { + // Wait for grandchild to run to completion; see comments below. + WaitForExit(pid2); + + // Fallthrough once grandchild has sent its hello and exited. + } + + // Check that we think we're pid 1. + CHECK_EQ(1, syscall(__NR_getpid)); + + SendHello(send_sock.get()); + _exit(0); + } + + // Parent process. + send_sock.reset(); + + // We have two messages to receive: first from the grand-child, + // then from the child. + for (unsigned iteration = 0; iteration < 2; ++iteration) { + base::ProcessId sender_pid; + base::ScopedFD pipe_fd; + RecvHello(recv_sock.get(), &sender_pid, &pipe_fd); + + // We need our child and grandchild processes to both be alive for + // GetParentProcessId() to return a valid pid, hence the pipe trickery. + // (On the first iteration, grandchild is blocked reading from the pipe + // until we close it, and child is blocked waiting for grandchild to exit.) + switch (iteration) { + case 0: // Grandchild's message + // Check that sender_pid refers to our grandchild by checking that pid + // (our child) is its parent. + CHECK_EQ(pid, GetParentProcessId(sender_pid)); + break; + case 1: // Child's message + CHECK_EQ(pid, sender_pid); + break; + default: + NOTREACHED(); + } + } + + WaitForExit(pid); +} + +// Tests that GetPeerPid() returns 0 if the peer does not exist in caller's +// namespace. +SANDBOX_TEST(UnixDomainSocketTest, ImpossiblePid) { + FakeRoot(); + + int fds[2]; + CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + base::ScopedFD send_sock(fds[0]); + base::ScopedFD recv_sock(fds[1]); + + CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); + CHECK_NE(-1, pid); + if (pid == 0) { + // Child process. + send_sock.reset(); + + base::ProcessId sender_pid; + RecvHello(recv_sock.get(), &sender_pid); + CHECK_EQ(0, sender_pid); + _exit(0); + } + + // Parent process. + recv_sock.reset(); + SendHello(send_sock.get()); + WaitForExit(pid); +} + +} // namespace + +} // namespace sandbox diff --git a/chromium/sandbox/linux/services/yama.cc b/chromium/sandbox/linux/services/yama.cc new file mode 100644 index 00000000000..49e1b36aab3 --- /dev/null +++ b/chromium/sandbox/linux/services/yama.cc @@ -0,0 +1,116 @@ +// Copyright 2014 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/yama.h" + +#include <fcntl.h> +#include <sys/prctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +#if !defined(PR_SET_PTRACER_ANY) +#define PR_SET_PTRACER_ANY ((unsigned long)-1) +#endif + +#if !defined(PR_SET_PTRACER) +#define PR_SET_PTRACER 0x59616d61 +#endif + +namespace sandbox { + +namespace { + +// Enable or disable the Yama ptracers restrictions. +// Return false if Yama is not present on this kernel. +bool SetYamaPtracersRestriction(bool enable_restrictions) { + unsigned long set_ptracer_arg; + if (enable_restrictions) { + set_ptracer_arg = 0; + } else { + set_ptracer_arg = PR_SET_PTRACER_ANY; + } + + const int ret = prctl(PR_SET_PTRACER, set_ptracer_arg); + const int prctl_errno = errno; + + if (0 == ret) { + return true; + } else { + // ENOSYS or EINVAL means Yama is not in the current kernel. + CHECK(ENOSYS == prctl_errno || EINVAL == prctl_errno); + return false; + } +} + +bool CanAccessProcFS() { + static const char kProcfsKernelSysPath[] = "/proc/sys/kernel/"; + int ret = access(kProcfsKernelSysPath, F_OK); + if (ret) { + return false; + } + return true; +} + +} // namespace + +// static +bool Yama::RestrictPtracersToAncestors() { + return SetYamaPtracersRestriction(true /* enable_restrictions */); +} + +// static +bool Yama::DisableYamaRestrictions() { + return SetYamaPtracersRestriction(false /* enable_restrictions */); +} + +// static +int Yama::GetStatus() { + if (!CanAccessProcFS()) { + return 0; + } + + static const char kPtraceScopePath[] = "/proc/sys/kernel/yama/ptrace_scope"; + + base::ScopedFD yama_scope(HANDLE_EINTR(open(kPtraceScopePath, O_RDONLY))); + + if (!yama_scope.is_valid()) { + const int open_errno = errno; + DCHECK(ENOENT == open_errno); + // The status is known, yama is not present. + return STATUS_KNOWN; + } + + char yama_scope_value = 0; + ssize_t num_read = HANDLE_EINTR(read(yama_scope.get(), &yama_scope_value, 1)); + PCHECK(1 == num_read); + + switch (yama_scope_value) { + case '0': + return STATUS_KNOWN | STATUS_PRESENT; + case '1': + return STATUS_KNOWN | STATUS_PRESENT | STATUS_ENFORCING; + case '2': + case '3': + return STATUS_KNOWN | STATUS_PRESENT | STATUS_ENFORCING | + STATUS_STRICT_ENFORCING; + default: + NOTREACHED(); + return 0; + } +} + +// static +bool Yama::IsPresent() { return GetStatus() & STATUS_PRESENT; } + +// static +bool Yama::IsEnforcing() { return GetStatus() & STATUS_ENFORCING; } + +} // namespace sandbox diff --git a/chromium/sandbox/linux/services/yama.h b/chromium/sandbox/linux/services/yama.h new file mode 100644 index 00000000000..20c28bae148 --- /dev/null +++ b/chromium/sandbox/linux/services/yama.h @@ -0,0 +1,58 @@ +// Copyright 2014 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_YAMA_H_ +#define SANDBOX_LINUX_SERVICES_YAMA_H_ + +#include "base/basictypes.h" +#include "base/process/process_handle.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Yama is a LSM kernel module which can restrict ptrace(). +// This class provides ways to detect if Yama is present and enabled +// and to restrict which processes can ptrace the current process. +class SANDBOX_EXPORT Yama { + public: + // This enum should be used to set or check a bitmask. + // A value of 0 would indicate that the status is not known. + enum GlobalStatus { + STATUS_KNOWN = 1 << 0, + STATUS_PRESENT = 1 << 1, + STATUS_ENFORCING = 1 << 2, + // STATUS_STRICT_ENFORCING corresponds to either mode 2 or mode 3 of Yama. + // Ptrace could be entirely denied, or restricted to CAP_SYS_PTRACE + // and PTRACE_TRACEME. + STATUS_STRICT_ENFORCING = 1 << 3 + }; + + // Restrict who can ptrace() the current process to its ancestors. + // If this succeeds, then Yama is available on this kernel. + // However, Yama may not be enforcing at this time. + static bool RestrictPtracersToAncestors(); + + // Disable Yama restrictions for the current process. + // This will fail if Yama is not available on this kernel. + // This is meant for testing only. If you need this, implement + // a per-pid authorization instead. + static bool DisableYamaRestrictions(); + + // Checks if Yama is currently in enforcing mode for the machine (not the + // current process). This requires access to the filesystem and will use + // /proc/sys/kernel/yama/ptrace_scope. + static int GetStatus(); + + // Helper for checking for STATUS_PRESENT in GetStatus(). + static bool IsPresent(); + // Helper for checkking for STATUS_ENFORCING in GetStatus(). + static bool IsEnforcing(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Yama); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_YAMA_H_ diff --git a/chromium/sandbox/linux/services/yama_unittests.cc b/chromium/sandbox/linux/services/yama_unittests.cc new file mode 100644 index 00000000000..17ef4b40de5 --- /dev/null +++ b/chromium/sandbox/linux/services/yama_unittests.cc @@ -0,0 +1,152 @@ +// Copyright 2014 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 <fcntl.h> +#include <sys/ptrace.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/services/scoped_process.h" +#include "sandbox/linux/services/yama.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +bool CanPtrace(pid_t pid) { + int ret; + ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL); + if (ret == -1) { + CHECK_EQ(EPERM, errno); + return false; + } + // Wait for the process to be stopped so that it can be detached. + siginfo_t process_info; + int wait_ret = HANDLE_EINTR(waitid(P_PID, pid, &process_info, WSTOPPED)); + PCHECK(0 == wait_ret); + PCHECK(0 == ptrace(PTRACE_DETACH, pid, NULL, NULL)); + return true; +} + +// _exit(0) if pid can be ptraced by the current process. +// _exit(1) otherwise. +void ExitZeroIfCanPtrace(pid_t pid) { + if (CanPtrace(pid)) { + _exit(0); + } else { + _exit(1); + } +} + +bool CanSubProcessPtrace(pid_t pid) { + ScopedProcess process(base::Bind(&ExitZeroIfCanPtrace, pid)); + bool signaled; + int exit_code = process.WaitForExit(&signaled); + CHECK(!signaled); + return 0 == exit_code; +} + +// The tests below assume that the system-level configuration will not change +// while they run. + +TEST(Yama, GetStatus) { + int status1 = Yama::GetStatus(); + + // Check that the value is a possible bitmask. + ASSERT_LE(0, status1); + ASSERT_GE(Yama::STATUS_KNOWN | Yama::STATUS_PRESENT | Yama::STATUS_ENFORCING | + Yama::STATUS_STRICT_ENFORCING, + status1); + + // The status should not just be a random value. + int status2 = Yama::GetStatus(); + EXPECT_EQ(status1, status2); + + // This test is not running sandboxed, there is no reason to not know the + // status. + EXPECT_NE(0, Yama::STATUS_KNOWN & status1); + + if (status1 & Yama::STATUS_STRICT_ENFORCING) { + // If Yama is strictly enforcing, it is also enforcing. + EXPECT_TRUE(status1 & Yama::STATUS_ENFORCING); + } + + if (status1 & Yama::STATUS_ENFORCING) { + // If Yama is enforcing, Yama is present. + EXPECT_NE(0, status1 & Yama::STATUS_PRESENT); + } + + // Verify that the helper functions work as intended. + EXPECT_EQ(static_cast<bool>(status1 & Yama::STATUS_ENFORCING), + Yama::IsEnforcing()); + EXPECT_EQ(static_cast<bool>(status1 & Yama::STATUS_PRESENT), + Yama::IsPresent()); + + fprintf(stdout, + "Yama present: %s - enforcing: %s\n", + Yama::IsPresent() ? "Y" : "N", + Yama::IsEnforcing() ? "Y" : "N"); +} + +SANDBOX_TEST(Yama, RestrictPtraceSucceedsWhenYamaPresent) { + // This call will succeed iff Yama is present. + bool restricted = Yama::RestrictPtracersToAncestors(); + CHECK_EQ(restricted, Yama::IsPresent()); +} + +// Attempts to enable or disable Yama restrictions. +void SetYamaRestrictions(bool enable_restriction) { + if (enable_restriction) { + Yama::RestrictPtracersToAncestors(); + } else { + Yama::DisableYamaRestrictions(); + } +} + +TEST(Yama, RestrictPtraceWorks) { + ScopedProcess process1(base::Bind(&SetYamaRestrictions, true)); + ASSERT_TRUE(process1.WaitForClosureToRun()); + + if (Yama::IsEnforcing()) { + // A sibling process cannot ptrace process1. + ASSERT_FALSE(CanSubProcessPtrace(process1.GetPid())); + } + + if (!(Yama::GetStatus() & Yama::STATUS_STRICT_ENFORCING)) { + // However, parent can ptrace process1. + ASSERT_TRUE(CanPtrace(process1.GetPid())); + + // A sibling can ptrace process2 which disables any Yama protection. + ScopedProcess process2(base::Bind(&SetYamaRestrictions, false)); + ASSERT_TRUE(process2.WaitForClosureToRun()); + ASSERT_TRUE(CanSubProcessPtrace(process2.GetPid())); + } +} + +void DoNothing() {} + +SANDBOX_TEST(Yama, RestrictPtraceIsDefault) { + if (!Yama::IsPresent()) + return; + + CHECK(Yama::DisableYamaRestrictions()); + ScopedProcess process1(base::Bind(&DoNothing)); + + if (Yama::IsEnforcing()) { + // Check that process1 is protected by Yama, even though it has + // been created from a process that disabled Yama. + CHECK(!CanSubProcessPtrace(process1.GetPid())); + } +} + +} // namespace + +} // namespace sandbox diff --git a/chromium/sandbox/linux/suid/client/DEPS b/chromium/sandbox/linux/suid/client/DEPS new file mode 100644 index 00000000000..99a337d7729 --- /dev/null +++ b/chromium/sandbox/linux/suid/client/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+sandbox/linux/services", +] diff --git a/chromium/sandbox/linux/suid/client/setuid_sandbox_client.cc b/chromium/sandbox/linux/suid/client/setuid_sandbox_client.cc index 740823a8b36..fc03cdd099e 100644 --- a/chromium/sandbox/linux/suid/client/setuid_sandbox_client.cc +++ b/chromium/sandbox/linux/suid/client/setuid_sandbox_client.cc @@ -2,23 +2,40 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "sandbox/linux/suid/client/setuid_sandbox_client.h" + +#include <fcntl.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> +#include "base/command_line.h" #include "base/environment.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" #include "base/logging.h" +#include "base/macros.h" #include "base/memory/scoped_ptr.h" +#include "base/path_service.h" #include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" +#include "base/process/process_metrics.h" #include "base/strings/string_number_conversions.h" - #include "sandbox/linux/services/init_process_reaper.h" #include "sandbox/linux/suid/common/sandbox.h" #include "sandbox/linux/suid/common/suid_unsafe_environment_variables.h" -#include "setuid_sandbox_client.h" namespace { +bool IsFileSystemAccessDenied() { + base::ScopedFD self_exe(HANDLE_EINTR(open(base::kProcSelfExe, O_RDONLY))); + return !self_exe.is_valid(); +} + // Set an environment variable that reflects the API version we expect from the // setuid sandbox. Old versions of the sandbox will ignore this. void SetSandboxAPIEnvironmentVariable(base::Environment* env) { @@ -26,6 +43,26 @@ void SetSandboxAPIEnvironmentVariable(base::Environment* env) { base::IntToString(sandbox::kSUIDSandboxApiNumber)); } +// Unset environment variables that are expected to be set by the setuid +// sandbox. This is to allow nesting of one instance of the SUID sandbox +// inside another. +void UnsetExpectedEnvironmentVariables(base::EnvironmentMap* env_map) { + DCHECK(env_map); + const base::NativeEnvironmentString environment_vars[] = { + sandbox::kSandboxDescriptorEnvironmentVarName, + sandbox::kSandboxHelperPidEnvironmentVarName, + sandbox::kSandboxEnvironmentApiProvides, + sandbox::kSandboxPIDNSEnvironmentVarName, + sandbox::kSandboxNETNSEnvironmentVarName, + }; + + for (size_t i = 0; i < arraysize(environment_vars); ++i) { + // Setting values in EnvironmentMap to an empty-string will make + // sure that they get unset from the environment via AlterEnvironment(). + (*env_map)[environment_vars[i]] = base::NativeEnvironmentString(); + } +} + // Wrapper around a shared C function. // Returns the "saved" environment variable name corresponding to |envvar| // in a new string or NULL. @@ -91,13 +128,17 @@ int GetIPCDescriptor(base::Environment* env) { return EnvToInt(env, sandbox::kSandboxDescriptorEnvironmentVarName); } +const char* GetDevelSandboxPath() { + return getenv("CHROME_DEVEL_SANDBOX"); +} + } // namespace namespace sandbox { SetuidSandboxClient* SetuidSandboxClient::Create() { base::Environment* environment(base::Environment::Create()); - SetuidSandboxClient* sandbox_client(new(SetuidSandboxClient)); + SetuidSandboxClient* sandbox_client(new SetuidSandboxClient); CHECK(environment); sandbox_client->env_ = environment; @@ -113,6 +154,21 @@ SetuidSandboxClient::~SetuidSandboxClient() { delete env_; } +void SetuidSandboxClient::CloseDummyFile() { + // When we're launched through the setuid sandbox, SetupLaunchOptions + // arranges for kZygoteIdFd to be a dummy file descriptor to satisfy an + // ancient setuid sandbox ABI requirement. However, the descriptor is no + // longer needed, so we can simply close it right away now. + CHECK(IsSuidSandboxChild()); + + // Sanity check that kZygoteIdFd refers to a pipe. + struct stat st; + PCHECK(0 == fstat(kZygoteIdFd, &st)); + CHECK(S_ISFIFO(st.st_mode)); + + PCHECK(0 == IGNORE_EINTR(close(kZygoteIdFd))); +} + bool SetuidSandboxClient::ChrootMe() { int ipc_fd = GetIPCDescriptor(env_); @@ -129,7 +185,7 @@ bool SetuidSandboxClient::ChrootMe() { // We need to reap the chroot helper process in any event. pid_t helper_pid = GetHelperPID(env_); // If helper_pid is -1 we wait for any child. - if (waitpid(helper_pid, NULL, 0) < 0) { + if (HANDLE_EINTR(waitpid(helper_pid, NULL, 0)) < 0) { PLOG(ERROR) << "Failed to wait for setuid helper to die"; return false; } @@ -147,6 +203,7 @@ bool SetuidSandboxClient::ChrootMe() { // We now consider ourselves "fully sandboxed" as far as the // setuid sandbox is concerned. + CHECK(IsFileSystemAccessDenied()); sandboxed_ = true; return true; } @@ -176,10 +233,87 @@ bool SetuidSandboxClient::IsSandboxed() const { return sandboxed_; } +// Check if CHROME_DEVEL_SANDBOX is set but empty. This currently disables +// the setuid sandbox. TODO(jln): fix this (crbug.com/245376). +bool SetuidSandboxClient::IsDisabledViaEnvironment() { + const char* devel_sandbox_path = GetDevelSandboxPath(); + if (devel_sandbox_path && '\0' == *devel_sandbox_path) { + return true; + } + return false; +} + +base::FilePath SetuidSandboxClient::GetSandboxBinaryPath() { + base::FilePath sandbox_binary; + base::FilePath exe_dir; + if (PathService::Get(base::DIR_EXE, &exe_dir)) { + base::FilePath sandbox_candidate = exe_dir.AppendASCII("chrome-sandbox"); + if (base::PathExists(sandbox_candidate)) + sandbox_binary = sandbox_candidate; + } + + // In user-managed builds, including development builds, an environment + // variable is required to enable the sandbox. See + // http://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment + struct stat st; + if (sandbox_binary.empty() && stat(base::kProcSelfExe, &st) == 0 && + st.st_uid == getuid()) { + const char* devel_sandbox_path = GetDevelSandboxPath(); + if (devel_sandbox_path) { + sandbox_binary = base::FilePath(devel_sandbox_path); + } + } + + return sandbox_binary; +} + +void SetuidSandboxClient::PrependWrapper(base::CommandLine* cmd_line) { + std::string sandbox_binary(GetSandboxBinaryPath().value()); + struct stat st; + if (sandbox_binary.empty() || stat(sandbox_binary.c_str(), &st) != 0) { + LOG(FATAL) << "The SUID sandbox helper binary is missing: " + << sandbox_binary << " Aborting now. See " + "https://code.google.com/p/chromium/wiki/" + "LinuxSUIDSandboxDevelopment."; + } + + if (access(sandbox_binary.c_str(), X_OK) != 0 || (st.st_uid != 0) || + ((st.st_mode & S_ISUID) == 0) || ((st.st_mode & S_IXOTH)) == 0) { + LOG(FATAL) << "The SUID sandbox helper binary was found, but is not " + "configured correctly. Rather than run without sandboxing " + "I'm aborting now. You need to make sure that " + << sandbox_binary << " is owned by root and has mode 4755."; + } + + cmd_line->PrependWrapper(sandbox_binary); +} + +void SetuidSandboxClient::SetupLaunchOptions( + base::LaunchOptions* options, + base::FileHandleMappingVector* fds_to_remap, + base::ScopedFD* dummy_fd) { + DCHECK(options); + DCHECK(fds_to_remap); + + // Launching a setuid binary requires PR_SET_NO_NEW_PRIVS to not be used. + options->allow_new_privs = true; + UnsetExpectedEnvironmentVariables(&options->environ); + + // Set dummy_fd to the reading end of a closed pipe. + int pipe_fds[2]; + PCHECK(0 == pipe(pipe_fds)); + PCHECK(0 == IGNORE_EINTR(close(pipe_fds[1]))); + dummy_fd->reset(pipe_fds[0]); + + // We no longer need a dummy socket for discovering the child's PID, + // but the sandbox is still hard-coded to expect a file descriptor at + // kZygoteIdFd. Fixing this requires a sandbox API change. :( + fds_to_remap->push_back(std::make_pair(dummy_fd->get(), kZygoteIdFd)); +} + void SetuidSandboxClient::SetupLaunchEnvironment() { SaveSUIDUnsafeEnvironmentVariables(env_); SetSandboxAPIEnvironmentVariable(env_); } } // namespace sandbox - diff --git a/chromium/sandbox/linux/suid/client/setuid_sandbox_client.h b/chromium/sandbox/linux/suid/client/setuid_sandbox_client.h index 5a6724dadd8..e6a3e4c5cad 100644 --- a/chromium/sandbox/linux/suid/client/setuid_sandbox_client.h +++ b/chromium/sandbox/linux/suid/client/setuid_sandbox_client.h @@ -7,26 +7,47 @@ #include "base/basictypes.h" #include "base/callback_forward.h" - -namespace base { class Environment; } +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/process/launch.h" +#include "sandbox/sandbox_export.h" namespace sandbox { // Helper class to use the setuid sandbox. This class is to be used both // before launching the setuid helper and after being executed through the // setuid helper. +// This class is difficult to use. It has been created by refactoring very old +// code scathered through the Chromium code base. // -// A typical use would be: -// 1. The browser calls SetupLaunchEnvironment() -// 2. The browser launches a renderer through the setuid sandbox. -// 3. The renderer requests being chroot-ed through ChrootMe() and +// A typical use for "A" launching a sandboxed process "B" would be: +// 1. A calls SetupLaunchEnvironment() +// 2. A sets up a CommandLine and then amends it with +// PrependWrapper() (or manually, by relying on GetSandboxBinaryPath()). +// 3. A uses SetupLaunchOptions() to arrange for a dummy descriptor for the +// setuid sandbox ABI. +// 4. A launches B with base::LaunchProcess, using the amended CommandLine. +// 5. B uses CloseDummyFile() to close the dummy file descriptor. +// 6. B performs various initializations that require access to the file +// system. +// 6.b (optional) B uses sandbox::Credentials::HasOpenDirectory() to verify +// that no directory is kept open (which would allow bypassing the setuid +// sandbox). +// 7. B should be prepared to assume the role of init(1). In particular, B +// cannot receive any signal from any other process, excluding SIGKILL. +// If B dies, all the processes in the namespace will die. +// B can fork() and the parent can assume the role of init(1), by using +// CreateInitProcessReaper(). +// 8. B requests being chroot-ed through ChrootMe() and // requests other sandboxing status via the status functions. -class SetuidSandboxClient { +class SANDBOX_EXPORT SetuidSandboxClient { public: // All instantation should go through this factory method. static class SetuidSandboxClient* Create(); ~SetuidSandboxClient(); + // Close the dummy file descriptor leftover from the sandbox ABI. + void CloseDummyFile(); // Ask the setuid helper over the setuid sandbox IPC channel to chroot() us // to an empty directory. // Will only work if we have been launched through the setuid helper. @@ -48,18 +69,38 @@ class SetuidSandboxClient { // Are we done and fully sandboxed ? bool IsSandboxed() const; + // The setuid sandbox may still be disabled via the environment. + // This is tracked in crbug.com/245376. + bool IsDisabledViaEnvironment(); + // Get the sandbox binary path. This method knows about the + // CHROME_DEVEL_SANDBOX environment variable used for user-managed builds. If + // the sandbox binary cannot be found, it will return an empty FilePath. + base::FilePath GetSandboxBinaryPath(); + // Modify |cmd_line| to launch via the setuid sandbox. Crash if the setuid + // sandbox binary cannot be found. |cmd_line| must not be NULL. + void PrependWrapper(base::CommandLine* cmd_line); + // Set-up the launch options for launching via the setuid sandbox. Caller is + // responsible for keeping |dummy_fd| alive until LaunchProcess() completes. + // |options| and |fds_to_remap| must not be NULL. + // (Keeping |dummy_fd| alive is an unfortunate historical artifact of the + // chrome-sandbox ABI.) + void SetupLaunchOptions(base::LaunchOptions* options, + base::FileHandleMappingVector* fds_to_remap, + base::ScopedFD* dummy_fd); // Set-up the environment. This should be done prior to launching the setuid // helper. void SetupLaunchEnvironment(); private: + SetuidSandboxClient(); + // Holds the environment. Will never be NULL. base::Environment* env_; bool sandboxed_; - DISALLOW_IMPLICIT_CONSTRUCTORS(SetuidSandboxClient); + + DISALLOW_COPY_AND_ASSIGN(SetuidSandboxClient); }; } // namespace sandbox #endif // SANDBOX_LINUX_SUID_SETUID_SANDBOX_CLIENT_H_ - diff --git a/chromium/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc b/chromium/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc index 552dc4b9d9c..d4f7dfef325 100644 --- a/chromium/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc +++ b/chromium/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc @@ -6,10 +6,9 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string_number_conversions.h" -#include "testing/gtest/include/gtest/gtest.h" - +#include "sandbox/linux/suid/client/setuid_sandbox_client.h" #include "sandbox/linux/suid/common/sandbox.h" -#include "setuid_sandbox_client.h" +#include "testing/gtest/include/gtest/gtest.h" namespace sandbox { @@ -90,5 +89,13 @@ TEST(SetuidSandboxClient, SandboxedClientAPI) { EXPECT_FALSE(sandbox_client->IsSandboxed()); } +// This test doesn't accomplish much, but will make sure that analysis tools +// will run this codepath. +TEST(SetuidSandboxClient, GetSandboxBinaryPath) { + scoped_ptr<SetuidSandboxClient> setuid_sandbox_client( + SetuidSandboxClient::Create()); + ignore_result(setuid_sandbox_client->GetSandboxBinaryPath()); +} + } // namespace sandbox diff --git a/chromium/sandbox/linux/suid/common/suid_unsafe_environment_variables.h b/chromium/sandbox/linux/suid/common/suid_unsafe_environment_variables.h index 1132a7a7161..33ba4b6ab72 100644 --- a/chromium/sandbox/linux/suid/common/suid_unsafe_environment_variables.h +++ b/chromium/sandbox/linux/suid/common/suid_unsafe_environment_variables.h @@ -54,7 +54,7 @@ static inline char* SandboxSavedEnvironmentVariable(const char* envvar) { const size_t envvar_len = strlen(envvar); const size_t kMaxSizeT = (size_t) -1; - if (envvar_len > kMaxSizeT - 1 -8) + if (envvar_len > kMaxSizeT - 1 - 8) return NULL; const size_t saved_envvarlen = envvar_len + 1 /* NUL terminator */ + diff --git a/chromium/sandbox/linux/suid/linux_util.c b/chromium/sandbox/linux/suid/linux_util.c index 256468ff4ea..9febe6d9cf2 100644 --- a/chromium/sandbox/linux/suid/linux_util.c +++ b/chromium/sandbox/linux/suid/linux_util.c @@ -5,8 +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" +// Needed for O_DIRECTORY, must be defined before fcntl.h is included +// (and it can be included earlier than the explicit #include below +// in some versions of glibc). +#define _GNU_SOURCE + +#include "sandbox/linux/suid/linux_util.h" #include <dirent.h> #include <errno.h> @@ -26,7 +30,8 @@ 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 ProcPathGetInodeAt(ino_t* inode_out, int base_dir_fd, +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 == '/') @@ -40,7 +45,7 @@ static bool ProcPathGetInodeAt(ino_t* inode_out, int base_dir_fd, if (memcmp(kSocketLinkPrefix, buf, sizeof(kSocketLinkPrefix) - 1)) return false; - char *endptr = NULL; + char* endptr = NULL; errno = 0; const unsigned long long int inode_ull = strtoull(buf + sizeof(kSocketLinkPrefix) - 1, &endptr, 10); @@ -72,7 +77,7 @@ bool FindProcessHoldingSocket(pid_t* pid_out, ino_t socket_inode) { const uid_t uid = getuid(); struct dirent* dent; while ((dent = readdir(proc))) { - char *endptr = NULL; + char* endptr = NULL; errno = 0; const unsigned long int pid_ul = strtoul(dent->d_name, &endptr, 10); if (pid_ul == ULONG_MAX || !endptr || *endptr || errno != 0) diff --git a/chromium/sandbox/linux/suid/process_util.h b/chromium/sandbox/linux/suid/process_util.h index f6b4c314e63..9fb9a8791a7 100644 --- a/chromium/sandbox/linux/suid/process_util.h +++ b/chromium/sandbox/linux/suid/process_util.h @@ -11,8 +11,6 @@ #include <stdbool.h> #include <sys/types.h> -#include "base/base_export.h" - // This adjusts /proc/process/oom_score_adj so the Linux OOM killer // will prefer certain process types over others. The range for the // adjustment is [-1000, 1000], with [0, 1000] being user accessible. @@ -21,12 +19,12 @@ // try to set the older oom_adj value instead, scaling the score to // the required range of [0, 15]. This may result in some aliasing of // values, of course. -BASE_EXPORT bool AdjustOOMScore(pid_t process, int score); +bool AdjustOOMScore(pid_t process, int score); // This adjusts /sys/kernel/mm/chromeos-low_mem/margin so that // the kernel notifies us that we are low on memory when less than // |margin_mb| megabytes are available. Setting |margin_mb| to -1 // turns off low memory notification. -BASE_EXPORT bool AdjustLowMemoryMargin(int64_t margin_mb); +bool AdjustLowMemoryMargin(int64_t margin_mb); #endif // SANDBOX_LINUX_SUID_PROCESS_UTIL_H_ diff --git a/chromium/sandbox/linux/suid/process_util_linux.c b/chromium/sandbox/linux/suid/process_util_linux.c index 78c27ef5075..8d9a53c3a41 100644 --- a/chromium/sandbox/linux/suid/process_util_linux.c +++ b/chromium/sandbox/linux/suid/process_util_linux.c @@ -5,9 +5,12 @@ // The following is the C version of code from base/process_utils_linux.cc. // We shouldn't link against C++ code in a setuid binary. -#define _GNU_SOURCE // needed for O_DIRECTORY +// Needed for O_DIRECTORY, must be defined before fcntl.h is included +// (and it can be included earlier than the explicit #include below +// in some versions of glibc). +#define _GNU_SOURCE -#include "process_util.h" +#include "sandbox/linux/suid/process_util.h" #include <fcntl.h> #include <inttypes.h> @@ -71,5 +74,5 @@ bool AdjustOOMScore(pid_t process, int score) { ssize_t bytes_written = write(fd, buf, len); close(fd); - return (bytes_written == len); + return (bytes_written == (ssize_t)len); } diff --git a/chromium/sandbox/linux/suid/sandbox.c b/chromium/sandbox/linux/suid/sandbox.c index 238a647abe9..7410b71c315 100644 --- a/chromium/sandbox/linux/suid/sandbox.c +++ b/chromium/sandbox/linux/suid/sandbox.c @@ -4,7 +4,7 @@ // http://code.google.com/p/chromium/wiki/LinuxSUIDSandbox -#include "common/sandbox.h" +#include "sandbox/linux/suid/common/sandbox.h" #define _GNU_SOURCE #include <asm/unistd.h> @@ -29,9 +29,9 @@ #include <sys/wait.h> #include <unistd.h> -#include "linux_util.h" -#include "process_util.h" -#include "common/suid_unsafe_environment_variables.h" +#include "sandbox/linux/suid/common/suid_unsafe_environment_variables.h" +#include "sandbox/linux/suid/linux_util.h" +#include "sandbox/linux/suid/process_util.h" #if !defined(CLONE_NEWPID) #define CLONE_NEWPID 0x20000000 @@ -44,10 +44,10 @@ static bool DropRoot(); #define HANDLE_EINTR(x) TEMP_FAILURE_RETRY(x) -static void FatalError(const char *msg, ...) +static void FatalError(const char* msg, ...) __attribute__((noreturn, format(printf, 1, 2))); -static void FatalError(const char *msg, ...) { +static void FatalError(const char* msg, ...) { va_list ap; va_start(ap, msg); @@ -85,20 +85,18 @@ static bool SpawnChrootHelper() { return false; } - char *safedir = NULL; + char* safedir = NULL; struct stat sdir_stat; - if (!stat(SAFE_DIR, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) + if (!stat(SAFE_DIR, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) { safedir = SAFE_DIR; - else - if (!stat(SAFE_DIR2, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) - safedir = SAFE_DIR2; - else { - fprintf(stderr, "Could not find %s\n", SAFE_DIR2); - return false; - } + } else if (!stat(SAFE_DIR2, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) { + safedir = SAFE_DIR2; + } else { + fprintf(stderr, "Could not find %s\n", SAFE_DIR2); + return false; + } - const pid_t pid = syscall( - __NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); + const pid_t pid = syscall(__NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); if (pid == -1) { perror("clone"); @@ -214,7 +212,7 @@ static void WaitForChildAndExit(pid_t child_pid) { } int wait_ret = - HANDLE_EINTR(waitid(P_PID, child_pid, &reaped_child_info, WEXITED)); + HANDLE_EINTR(waitid(P_PID, child_pid, &reaped_child_info, WEXITED)); if (!wait_ret && reaped_child_info.si_pid == child_pid) { if (reaped_child_info.si_code == CLD_EXITED) { @@ -229,10 +227,7 @@ static void WaitForChildAndExit(pid_t child_pid) { static bool MoveToNewNamespaces() { // These are the sets of flags which we'll try, in order. - const int kCloneExtraFlags[] = { - CLONE_NEWPID | CLONE_NEWNET, - CLONE_NEWPID, - }; + const int kCloneExtraFlags[] = {CLONE_NEWPID | CLONE_NEWNET, CLONE_NEWPID, }; // We need to close kZygoteIdFd before the child can continue. We use this // socketpair to tell the child when to continue; @@ -241,10 +236,10 @@ static bool MoveToNewNamespaces() { FatalError("Failed to create a socketpair"); } - for (size_t i = 0; - i < sizeof(kCloneExtraFlags) / sizeof(kCloneExtraFlags[0]); + for (size_t i = 0; i < sizeof(kCloneExtraFlags) / sizeof(kCloneExtraFlags[0]); i++) { pid_t pid = syscall(__NR_clone, SIGCHLD | kCloneExtraFlags[i], 0, 0, 0); + const int clone_errno = errno; if (pid > 0) { if (!DropRoot()) { @@ -296,8 +291,20 @@ static bool MoveToNewNamespaces() { break; } + // If EINVAL then the system doesn't support the requested flags, so + // continue to try a different set. + // On any other errno value the system *does* support these flags but + // something went wrong, hence we bail with an error message rather then + // provide less security. if (errno != EINVAL) { - perror("Failed to move to new PID namespace"); + fprintf(stderr, "Failed to move to new namespace:"); + if (kCloneExtraFlags[i] & CLONE_NEWPID) { + fprintf(stderr, " PID namespaces supported,"); + } + if (kCloneExtraFlags[i] & CLONE_NEWNET) { + fprintf(stderr, " Network namespace supported,"); + } + fprintf(stderr, " but failed: errno = %s\n", strerror(clone_errno)); return false; } } @@ -373,7 +380,7 @@ bool CheckAndExportApiVersion() { // Check the environment to see if a specific API version was requested. // assume version 0 if none. long api_number = -1; - char *api_string = getenv(kSandboxEnvironmentApiRequest); + char* api_string = getenv(kSandboxEnvironmentApiRequest); if (!api_string) { api_number = 0; } else { @@ -386,20 +393,22 @@ bool CheckAndExportApiVersion() { // Warn only for now. if (api_number != kSUIDSandboxApiNumber) { - fprintf(stderr, "The setuid sandbox provides API version %ld, " - "but you need %ld\n" - "Please read " - "https://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment." - "\n\n", - kSUIDSandboxApiNumber, - api_number); + fprintf( + stderr, + "The setuid sandbox provides API version %ld, " + "but you need %ld\n" + "Please read " + "https://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment." + "\n\n", + kSUIDSandboxApiNumber, + api_number); } // Export our version so that the sandboxed process can verify it did not // use an old sandbox. char version_string[64]; - snprintf(version_string, sizeof(version_string), "%ld", - kSUIDSandboxApiNumber); + snprintf( + version_string, sizeof(version_string), "%ld", kSUIDSandboxApiNumber); if (setenv(kSandboxEnvironmentApiProvides, version_string, 1)) { perror("setenv"); return false; @@ -408,7 +417,7 @@ bool CheckAndExportApiVersion() { return true; } -int main(int argc, char **argv) { +int main(int argc, char** argv) { if (argc <= 1) { if (argc <= 0) { return 1; @@ -463,9 +472,10 @@ int main(int argc, char **argv) { endptr = NULL; errno = 0; score = strtol(argv[3], &endptr, 10); - if (score == LONG_MAX || score == LONG_MIN || - !endptr || *endptr || errno != 0) + if (score == LONG_MAX || score == LONG_MIN || !endptr || *endptr || + errno != 0) { return 1; + } return AdjustOOMScore(pid, score); } @@ -474,6 +484,13 @@ int main(int argc, char **argv) { return 1; } + if (geteuid() != 0) { + fprintf(stderr, + "The setuid sandbox is not running as root. Common causes:\n" + " * An unprivileged process using ptrace on it, like a debugger.\n" + " * A parent process set prctl(PR_SET_NO_NEW_PRIVS, ...)\n"); + } + if (!MoveToNewNamespaces()) return 1; if (!SpawnChrootHelper()) |