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 | |
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')
172 files changed, 7619 insertions, 1766 deletions
diff --git a/chromium/sandbox/BUILD.gn b/chromium/sandbox/BUILD.gn new file mode 100644 index 00000000000..e3a8b624d59 --- /dev/null +++ b/chromium/sandbox/BUILD.gn @@ -0,0 +1,14 @@ +# 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. + +# Meta-target that forwards to the proper platform one. +group("sandbox") { + if (is_win) { + deps = [ "//sandbox/win:sandbox" ] + } else if (is_mac) { + deps = [ "//sandbox/mac:sandbox" ] + } else if (is_linux || is_android) { + deps = [ "//sandbox/linux:sandbox" ] + } +} diff --git a/chromium/sandbox/OWNERS b/chromium/sandbox/OWNERS index 5d15856389d..5d3f6ff9e17 100644 --- a/chromium/sandbox/OWNERS +++ b/chromium/sandbox/OWNERS @@ -1,10 +1,3 @@ -# For Windows: cpu@chromium.org -jschuh@chromium.org -nsylvain@chromium.org -rvargas@chromium.org -# For Linux: -markus@chromium.org jln@chromium.org -cevans@chromium.org -jorgelo@chromium.org +jschuh@chromium.org 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()) diff --git a/chromium/sandbox/mac/BUILD.gn b/chromium/sandbox/mac/BUILD.gn new file mode 100644 index 00000000000..e6151979d1a --- /dev/null +++ b/chromium/sandbox/mac/BUILD.gn @@ -0,0 +1,72 @@ +# 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. + +component("sandbox") { + sources = [ + "bootstrap_sandbox.cc", + "bootstrap_sandbox.h", + "launchd_interception_server.cc", + "launchd_interception_server.h", + "mach_message_server.cc", + "mach_message_server.h", + "os_compatibility.cc", + "os_compatibility.h", + "policy.cc", + "policy.h", + "xpc.h", + ] + + defines = [ "SANDBOX_IMPLEMENTATION" ] + libs = [ "bsm" ] + + deps = [ + "//base", + ":generate_stubs", + ] +} + +generate_stubs_script = "//tools/generate_stubs/generate_stubs.py" +generate_stubs_header = "xpc_stubs_header.fragment" +generate_stubs_sig_public = "xpc_stubs.sig" +generate_stubs_sig_private = "xpc_private_stubs.sig" +generate_stubs_project = "sandbox/mac" +generate_stubs_output_stem = "xpc_stubs" + +action("generate_stubs") { + script = generate_stubs_script + sources = [ generate_stubs_sig_public, generate_stubs_sig_private ] + source_prereqs = [ generate_stubs_header ] + outputs = [ + "$target_gen_dir/$generate_stubs_output_stem.cc", + "$target_gen_dir/$generate_stubs_output_stem.h", + ] + args = [ + "-i", rebase_path(target_gen_dir, root_build_dir), + "-o", rebase_path(target_gen_dir, root_build_dir), + "-t", "posix_stubs", + "-e", rebase_path(generate_stubs_header, root_build_dir), + "-s", generate_stubs_output_stem, + "-p", generate_stubs_project, + ] + args += rebase_path(sources, root_build_dir) +} + +test("sandbox_mac_unittests") { + sources = [ + "bootstrap_sandbox_unittest.mm", + "policy_unittest.cc", + ] + + libs = [ + "CoreFoundation.framework", + "Foundation.framework", + ] + + deps = [ + ":sandbox", + "//base", + "//base/test:run_all_unittests", + "//testing/gtest", + ] +} diff --git a/chromium/sandbox/mac/OWNERS b/chromium/sandbox/mac/OWNERS new file mode 100644 index 00000000000..163563f967d --- /dev/null +++ b/chromium/sandbox/mac/OWNERS @@ -0,0 +1,2 @@ +mark@chromium.org +rsesek@chromium.org diff --git a/chromium/sandbox/mac/bootstrap_sandbox.cc b/chromium/sandbox/mac/bootstrap_sandbox.cc new file mode 100644 index 00000000000..a90f570eb47 --- /dev/null +++ b/chromium/sandbox/mac/bootstrap_sandbox.cc @@ -0,0 +1,133 @@ +// 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/mac/bootstrap_sandbox.h" + +#include <servers/bootstrap.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/mac/mach_logging.h" +#include "base/strings/stringprintf.h" +#include "sandbox/mac/launchd_interception_server.h" + +namespace sandbox { + +const int kNotAPolicy = -1; + +// static +scoped_ptr<BootstrapSandbox> BootstrapSandbox::Create() { + scoped_ptr<BootstrapSandbox> null; // Used for early returns. + scoped_ptr<BootstrapSandbox> sandbox(new BootstrapSandbox()); + sandbox->server_.reset(new LaunchdInterceptionServer(sandbox.get())); + + // Check in with launchd to get the receive right for the server that is + // published in the bootstrap namespace. + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = bootstrap_check_in(bootstrap_port, + sandbox->server_bootstrap_name().c_str(), &port); + if (kr != KERN_SUCCESS) { + BOOTSTRAP_LOG(ERROR, kr) + << "Failed to bootstrap_check_in the sandbox server."; + return null.Pass(); + } + base::mac::ScopedMachReceiveRight scoped_port(port); + + // Start the sandbox server. + if (sandbox->server_->Initialize(scoped_port.get())) + ignore_result(scoped_port.release()); // Transferred to server_. + else + return null.Pass(); + + return sandbox.Pass(); +} + +BootstrapSandbox::~BootstrapSandbox() { +} + +void BootstrapSandbox::RegisterSandboxPolicy( + int sandbox_policy_id, + const BootstrapSandboxPolicy& policy) { + CHECK(IsPolicyValid(policy)); + CHECK_GT(sandbox_policy_id, kNotAPolicy); + base::AutoLock lock(lock_); + DCHECK(policies_.find(sandbox_policy_id) == policies_.end()); + policies_.insert(std::make_pair(sandbox_policy_id, policy)); +} + +void BootstrapSandbox::PrepareToForkWithPolicy(int sandbox_policy_id) { + base::AutoLock lock(lock_); + + // Verify that this is a real policy. + CHECK(policies_.find(sandbox_policy_id) != policies_.end()); + CHECK_EQ(kNotAPolicy, effective_policy_id_) + << "Cannot nest calls to PrepareToForkWithPolicy()"; + + // Store the policy for the process we're about to create. + effective_policy_id_ = sandbox_policy_id; +} + +// TODO(rsesek): The |lock_| needs to be taken twice because +// base::LaunchProcess handles both fork+exec, and holding the lock for the +// duration would block servicing of other bootstrap messages. If a better +// LaunchProcess existed (do arbitrary work without layering violations), this +// could be avoided. + +void BootstrapSandbox::FinishedFork(base::ProcessHandle handle) { + base::AutoLock lock(lock_); + + CHECK_NE(kNotAPolicy, effective_policy_id_) + << "Must PrepareToForkWithPolicy() before FinishedFork()"; + + // Apply the policy to the new process. + if (handle != base::kNullProcessHandle) { + const auto& existing_process = sandboxed_processes_.find(handle); + CHECK(existing_process == sandboxed_processes_.end()); + sandboxed_processes_.insert(std::make_pair(handle, effective_policy_id_)); + VLOG(3) << "Bootstrap sandbox enforced for pid " << handle; + } + + effective_policy_id_ = kNotAPolicy; +} + +void BootstrapSandbox::ChildDied(base::ProcessHandle handle) { + base::AutoLock lock(lock_); + const auto& it = sandboxed_processes_.find(handle); + if (it != sandboxed_processes_.end()) + sandboxed_processes_.erase(it); +} + +const BootstrapSandboxPolicy* BootstrapSandbox::PolicyForProcess( + pid_t pid) const { + base::AutoLock lock(lock_); + const auto& process = sandboxed_processes_.find(pid); + + // The new child could send bootstrap requests before the parent calls + // FinishedFork(). + int policy_id = effective_policy_id_; + if (process != sandboxed_processes_.end()) { + policy_id = process->second; + } + + if (policy_id == kNotAPolicy) + return NULL; + + return &policies_.find(policy_id)->second; +} + +BootstrapSandbox::BootstrapSandbox() + : server_bootstrap_name_( + base::StringPrintf("%s.sandbox.%d", base::mac::BaseBundleID(), + getpid())), + real_bootstrap_port_(MACH_PORT_NULL), + effective_policy_id_(kNotAPolicy) { + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = task_get_special_port( + mach_task_self(), TASK_BOOTSTRAP_PORT, &port); + MACH_CHECK(kr == KERN_SUCCESS, kr); + real_bootstrap_port_.reset(port); +} + +} // namespace sandbox diff --git a/chromium/sandbox/mac/bootstrap_sandbox.h b/chromium/sandbox/mac/bootstrap_sandbox.h new file mode 100644 index 00000000000..dff7814d545 --- /dev/null +++ b/chromium/sandbox/mac/bootstrap_sandbox.h @@ -0,0 +1,114 @@ +// 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_MAC_BOOTSTRAP_SANDBOX_H_ +#define SANDBOX_MAC_BOOTSTRAP_SANDBOX_H_ + +#include <mach/mach.h> + +#include <map> +#include <string> + +#include "base/mac/scoped_mach_port.h" +#include "base/memory/scoped_ptr.h" +#include "base/process/process_handle.h" +#include "base/synchronization/lock.h" +#include "sandbox/mac/policy.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +class LaunchdInterceptionServer; + +// The BootstrapSandbox is a second-layer sandbox for Mac. It is used to limit +// the bootstrap namespace attack surface of child processes. The parent +// process creates an instance of this class and registers policies that it +// can enforce on its children. +// +// With this sandbox, the parent process must replace the bootstrap port prior +// to the sandboxed target's execution. This should be done by setting the +// base::LaunchOptions.replacement_bootstrap_name to the +// server_bootstrap_name() of this class. Requests from the child that would +// normally go to launchd are filtered based on the specified per-process +// policies. If a request is permitted by the policy, it is forwarded on to +// launchd for servicing. If it is not, then the sandbox will reply with a +// primitive that does not grant additional capabilities to the receiver. +// +// Clients that which to use the sandbox must inform it of the creation and +// death of child processes for which the sandbox should be enforced. The +// client of the sandbox is intended to be an unsandboxed parent process that +// fork()s sandboxed (and other unsandboxed) child processes. +// +// When the parent is ready to fork a new child process with this sandbox +// being enforced, it should use the pair of methods PrepareToForkWithPolicy() +// and FinishedFork(), and call fork() between them. The first method will +// set the policy for the new process, and the second will finialize the +// association between the process ID and sandbox policy ID. +// +// All methods of this class may be called from any thread, but +// PrepareToForkWithPolicy() and FinishedFork() must be non-nested and balanced. +class SANDBOX_EXPORT BootstrapSandbox { + public: + // Creates a new sandbox manager. Returns NULL on failure. + static scoped_ptr<BootstrapSandbox> Create(); + + ~BootstrapSandbox(); + + // Registers a bootstrap policy associated it with an identifier. The + // |sandbox_policy_id| must be greater than 0. + void RegisterSandboxPolicy(int sandbox_policy_id, + const BootstrapSandboxPolicy& policy); + + // Called in the parent prior to fork()ing a child. The policy registered + // to |sandbox_policy_id| will be enforced on the new child. This must be + // followed by a call to FinishedFork(). + void PrepareToForkWithPolicy(int sandbox_policy_id); + + // Called in the parent after fork()ing a child. It records the |handle| + // and associates it with the specified-above |sandbox_policy_id|. + // If fork() failed and a new child was not created, pass kNullProcessHandle. + void FinishedFork(base::ProcessHandle handle); + + // Called in the parent when a process has died. It cleans up the references + // to the process. + void ChildDied(base::ProcessHandle handle); + + // Looks up the policy for a given process ID. If no policy is associated + // with the |pid|, this returns NULL. + const BootstrapSandboxPolicy* PolicyForProcess(pid_t pid) const; + + std::string server_bootstrap_name() const { return server_bootstrap_name_; } + mach_port_t real_bootstrap_port() const { return real_bootstrap_port_; } + + private: + BootstrapSandbox(); + + // A Mach IPC message server that is used to intercept and filter bootstrap + // requests. + scoped_ptr<LaunchdInterceptionServer> server_; + + // The name in the system bootstrap server by which the |server_|'s port + // is known. + const std::string server_bootstrap_name_; + + // The original bootstrap port of the process, which is connected to the + // real launchd server. + base::mac::ScopedMachSendRight real_bootstrap_port_; + + // The |lock_| protects all the following variables. + mutable base::Lock lock_; + + // The sandbox_policy_id that will be enforced for the new child. + int effective_policy_id_; + + // All the policies that have been registered with this sandbox manager. + std::map<int, const BootstrapSandboxPolicy> policies_; + + // The association between process ID and sandbox policy ID. + std::map<base::ProcessHandle, int> sandboxed_processes_; +}; + +} // namespace sandbox + +#endif // SANDBOX_MAC_BOOTSTRAP_SANDBOX_H_ diff --git a/chromium/sandbox/mac/bootstrap_sandbox_unittest.mm b/chromium/sandbox/mac/bootstrap_sandbox_unittest.mm new file mode 100644 index 00000000000..85f627f10a3 --- /dev/null +++ b/chromium/sandbox/mac/bootstrap_sandbox_unittest.mm @@ -0,0 +1,417 @@ +// 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/mac/bootstrap_sandbox.h" + +#include <CoreFoundation/CoreFoundation.h> +#import <Foundation/Foundation.h> +#include <mach/mach.h> +#include <servers/bootstrap.h> + +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_nsobject.h" +#include "base/mac/scoped_mach_port.h" +#include "base/process/kill.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#import "testing/gtest_mac.h" +#include "testing/multiprocess_func_list.h" + +NSString* const kTestNotification = @"org.chromium.bootstrap_sandbox_test"; + +@interface DistributedNotificationObserver : NSObject { + @private + int receivedCount_; + base::scoped_nsobject<NSString> object_; +} +- (int)receivedCount; +- (NSString*)object; +- (void)waitForNotification; +@end + +@implementation DistributedNotificationObserver +- (id)init { + if ((self = [super init])) { + [[NSDistributedNotificationCenter defaultCenter] + addObserver:self + selector:@selector(observeNotification:) + name:kTestNotification + object:nil]; + } + return self; +} + +- (void)dealloc { + [[NSDistributedNotificationCenter defaultCenter] + removeObserver:self + name:kTestNotification + object:nil]; + [super dealloc]; +} + +- (int)receivedCount { + return receivedCount_; +} + +- (NSString*)object { + return object_.get(); +} + +- (void)waitForNotification { + object_.reset(); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, + TestTimeouts::action_timeout().InSeconds(), false); +} + +- (void)observeNotification:(NSNotification*)notification { + ++receivedCount_; + object_.reset([[notification object] copy]); + CFRunLoopStop(CFRunLoopGetCurrent()); +} +@end + +//////////////////////////////////////////////////////////////////////////////// + +namespace sandbox { + +class BootstrapSandboxTest : public base::MultiProcessTest { + public: + virtual void SetUp() OVERRIDE { + base::MultiProcessTest::SetUp(); + + sandbox_ = BootstrapSandbox::Create(); + ASSERT_TRUE(sandbox_.get()); + } + + BootstrapSandboxPolicy BaselinePolicy() { + BootstrapSandboxPolicy policy; + if (base::mac::IsOSSnowLeopard()) + policy.rules["com.apple.SecurityServer"] = Rule(POLICY_ALLOW); + return policy; + } + + void RunChildWithPolicy(int policy_id, + const char* child_name, + base::ProcessHandle* out_pid) { + sandbox_->PrepareToForkWithPolicy(policy_id); + base::LaunchOptions options; + options.replacement_bootstrap_name = sandbox_->server_bootstrap_name(); + base::ProcessHandle pid = SpawnChildWithOptions(child_name, options); + ASSERT_GT(pid, 0); + sandbox_->FinishedFork(pid); + int code = 0; + EXPECT_TRUE(base::WaitForExitCode(pid, &code)); + EXPECT_EQ(0, code); + if (out_pid) + *out_pid = pid; + } + + protected: + scoped_ptr<BootstrapSandbox> sandbox_; +}; + +const char kNotificationTestMain[] = "PostNotification"; + +// Run the test without the sandbox. +TEST_F(BootstrapSandboxTest, DistributedNotifications_Unsandboxed) { + base::scoped_nsobject<DistributedNotificationObserver> observer( + [[DistributedNotificationObserver alloc] init]); + + base::ProcessHandle pid = SpawnChild(kNotificationTestMain); + ASSERT_GT(pid, 0); + int code = 0; + EXPECT_TRUE(base::WaitForExitCode(pid, &code)); + EXPECT_EQ(0, code); + + [observer waitForNotification]; + EXPECT_EQ(1, [observer receivedCount]); + EXPECT_EQ(pid, [[observer object] intValue]); +} + +// Run the test with the sandbox enabled without notifications on the policy +// whitelist. +TEST_F(BootstrapSandboxTest, DistributedNotifications_SandboxDeny) { + base::scoped_nsobject<DistributedNotificationObserver> observer( + [[DistributedNotificationObserver alloc] init]); + + sandbox_->RegisterSandboxPolicy(1, BaselinePolicy()); + RunChildWithPolicy(1, kNotificationTestMain, NULL); + + [observer waitForNotification]; + EXPECT_EQ(0, [observer receivedCount]); + EXPECT_EQ(nil, [observer object]); +} + +// Run the test with notifications permitted. +TEST_F(BootstrapSandboxTest, DistributedNotifications_SandboxAllow) { + base::scoped_nsobject<DistributedNotificationObserver> observer( + [[DistributedNotificationObserver alloc] init]); + + BootstrapSandboxPolicy policy(BaselinePolicy()); + // 10.9: + policy.rules["com.apple.distributed_notifications@Uv3"] = Rule(POLICY_ALLOW); + policy.rules["com.apple.distributed_notifications@1v3"] = Rule(POLICY_ALLOW); + // 10.6: + policy.rules["com.apple.system.notification_center"] = Rule(POLICY_ALLOW); + policy.rules["com.apple.distributed_notifications.2"] = Rule(POLICY_ALLOW); + sandbox_->RegisterSandboxPolicy(2, policy); + + base::ProcessHandle pid; + RunChildWithPolicy(2, kNotificationTestMain, &pid); + + [observer waitForNotification]; + EXPECT_EQ(1, [observer receivedCount]); + EXPECT_EQ(pid, [[observer object] intValue]); +} + +MULTIPROCESS_TEST_MAIN(PostNotification) { + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:kTestNotification + object:[NSString stringWithFormat:@"%d", getpid()]]; + return 0; +} + +const char kTestServer[] = "org.chromium.test_bootstrap_server"; + +TEST_F(BootstrapSandboxTest, PolicyDenyError) { + BootstrapSandboxPolicy policy(BaselinePolicy()); + policy.rules[kTestServer] = Rule(POLICY_DENY_ERROR); + sandbox_->RegisterSandboxPolicy(1, policy); + + RunChildWithPolicy(1, "PolicyDenyError", NULL); +} + +MULTIPROCESS_TEST_MAIN(PolicyDenyError) { + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = bootstrap_look_up(bootstrap_port, kTestServer, + &port); + CHECK_EQ(BOOTSTRAP_UNKNOWN_SERVICE, kr); + CHECK(port == MACH_PORT_NULL); + + kr = bootstrap_look_up(bootstrap_port, "org.chromium.some_other_server", + &port); + CHECK_EQ(BOOTSTRAP_UNKNOWN_SERVICE, kr); + CHECK(port == MACH_PORT_NULL); + + return 0; +} + +TEST_F(BootstrapSandboxTest, PolicyDenyDummyPort) { + BootstrapSandboxPolicy policy(BaselinePolicy()); + policy.rules[kTestServer] = Rule(POLICY_DENY_DUMMY_PORT); + sandbox_->RegisterSandboxPolicy(1, policy); + + RunChildWithPolicy(1, "PolicyDenyDummyPort", NULL); +} + +MULTIPROCESS_TEST_MAIN(PolicyDenyDummyPort) { + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = bootstrap_look_up(bootstrap_port, kTestServer, + &port); + CHECK_EQ(KERN_SUCCESS, kr); + CHECK(port != MACH_PORT_NULL); + return 0; +} + +struct SubstitutePortAckSend { + mach_msg_header_t header; + char buf[32]; +}; + +struct SubstitutePortAckRecv : public SubstitutePortAckSend { + mach_msg_trailer_t trailer; +}; + +const char kSubstituteAck[] = "Hello, this is doge!"; + +TEST_F(BootstrapSandboxTest, PolicySubstitutePort) { + mach_port_t task = mach_task_self(); + + mach_port_t port; + ASSERT_EQ(KERN_SUCCESS, mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, + &port)); + base::mac::ScopedMachReceiveRight scoped_port(port); + + mach_port_urefs_t send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(0u, send_rights); + + ASSERT_EQ(KERN_SUCCESS, mach_port_insert_right(task, port, port, + MACH_MSG_TYPE_MAKE_SEND)); + base::mac::ScopedMachSendRight scoped_port_send(port); + + send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(1u, send_rights); + + BootstrapSandboxPolicy policy(BaselinePolicy()); + policy.rules[kTestServer] = Rule(port); + sandbox_->RegisterSandboxPolicy(1, policy); + + RunChildWithPolicy(1, "PolicySubstitutePort", NULL); + + struct SubstitutePortAckRecv msg; + bzero(&msg, sizeof(msg)); + msg.header.msgh_size = sizeof(msg); + msg.header.msgh_local_port = port; + kern_return_t kr = mach_msg(&msg.header, MACH_RCV_MSG, 0, + msg.header.msgh_size, port, + TestTimeouts::tiny_timeout().InMilliseconds(), MACH_PORT_NULL); + EXPECT_EQ(KERN_SUCCESS, kr); + + send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(1u, send_rights); + + EXPECT_EQ(0, strncmp(kSubstituteAck, msg.buf, sizeof(msg.buf))); +} + +MULTIPROCESS_TEST_MAIN(PolicySubstitutePort) { + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = bootstrap_look_up(bootstrap_port, kTestServer, &port); + CHECK_EQ(KERN_SUCCESS, kr); + CHECK(port != MACH_PORT_NULL); + + struct SubstitutePortAckSend msg; + bzero(&msg, sizeof(msg)); + msg.header.msgh_size = sizeof(msg); + msg.header.msgh_remote_port = port; + msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND); + strncpy(msg.buf, kSubstituteAck, sizeof(msg.buf)); + + CHECK_EQ(KERN_SUCCESS, mach_msg_send(&msg.header)); + + return 0; +} + +TEST_F(BootstrapSandboxTest, ForwardMessageInProcess) { + mach_port_t task = mach_task_self(); + + mach_port_t port; + ASSERT_EQ(KERN_SUCCESS, mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, + &port)); + base::mac::ScopedMachReceiveRight scoped_port_recv(port); + + mach_port_urefs_t send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(0u, send_rights); + + ASSERT_EQ(KERN_SUCCESS, mach_port_insert_right(task, port, port, + MACH_MSG_TYPE_MAKE_SEND)); + base::mac::ScopedMachSendRight scoped_port_send(port); + + send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(1u, send_rights); + + mach_port_t bp; + ASSERT_EQ(KERN_SUCCESS, task_get_bootstrap_port(task, &bp)); + base::mac::ScopedMachSendRight scoped_bp(bp); + + char service_name[] = "org.chromium.sandbox.test.ForwardMessageInProcess"; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + kern_return_t kr = bootstrap_register(bp, service_name, port); +#pragma GCC diagnostic pop + EXPECT_EQ(KERN_SUCCESS, kr); + + send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(1u, send_rights); + + mach_port_t service_port; + EXPECT_EQ(KERN_SUCCESS, bootstrap_look_up(bp, service_name, &service_port)); + base::mac::ScopedMachSendRight scoped_service_port(service_port); + + send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + // On 10.6, bootstrap_lookup2 may add an extra right to place it in a per- + // process cache. + if (base::mac::IsOSSnowLeopard()) + EXPECT_TRUE(send_rights == 3u || send_rights == 2u) << send_rights; + else + EXPECT_EQ(2u, send_rights); +} + +const char kDefaultRuleTestAllow[] = + "org.chromium.sandbox.test.DefaultRuleAllow"; +const char kDefaultRuleTestDeny[] = + "org.chromium.sandbox.test.DefaultRuleAllow.Deny"; + +TEST_F(BootstrapSandboxTest, DefaultRuleAllow) { + mach_port_t task = mach_task_self(); + + mach_port_t port; + ASSERT_EQ(KERN_SUCCESS, mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, + &port)); + base::mac::ScopedMachReceiveRight scoped_port_recv(port); + + ASSERT_EQ(KERN_SUCCESS, mach_port_insert_right(task, port, port, + MACH_MSG_TYPE_MAKE_SEND)); + base::mac::ScopedMachSendRight scoped_port_send(port); + + BootstrapSandboxPolicy policy; + policy.default_rule = Rule(POLICY_ALLOW); + policy.rules[kDefaultRuleTestAllow] = Rule(port); + policy.rules[kDefaultRuleTestDeny] = Rule(POLICY_DENY_ERROR); + sandbox_->RegisterSandboxPolicy(3, policy); + + base::scoped_nsobject<DistributedNotificationObserver> observer( + [[DistributedNotificationObserver alloc] init]); + + int pid = 0; + RunChildWithPolicy(3, "DefaultRuleAllow", &pid); + EXPECT_GT(pid, 0); + + [observer waitForNotification]; + EXPECT_EQ(1, [observer receivedCount]); + EXPECT_EQ(pid, [[observer object] intValue]); + + struct SubstitutePortAckRecv msg; + bzero(&msg, sizeof(msg)); + msg.header.msgh_size = sizeof(msg); + msg.header.msgh_local_port = port; + kern_return_t kr = mach_msg(&msg.header, MACH_RCV_MSG, 0, + msg.header.msgh_size, port, + TestTimeouts::tiny_timeout().InMilliseconds(), MACH_PORT_NULL); + EXPECT_EQ(KERN_SUCCESS, kr); + + EXPECT_EQ(0, strncmp(kSubstituteAck, msg.buf, sizeof(msg.buf))); +} + +MULTIPROCESS_TEST_MAIN(DefaultRuleAllow) { + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:kTestNotification + object:[NSString stringWithFormat:@"%d", getpid()]]; + + mach_port_t port = MACH_PORT_NULL; + CHECK_EQ(BOOTSTRAP_UNKNOWN_SERVICE, bootstrap_look_up(bootstrap_port, + const_cast<char*>(kDefaultRuleTestDeny), &port)); + CHECK(port == MACH_PORT_NULL); + + CHECK_EQ(KERN_SUCCESS, bootstrap_look_up(bootstrap_port, + const_cast<char*>(kDefaultRuleTestAllow), &port)); + CHECK(port != MACH_PORT_NULL); + + struct SubstitutePortAckSend msg; + bzero(&msg, sizeof(msg)); + msg.header.msgh_size = sizeof(msg); + msg.header.msgh_remote_port = port; + msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND); + strncpy(msg.buf, kSubstituteAck, sizeof(msg.buf)); + + CHECK_EQ(KERN_SUCCESS, mach_msg_send(&msg.header)); + + return 0; +} + +} // namespace sandbox diff --git a/chromium/sandbox/mac/launchd_interception_server.cc b/chromium/sandbox/mac/launchd_interception_server.cc new file mode 100644 index 00000000000..c3d6eaac579 --- /dev/null +++ b/chromium/sandbox/mac/launchd_interception_server.cc @@ -0,0 +1,153 @@ +// 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/mac/launchd_interception_server.h" + +#include <servers/bootstrap.h> + +#include "base/logging.h" +#include "base/mac/mach_logging.h" +#include "sandbox/mac/bootstrap_sandbox.h" + +namespace sandbox { + +// The buffer size for all launchd messages. This comes from +// sizeof(union __RequestUnion__vproc_mig_job_subsystem) in launchd, and it +// is larger than the __ReplyUnion. +const mach_msg_size_t kBufferSize = 2096; + +LaunchdInterceptionServer::LaunchdInterceptionServer( + const BootstrapSandbox* sandbox) + : sandbox_(sandbox), + sandbox_port_(MACH_PORT_NULL), + compat_shim_(GetLaunchdCompatibilityShim()) { +} + +LaunchdInterceptionServer::~LaunchdInterceptionServer() { +} + +bool LaunchdInterceptionServer::Initialize(mach_port_t server_receive_right) { + mach_port_t task = mach_task_self(); + kern_return_t kr; + + // Allocate the dummy sandbox port. + mach_port_t port; + if ((kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port)) != + KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Failed to allocate dummy sandbox port."; + return false; + } + sandbox_port_.reset(port); + if ((kr = mach_port_insert_right(task, sandbox_port_, sandbox_port_, + MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS)) { + MACH_LOG(ERROR, kr) << "Failed to allocate dummy sandbox port send right."; + return false; + } + sandbox_send_port_.reset(sandbox_port_); + + message_server_.reset( + new MachMessageServer(this, server_receive_right, kBufferSize)); + return message_server_->Initialize(); +} + +void LaunchdInterceptionServer::DemuxMessage(mach_msg_header_t* request, + mach_msg_header_t* reply) { + VLOG(3) << "Incoming message #" << request->msgh_id; + + pid_t sender_pid = message_server_->GetMessageSenderPID(request); + const BootstrapSandboxPolicy* policy = + sandbox_->PolicyForProcess(sender_pid); + if (policy == NULL) { + // No sandbox policy is in place for the sender of this message, which + // means it came from the unknown. Reject it. + VLOG(3) << "Message from unknown pid " << sender_pid << " rejected."; + message_server_->RejectMessage(request, MIG_REMOTE_ERROR); + return; + } + + if (request->msgh_id == compat_shim_.msg_id_look_up2) { + // Filter messages sent via bootstrap_look_up to enforce the sandbox policy + // over the bootstrap namespace. + HandleLookUp(request, reply, policy); + } else if (request->msgh_id == compat_shim_.msg_id_swap_integer) { + // Ensure that any vproc_swap_integer requests are safe. + HandleSwapInteger(request, reply); + } else { + // All other messages are not permitted. + VLOG(1) << "Rejecting unhandled message #" << request->msgh_id; + message_server_->RejectMessage(reply, MIG_REMOTE_ERROR); + } +} + +void LaunchdInterceptionServer::HandleLookUp( + mach_msg_header_t* request, + mach_msg_header_t* reply, + const BootstrapSandboxPolicy* policy) { + const std::string request_service_name( + compat_shim_.look_up2_get_request_name(request)); + VLOG(2) << "Incoming look_up2 request for " << request_service_name; + + // Find the Rule for this service. If a named rule is not found, use the + // default specified by the policy. + const BootstrapSandboxPolicy::NamedRules::const_iterator it = + policy->rules.find(request_service_name); + Rule rule(policy->default_rule); + if (it != policy->rules.end()) + rule = it->second; + + if (rule.result == POLICY_ALLOW) { + // This service is explicitly allowed, so this message will not be + // intercepted by the sandbox. + VLOG(1) << "Permitting and forwarding look_up2: " << request_service_name; + ForwardMessage(request); + } else if (rule.result == POLICY_DENY_ERROR) { + // The child is not permitted to look up this service. Send a MIG error + // reply to the client. Returning a NULL or unserviced port for a look up + // can cause clients to crash or hang. + VLOG(1) << "Denying look_up2 with MIG error: " << request_service_name; + message_server_->RejectMessage(reply, BOOTSTRAP_UNKNOWN_SERVICE); + } else if (rule.result == POLICY_DENY_DUMMY_PORT || + rule.result == POLICY_SUBSTITUTE_PORT) { + // The policy result is to deny access to the real service port, replying + // with a sandboxed port in its stead. Use either the dummy sandbox_port_ + // or the one specified in the policy. + VLOG(1) << "Intercepting look_up2 with a sandboxed service port: " + << request_service_name; + + mach_port_t result_port; + if (rule.result == POLICY_DENY_DUMMY_PORT) + result_port = sandbox_port_.get(); + else + result_port = rule.substitute_port; + + compat_shim_.look_up2_fill_reply(reply, result_port); + // If the message was sent successfully, clear the result_port out of the + // message so that it is not destroyed at the end of ReceiveMessage. The + // above-inserted right has been moved out of the process, and destroying + // the message will unref yet another right. + if (message_server_->SendReply(reply)) + compat_shim_.look_up2_fill_reply(reply, MACH_PORT_NULL); + } else { + NOTREACHED(); + } +} + +void LaunchdInterceptionServer::HandleSwapInteger(mach_msg_header_t* request, + mach_msg_header_t* reply) { + // Only allow getting information out of launchd. Do not allow setting + // values. Two commonly observed values that are retrieved are + // VPROC_GSK_MGR_PID and VPROC_GSK_TRANSACTIONS_ENABLED. + if (compat_shim_.swap_integer_is_get_only(request)) { + VLOG(2) << "Forwarding vproc swap_integer message."; + ForwardMessage(request); + } else { + VLOG(2) << "Rejecting non-read-only swap_integer message."; + message_server_->RejectMessage(reply, BOOTSTRAP_NOT_PRIVILEGED); + } +} +void LaunchdInterceptionServer::ForwardMessage(mach_msg_header_t* request) { + message_server_->ForwardMessage(request, sandbox_->real_bootstrap_port()); +} + +} // namespace sandbox diff --git a/chromium/sandbox/mac/launchd_interception_server.h b/chromium/sandbox/mac/launchd_interception_server.h new file mode 100644 index 00000000000..ec25be16133 --- /dev/null +++ b/chromium/sandbox/mac/launchd_interception_server.h @@ -0,0 +1,78 @@ +// 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_MAC_LAUNCHD_INTERCEPTION_SERVER_H_ +#define SANDBOX_MAC_LAUNCHD_INTERCEPTION_SERVER_H_ + +#include <dispatch/dispatch.h> +#include <mach/mach.h> + +#include "base/mac/scoped_mach_port.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/mac/mach_message_server.h" +#include "sandbox/mac/os_compatibility.h" + +namespace sandbox { + +class BootstrapSandbox; +struct BootstrapSandboxPolicy; + +// This class is used to run a Mach IPC message server. This server can +// hold the receive right for a bootstrap_port of a process, and it filters +// a subset of the launchd/bootstrap IPC call set for sandboxing. It permits +// or rejects requests based on the per-process policy specified in the +// BootstrapSandbox. +class LaunchdInterceptionServer : public MessageDemuxer { + public: + explicit LaunchdInterceptionServer(const BootstrapSandbox* sandbox); + virtual ~LaunchdInterceptionServer(); + + // Initializes the class and starts running the message server. If the + // |server_receive_right| is non-NULL, this class will take ownership of + // the receive right and intercept messages sent to that port. + bool Initialize(mach_port_t server_receive_right); + + // MessageDemuxer: + virtual void DemuxMessage(mach_msg_header_t* request, + mach_msg_header_t* reply) OVERRIDE; + + mach_port_t server_port() const { return message_server_->server_port(); } + + private: + // Given a look_up2 request message, this looks up the appropriate sandbox + // policy for the service name then formulates and sends the reply message. + void HandleLookUp(mach_msg_header_t* request, + mach_msg_header_t* reply, + const BootstrapSandboxPolicy* policy); + + // Given a swap_integer request message, this verifies that it is safe, and + // if so, forwards it on to launchd for servicing. If the request is unsafe, + // it replies with an error. + void HandleSwapInteger(mach_msg_header_t* request, + mach_msg_header_t* reply); + + // Forwards the original |request| on to real bootstrap server for handling. + void ForwardMessage(mach_msg_header_t* request); + + // The sandbox for which this message server is running. + const BootstrapSandbox* sandbox_; + + // The Mach IPC server. + scoped_ptr<MachMessageServer> message_server_; + + // The Mach port handed out in reply to denied look up requests. All denied + // requests share the same port, though nothing reads messages from it. + base::mac::ScopedMachReceiveRight sandbox_port_; + // The send right for the above |sandbox_port_|, used with + // MACH_MSG_TYPE_COPY_SEND when handing out references to the dummy port. + base::mac::ScopedMachSendRight sandbox_send_port_; + + // The compatibility shim that handles differences in message header IDs and + // request/reply structures between different OS X versions. + const LaunchdCompatibilityShim compat_shim_; +}; + +} // namespace sandbox + +#endif // SANDBOX_MAC_LAUNCHD_INTERCEPTION_SERVER_H_ diff --git a/chromium/sandbox/mac/mach_message_server.cc b/chromium/sandbox/mac/mach_message_server.cc new file mode 100644 index 00000000000..555ee0f494e --- /dev/null +++ b/chromium/sandbox/mac/mach_message_server.cc @@ -0,0 +1,184 @@ +// 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/mac/mach_message_server.h" + +#include <bsm/libbsm.h> +#include <servers/bootstrap.h> + +#include <string> + +#include "base/logging.h" +#include "base/mac/mach_logging.h" +#include "base/strings/stringprintf.h" + +namespace sandbox { + +MachMessageServer::MachMessageServer( + MessageDemuxer* demuxer, + mach_port_t server_receive_right, + mach_msg_size_t buffer_size) + : demuxer_(demuxer), + server_port_(server_receive_right), + server_queue_(NULL), + server_source_(NULL), + buffer_size_( + mach_vm_round_page(buffer_size + sizeof(mach_msg_audit_trailer_t))), + did_forward_message_(false) { + DCHECK(demuxer_); +} + +MachMessageServer::~MachMessageServer() { + if (server_source_) + dispatch_release(server_source_); + if (server_queue_) + dispatch_release(server_queue_); +} + +bool MachMessageServer::Initialize() { + mach_port_t task = mach_task_self(); + kern_return_t kr; + + // Allocate a port for use as a new server port if one was not passed to the + // constructor. + if (!server_port_.is_valid()) { + mach_port_t port; + if ((kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port)) != + KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Failed to allocate new server port."; + return false; + } + server_port_.reset(port); + } + + // Allocate the message request and reply buffers. + const int kMachMsgMemoryFlags = VM_MAKE_TAG(VM_MEMORY_MACH_MSG) | + VM_FLAGS_ANYWHERE; + vm_address_t buffer = 0; + + kr = vm_allocate(task, &buffer, buffer_size_, kMachMsgMemoryFlags); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Failed to allocate request buffer."; + return false; + } + request_buffer_.reset(buffer, buffer_size_); + + kr = vm_allocate(task, &buffer, buffer_size_, kMachMsgMemoryFlags); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Failed to allocate reply buffer."; + return false; + } + reply_buffer_.reset(buffer, buffer_size_); + + // Set up the dispatch queue to service the bootstrap port. + // TODO(rsesek): Specify DISPATCH_QUEUE_SERIAL, in the 10.7 SDK. NULL means + // the same thing but is not symbolically clear. + std::string label = base::StringPrintf( + "org.chromium.sandbox.MachMessageServer.%p", demuxer_); + server_queue_ = dispatch_queue_create(label.c_str(), NULL); + server_source_ = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, + server_port_.get(), 0, server_queue_); + dispatch_source_set_event_handler(server_source_, ^{ ReceiveMessage(); }); + dispatch_resume(server_source_); + + return true; +} + +pid_t MachMessageServer::GetMessageSenderPID(mach_msg_header_t* request) { + // Get the PID of the task that sent this request. This requires getting at + // the trailer of the message, from the header. + mach_msg_audit_trailer_t* trailer = + reinterpret_cast<mach_msg_audit_trailer_t*>( + reinterpret_cast<vm_address_t>(request) + + round_msg(request->msgh_size)); + // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid(). + pid_t sender_pid; + audit_token_to_au32(trailer->msgh_audit, + NULL, NULL, NULL, NULL, NULL, &sender_pid, NULL, NULL); + return sender_pid; +} + +bool MachMessageServer::SendReply(mach_msg_header_t* reply) { + kern_return_t kr = mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, + MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) + << "Unable to send intercepted reply message."; + return kr == KERN_SUCCESS; +} + +void MachMessageServer::ForwardMessage(mach_msg_header_t* request, + mach_port_t destination) { + request->msgh_local_port = request->msgh_remote_port; + request->msgh_remote_port = destination; + // Preserve the msgh_bits that do not deal with the local and remote ports. + request->msgh_bits = (request->msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) | + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND_ONCE); + kern_return_t kr = mach_msg_send(request); + if (kr == KERN_SUCCESS) { + did_forward_message_ = true; + } else { + MACH_LOG(ERROR, kr) << "Unable to forward message to the real launchd."; + } +} + +void MachMessageServer::RejectMessage(mach_msg_header_t* reply, + int error_code) { + mig_reply_error_t* error_reply = reinterpret_cast<mig_reply_error_t*>(reply); + error_reply->Head.msgh_size = sizeof(mig_reply_error_t); + error_reply->Head.msgh_bits = + MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE); + error_reply->NDR = NDR_record; + error_reply->RetCode = error_code; + SendReply(&error_reply->Head); +} + +void MachMessageServer::ReceiveMessage() { + const mach_msg_options_t kRcvOptions = MACH_RCV_MSG | + MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | + MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); + + mach_msg_header_t* request = + reinterpret_cast<mach_msg_header_t*>(request_buffer_.address()); + mach_msg_header_t* reply = + reinterpret_cast<mach_msg_header_t*>(reply_buffer_.address()); + + // Zero out the buffers from handling any previous message. + bzero(request, buffer_size_); + bzero(reply, buffer_size_); + did_forward_message_ = false; + + // A Mach message server-once. The system library to run a message server + // cannot be used here, because some requests are conditionally forwarded + // to another server. + kern_return_t kr = mach_msg(request, kRcvOptions, 0, buffer_size_, + server_port_.get(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Unable to receive message."; + return; + } + + // Set up a reply message in case it will be used. + reply->msgh_bits = MACH_MSGH_BITS_REMOTE(reply->msgh_bits); + // Since mach_msg will automatically swap the request and reply ports, + // undo that. + reply->msgh_remote_port = request->msgh_remote_port; + reply->msgh_local_port = MACH_PORT_NULL; + // MIG servers simply add 100 to the request ID to generate the reply ID. + reply->msgh_id = request->msgh_id + 100; + + // Process the message. + demuxer_->DemuxMessage(request, reply); + + // Free any descriptors in the message body. If the message was forwarded, + // any descriptors would have been moved out of the process on send. If the + // forwarded message was sent from the process hosting this sandbox server, + // destroying the message could also destroy rights held outside the scope of + // this message server. + if (!did_forward_message_) { + mach_msg_destroy(request); + mach_msg_destroy(reply); + } +} + +} // namespace sandbox diff --git a/chromium/sandbox/mac/mach_message_server.h b/chromium/sandbox/mac/mach_message_server.h new file mode 100644 index 00000000000..5c05c39b414 --- /dev/null +++ b/chromium/sandbox/mac/mach_message_server.h @@ -0,0 +1,95 @@ +// 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_MAC_MACH_MESSAGE_SERVER_H_ +#define SANDBOX_MAC_MACH_MESSAGE_SERVER_H_ + +#include <dispatch/dispatch.h> +#include <mach/mach.h> + +#include "base/mac/scoped_mach_port.h" +#include "base/mac/scoped_mach_vm.h" + +namespace sandbox { + +// A delegate interface for MachMessageServer that handles processing of +// incoming intercepted IPC messages. +class MessageDemuxer { + public: + // Handle a |request| message and optionally create a |reply|. Both message + // objects are owned by the server. Use the server's methods to send a + // reply message. + virtual void DemuxMessage(mach_msg_header_t* request, + mach_msg_header_t* reply) = 0; + + protected: + virtual ~MessageDemuxer() {} +}; + +// A Mach message server that operates a receive port. Messages are received +// and then passed to the MessageDemuxer for handling. The Demuxer +// can use the server class to send a reply, forward the message to a +// different port, or reply to the message with a MIG error. +class MachMessageServer { + public: + // Creates a new Mach message server that will send messages to |demuxer| + // for handling. If the |server_receive_right| is non-NULL, this class will + // take ownership of the port and it will be used to receive messages. + // Otherwise the server will create a new receive right. + // The maximum size of messages is specified by |buffer_size|. + MachMessageServer(MessageDemuxer* demuxer, + mach_port_t server_receive_right, + mach_msg_size_t buffer_size); + ~MachMessageServer(); + + // Initializes the class and starts running the message server. If this + // returns false, no other methods may be called on this class. + bool Initialize(); + + // Given a received request message, returns the PID of the sending process. + pid_t GetMessageSenderPID(mach_msg_header_t* request); + + // Sends a reply message. Returns true if the message was sent successfully. + bool SendReply(mach_msg_header_t* reply); + + // Forwards the original |request| to the |destination| for handling. + void ForwardMessage(mach_msg_header_t* request, mach_port_t destination); + + // Replies to the message with the specified |error_code| as a MIG + // error_reply RetCode. + void RejectMessage(mach_msg_header_t* reply, int error_code); + + mach_port_t server_port() const { return server_port_.get(); } + + private: + // Event handler for the |server_source_| that reads a message from the queue + // and processes it. + void ReceiveMessage(); + + // The demuxer delegate. Weak. + MessageDemuxer* demuxer_; + + // The Mach port on which the server is receiving requests. + base::mac::ScopedMachReceiveRight server_port_; + + // The dispatch queue used to service the server_source_. + dispatch_queue_t server_queue_; + + // A MACH_RECV dispatch source for the server_port_. + dispatch_source_t server_source_; + + // The size of the two message buffers below. + const mach_msg_size_t buffer_size_; + + // Request and reply buffers used in ReceiveMessage. + base::mac::ScopedMachVM request_buffer_; + base::mac::ScopedMachVM reply_buffer_; + + // Whether or not ForwardMessage() was called during ReceiveMessage(). + bool did_forward_message_; +}; + +} // namespace sandbox + +#endif // SANDBOX_MAC_MACH_MESSAGE_SERVER_H_ diff --git a/chromium/sandbox/mac/os_compatibility.cc b/chromium/sandbox/mac/os_compatibility.cc new file mode 100644 index 00000000000..4485006edb5 --- /dev/null +++ b/chromium/sandbox/mac/os_compatibility.cc @@ -0,0 +1,127 @@ +// 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/mac/os_compatibility.h" + +#include <servers/bootstrap.h> +#include <unistd.h> + +#include "base/mac/mac_util.h" + +namespace sandbox { + +namespace { + +// Verified from launchd-329.3.3 (10.6.8). +struct look_up2_request_10_6 { + mach_msg_header_t Head; + NDR_record_t NDR; + name_t servicename; + pid_t targetpid; + uint64_t flags; +}; + +struct look_up2_reply_10_6 { + mach_msg_header_t Head; + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t service_port; +}; + +// Verified from: +// launchd-392.39 (10.7.5) +// launchd-442.26.2 (10.8.5) +// launchd-842.1.4 (10.9.0) +struct look_up2_request_10_7 { + mach_msg_header_t Head; + NDR_record_t NDR; + name_t servicename; + pid_t targetpid; + uuid_t instanceid; + uint64_t flags; +}; + +// look_up2_reply_10_7 is the same as the 10_6 version. + +// Verified from: +// launchd-329.3.3 (10.6.8) +// launchd-392.39 (10.7.5) +// launchd-442.26.2 (10.8.5) +// launchd-842.1.4 (10.9.0) +typedef int vproc_gsk_t; // Defined as an enum in liblaunch/vproc_priv.h. +struct swap_integer_request_10_6 { + mach_msg_header_t Head; + NDR_record_t NDR; + vproc_gsk_t inkey; + vproc_gsk_t outkey; + int64_t inval; +}; + +// TODO(rsesek): Libc provides strnlen() starting in 10.7. +size_t strnlen(const char* str, size_t maxlen) { + size_t len = 0; + for (; len < maxlen; ++len, ++str) { + if (*str == '\0') + break; + } + return len; +} + +template <typename R> +std::string LaunchdLookUp2GetRequestName(const mach_msg_header_t* header) { + DCHECK_EQ(sizeof(R), header->msgh_size); + const R* request = reinterpret_cast<const R*>(header); + // Make sure the name is properly NUL-terminated. + const size_t name_length = + strnlen(request->servicename, BOOTSTRAP_MAX_NAME_LEN); + std::string name = std::string(request->servicename, name_length); + return name; +} + +template <typename R> +void LaunchdLookUp2FillReply(mach_msg_header_t* header, mach_port_t port) { + R* reply = reinterpret_cast<R*>(header); + reply->Head.msgh_size = sizeof(R); + reply->Head.msgh_bits = + MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE) | + MACH_MSGH_BITS_COMPLEX; + reply->msgh_body.msgh_descriptor_count = 1; + reply->service_port.name = port; + reply->service_port.disposition = MACH_MSG_TYPE_COPY_SEND; + reply->service_port.type = MACH_MSG_PORT_DESCRIPTOR; +} + +template <typename R> +bool LaunchdSwapIntegerIsGetOnly(const mach_msg_header_t* header) { + const R* request = reinterpret_cast<const R*>(header); + return request->inkey == 0 && request->inval == 0 && request->outkey != 0; +} + +} // namespace + +const LaunchdCompatibilityShim GetLaunchdCompatibilityShim() { + LaunchdCompatibilityShim shim = { + .msg_id_look_up2 = 404, + .msg_id_swap_integer = 416, + .look_up2_fill_reply = &LaunchdLookUp2FillReply<look_up2_reply_10_6>, + .swap_integer_is_get_only = + &LaunchdSwapIntegerIsGetOnly<swap_integer_request_10_6>, + }; + + if (base::mac::IsOSSnowLeopard()) { + shim.look_up2_get_request_name = + &LaunchdLookUp2GetRequestName<look_up2_request_10_6>; + } else if (base::mac::IsOSLionOrLater() && + !base::mac::IsOSYosemiteOrLater()) { + shim.look_up2_get_request_name = + &LaunchdLookUp2GetRequestName<look_up2_request_10_7>; + } else { + DLOG(ERROR) << "Unknown OS, using launchd compatibility shim from 10.7."; + shim.look_up2_get_request_name = + &LaunchdLookUp2GetRequestName<look_up2_request_10_7>; + } + + return shim; +} + +} // namespace sandbox diff --git a/chromium/sandbox/mac/os_compatibility.h b/chromium/sandbox/mac/os_compatibility.h new file mode 100644 index 00000000000..aaedcb8c812 --- /dev/null +++ b/chromium/sandbox/mac/os_compatibility.h @@ -0,0 +1,52 @@ +// 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. + +// This file is used to handle differences in Mach message IDs and structures +// that occur between different OS versions. The Mach messages that the sandbox +// is interested in are decoded using information derived from the open-source +// libraries, i.e. <http://www.opensource.apple.com/source/launchd/>. While +// these messages definitions are open source, they are not considered part of +// the stable OS API, and so differences do exist between OS versions. + +#ifndef SANDBOX_MAC_OS_COMPATIBILITY_H_ +#define SANDBOX_MAC_OS_COMPATIBILITY_H_ + +#include <mach/mach.h> + +#include <string> + +namespace sandbox { + +typedef std::string (*LookUp2GetRequestName)(const mach_msg_header_t*); +typedef void (*LookUp2FillReply)(mach_msg_header_t*, mach_port_t service_port); + +typedef bool (*SwapIntegerIsGetOnly)(const mach_msg_header_t*); + +struct LaunchdCompatibilityShim { + // The msgh_id for look_up2. + mach_msg_id_t msg_id_look_up2; + + // The msgh_id for swap_integer. + mach_msg_id_t msg_id_swap_integer; + + // A function to take a look_up2 message and return the string service name + // that was requested via the message. + LookUp2GetRequestName look_up2_get_request_name; + + // A function to formulate a reply to a look_up2 message, given the reply + // message and the port to return as the service. + LookUp2FillReply look_up2_fill_reply; + + // A function to take a swap_integer message and return true if the message + // is only getting the value of a key, neither setting it directly, nor + // swapping two keys. + SwapIntegerIsGetOnly swap_integer_is_get_only; +}; + +// Gets the compatibility shim for the launchd job subsystem. +const LaunchdCompatibilityShim GetLaunchdCompatibilityShim(); + +} // namespace sandbox + +#endif // SANDBOX_MAC_OS_COMPATIBILITY_H_ diff --git a/chromium/sandbox/mac/policy.cc b/chromium/sandbox/mac/policy.cc new file mode 100644 index 00000000000..293255adefc --- /dev/null +++ b/chromium/sandbox/mac/policy.cc @@ -0,0 +1,56 @@ +// 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/mac/policy.h" + +namespace sandbox { + +Rule::Rule() + : result(POLICY_DECISION_INVALID), + substitute_port(MACH_PORT_NULL) { +} + +Rule::Rule(PolicyDecision result) + : result(result), + substitute_port(MACH_PORT_NULL) { +} + +Rule::Rule(mach_port_t override_port) + : result(POLICY_SUBSTITUTE_PORT), + substitute_port(override_port) { +} + +BootstrapSandboxPolicy::BootstrapSandboxPolicy() + : default_rule(POLICY_DENY_ERROR) { +} + +BootstrapSandboxPolicy::~BootstrapSandboxPolicy() {} + +static bool IsRuleValid(const Rule& rule) { + if (!(rule.result > POLICY_DECISION_INVALID && + rule.result < POLICY_DECISION_LAST)) { + return false; + } + if (rule.result == POLICY_SUBSTITUTE_PORT) { + if (rule.substitute_port == MACH_PORT_NULL) + return false; + } else { + if (rule.substitute_port != MACH_PORT_NULL) + return false; + } + return true; +} + +bool IsPolicyValid(const BootstrapSandboxPolicy& policy) { + if (!IsRuleValid(policy.default_rule)) + return false; + + for (const auto& pair : policy.rules) { + if (!IsRuleValid(pair.second)) + return false; + } + return true; +} + +} // namespace sandbox diff --git a/chromium/sandbox/mac/policy.h b/chromium/sandbox/mac/policy.h new file mode 100644 index 00000000000..e500468237a --- /dev/null +++ b/chromium/sandbox/mac/policy.h @@ -0,0 +1,70 @@ +// 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_MAC_POLICY_H_ +#define SANDBOX_MAC_POLICY_H_ + +#include <mach/mach.h> + +#include <map> +#include <string> + +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +enum PolicyDecision { + POLICY_DECISION_INVALID, + // Explicitly allows the real service to be looked up from launchd. + POLICY_ALLOW, + // Deny the look up request by replying with a MIG error. This is the + // default behavior for servers not given an explicit rule. + POLICY_DENY_ERROR, + // Deny the look up request with a well-formed reply containing a + // Mach port with a send right, messages to which will be ignored. + POLICY_DENY_DUMMY_PORT, + // Reply to the look up request with a send right to the substitute_port + // specified in the Rule. + POLICY_SUBSTITUTE_PORT, + POLICY_DECISION_LAST, +}; + +// A Rule expresses the action to take when a service port is requested via +// bootstrap_look_up. If |result| is not POLICY_SUBSTITUTE_PORT, then +// |substitute_port| must be NULL. If result is POLICY_SUBSTITUTE_PORT, then +// |substitute_port| must not be NULL. +struct SANDBOX_EXPORT Rule { + Rule(); + explicit Rule(PolicyDecision result); + explicit Rule(mach_port_t override_port); + + PolicyDecision result; + + // The Rule does not take ownership of this port, but additional send rights + // will be allocated to it before it is sent to a client. This name must + // denote a send right that can duplicated with MACH_MSG_TYPE_COPY_SEND. + mach_port_t substitute_port; +}; + +// A policy object manages the rules enforced on a target sandboxed process. +struct SANDBOX_EXPORT BootstrapSandboxPolicy { + typedef std::map<std::string, Rule> NamedRules; + + BootstrapSandboxPolicy(); + ~BootstrapSandboxPolicy(); + + // The default action to take if the server name being looked up is not + // present in |rules|. + Rule default_rule; + + // A map of bootstrap server names to policy Rules. + NamedRules rules; +}; + +// Checks that a policy is well-formed. +SANDBOX_EXPORT bool IsPolicyValid(const BootstrapSandboxPolicy& policy); + +} // namespace sandbox + +#endif // SANDBOX_MAC_POLICY_H_ diff --git a/chromium/sandbox/mac/policy_unittest.cc b/chromium/sandbox/mac/policy_unittest.cc new file mode 100644 index 00000000000..54e0e748951 --- /dev/null +++ b/chromium/sandbox/mac/policy_unittest.cc @@ -0,0 +1,98 @@ +// 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/mac/policy.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +TEST(PolicyTest, ValidEmptyPolicy) { + EXPECT_TRUE(IsPolicyValid(BootstrapSandboxPolicy())); +} + +TEST(PolicyTest, ValidPolicy) { + BootstrapSandboxPolicy policy; + policy.rules["allow"] = Rule(POLICY_ALLOW); + policy.rules["deny_error"] = Rule(POLICY_DENY_ERROR); + policy.rules["deny_dummy"] = Rule(POLICY_DENY_DUMMY_PORT); + policy.rules["substitue"] = Rule(mach_task_self()); + EXPECT_TRUE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyEmptyRule) { + Rule rule; + BootstrapSandboxPolicy policy; + policy.rules["test"] = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicySubstitue) { + Rule rule(POLICY_SUBSTITUTE_PORT); + BootstrapSandboxPolicy policy; + policy.rules["test"] = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyWithPortAllow) { + Rule rule(POLICY_ALLOW); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.rules["allow"] = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyWithPortDenyError) { + Rule rule(POLICY_DENY_ERROR); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.rules["deny_error"] = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyWithPortDummy) { + Rule rule(POLICY_DENY_DUMMY_PORT); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.rules["deny_dummy"] = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyDefaultRule) { + BootstrapSandboxPolicy policy; + policy.default_rule = Rule(); + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyDefaultRuleSubstitue) { + BootstrapSandboxPolicy policy; + policy.default_rule = Rule(POLICY_SUBSTITUTE_PORT); + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyDefaultRuleWithPortAllow) { + Rule rule(POLICY_ALLOW); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.default_rule = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyDefaultRuleWithPortDenyError) { + Rule rule(POLICY_DENY_ERROR); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.default_rule = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyDefaultRuleWithPortDummy) { + Rule rule(POLICY_DENY_DUMMY_PORT); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.default_rule = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +} // namespace sandbox diff --git a/chromium/sandbox/mac/sandbox_mac.gypi b/chromium/sandbox/mac/sandbox_mac.gypi new file mode 100644 index 00000000000..36d6901667b --- /dev/null +++ b/chromium/sandbox/mac/sandbox_mac.gypi @@ -0,0 +1,100 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'sandbox', + 'type': '<(component)', + 'sources': [ + 'bootstrap_sandbox.cc', + 'bootstrap_sandbox.h', + 'launchd_interception_server.cc', + 'launchd_interception_server.h', + 'mach_message_server.cc', + 'mach_message_server.h', + 'os_compatibility.cc', + 'os_compatibility.h', + 'policy.cc', + 'policy.h', + 'xpc.h', + ], + 'dependencies': [ + '../base/base.gyp:base', + ], + 'include_dirs': [ + '..', + '<(SHARED_INTERMEDIATE_DIR)', + ], + 'defines': [ + 'SANDBOX_IMPLEMENTATION', + ], + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/usr/lib/libbsm.dylib', + ], + }, + 'actions': [ + { + 'variables': { + 'generate_stubs_script': '../tools/generate_stubs/generate_stubs.py', + 'generate_stubs_header_path': 'xpc_stubs_header.fragment', + 'generate_stubs_sig_public_path': 'xpc_stubs.sig', + 'generate_stubs_sig_private_path': 'xpc_private_stubs.sig', + 'generate_stubs_project': 'sandbox/mac', + 'generate_stubs_output_stem': 'xpc_stubs', + }, + 'action_name': 'generate_stubs', + 'inputs': [ + '<(generate_stubs_script)', + '<(generate_stubs_header_path)', + '<(generate_stubs_sig_public_path)', + '<(generate_stubs_sig_private_path)', + ], + 'outputs': [ + '<(INTERMEDIATE_DIR)/<(generate_stubs_output_stem).cc', + '<(SHARED_INTERMEDIATE_DIR)/<(generate_stubs_project)/<(generate_stubs_output_stem).h', + ], + 'action': [ + 'python', + '<(generate_stubs_script)', + '-i', '<(INTERMEDIATE_DIR)', + '-o', '<(SHARED_INTERMEDIATE_DIR)/<(generate_stubs_project)', + '-t', 'posix_stubs', + '-e', '<(generate_stubs_header_path)', + '-s', '<(generate_stubs_output_stem)', + '-p', '<(generate_stubs_project)', + '<(generate_stubs_sig_public_path)', + '<(generate_stubs_sig_private_path)', + ], + 'process_outputs_as_sources': 1, + 'message': 'Generating XPC stubs for 10.6 compatability.', + }, + ], + }, + { + 'target_name': 'sandbox_mac_unittests', + 'type': 'executable', + 'sources': [ + 'bootstrap_sandbox_unittest.mm', + 'policy_unittest.cc', + ], + 'dependencies': [ + 'sandbox', + '../base/base.gyp:base', + '../base/base.gyp:run_all_unittests', + '../testing/gtest.gyp:gtest', + ], + 'include_dirs': [ + '..', + ], + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/CoreFoundation.framework', + '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', + ], + }, + }, + ], +} diff --git a/chromium/sandbox/mac/xpc.h b/chromium/sandbox/mac/xpc.h new file mode 100644 index 00000000000..1cbe9ca7d40 --- /dev/null +++ b/chromium/sandbox/mac/xpc.h @@ -0,0 +1,40 @@ +// 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. + +// This file provides forward declarations for XPC symbols that are not +// present in the 10.6 SDK. It uses generate_stubs to produce code to +// dynamically load the libxpc.dylib library and set up a stub table, with +// the same names as the real XPC functions. + +#ifndef SANDBOX_MAC_XPC_H_ +#define SANDBOX_MAC_XPC_H_ + +#include <mach/mach.h> + +// C++ library loader. +#include "sandbox/mac/xpc_stubs.h" + +// Declares XPC object types. This includes <xpc/xpc.h> if available. +#include "sandbox/mac/xpc_stubs_header.fragment" + +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 + +extern "C" { +// Signatures for XPC public functions that are loaded by xpc_stubs.h. +#include "sandbox/mac/xpc_stubs.sig" +// Signatures for private XPC functions. +#include "sandbox/mac/xpc_private_stubs.sig" +} // extern "C" + +#else + +// Signatures for private XPC functions. +extern "C" { +#include "sandbox/mac/xpc_private_stubs.sig" +} // extern "C" + +#endif + +#endif // SANDBOX_MAC_XPC_H_ diff --git a/chromium/sandbox/mac/xpc_private_stubs.sig b/chromium/sandbox/mac/xpc_private_stubs.sig new file mode 100644 index 00000000000..33db194ebdf --- /dev/null +++ b/chromium/sandbox/mac/xpc_private_stubs.sig @@ -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. + +// This file contains declarations of private XPC functions. This file is +// used for both forward declarations of private symbols and to use with +// tools/generate_stubs for creating a dynamic library loader. + +// Dictionary manipulation. +void xpc_dictionary_set_mach_send(xpc_object_t dict, const char* name, mach_port_t port); + +// Pipe methods. +xpc_pipe_t xpc_pipe_create_from_port(mach_port_t port, int flags); +int xpc_pipe_receive(mach_port_t port, xpc_object_t* message); +int xpc_pipe_routine(xpc_pipe_t pipe, xpc_object_t request, xpc_object_t* reply); +int xpc_pipe_routine_reply(xpc_object_t reply); +int xpc_pipe_routine_forward(xpc_pipe_t forward_to, xpc_object_t request); diff --git a/chromium/sandbox/mac/xpc_stubs.sig b/chromium/sandbox/mac/xpc_stubs.sig new file mode 100644 index 00000000000..5020ffd5360 --- /dev/null +++ b/chromium/sandbox/mac/xpc_stubs.sig @@ -0,0 +1,16 @@ +// 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. + +// This file contains declarations of public XPC functions used in the sandbox. +// This file is used with tools/generate_stubs for creating a dynamic library +// loader. + +// XPC object management. +void xpc_release(xpc_object_t object); + +// Dictionary manipulation. +const char* xpc_dictionary_get_string(xpc_object_t dictionary, const char* key); +uint64_t xpc_dictionary_get_uint64(xpc_object_t dictionary, const char* key); +void xpc_dictionary_set_int64(xpc_object_t dictionary, const char* key, int64_t value); +xpc_object_t xpc_dictionary_create_reply(xpc_object_t request); diff --git a/chromium/sandbox/mac/xpc_stubs_header.fragment b/chromium/sandbox/mac/xpc_stubs_header.fragment new file mode 100644 index 00000000000..a29907ef3fd --- /dev/null +++ b/chromium/sandbox/mac/xpc_stubs_header.fragment @@ -0,0 +1,27 @@ +// 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_MAC_XPC_STUBS_HEADER_FRAGMENT_ +#define SANDBOX_MAC_XPC_STUBS_HEADER_FRAGMENT_ + +// Declare or include public types. +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 + +extern "C" { +typedef void* xpc_object_t; +} // extern "C" + +#else + +#include <xpc/xpc.h> + +#endif + +// Declare private types. +extern "C" { +typedef struct _xpc_pipe_s* xpc_pipe_t; +} // extern "C" + +#endif // SANDBOX_MAC_XPC_STUBS_HEADER_FRAGMENT_ diff --git a/chromium/sandbox/sandbox.gyp b/chromium/sandbox/sandbox.gyp index b48727a28ba..f93fa1862a7 100644 --- a/chromium/sandbox/sandbox.gyp +++ b/chromium/sandbox/sandbox.gyp @@ -17,6 +17,11 @@ 'linux/sandbox_linux.gypi', ], }], + [ 'OS=="mac" and OS!="ios"', { + 'includes': [ + 'mac/sandbox_mac.gypi', + ], + }], [ 'OS!="win" and OS!="mac" and OS!="linux" and OS!="android"', { # A 'default' to accomodate the "sandbox" target. 'targets': [ diff --git a/chromium/sandbox/sandbox_export.h b/chromium/sandbox/sandbox_export.h new file mode 100644 index 00000000000..40a40366406 --- /dev/null +++ b/chromium/sandbox/sandbox_export.h @@ -0,0 +1,29 @@ +// 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_SANDBOX_EXPORT_H_ +#define SANDBOX_SANDBOX_EXPORT_H_ + +#if defined(WIN32) +#error "sandbox_export.h does not support WIN32." +#endif + +#if defined(COMPONENT_BUILD) + +#if defined(SANDBOX_IMPLEMENTATION) +#define SANDBOX_EXPORT __attribute__((visibility("default"))) +#define SANDBOX_EXPORT_PRIVATE __attribute__((visibility("default"))) +#else +#define SANDBOX_EXPORT +#define SANDBOX_EXPORT_PRIVATE +#endif // defined(SANDBOX_IMPLEMENTATION) + +#else // defined(COMPONENT_BUILD) + +#define SANDBOX_EXPORT +#define SANDBOX_EXPORT_PRIVATE + +#endif // defined(COMPONENT_BUILD) + +#endif // SANDBOX_SANDBOX_EXPORT_H_ diff --git a/chromium/sandbox/sandbox_linux_unittests.isolate b/chromium/sandbox/sandbox_linux_unittests.isolate new file mode 100644 index 00000000000..89865668ba1 --- /dev/null +++ b/chromium/sandbox/sandbox_linux_unittests.isolate @@ -0,0 +1,27 @@ +# 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. + +# Because of a limitation in isolate_driver.py, this file needs to be in +# the same directory as the main .gyp file. + +{ + 'conditions': [ + ['OS=="android" or OS=="linux"', { + 'variables': { + 'command': [ + '<(PRODUCT_DIR)/sandbox_linux_unittests', + ], + 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/sandbox_linux_unittests', + ], + 'read_only': 1, + }, + }], + ], + 'includes': [ + # This is needed because of base/ dependencies on + # icudtl.dat. + '../base/base.isolate', + ], +} diff --git a/chromium/sandbox/win/BUILD.gn b/chromium/sandbox/win/BUILD.gn new file mode 100644 index 00000000000..3c5bca61001 --- /dev/null +++ b/chromium/sandbox/win/BUILD.gn @@ -0,0 +1,285 @@ +# 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.
+
+source_set("sandbox") {
+ sources = [
+ "src/acl.cc",
+ "src/acl.h",
+ "src/app_container.cc",
+ "src/app_container.h",
+ "src/broker_services.cc",
+ "src/broker_services.h",
+ "src/crosscall_client.h",
+ "src/crosscall_params.h",
+ "src/crosscall_server.cc",
+ "src/crosscall_server.h",
+ "src/eat_resolver.cc",
+ "src/eat_resolver.h",
+ "src/filesystem_dispatcher.cc",
+ "src/filesystem_dispatcher.h",
+ "src/filesystem_interception.cc",
+ "src/filesystem_interception.h",
+ "src/filesystem_policy.cc",
+ "src/filesystem_policy.h",
+ "src/handle_closer.cc",
+ "src/handle_closer.h",
+ "src/handle_closer_agent.cc",
+ "src/handle_closer_agent.h",
+ "src/handle_dispatcher.cc",
+ "src/handle_dispatcher.h",
+ "src/handle_interception.cc",
+ "src/handle_interception.h",
+ "src/handle_policy.cc",
+ "src/handle_policy.h",
+ "src/handle_table.cc",
+ "src/handle_table.h",
+ "src/interception.cc",
+ "src/interception.h",
+ "src/interception_agent.cc",
+ "src/interception_agent.h",
+ "src/interception_internal.h",
+ "src/interceptors.h",
+ "src/internal_types.h",
+ "src/ipc_tags.h",
+ "src/job.cc",
+ "src/job.h",
+ "src/named_pipe_dispatcher.cc",
+ "src/named_pipe_dispatcher.h",
+ "src/named_pipe_interception.cc",
+ "src/named_pipe_interception.h",
+ "src/named_pipe_policy.cc",
+ "src/named_pipe_policy.h",
+ "src/nt_internals.h",
+ "src/policy_broker.cc",
+ "src/policy_broker.h",
+ "src/policy_engine_opcodes.cc",
+ "src/policy_engine_opcodes.h",
+ "src/policy_engine_params.h",
+ "src/policy_engine_processor.cc",
+ "src/policy_engine_processor.h",
+ "src/policy_low_level.cc",
+ "src/policy_low_level.h",
+ "src/policy_params.h",
+ "src/policy_target.cc",
+ "src/policy_target.h",
+ "src/process_mitigations.cc",
+ "src/process_mitigations.h",
+ "src/process_thread_dispatcher.cc",
+ "src/process_thread_dispatcher.h",
+ "src/process_thread_interception.cc",
+ "src/process_thread_interception.h",
+ "src/process_thread_policy.cc",
+ "src/process_thread_policy.h",
+ "src/registry_dispatcher.cc",
+ "src/registry_dispatcher.h",
+ "src/registry_interception.cc",
+ "src/registry_interception.h",
+ "src/registry_policy.cc",
+ "src/registry_policy.h",
+ "src/resolver.cc",
+ "src/resolver.h",
+ "src/restricted_token_utils.cc",
+ "src/restricted_token_utils.h",
+ "src/restricted_token.cc",
+ "src/restricted_token.h",
+ "src/sandbox_factory.h",
+ "src/sandbox_globals.cc",
+ "src/sandbox_nt_types.h",
+ "src/sandbox_nt_util.cc",
+ "src/sandbox_nt_util.h",
+ "src/sandbox_policy_base.cc",
+ "src/sandbox_policy_base.h",
+ "src/sandbox_policy.h",
+ "src/sandbox_types.h",
+ "src/sandbox_utils.cc",
+ "src/sandbox_utils.h",
+ "src/sandbox.cc",
+ "src/sandbox.h",
+ "src/security_level.h",
+ "src/service_resolver.cc",
+ "src/service_resolver.h",
+ "src/shared_handles.cc",
+ "src/shared_handles.h",
+ "src/sharedmem_ipc_client.cc",
+ "src/sharedmem_ipc_client.h",
+ "src/sharedmem_ipc_server.cc",
+ "src/sharedmem_ipc_server.h",
+ "src/sid.cc",
+ "src/sid.h",
+ "src/sync_dispatcher.cc",
+ "src/sync_dispatcher.h",
+ "src/sync_interception.cc",
+ "src/sync_interception.h",
+ "src/sync_policy.cc",
+ "src/sync_policy.h",
+ "src/target_interceptions.cc",
+ "src/target_interceptions.h",
+ "src/target_process.cc",
+ "src/target_process.h",
+ "src/target_services.cc",
+ "src/target_services.h",
+ "src/win_utils.cc",
+ "src/win_utils.h",
+ "src/win2k_threadpool.cc",
+ "src/win2k_threadpool.h",
+ "src/window.cc",
+ "src/window.h",
+ ]
+
+ if (cpu_arch == "x64" ){
+ sources += [
+ "src/interceptors_64.cc",
+ "src/interceptors_64.h",
+ "src/resolver_64.cc",
+ "src/service_resolver_64.cc",
+ "src/Wow64_64.cc",
+ ]
+ } else if (cpu_arch == "x86") {
+ sources += [
+ "src/resolver_32.cc",
+ "src/service_resolver_32.cc",
+ "src/sidestep_resolver.cc",
+ "src/sidestep_resolver.h",
+ "src/sidestep/ia32_modrm_map.cpp",
+ "src/sidestep/ia32_opcode_map.cpp",
+ "src/sidestep/mini_disassembler_types.h",
+ "src/sidestep/mini_disassembler.cpp",
+ "src/sidestep/mini_disassembler.h",
+ "src/sidestep/preamble_patcher_with_stub.cpp",
+ "src/sidestep/preamble_patcher.h",
+ "src/Wow64.cc",
+ "src/Wow64.h",
+ ]
+ }
+
+ deps = [
+ ":copy_wow_helper",
+ "//base",
+ ]
+}
+
+if (cpu_arch == "x86") {
+ # Make a target that copies the wow_helper files to the out dir.
+ #
+ # TODO(brettw) we can probably just build this now that we have proper
+ # toolchain support.
+ copy("copy_wow_helper") {
+ sources = [
+ "wow_helper/wow_helper.exe",
+ "wow_helper/wow_helper.pdb",
+ ]
+ outputs = [ "$root_out_dir/{{source_file_part}}" ]
+ }
+}
+
+test("sbox_integration_tests") {
+ sources = [
+ "src/app_container_test.cc",
+ "src/file_policy_test.cc",
+ "src/handle_inheritance_test.cc",
+ "src/handle_policy_test.cc",
+ "tests/integration_tests/integration_tests_test.cc",
+ "src/handle_closer_test.cc",
+ "src/integrity_level_test.cc",
+ "src/ipc_ping_test.cc",
+ "src/named_pipe_policy_test.cc",
+ "src/policy_target_test.cc",
+ "src/process_mitigations_test.cc",
+ "src/process_policy_test.cc",
+ "src/registry_policy_test.cc",
+ "src/sync_policy_test.cc",
+ "src/sync_policy_test.h",
+ "src/unload_dll_test.cc",
+ "tests/common/controller.cc",
+ "tests/common/controller.h",
+ "tests/common/test_utils.cc",
+ "tests/common/test_utils.h",
+ "tests/integration_tests/integration_tests.cc",
+ ]
+
+ deps = [
+ ":sandbox",
+ "//testing/gtest",
+ ]
+}
+
+test("sbox_validation_tests") {
+ sources = [
+ "tests/common/controller.cc",
+ "tests/common/controller.h",
+ "tests/validation_tests/unit_tests.cc",
+ "tests/validation_tests/commands.cc",
+ "tests/validation_tests/commands.h",
+ "tests/validation_tests/suite.cc",
+ ]
+
+ deps = [
+ ":sandbox",
+ "//testing/gtest",
+ ]
+}
+
+test("sbox_unittests") {
+ sources = [
+ "src/app_container_unittest.cc",
+ "src/interception_unittest.cc",
+ "src/service_resolver_unittest.cc",
+ "src/restricted_token_unittest.cc",
+ "src/job_unittest.cc",
+ "src/sid_unittest.cc",
+ "src/policy_engine_unittest.cc",
+ "src/policy_low_level_unittest.cc",
+ "src/policy_opcodes_unittest.cc",
+ "src/ipc_unittest.cc",
+ "src/threadpool_unittest.cc",
+ "src/win_utils_unittest.cc",
+ "tests/common/test_utils.cc",
+ "tests/common/test_utils.h",
+ "tests/unit_tests/unit_tests.cc",
+ ]
+
+ deps = [
+ ":sandbox",
+ "//testing/gtest",
+ ]
+}
+
+test("sandbox_poc") {
+ sources = [
+ "sandbox_poc/main_ui_window.cc",
+ "sandbox_poc/main_ui_window.h",
+ "sandbox_poc/resource.h",
+ "sandbox_poc/sandbox.cc",
+ "sandbox_poc/sandbox.h",
+ "sandbox_poc/sandbox.ico",
+ "sandbox_poc/sandbox.rc",
+ ]
+
+ configs -= [ "//build/config/win:console" ]
+ configs += [ "//build/config/win:windowed" ]
+
+ libs = [ "comctl32.lib" ]
+
+ deps = [
+ ":sandbox",
+ ":pocdll",
+ ]
+}
+
+shared_library("pocdll") {
+ sources = [
+ "sandbox_poc/pocdll/exports.h",
+ "sandbox_poc/pocdll/fs.cc",
+ "sandbox_poc/pocdll/handles.cc",
+ "sandbox_poc/pocdll/invasive.cc",
+ "sandbox_poc/pocdll/network.cc",
+ "sandbox_poc/pocdll/pocdll.cc",
+ "sandbox_poc/pocdll/processes_and_threads.cc",
+ "sandbox_poc/pocdll/registry.cc",
+ "sandbox_poc/pocdll/spyware.cc",
+ "sandbox_poc/pocdll/utils.h",
+ ]
+
+ defines = [ "POCDLL_EXPORTS" ]
+}
diff --git a/chromium/sandbox/win/OWNERS b/chromium/sandbox/win/OWNERS new file mode 100644 index 00000000000..9ccaeb39f55 --- /dev/null +++ b/chromium/sandbox/win/OWNERS @@ -0,0 +1,3 @@ +cpu@chromium.org +jschuh@chromium.org +rvargas@chromium.org diff --git a/chromium/sandbox/win/sandbox_win.gypi b/chromium/sandbox/win/sandbox_win.gypi index 1fa82794bbf..7d9cf949fa7 100644 --- a/chromium/sandbox/win/sandbox_win.gypi +++ b/chromium/sandbox/win/sandbox_win.gypi @@ -74,6 +74,12 @@ 'src/policy_target.h', 'src/process_mitigations.cc', 'src/process_mitigations.h', + 'src/process_mitigations_win32k_dispatcher.cc', + 'src/process_mitigations_win32k_dispatcher.h', + 'src/process_mitigations_win32k_interception.cc', + 'src/process_mitigations_win32k_interception.h', + 'src/process_mitigations_win32k_policy.cc', + 'src/process_mitigations_win32k_policy.h', 'src/process_thread_dispatcher.cc', 'src/process_thread_dispatcher.h', 'src/process_thread_interception.cc', @@ -174,7 +180,6 @@ 'sandbox_windows_target': 1, }, 'dependencies': [ - '../testing/gtest.gyp:gtest', '../base/base.gyp:base', '../base/base.gyp:base_static', ], @@ -209,6 +214,7 @@ 'type': 'executable', 'dependencies': [ 'sandbox', + '../base/base.gyp:test_support_base', '../testing/gtest.gyp:gtest', ], 'sources': [ @@ -240,6 +246,7 @@ 'type': 'executable', 'dependencies': [ 'sandbox', + '../base/base.gyp:test_support_base', '../testing/gtest.gyp:gtest', ], 'sources': [ @@ -256,6 +263,7 @@ 'type': 'executable', 'dependencies': [ 'sandbox', + '../base/base.gyp:test_support_base', '../testing/gtest.gyp:gtest', ], 'sources': [ @@ -337,7 +345,7 @@ 'target_arch': 'x64', }, 'dependencies': [ - '../base/base.gyp:base_nacl_win64', + '../base/base.gyp:base_win64', '../base/base.gyp:base_static_win64', ], 'configurations': { diff --git a/chromium/sandbox/win/src/Wow64.cc b/chromium/sandbox/win/src/Wow64.cc index b11026b1a52..59df1d6f674 100644 --- a/chromium/sandbox/win/src/Wow64.cc +++ b/chromium/sandbox/win/src/Wow64.cc @@ -153,7 +153,8 @@ bool Wow64::RunWowHelper(void* buffer) { L"wow_helper.exe\" " << child_->ProcessId() << " " << bit_cast<ULONG>(buffer); - scoped_ptr_malloc<wchar_t> writable_command(_wcsdup(command.str().c_str())); + scoped_ptr<wchar_t, base::FreeDeleter> + writable_command(_wcsdup(command.str().c_str())); STARTUPINFO startup_info = {0}; startup_info.cb = sizeof(startup_info); diff --git a/chromium/sandbox/win/src/acl.cc b/chromium/sandbox/win/src/acl.cc index bf59d962a54..f140c7e6c4f 100644 --- a/chromium/sandbox/win/src/acl.cc +++ b/chromium/sandbox/win/src/acl.cc @@ -11,8 +11,9 @@ namespace sandbox { -bool GetDefaultDacl(HANDLE token, - scoped_ptr_malloc<TOKEN_DEFAULT_DACL>* default_dacl) { +bool GetDefaultDacl( + HANDLE token, + scoped_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter>* default_dacl) { if (token == NULL) return false; @@ -59,7 +60,7 @@ bool AddSidToDefaultDacl(HANDLE token, const Sid& sid, ACCESS_MASK access) { if (token == NULL) return false; - scoped_ptr_malloc<TOKEN_DEFAULT_DACL> default_dacl; + scoped_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter> default_dacl; if (!GetDefaultDacl(token, &default_dacl)) return false; @@ -81,7 +82,7 @@ bool AddUserSidToDefaultDacl(HANDLE token, ACCESS_MASK access) { DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(malloc(size)); - scoped_ptr_malloc<TOKEN_USER> token_user_ptr(token_user); + scoped_ptr<TOKEN_USER, base::FreeDeleter> token_user_ptr(token_user); if (!::GetTokenInformation(token, TokenUser, token_user, size, &size)) return false; diff --git a/chromium/sandbox/win/src/acl.h b/chromium/sandbox/win/src/acl.h index 531259fbebd..b5021e7be86 100644 --- a/chromium/sandbox/win/src/acl.h +++ b/chromium/sandbox/win/src/acl.h @@ -14,8 +14,9 @@ namespace sandbox { // Returns the default dacl from the token passed in. -bool GetDefaultDacl(HANDLE token, - scoped_ptr_malloc<TOKEN_DEFAULT_DACL>* default_dacl); +bool GetDefaultDacl( + HANDLE token, + scoped_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter>* default_dacl); // Appends an ACE represented by |sid|, |access_mode|, and |access| to // |old_dacl|. If the function succeeds, new_dacl contains the new dacl and diff --git a/chromium/sandbox/win/src/app_container.cc b/chromium/sandbox/win/src/app_container.cc index 826b5614f71..f8d75419159 100644 --- a/chromium/sandbox/win/src/app_container.cc +++ b/chromium/sandbox/win/src/app_container.cc @@ -16,7 +16,7 @@ namespace { // Converts the passed in sid string to a PSID that must be relased with // LocalFree. -PSID ConvertSid(const string16& sid) { +PSID ConvertSid(const base::string16& sid) { PSID local_sid; if (!ConvertStringSidToSid(sid.c_str(), &local_sid)) return NULL; @@ -49,8 +49,8 @@ AppContainerAttributes::~AppContainerAttributes() { } ResultCode AppContainerAttributes::SetAppContainer( - const string16& app_container_sid, - const std::vector<string16>& capabilities) { + const base::string16& app_container_sid, + const std::vector<base::string16>& capabilities) { DCHECK(!capabilities_.AppContainerSid); DCHECK(attributes_.empty()); capabilities_.AppContainerSid = ConvertSid(app_container_sid); @@ -94,7 +94,8 @@ bool AppContainerAttributes::HasAppContainer() const { return (capabilities_.AppContainerSid != NULL); } -ResultCode CreateAppContainer(const string16& sid, const string16& name) { +ResultCode CreateAppContainer(const base::string16& sid, + const base::string16& name) { PSID local_sid; if (!ConvertStringSidToSid(sid.c_str(), &local_sid)) return SBOX_ERROR_INVALID_APP_CONTAINER; @@ -121,7 +122,7 @@ ResultCode CreateAppContainer(const string16& sid, const string16& name) { return operation_result; } -ResultCode DeleteAppContainer(const string16& sid) { +ResultCode DeleteAppContainer(const base::string16& sid) { PSID local_sid; if (!ConvertStringSidToSid(sid.c_str(), &local_sid)) return SBOX_ERROR_INVALID_APP_CONTAINER; @@ -146,10 +147,10 @@ ResultCode DeleteAppContainer(const string16& sid) { return operation_result; } -string16 LookupAppContainer(const string16& sid) { +base::string16 LookupAppContainer(const base::string16& sid) { PSID local_sid; if (!ConvertStringSidToSid(sid.c_str(), &local_sid)) - return string16(); + return base::string16(); typedef HRESULT (WINAPI* AppContainerLookupMonikerPtr)(PSID sid, LPWSTR* moniker); @@ -166,14 +167,14 @@ string16 LookupAppContainer(const string16& sid) { } if (!AppContainerLookupMoniker || !AppContainerFreeMemory) - return string16(); + return base::string16(); wchar_t* buffer = NULL; HRESULT rv = AppContainerLookupMoniker(local_sid, &buffer); if (FAILED(rv)) - return string16(); + return base::string16(); - string16 name(buffer); + base::string16 name(buffer); if (!AppContainerFreeMemory(buffer)) NOTREACHED(); return name; diff --git a/chromium/sandbox/win/src/app_container.h b/chromium/sandbox/win/src/app_container.h index 34b43e95f57..8125d706fb4 100644 --- a/chromium/sandbox/win/src/app_container.h +++ b/chromium/sandbox/win/src/app_container.h @@ -29,8 +29,8 @@ class AppContainerAttributes { ~AppContainerAttributes(); // Sets the AppContainer and capabilities to be used with the new process. - ResultCode SetAppContainer(const string16& app_container_sid, - const std::vector<string16>& capabilities); + ResultCode SetAppContainer(const base::string16& app_container_sid, + const std::vector<base::string16>& capabilities); // Updates the proc_thred attribute list of the provided startup_information // with the app container related data. @@ -53,15 +53,16 @@ class AppContainerAttributes { // AppContainer, and |name| will be used as both the display name and moniker. // This function fails if the OS doesn't support AppContainers, or if there is // an AppContainer registered with the same id. -ResultCode CreateAppContainer(const string16& sid, const string16& name); +ResultCode CreateAppContainer(const base::string16& sid, + const base::string16& name); // Deletes an AppContainer previously created with a successfull call to // CreateAppContainer. -ResultCode DeleteAppContainer(const string16& sid); +ResultCode DeleteAppContainer(const base::string16& sid); // Retrieves the name associated with the provided AppContainer sid. Returns an // empty string if the AppContainer is not registered with the system. -string16 LookupAppContainer(const string16& sid); +base::string16 LookupAppContainer(const base::string16& sid); } // namespace sandbox diff --git a/chromium/sandbox/win/src/app_container_test.cc b/chromium/sandbox/win/src/app_container_test.cc index 3b33ca5c901..1bfab2c3145 100644 --- a/chromium/sandbox/win/src/app_container_test.cc +++ b/chromium/sandbox/win/src/app_container_test.cc @@ -23,7 +23,8 @@ const wchar_t kAppContainerSid[] = const ULONG kSharing = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; -HANDLE CreateTaggedEvent(const string16& name, const string16& sid) { +HANDLE CreateTaggedEvent(const base::string16& name, + const base::string16& sid) { base::win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, name.c_str())); if (!event.IsValid()) return NULL; diff --git a/chromium/sandbox/win/src/app_container_unittest.cc b/chromium/sandbox/win/src/app_container_unittest.cc index 936a9cbc7bf..4bce16a42b5 100644 --- a/chromium/sandbox/win/src/app_container_unittest.cc +++ b/chromium/sandbox/win/src/app_container_unittest.cc @@ -37,7 +37,7 @@ TEST(AppContainerTest, SecurityCapabilities) { return; scoped_ptr<AppContainerAttributes> attributes(new AppContainerAttributes); - std::vector<string16> capabilities; + std::vector<base::string16> capabilities; EXPECT_EQ(SBOX_ERROR_INVALID_APP_CONTAINER, attributes->SetAppContainer(L"S-1-foo", capabilities)); diff --git a/chromium/sandbox/win/src/broker_services.cc b/chromium/sandbox/win/src/broker_services.cc index 921eb4f89e0..895d535a486 100644 --- a/chromium/sandbox/win/src/broker_services.cc +++ b/chromium/sandbox/win/src/broker_services.cc @@ -4,6 +4,8 @@ #include "sandbox/win/src/broker_services.h" +#include <AclAPI.h> + #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/threading/platform_thread.h" @@ -80,6 +82,29 @@ void DeregisterPeerTracker(PeerTracker* peer) { } } +// Utility function to pack token values into a key for the cache map. +uint32_t GenerateTokenCacheKey(const sandbox::PolicyBase* policy) { + const size_t kTokenShift = 3; + uint32_t key; + + // Make sure our token values aren't too large to pack into the key. + static_assert(sandbox::USER_LAST <= (1 << kTokenShift), + "TokenLevel too large"); + static_assert(sandbox::INTEGRITY_LEVEL_LAST <= (1 << kTokenShift), + "IntegrityLevel too large"); + static_assert(sizeof(key) < (kTokenShift * 3), + "Token key type too small"); + + // The key is the enum values shifted to avoid overlap and OR'd together. + key = policy->GetInitialTokenLevel(); + key <<= kTokenShift; + key |= policy->GetLockdownTokenLevel(); + key <<= kTokenShift; + key |= policy->GetIntegrityLevel(); + + return key; +} + } // namespace namespace sandbox { @@ -153,6 +178,13 @@ BrokerServicesBase::~BrokerServicesBase() { // If job_port_ isn't NULL, assumes that the lock has been initialized. if (job_port_) ::DeleteCriticalSection(&lock_); + + // Close any token in the cache. + for (TokenCacheMap::iterator it = token_cache_.begin(); + it != token_cache_.end(); ++it) { + ::CloseHandle(it->second.first); + ::CloseHandle(it->second.second); + } } TargetPolicy* BrokerServicesBase::CreatePolicy() { @@ -246,6 +278,13 @@ DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) { break; } + case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: { + BOOL res = ::TerminateJobObject(tracker->job, + SBOX_FATAL_MEMORY_EXCEEDED); + DCHECK(res); + break; + } + default: { NOTREACHED(); break; @@ -299,10 +338,35 @@ ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, // with the soon to be created target process. HANDLE initial_token_temp; HANDLE lockdown_token_temp; - ResultCode result = policy_base->MakeTokens(&initial_token_temp, - &lockdown_token_temp); - if (SBOX_ALL_OK != result) - return result; + ResultCode result = SBOX_ALL_OK; + + // Create the master tokens only once and save them in a cache. That way + // can just duplicate them to avoid hammering LSASS on every sandboxed + // process launch. + uint32_t token_key = GenerateTokenCacheKey(policy_base); + TokenCacheMap::iterator it = token_cache_.find(token_key); + if (it != token_cache_.end()) { + initial_token_temp = it->second.first; + lockdown_token_temp = it->second.second; + } else { + result = policy_base->MakeTokens(&initial_token_temp, + &lockdown_token_temp); + if (SBOX_ALL_OK != result) + return result; + token_cache_[token_key] = + std::pair<HANDLE, HANDLE>(initial_token_temp, lockdown_token_temp); + } + + if (!::DuplicateToken(initial_token_temp, SecurityImpersonation, + &initial_token_temp)) { + return SBOX_ERROR_GENERIC; + } + + if (!::DuplicateTokenEx(lockdown_token_temp, TOKEN_ALL_ACCESS, 0, + SecurityIdentification, TokenPrimary, + &lockdown_token_temp)) { + return SBOX_ERROR_GENERIC; + } base::win::ScopedHandle initial_token(initial_token_temp); base::win::ScopedHandle lockdown_token(lockdown_token_temp); @@ -316,7 +380,7 @@ ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, // Initialize the startup information from the policy. base::win::StartupInformation startup_info; - string16 desktop = policy_base->GetAlternateDesktop(); + base::string16 desktop = policy_base->GetAlternateDesktop(); if (!desktop.empty()) { startup_info.startup_info()->lpDesktop = const_cast<wchar_t*>(desktop.c_str()); @@ -486,7 +550,7 @@ ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid, if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) return SBOX_ERROR_UNSUPPORTED; - string16 old_name = LookupAppContainer(sid); + base::string16 old_name = LookupAppContainer(sid); if (old_name.empty()) return CreateAppContainer(sid, name); @@ -500,7 +564,7 @@ ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) { if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) return SBOX_ERROR_UNSUPPORTED; - string16 name = LookupAppContainer(sid); + base::string16 name = LookupAppContainer(sid); if (name.empty()) return SBOX_ERROR_INVALID_APP_CONTAINER; diff --git a/chromium/sandbox/win/src/broker_services.h b/chromium/sandbox/win/src/broker_services.h index 11c10e0f884..ba189f41f83 100644 --- a/chromium/sandbox/win/src/broker_services.h +++ b/chromium/sandbox/win/src/broker_services.h @@ -8,6 +8,7 @@ #include <list> #include <map> #include <set> +#include <utility> #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/win/scoped_handle.h" @@ -36,8 +37,8 @@ class PolicyBase; // of the associated TargetProcess interface. In this implementation // TargetProcess is a friend of BrokerServices where the later manages a // collection of the former. -class BrokerServicesBase : public BrokerServices, - public SingletonBase<BrokerServicesBase> { +class BrokerServicesBase FINAL : public BrokerServices, + public SingletonBase<BrokerServicesBase> { public: BrokerServicesBase(); @@ -105,6 +106,9 @@ class BrokerServicesBase : public BrokerServices, // job. Consult |jobless_process_handles_| for handles of pocess without job. std::set<DWORD> child_process_ids_; + typedef std::map<uint32_t, std::pair<HANDLE, HANDLE>> TokenCacheMap; + TokenCacheMap token_cache_; + DISALLOW_COPY_AND_ASSIGN(BrokerServicesBase); }; diff --git a/chromium/sandbox/win/src/crosscall_server.cc b/chromium/sandbox/win/src/crosscall_server.cc index ab8b42113fa..65a908478f6 100644 --- a/chromium/sandbox/win/src/crosscall_server.cc +++ b/chromium/sandbox/win/src/crosscall_server.cc @@ -60,7 +60,6 @@ uint32 GetActualBufferSize(uint32 param_count, void* buffer_base) { case 9: return reinterpret_cast<ActualCP9*>(buffer_base)->GetSize(); default: - NOTREACHED(); return 0; } } diff --git a/chromium/sandbox/win/src/handle_closer.cc b/chromium/sandbox/win/src/handle_closer.cc index 39915a9b0a1..30e8977fa85 100644 --- a/chromium/sandbox/win/src/handle_closer.cc +++ b/chromium/sandbox/win/src/handle_closer.cc @@ -34,8 +34,8 @@ SANDBOX_INTERCEPT HandleCloserInfo* g_handles_to_close; HandleCloser::HandleCloser() {} -ResultCode HandleCloser::AddHandle(const char16* handle_type, - const char16* handle_name) { +ResultCode HandleCloser::AddHandle(const base::char16* handle_type, + const base::char16* handle_name) { if (!handle_type) return SBOX_ERROR_BAD_PARAMS; @@ -61,10 +61,10 @@ size_t HandleCloser::GetBufferSize() { for (HandleMap::iterator i = handles_to_close_.begin(); i != handles_to_close_.end(); ++i) { size_t bytes_entry = offsetof(HandleListEntry, handle_type) + - (i->first.size() + 1) * sizeof(char16); + (i->first.size() + 1) * sizeof(base::char16); for (HandleMap::mapped_type::iterator j = i->second.begin(); j != i->second.end(); ++j) { - bytes_entry += ((*j).size() + 1) * sizeof(char16); + bytes_entry += ((*j).size() + 1) * sizeof(base::char16); } // Round up to the nearest multiple of word size. @@ -119,8 +119,9 @@ bool HandleCloser::SetupHandleList(void* buffer, size_t buffer_bytes) { handle_info->record_bytes = buffer_bytes; handle_info->num_handle_types = handles_to_close_.size(); - char16* output = reinterpret_cast<char16*>(&handle_info->handle_entries[0]); - char16* end = reinterpret_cast<char16*>( + base::char16* output = reinterpret_cast<base::char16*>( + &handle_info->handle_entries[0]); + base::char16* end = reinterpret_cast<base::char16*>( reinterpret_cast<char*>(buffer) + buffer_bytes); for (HandleMap::iterator i = handles_to_close_.begin(); i != handles_to_close_.end(); ++i) { @@ -153,28 +154,7 @@ bool HandleCloser::SetupHandleList(void* buffer, size_t buffer_bytes) { return output <= end; } -bool HandleCloser::SetupHandleInterceptions(InterceptionManager* manager) { - // We need to intercept CreateThread if we're closing ALPC port clients. - HandleMap::iterator names = handles_to_close_.find(L"ALPC Port"); - if (base::win::GetVersion() >= base::win::VERSION_VISTA && - names != handles_to_close_.end() && - (names->second.empty() || names->second.size() == 0)) { - if (!INTERCEPT_EAT(manager, kKerneldllName, CreateThread, - CREATE_THREAD_ID, 28)) { - return false; - } - if (!INTERCEPT_EAT(manager, kKerneldllName, GetUserDefaultLCID, - GET_USER_DEFAULT_LCID_ID, 4)) { - return false; - } - - return true; - } - - return true; -} - -bool GetHandleName(HANDLE handle, string16* handle_name) { +bool GetHandleName(HANDLE handle, base::string16* handle_name) { static NtQueryObject QueryObject = NULL; if (!QueryObject) ResolveNTFunctionPtr("NtQueryObject", &QueryObject); diff --git a/chromium/sandbox/win/src/handle_closer.h b/chromium/sandbox/win/src/handle_closer.h index a6f81d5b290..60473b3ea00 100644 --- a/chromium/sandbox/win/src/handle_closer.h +++ b/chromium/sandbox/win/src/handle_closer.h @@ -19,14 +19,15 @@ namespace sandbox { // This is a map of handle-types to names that we need to close in the // target process. A null set means we need to close all handles of the // given type. -typedef std::map<const string16, std::set<const string16> > HandleMap; +typedef std::map<const base::string16, std::set<const base::string16> > + HandleMap; // Type and set of corresponding handle names to close. struct HandleListEntry { size_t record_bytes; // Rounded to sizeof(size_t) bytes. size_t offset_to_names; // Nul terminated strings of name_count names. size_t name_count; - char16 handle_type[1]; + base::char16 handle_type[1]; }; // Global parameters and a pointer to the list of entries. @@ -46,14 +47,12 @@ class HandleCloser { // Adds a handle that will be closed in the target process after lockdown. // A NULL value for handle_name indicates all handles of the specified type. // An empty string for handle_name indicates the handle is unnamed. - ResultCode AddHandle(const char16* handle_type, const char16* handle_name); + ResultCode AddHandle(const base::char16* handle_type, + const base::char16* handle_name); // Serializes and copies the closer table into the target process. bool InitializeTargetHandles(TargetProcess* target); - // Adds any interceptions that may be required due to closed system handles. - bool SetupHandleInterceptions(InterceptionManager* manager); - private: // Calculates the memory needed to copy the serialized handles list (rounded // to the nearest machine-word size). @@ -68,7 +67,7 @@ class HandleCloser { }; // Returns the object manager's name associated with a handle -bool GetHandleName(HANDLE handle, string16* handle_name); +bool GetHandleName(HANDLE handle, base::string16* handle_name); } // namespace sandbox diff --git a/chromium/sandbox/win/src/handle_closer_agent.cc b/chromium/sandbox/win/src/handle_closer_agent.cc index bc75e7322b2..07c6a09854d 100644 --- a/chromium/sandbox/win/src/handle_closer_agent.cc +++ b/chromium/sandbox/win/src/handle_closer_agent.cc @@ -49,9 +49,9 @@ void HandleCloserAgent::InitializeHandlesToClose() { HandleListEntry* entry = g_handles_to_close->handle_entries; for (size_t i = 0; i < g_handles_to_close->num_handle_types; ++i) { // Set the type name. - char16* input = entry->handle_type; + base::char16* input = entry->handle_type; HandleMap::mapped_type& handle_names = handles_to_close_[input]; - input = reinterpret_cast<char16*>(reinterpret_cast<char*>(entry) + input = reinterpret_cast<base::char16*>(reinterpret_cast<char*>(entry) + entry->offset_to_names); // Grab all the handle names. for (size_t j = 0; j < entry->name_count; ++j) { @@ -65,9 +65,9 @@ void HandleCloserAgent::InitializeHandlesToClose() { entry = reinterpret_cast<HandleListEntry*>(reinterpret_cast<char*>(entry) + entry->record_bytes); - DCHECK(reinterpret_cast<char16*>(entry) >= input); - DCHECK(reinterpret_cast<char16*>(entry) - input < - sizeof(size_t) / sizeof(char16)); + DCHECK(reinterpret_cast<base::char16*>(entry) >= input); + DCHECK(reinterpret_cast<base::char16*>(entry) - input < + sizeof(size_t) / sizeof(base::char16)); } // Clean up the memory we copied over. @@ -78,7 +78,7 @@ void HandleCloserAgent::InitializeHandlesToClose() { bool HandleCloserAgent::CloseHandles() { DWORD handle_count = UINT_MAX; const int kInvalidHandleThreshold = 100; - const size_t kHandleOffset = sizeof(HANDLE); + const size_t kHandleOffset = 4; // Handles are always a multiple of 4. if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) return false; @@ -88,7 +88,7 @@ bool HandleCloserAgent::CloseHandles() { 32 * sizeof(wchar_t)); OBJECT_TYPE_INFORMATION* type_info = reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); - string16 handle_name; + base::string16 handle_name; HANDLE handle = NULL; int invalid_count = 0; diff --git a/chromium/sandbox/win/src/handle_closer_test.cc b/chromium/sandbox/win/src/handle_closer_test.cc index ba0e33a2204..7fa259889d8 100644 --- a/chromium/sandbox/win/src/handle_closer_test.cc +++ b/chromium/sandbox/win/src/handle_closer_test.cc @@ -19,7 +19,7 @@ const wchar_t *kFileExtensions[] = { L".1", L".2", L".3", L".4" }; HANDLE GetMarkerFile(const wchar_t *extension) { wchar_t path_buffer[MAX_PATH + 1]; CHECK(::GetTempPath(MAX_PATH, path_buffer)); - string16 marker_path = path_buffer; + base::string16 marker_path = path_buffer; marker_path += L"\\sbox_marker_"; // Generate a unique value from the exe's size and timestamp. @@ -73,10 +73,10 @@ SBOX_TESTS_COMMAND int CheckForFileHandles(int argc, wchar_t **argv) { // Brute force the handle table to find what we're looking for. DWORD handle_count = UINT_MAX; const int kInvalidHandleThreshold = 100; - const size_t kHandleOffset = sizeof(HANDLE); + const size_t kHandleOffset = 4; // Handles are always a multiple of 4. HANDLE handle = NULL; int invalid_count = 0; - string16 handle_name; + base::string16 handle_name; if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) return SBOX_TEST_FAILED_TO_RUN_TEST; @@ -110,9 +110,9 @@ TEST(HandleCloserTest, CheckForMarkerFiles) { runner.SetTestState(EVERY_STATE); sandbox::TargetPolicy* policy = runner.GetPolicy(); - string16 command = string16(L"CheckForFileHandles Y"); + base::string16 command = base::string16(L"CheckForFileHandles Y"); for (int i = 0; i < arraysize(kFileExtensions); ++i) { - string16 handle_name; + base::string16 handle_name; base::win::ScopedHandle marker(GetMarkerFile(kFileExtensions[i])); CHECK(marker.IsValid()); CHECK(sandbox::GetHandleName(marker, &handle_name)); @@ -130,9 +130,9 @@ TEST(HandleCloserTest, CloseMarkerFiles) { runner.SetTestState(EVERY_STATE); sandbox::TargetPolicy* policy = runner.GetPolicy(); - string16 command = string16(L"CheckForFileHandles N"); + base::string16 command = base::string16(L"CheckForFileHandles N"); for (int i = 0; i < arraysize(kFileExtensions); ++i) { - string16 handle_name; + base::string16 handle_name; base::win::ScopedHandle marker(GetMarkerFile(kFileExtensions[i])); CHECK(marker.IsValid()); CHECK(sandbox::GetHandleName(marker, &handle_name)); diff --git a/chromium/sandbox/win/src/handle_dispatcher.cc b/chromium/sandbox/win/src/handle_dispatcher.cc index 26b8fc365ad..6acb6f9ceb3 100644 --- a/chromium/sandbox/win/src/handle_dispatcher.cc +++ b/chromium/sandbox/win/src/handle_dispatcher.cc @@ -53,10 +53,11 @@ bool HandleDispatcher::DuplicateHandleProxy(IPCInfo* ipc, HANDLE handle_temp; if (!::DuplicateHandle(ipc->client_info->process, source_handle, ::GetCurrentProcess(), &handle_temp, - 0, FALSE, DUPLICATE_SAME_ACCESS)) { + 0, FALSE, DUPLICATE_SAME_ACCESS | options)) { ipc->return_info.win32_result = ::GetLastError(); return false; } + options &= ~DUPLICATE_CLOSE_SOURCE; base::win::ScopedHandle handle(handle_temp); // Get the object type (32 characters is safe; current max is 14). @@ -78,8 +79,7 @@ bool HandleDispatcher::DuplicateHandleProxy(IPCInfo* ipc, EvalResult eval = policy_base_->EvalPolicy(IPC_DUPLICATEHANDLEPROXY_TAG, params.GetBase()); ipc->return_info.win32_result = - HandlePolicy::DuplicateHandleProxyAction(eval, *ipc->client_info, - source_handle, + HandlePolicy::DuplicateHandleProxyAction(eval, handle, target_process_id, &ipc->return_info.handle, desired_access, options); diff --git a/chromium/sandbox/win/src/handle_policy.cc b/chromium/sandbox/win/src/handle_policy.cc index 718376ecee1..f5f1c273b96 100644 --- a/chromium/sandbox/win/src/handle_policy.cc +++ b/chromium/sandbox/win/src/handle_policy.cc @@ -52,7 +52,6 @@ bool HandlePolicy::GenerateRules(const wchar_t* type_name, } DWORD HandlePolicy::DuplicateHandleProxyAction(EvalResult eval_result, - const ClientInfo& client_info, HANDLE source_handle, DWORD target_process_id, HANDLE* target_handle, @@ -81,7 +80,7 @@ DWORD HandlePolicy::DuplicateHandleProxyAction(EvalResult eval_result, HANDLE target_process = remote_target_process.IsValid() ? remote_target_process.Get() : ::GetCurrentProcess(); DWORD result = ERROR_SUCCESS; - if (!::DuplicateHandle(client_info.process, source_handle, target_process, + if (!::DuplicateHandle(::GetCurrentProcess(), source_handle, target_process, target_handle, desired_access, FALSE, options)) { return ::GetLastError(); diff --git a/chromium/sandbox/win/src/handle_policy.h b/chromium/sandbox/win/src/handle_policy.h index d91a0393aef..ffe54b8ae2e 100644 --- a/chromium/sandbox/win/src/handle_policy.h +++ b/chromium/sandbox/win/src/handle_policy.h @@ -27,7 +27,6 @@ class HandlePolicy { // Processes a 'TargetPolicy::DuplicateHandle()' request from the target. static DWORD DuplicateHandleProxyAction(EvalResult eval_result, - const ClientInfo& client_info, HANDLE source_handle, DWORD target_process_id, HANDLE* target_handle, diff --git a/chromium/sandbox/win/src/handle_table.cc b/chromium/sandbox/win/src/handle_table.cc index 7230dff87b5..3b2febe6251 100644 --- a/chromium/sandbox/win/src/handle_table.cc +++ b/chromium/sandbox/win/src/handle_table.cc @@ -22,22 +22,22 @@ bool CompareHandleEntries(const SYSTEM_HANDLE_INFORMATION& a, namespace sandbox { -const char16* HandleTable::kTypeProcess = L"Process"; -const char16* HandleTable::kTypeThread = L"Thread"; -const char16* HandleTable::kTypeFile = L"File"; -const char16* HandleTable::kTypeDirectory = L"Directory"; -const char16* HandleTable::kTypeKey = L"Key"; -const char16* HandleTable::kTypeWindowStation = L"WindowStation"; -const char16* HandleTable::kTypeDesktop = L"Desktop"; -const char16* HandleTable::kTypeService = L"Service"; -const char16* HandleTable::kTypeMutex = L"Mutex"; -const char16* HandleTable::kTypeSemaphore = L"Semaphore"; -const char16* HandleTable::kTypeEvent = L"Event"; -const char16* HandleTable::kTypeTimer = L"Timer"; -const char16* HandleTable::kTypeNamedPipe = L"NamedPipe"; -const char16* HandleTable::kTypeJobObject = L"JobObject"; -const char16* HandleTable::kTypeFileMap = L"FileMap"; -const char16* HandleTable::kTypeAlpcPort = L"ALPC Port"; +const base::char16* HandleTable::kTypeProcess = L"Process"; +const base::char16* HandleTable::kTypeThread = L"Thread"; +const base::char16* HandleTable::kTypeFile = L"File"; +const base::char16* HandleTable::kTypeDirectory = L"Directory"; +const base::char16* HandleTable::kTypeKey = L"Key"; +const base::char16* HandleTable::kTypeWindowStation = L"WindowStation"; +const base::char16* HandleTable::kTypeDesktop = L"Desktop"; +const base::char16* HandleTable::kTypeService = L"Service"; +const base::char16* HandleTable::kTypeMutex = L"Mutex"; +const base::char16* HandleTable::kTypeSemaphore = L"Semaphore"; +const base::char16* HandleTable::kTypeEvent = L"Event"; +const base::char16* HandleTable::kTypeTimer = L"Timer"; +const base::char16* HandleTable::kTypeNamedPipe = L"NamedPipe"; +const base::char16* HandleTable::kTypeJobObject = L"JobObject"; +const base::char16* HandleTable::kTypeFileMap = L"FileMap"; +const base::char16* HandleTable::kTypeAlpcPort = L"ALPC Port"; HandleTable::HandleTable() { static NtQuerySystemInformation QuerySystemInformation = NULL; @@ -151,17 +151,17 @@ const OBJECT_TYPE_INFORMATION* HandleTable::HandleEntry::TypeInfo() { return type_info_buffer_.empty() ? NULL : type_info_internal(); } -const string16& HandleTable::HandleEntry::Name() { +const base::string16& HandleTable::HandleEntry::Name() { UpdateInfo(UPDATE_INFO_AND_NAME); return handle_name_; } -const string16& HandleTable::HandleEntry::Type() { +const base::string16& HandleTable::HandleEntry::Type() { UpdateInfo(UPDATE_INFO_AND_TYPE_NAME); return type_name_; } -bool HandleTable::HandleEntry::IsType(const string16& type_string) { +bool HandleTable::HandleEntry::IsType(const base::string16& type_string) { UpdateInfo(UPDATE_INFO_ONLY); if (type_info_buffer_.empty()) return false; diff --git a/chromium/sandbox/win/src/handle_table.h b/chromium/sandbox/win/src/handle_table.h index 21ff80ff481..1b553fae3aa 100644 --- a/chromium/sandbox/win/src/handle_table.h +++ b/chromium/sandbox/win/src/handle_table.h @@ -18,22 +18,22 @@ namespace sandbox { // for iterating through the table and retrieving handle info. class HandleTable { public: - static const char16* HandleTable::kTypeProcess; - static const char16* HandleTable::kTypeThread; - static const char16* HandleTable::kTypeFile; - static const char16* HandleTable::kTypeDirectory; - static const char16* HandleTable::kTypeKey; - static const char16* HandleTable::kTypeWindowStation; - static const char16* HandleTable::kTypeDesktop; - static const char16* HandleTable::kTypeService; - static const char16* HandleTable::kTypeMutex; - static const char16* HandleTable::kTypeSemaphore; - static const char16* HandleTable::kTypeEvent; - static const char16* HandleTable::kTypeTimer; - static const char16* HandleTable::kTypeNamedPipe; - static const char16* HandleTable::kTypeJobObject; - static const char16* HandleTable::kTypeFileMap; - static const char16* HandleTable::kTypeAlpcPort; + static const base::char16* HandleTable::kTypeProcess; + static const base::char16* HandleTable::kTypeThread; + static const base::char16* HandleTable::kTypeFile; + static const base::char16* HandleTable::kTypeDirectory; + static const base::char16* HandleTable::kTypeKey; + static const base::char16* HandleTable::kTypeWindowStation; + static const base::char16* HandleTable::kTypeDesktop; + static const base::char16* HandleTable::kTypeService; + static const base::char16* HandleTable::kTypeMutex; + static const base::char16* HandleTable::kTypeSemaphore; + static const base::char16* HandleTable::kTypeEvent; + static const base::char16* HandleTable::kTypeTimer; + static const base::char16* HandleTable::kTypeNamedPipe; + static const base::char16* HandleTable::kTypeJobObject; + static const base::char16* HandleTable::kTypeFileMap; + static const base::char16* HandleTable::kTypeAlpcPort; class Iterator; @@ -54,11 +54,11 @@ class HandleTable { const OBJECT_TYPE_INFORMATION* TypeInfo(); - const string16& Name(); + const base::string16& Name(); - const string16& Type(); + const base::string16& Type(); - bool IsType(const string16& type_string); + bool IsType(const base::string16& type_string); private: friend class Iterator; @@ -84,8 +84,8 @@ class HandleTable { const SYSTEM_HANDLE_INFORMATION* handle_entry_; const SYSTEM_HANDLE_INFORMATION* last_entry_; std::vector<BYTE> type_info_buffer_; - string16 handle_name_; - string16 type_name_; + base::string16 handle_name_; + base::string16 type_name_; DISALLOW_COPY_AND_ASSIGN(HandleEntry); }; diff --git a/chromium/sandbox/win/src/interception.cc b/chromium/sandbox/win/src/interception.cc index dde585735f1..5439db65b53 100644 --- a/chromium/sandbox/win/src/interception.cc +++ b/chromium/sandbox/win/src/interception.cc @@ -17,7 +17,6 @@ #include "sandbox/win/src/interception_internal.h" #include "sandbox/win/src/interceptors.h" #include "sandbox/win/src/sandbox.h" -#include "sandbox/win/src/sandbox_utils.h" #include "sandbox/win/src/service_resolver.h" #include "sandbox/win/src/target_interceptions.h" #include "sandbox/win/src/target_process.h" @@ -399,7 +398,7 @@ bool InterceptionManager::PatchNtdll(bool hot_patch_needed) { thunk_offset &= kPageSize - 1; // Make an aligned, padded allocation, and move the pointer to our chunk. - size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & kPageSize; + size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & ~(kPageSize - 1); thunk_base = reinterpret_cast<BYTE*>( ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded, MEM_COMMIT, PAGE_EXECUTE_READWRITE)); @@ -483,8 +482,6 @@ bool InterceptionManager::PatchClientFunctions(DllInterceptionData* thunks, thunk = new Wow64W8ResolverThunk(child_->Process(), relaxed_); else thunk = new Wow64ResolverThunk(child_->Process(), relaxed_); - } else if (!IsXPSP2OrLater()) { - thunk = new Win2kResolverThunk(child_->Process(), relaxed_); } else if (os_info->version() >= base::win::VERSION_WIN8) { thunk = new Win8ResolverThunk(child_->Process(), relaxed_); } else { diff --git a/chromium/sandbox/win/src/interceptors.h b/chromium/sandbox/win/src/interceptors.h index 43126d005ac..a17447aa18c 100644 --- a/chromium/sandbox/win/src/interceptors.h +++ b/chromium/sandbox/win/src/interceptors.h @@ -41,9 +41,10 @@ enum InterceptorId { // Sync dispatcher: CREATE_EVENT_ID, OPEN_EVENT_ID, - // CSRSS bypasses for HandleCloser: - CREATE_THREAD_ID, - GET_USER_DEFAULT_LCID_ID, + // Process mitigations Win32k dispatcher: + GDIINITIALIZE_ID, + GETSTOCKOBJECT_ID, + REGISTERCLASSW_ID, INTERCEPTOR_MAX_ID }; diff --git a/chromium/sandbox/win/src/interceptors_64.cc b/chromium/sandbox/win/src/interceptors_64.cc index c71d5a2803d..ef0b5f0017a 100644 --- a/chromium/sandbox/win/src/interceptors_64.cc +++ b/chromium/sandbox/win/src/interceptors_64.cc @@ -8,6 +8,7 @@ #include "sandbox/win/src/filesystem_interception.h" #include "sandbox/win/src/named_pipe_interception.h" #include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/process_mitigations_win32k_interception.h" #include "sandbox/win/src/process_thread_interception.h" #include "sandbox/win/src/registry_interception.h" #include "sandbox/win/src/sandbox_nt_types.h" @@ -68,23 +69,6 @@ NTSTATUS WINAPI TargetNtOpenThreadTokenEx64( open_as_self, handle_attributes, token); } -HANDLE WINAPI TargetCreateThread64( - LPSECURITY_ATTRIBUTES thread_attributes, SIZE_T stack_size, - LPTHREAD_START_ROUTINE start_address, PVOID parameter, DWORD creation_flags, - LPDWORD thread_id) { - CreateThreadFunction orig_fn = reinterpret_cast< - CreateThreadFunction>(g_originals[CREATE_THREAD_ID]); - return TargetCreateThread(orig_fn, thread_attributes, stack_size, - start_address, parameter, creation_flags, - thread_id); -} - -LCID WINAPI TargetGetUserDefaultLCID64(void) { - GetUserDefaultLCIDFunction orig_fn = reinterpret_cast< - GetUserDefaultLCIDFunction>(g_originals[GET_USER_DEFAULT_LCID_ID]); - return TargetGetUserDefaultLCID(orig_fn); -} - // ----------------------------------------------------------------------- SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateFile64( @@ -268,4 +252,27 @@ SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenEvent64( object_attributes); } +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT BOOL WINAPI TargetGdiDllInitialize64( + HANDLE dll, + DWORD reason) { + GdiDllInitializeFunction orig_fn = reinterpret_cast< + GdiDllInitializeFunction>(g_originals[GDIINITIALIZE_ID]); + return TargetGdiDllInitialize(orig_fn, dll, reason); +} + +SANDBOX_INTERCEPT HGDIOBJ WINAPI TargetGetStockObject64(int object) { + GetStockObjectFunction orig_fn = reinterpret_cast< + GetStockObjectFunction>(g_originals[GETSTOCKOBJECT_ID]); + return TargetGetStockObject(orig_fn, object); +} + +SANDBOX_INTERCEPT ATOM WINAPI TargetRegisterClassW64( + const WNDCLASS* wnd_class) { + RegisterClassWFunction orig_fn = reinterpret_cast< + RegisterClassWFunction>(g_originals[REGISTERCLASSW_ID]); + return TargetRegisterClassW(orig_fn, wnd_class); +} + } // namespace sandbox diff --git a/chromium/sandbox/win/src/interceptors_64.h b/chromium/sandbox/win/src/interceptors_64.h index ef2c10d412c..7368ceb8450 100644 --- a/chromium/sandbox/win/src/interceptors_64.h +++ b/chromium/sandbox/win/src/interceptors_64.h @@ -44,15 +44,6 @@ SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThreadTokenEx64( HANDLE thread, ACCESS_MASK desired_access, BOOLEAN open_as_self, ULONG handle_attributes, PHANDLE token); -// Interception of CreateThread on the child process. -SANDBOX_INTERCEPT HANDLE WINAPI TargetCreateThread64( - LPSECURITY_ATTRIBUTES thread_attributes, SIZE_T stack_size, - LPTHREAD_START_ROUTINE start_address, PVOID parameter, - DWORD creation_flags, LPDWORD thread_id); - -// Interception of GetUserDefaultLCID on the child process. -SANDBOX_INTERCEPT LCID WINAPI TargetGetUserDefaultLCID64(); - // ----------------------------------------------------------------------- // Interceptors handled by the file system dispatcher. @@ -163,6 +154,20 @@ SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenEvent64( PHANDLE event_handle, ACCESS_MASK desired_access, POBJECT_ATTRIBUTES object_attributes); +// ----------------------------------------------------------------------- +// Interceptors handled by the process mitigations win32k lockdown code. + +// Interceptor for the GdiDllInitialize function. +SANDBOX_INTERCEPT BOOL WINAPI TargetGdiDllInitialize64( + HANDLE dll, + DWORD reason); + +// Interceptor for the GetStockObject function. +SANDBOX_INTERCEPT HGDIOBJ WINAPI TargetGetStockObject64(int object); + +// Interceptor for the RegisterClassW function. +SANDBOX_INTERCEPT ATOM WINAPI TargetRegisterClassW64(const WNDCLASS* wnd_class); + } // extern "C" } // namespace sandbox diff --git a/chromium/sandbox/win/src/ipc_tags.h b/chromium/sandbox/win/src/ipc_tags.h index 4e3a8061d86..d680411dace 100644 --- a/chromium/sandbox/win/src/ipc_tags.h +++ b/chromium/sandbox/win/src/ipc_tags.h @@ -29,6 +29,9 @@ enum { IPC_NTCREATEKEY_TAG, IPC_NTOPENKEY_TAG, IPC_DUPLICATEHANDLEPROXY_TAG, + IPC_GDI_GDIDLLINITIALIZE_TAG, + IPC_GDI_GETSTOCKOBJECT_TAG, + IPC_USER_REGISTERCLASSW_TAG, IPC_LAST_TAG }; diff --git a/chromium/sandbox/win/src/ipc_unittest.cc b/chromium/sandbox/win/src/ipc_unittest.cc index 53b870c3f15..7bdd286d399 100644 --- a/chromium/sandbox/win/src/ipc_unittest.cc +++ b/chromium/sandbox/win/src/ipc_unittest.cc @@ -159,7 +159,7 @@ TEST(IPCTest, CrossCallStrPacking) { CrossCallReturn answer; uint32 tag1 = 666; - const wchar_t text[] = L"98765 - 43210"; + const wchar_t *text = L"98765 - 43210"; base::string16 copied_text; CrossCallParamsEx* actual_params; @@ -206,7 +206,7 @@ TEST(IPCTest, CrossCallStrPacking) { param_size = 1; base::string16 copied_text_p0, copied_text_p2; - const wchar_t text2[] = L"AeFG"; + const wchar_t *text2 = L"AeFG"; CrossCall(client, tag1, text2, null_text, text, &answer); actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); EXPECT_EQ(3, actual_params->GetParamsCount()); @@ -235,7 +235,7 @@ TEST(IPCTest, CrossCallIntPacking) { uint32 tag1 = 999; uint32 tag2 = 111; - const wchar_t text[] = L"godzilla"; + const wchar_t *text = L"godzilla"; CrossCallParamsEx* actual_params; char* mem = reinterpret_cast<char*>(client_control); @@ -315,8 +315,7 @@ TEST(IPCTest, CrossCallValidation) { EXPECT_EQ(1, ccp->GetParamsCount()); delete[] (reinterpret_cast<char*>(ccp)); -#if defined(NDEBUG) - // Test hat we handle integer overflow on the number of params + // Test that we handle integer overflow on the number of params // correctly. We use a test-only ctor for ActualCallParams that // allows to create malformed cross-call buffers. const int32 kPtrDiffSz = sizeof(ptrdiff_t); @@ -332,7 +331,6 @@ TEST(IPCTest, CrossCallValidation) { // If the buffer is malformed the return is NULL. EXPECT_TRUE(NULL == ccp); } -#endif // defined(NDEBUG) ActualCallParams<1, kBufferSize> params_3(kTag, 1); params_3.CopyParamIn(0, &value, sizeof(value), false, ULONG_TYPE); diff --git a/chromium/sandbox/win/src/job.cc b/chromium/sandbox/win/src/job.cc index 060ffa52438..8852ab0c720 100644 --- a/chromium/sandbox/win/src/job.cc +++ b/chromium/sandbox/win/src/job.cc @@ -14,8 +14,10 @@ Job::~Job() { ::CloseHandle(job_handle_); }; -DWORD Job::Init(JobLevel security_level, wchar_t *job_name, - DWORD ui_exceptions) { +DWORD Job::Init(JobLevel security_level, + const wchar_t* job_name, + DWORD ui_exceptions, + size_t memory_limit) { if (job_handle_) return ERROR_ALREADY_INITIALIZED; @@ -51,11 +53,11 @@ DWORD Job::Init(JobLevel security_level, wchar_t *job_name, jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS; } case JOB_UNPROTECTED: { - // The JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag is not supported on - // Windows 2000. We need a mechanism on Windows 2000 to ensure - // that processes in the job are terminated when the job is closed - if (base::win::GetVersion() == base::win::VERSION_PRE_XP) - break; + if (memory_limit) { + jeli.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_PROCESS_MEMORY; + jeli.ProcessMemoryLimit = memory_limit; + } jeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; diff --git a/chromium/sandbox/win/src/job.h b/chromium/sandbox/win/src/job.h index 487722f0bb2..60dc3146b70 100644 --- a/chromium/sandbox/win/src/job.h +++ b/chromium/sandbox/win/src/job.h @@ -29,7 +29,10 @@ class Job { // If the function succeeds, the return value is ERROR_SUCCESS. If the // function fails, the return value is the win32 error code corresponding to // the error. - DWORD Init(JobLevel security_level, wchar_t *job_name, DWORD ui_exceptions); + DWORD Init(JobLevel security_level, + const wchar_t* job_name, + DWORD ui_exceptions, + size_t memory_limit); // Assigns the process referenced by process_handle to the job. // If the function succeeds, the return value is ERROR_SUCCESS. If the diff --git a/chromium/sandbox/win/src/job_unittest.cc b/chromium/sandbox/win/src/job_unittest.cc index 3b2b331046c..a1b7acafe3e 100644 --- a/chromium/sandbox/win/src/job_unittest.cc +++ b/chromium/sandbox/win/src/job_unittest.cc @@ -16,7 +16,7 @@ TEST(JobTest, TestCreation) { { // Create the job. Job job; - ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0)); + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); // check if the job exists. HANDLE job_handle = ::OpenJobObjectW(GENERIC_ALL, FALSE, @@ -40,7 +40,7 @@ TEST(JobTest, TestDetach) { { // Create the job. Job job; - ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0)); + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); job_handle = job.Detach(); ASSERT_TRUE(job_handle != NULL); @@ -73,7 +73,7 @@ TEST(JobTest, TestExceptions) { // Create the job. Job job; ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", - JOB_OBJECT_UILIMIT_READCLIPBOARD)); + JOB_OBJECT_UILIMIT_READCLIPBOARD, 0)); job_handle = job.Detach(); ASSERT_TRUE(job_handle != NULL); @@ -93,7 +93,7 @@ TEST(JobTest, TestExceptions) { { // Create the job. Job job; - ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0)); + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); job_handle = job.Detach(); ASSERT_TRUE(job_handle != NULL); @@ -115,8 +115,8 @@ TEST(JobTest, TestExceptions) { TEST(JobTest, DoubleInit) { // Create the job. Job job; - ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0)); - ASSERT_EQ(ERROR_ALREADY_INITIALIZED, job.Init(JOB_LOCKDOWN, L"test", 0)); + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + ASSERT_EQ(ERROR_ALREADY_INITIALIZED, job.Init(JOB_LOCKDOWN, L"test", 0, 0)); } // Tests the error case when we use a method and the object is not yet @@ -131,34 +131,35 @@ TEST(JobTest, NoInit) { // Tests the initialization of the job with different security level. TEST(JobTest, SecurityLevel) { Job job1; - ASSERT_EQ(ERROR_SUCCESS, job1.Init(JOB_LOCKDOWN, L"job1", 0)); + ASSERT_EQ(ERROR_SUCCESS, job1.Init(JOB_LOCKDOWN, L"job1", 0, 0)); Job job2; - ASSERT_EQ(ERROR_SUCCESS, job2.Init(JOB_RESTRICTED, L"job2", 0)); + ASSERT_EQ(ERROR_SUCCESS, job2.Init(JOB_RESTRICTED, L"job2", 0, 0)); Job job3; - ASSERT_EQ(ERROR_SUCCESS, job3.Init(JOB_LIMITED_USER, L"job3", 0)); + ASSERT_EQ(ERROR_SUCCESS, job3.Init(JOB_LIMITED_USER, L"job3", 0, 0)); Job job4; - ASSERT_EQ(ERROR_SUCCESS, job4.Init(JOB_INTERACTIVE, L"job4", 0)); + ASSERT_EQ(ERROR_SUCCESS, job4.Init(JOB_INTERACTIVE, L"job4", 0, 0)); Job job5; - ASSERT_EQ(ERROR_SUCCESS, job5.Init(JOB_UNPROTECTED, L"job5", 0)); + ASSERT_EQ(ERROR_SUCCESS, job5.Init(JOB_UNPROTECTED, L"job5", 0, 0)); // JOB_NONE means we run without a job object so Init should fail. Job job6; - ASSERT_EQ(ERROR_BAD_ARGUMENTS, job6.Init(JOB_NONE, L"job6", 0)); + ASSERT_EQ(ERROR_BAD_ARGUMENTS, job6.Init(JOB_NONE, L"job6", 0, 0)); Job job7; ASSERT_EQ(ERROR_BAD_ARGUMENTS, job7.Init( - static_cast<JobLevel>(JOB_NONE+1), L"job7", 0)); + static_cast<JobLevel>(JOB_NONE+1), L"job7", 0, 0)); } // Tests the method "AssignProcessToJob". TEST(JobTest, ProcessInJob) { // Create the job. Job job; - ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_UNPROTECTED, L"job_test_process", 0)); + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_UNPROTECTED, L"job_test_process", 0, + 0)); BOOL result = FALSE; diff --git a/chromium/sandbox/win/src/named_pipe_dispatcher.cc b/chromium/sandbox/win/src/named_pipe_dispatcher.cc index da4045c2745..daf88f805dc 100644 --- a/chromium/sandbox/win/src/named_pipe_dispatcher.cc +++ b/chromium/sandbox/win/src/named_pipe_dispatcher.cc @@ -55,9 +55,9 @@ bool NamedPipeDispatcher::CreateNamedPipe( iter != paths.end(); ++iter) { base::SplitString(*iter, '\\', &innerpaths); for (std::vector<base::string16>::const_iterator iter2 = innerpaths.begin(); - iter2 != innerpaths.end(); ++iter2) { + iter2 != innerpaths.end(); ++iter2) { if (*iter2 == L"..") - return true; + return true; } } diff --git a/chromium/sandbox/win/src/named_pipe_policy_test.cc b/chromium/sandbox/win/src/named_pipe_policy_test.cc index fe8c71f70b9..813cf1f4640 100644 --- a/chromium/sandbox/win/src/named_pipe_policy_test.cc +++ b/chromium/sandbox/win/src/named_pipe_policy_test.cc @@ -109,9 +109,10 @@ TEST(NamedPipePolicyTest, CreatePipeCanonicalization) { // disable all string parsing and to send the string that follows it straight // to the file system." // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx - wchar_t* argv[2] = { L"\\\\?\\pipe\\test\\..\\bleh", - L"\\Device\\NamedPipe\\test" }; - EXPECT_EQ(SBOX_TEST_SUCCEEDED, NamedPipe_Create(2, argv)); + const wchar_t* argv[2] = { L"\\\\?\\pipe\\test\\..\\bleh", + L"\\Device\\NamedPipe\\test" }; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + NamedPipe_Create(2, const_cast<wchar_t**>(argv))); } // The same test as CreatePipe but this time using strict interceptions. diff --git a/chromium/sandbox/win/src/nt_internals.h b/chromium/sandbox/win/src/nt_internals.h index e0c74ac1fb4..8b22e0e9a3d 100644 --- a/chromium/sandbox/win/src/nt_internals.h +++ b/chromium/sandbox/win/src/nt_internals.h @@ -25,6 +25,7 @@ typedef LONG NTSTATUS; #define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L) #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) #define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L) +#define STATUS_OBJECT_NAME_COLLISION ((NTSTATUS)0xC0000035L) #define STATUS_PROCEDURE_NOT_FOUND ((NTSTATUS)0xC000007AL) #define STATUS_INVALID_IMAGE_FORMAT ((NTSTATUS)0xC000007BL) #define STATUS_NO_TOKEN ((NTSTATUS)0xC000007CL) @@ -125,6 +126,15 @@ typedef struct _IO_STATUS_BLOCK { #define FILE_OPEN_NO_RECALL 0x00400000 #define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 +// Create/open result values. These are the disposition values returned on the +// io status information. +#define FILE_SUPERSEDED 0x00000000 +#define FILE_OPENED 0x00000001 +#define FILE_CREATED 0x00000002 +#define FILE_OVERWRITTEN 0x00000003 +#define FILE_EXISTS 0x00000004 +#define FILE_DOES_NOT_EXIST 0x00000005 + typedef NTSTATUS (WINAPI *NtCreateFileFunction)( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, diff --git a/chromium/sandbox/win/src/policy_engine_opcodes.cc b/chromium/sandbox/win/src/policy_engine_opcodes.cc index e8a39ed6122..5a03ea13594 100644 --- a/chromium/sandbox/win/src/policy_engine_opcodes.cc +++ b/chromium/sandbox/win/src/policy_engine_opcodes.cc @@ -162,7 +162,7 @@ PolicyOpcode* OpcodeFactory::MakeOpUlongMatchRange(int16 selected_param, unsigned long upper_bound, uint32 options) { if (lower_bound > upper_bound) { - return false; + return NULL; } PolicyOpcode* opcode = MakeBase(OP_ULONG_MATCH_RANGE, options, selected_param); diff --git a/chromium/sandbox/win/src/policy_engine_unittest.cc b/chromium/sandbox/win/src/policy_engine_unittest.cc index e6c343500fe..f96ff37d9f1 100644 --- a/chromium/sandbox/win/src/policy_engine_unittest.cc +++ b/chromium/sandbox/win/src/policy_engine_unittest.cc @@ -60,7 +60,7 @@ TEST(PolicyEngineTest, Rules1) { opcode_maker.MakeOpAction(FAKE_ACCESS_DENIED, kPolNone); policy->opcode_count = 7; - wchar_t* filename = L"c:\\Documents and Settings\\Microsoft\\BLAH.txt"; + const wchar_t* filename = L"c:\\Documents and Settings\\Microsoft\\BLAH.txt"; unsigned long creation_mode = OPEN_EXISTING; unsigned long flags = FILE_ATTRIBUTE_NORMAL; void* security_descriptor = NULL; diff --git a/chromium/sandbox/win/src/policy_low_level_unittest.cc b/chromium/sandbox/win/src/policy_low_level_unittest.cc index 2b5d0f7a6ec..c9424c6cf4f 100644 --- a/chromium/sandbox/win/src/policy_low_level_unittest.cc +++ b/chromium/sandbox/win/src/policy_low_level_unittest.cc @@ -62,7 +62,7 @@ TEST(PolicyEngineTest, SimpleStrMatch) { EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); EXPECT_TRUE(policyGen.Done()); - wchar_t* filename = L"Z:\\Directory\\domo.txt"; + const wchar_t* filename = L"Z:\\Directory\\domo.txt"; POLPARAMS_BEGIN(eval_params) POLPARAM(filename) // Argument 0 @@ -95,7 +95,7 @@ TEST(PolicyEngineTest, SimpleIfNotStrMatch) { EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); EXPECT_TRUE(policyGen.Done()); - wchar_t* filename = NULL; + const wchar_t* filename = NULL; POLPARAMS_BEGIN(eval_params) POLPARAM(filename) // Argument 0 POLPARAMS_END; @@ -133,7 +133,7 @@ TEST(PolicyEngineTest, SimpleIfNotStrMatchWild1) { EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); EXPECT_TRUE(policyGen.Done()); - wchar_t* filename = NULL; + const wchar_t* filename = NULL; POLPARAMS_BEGIN(eval_params) POLPARAM(filename) // Argument 0 POLPARAMS_END; @@ -166,7 +166,7 @@ TEST(PolicyEngineTest, SimpleIfNotStrMatchWild2) { EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); EXPECT_TRUE(policyGen.Done()); - wchar_t* filename = NULL; + const wchar_t* filename = NULL; POLPARAMS_BEGIN(eval_params) POLPARAM(filename) // Argument 0 POLPARAMS_END; @@ -205,7 +205,7 @@ TEST(PolicyEngineTest, IfNotStrMatchTwoRulesWild1) { EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); EXPECT_TRUE(policyGen.Done()); - wchar_t* filename = NULL; + const wchar_t* filename = NULL; unsigned long access = 0; POLPARAMS_BEGIN(eval_params) POLPARAM(filename) // Argument 0 @@ -254,7 +254,7 @@ TEST(PolicyEngineTest, IfNotStrMatchTwoRulesWild2) { EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); EXPECT_TRUE(policyGen.Done()); - wchar_t* filename = NULL; + const wchar_t* filename = NULL; unsigned long access = 0; unsigned long sharing = 66; @@ -328,7 +328,7 @@ TEST(PolicyEngineTest, OneRuleTest) { EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr)); EXPECT_TRUE(policyGen.Done()); - wchar_t* filename = L"c:\\Documents and Settings\\Microsoft\\BLAH.txt"; + const wchar_t* filename = L"c:\\Documents and Settings\\Microsoft\\BLAH.txt"; unsigned long creation_mode = OPEN_EXISTING; unsigned long flags = FILE_ATTRIBUTE_NORMAL; void* security_descriptor = NULL; @@ -484,7 +484,7 @@ TEST(PolicyEngineTest, ThreeRulesTest) { // Test the policy evaluation. - wchar_t* filename = L""; + const wchar_t* filename = L""; unsigned long creation_mode = OPEN_EXISTING; unsigned long flags = FILE_ATTRIBUTE_NORMAL; void* security_descriptor = NULL; @@ -588,7 +588,7 @@ TEST(PolicyEngineTest, PolicyRuleCopyConstructorTwoStrings) { EXPECT_TRUE(policyGen.AddRule(2, &pr_copy)); EXPECT_TRUE(policyGen.Done()); - wchar_t* name = NULL; + const wchar_t* name = NULL; POLPARAMS_BEGIN(eval_params) POLPARAM(name) POLPARAMS_END; diff --git a/chromium/sandbox/win/src/policy_opcodes_unittest.cc b/chromium/sandbox/win/src/policy_opcodes_unittest.cc index e1a7ad63d20..c69aad8c84e 100644 --- a/chromium/sandbox/win/src/policy_opcodes_unittest.cc +++ b/chromium/sandbox/win/src/policy_opcodes_unittest.cc @@ -10,9 +10,9 @@ #define INIT_GLOBAL_RTL(member) \ - g_nt.##member = reinterpret_cast<##member##Function>( \ + g_nt.member = reinterpret_cast<member##Function>( \ ::GetProcAddress(ntdll, #member)); \ - if (NULL == g_nt.##member) \ + if (NULL == g_nt.member) \ return false namespace sandbox { @@ -185,8 +185,7 @@ TEST(PolicyEngineTest, IntegerOpcodes) { OpcodeFactory opcode_maker(memory, sizeof(memory)); // Test basic match for unsigned longs 42 == 42 and 42 != 113377. - PolicyOpcode* op_m42 = opcode_maker.MakeOpNumberMatch(0, unsigned long(42), - kPolNone); + PolicyOpcode* op_m42 = opcode_maker.MakeOpNumberMatch(0, 42UL, kPolNone); EXPECT_EQ(EVAL_TRUE, op_m42->Evaluate(&pp_num1, 1, NULL)); EXPECT_EQ(EVAL_FALSE, op_m42->Evaluate(&pp_num2, 1, NULL)); EXPECT_EQ(EVAL_ERROR, op_m42->Evaluate(&pp_wrong1, 1, NULL)); diff --git a/chromium/sandbox/win/src/policy_target_test.cc b/chromium/sandbox/win/src/policy_target_test.cc index dba670a9989..268f8d627a9 100644 --- a/chromium/sandbox/win/src/policy_target_test.cc +++ b/chromium/sandbox/win/src/policy_target_test.cc @@ -243,6 +243,7 @@ TEST(PolicyTargetTest, DesktopPolicy) { PROCESS_INFORMATION temp_process_info = {}; result = broker->SpawnTarget(prog_name, arguments.c_str(), policy, &temp_process_info); + base::string16 desktop_name = policy->GetAlternateDesktop(); policy->Release(); EXPECT_EQ(SBOX_ALL_OK, result); @@ -256,7 +257,6 @@ TEST(PolicyTargetTest, DesktopPolicy) { EXPECT_NE(::GetThreadDesktop(target.thread_id()), ::GetThreadDesktop(::GetCurrentThreadId())); - base::string16 desktop_name = policy->GetAlternateDesktop(); HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, FALSE, DESKTOP_ENUMERATE); EXPECT_TRUE(NULL != desk); EXPECT_TRUE(::CloseDesktop(desk)); @@ -306,6 +306,7 @@ TEST(PolicyTargetTest, WinstaPolicy) { PROCESS_INFORMATION temp_process_info = {}; result = broker->SpawnTarget(prog_name, arguments.c_str(), policy, &temp_process_info); + base::string16 desktop_name = policy->GetAlternateDesktop(); policy->Release(); EXPECT_EQ(SBOX_ALL_OK, result); @@ -319,7 +320,6 @@ TEST(PolicyTargetTest, WinstaPolicy) { EXPECT_NE(::GetThreadDesktop(target.thread_id()), ::GetThreadDesktop(::GetCurrentThreadId())); - base::string16 desktop_name = policy->GetAlternateDesktop(); ASSERT_FALSE(desktop_name.empty()); // Make sure there is a backslash, for the window station name. diff --git a/chromium/sandbox/win/src/process_mitigations.cc b/chromium/sandbox/win/src/process_mitigations.cc index d1130475c35..5e242f3abfa 100644 --- a/chromium/sandbox/win/src/process_mitigations.cc +++ b/chromium/sandbox/win/src/process_mitigations.cc @@ -8,7 +8,6 @@ #include "base/win/windows_version.h" #include "sandbox/win/src/nt_internals.h" -#include "sandbox/win/src/sandbox_utils.h" #include "sandbox/win/src/win_utils.h" namespace { @@ -32,10 +31,6 @@ bool ApplyProcessMitigationsToCurrentProcess(MitigationFlags flags) { if (!CanSetProcessMitigationsPostStartup(flags)) return false; - // We can't apply anything before Win XP, so just return cleanly. - if (!IsXPSP2OrLater()) - return true; - base::win::Version version = base::win::GetVersion(); HMODULE module = ::GetModuleHandleA("kernel32.dll"); @@ -250,28 +245,23 @@ void ConvertProcessMitigationsToPolicy(MitigationFlags flags, } MitigationFlags FilterPostStartupProcessMitigations(MitigationFlags flags) { - // Anything prior to XP SP2. - if (!IsXPSP2OrLater()) - return 0; - base::win::Version version = base::win::GetVersion(); // Windows XP SP2+. if (version < base::win::VERSION_VISTA) { return flags & (MITIGATION_DEP | MITIGATION_DEP_NO_ATL_THUNK); + } // Windows Vista if (version < base::win::VERSION_WIN7) { - return flags & (MITIGATION_DEP | - MITIGATION_DEP_NO_ATL_THUNK | - MITIGATION_BOTTOM_UP_ASLR | + return flags & (MITIGATION_BOTTOM_UP_ASLR | MITIGATION_DLL_SEARCH_ORDER | MITIGATION_HEAP_TERMINATE); } - // Windows 7 and Vista. - } else if (version < base::win::VERSION_WIN8) { + // Windows 7. + if (version < base::win::VERSION_WIN8) { return flags & (MITIGATION_BOTTOM_UP_ASLR | MITIGATION_DLL_SEARCH_ORDER | MITIGATION_HEAP_TERMINATE); @@ -318,7 +308,6 @@ bool CanSetProcessMitigationsPostStartup(MitigationFlags flags) { MITIGATION_RELOCATE_IMAGE_REQUIRED | MITIGATION_BOTTOM_UP_ASLR | MITIGATION_STRICT_HANDLE_CHECKS | - MITIGATION_WIN32K_DISABLE | MITIGATION_EXTENSION_DLL_DISABLE | MITIGATION_DLL_SEARCH_ORDER)); } @@ -326,7 +315,6 @@ bool CanSetProcessMitigationsPostStartup(MitigationFlags flags) { bool CanSetProcessMitigationsPreStartup(MitigationFlags flags) { // These mitigations cannot be enabled prior to startup. return !(flags & (MITIGATION_STRICT_HANDLE_CHECKS | - MITIGATION_WIN32K_DISABLE | MITIGATION_DLL_SEARCH_ORDER)); } diff --git a/chromium/sandbox/win/src/process_mitigations_test.cc b/chromium/sandbox/win/src/process_mitigations_test.cc index f1274c54406..e6062564fe0 100644 --- a/chromium/sandbox/win/src/process_mitigations_test.cc +++ b/chromium/sandbox/win/src/process_mitigations_test.cc @@ -10,7 +10,6 @@ #include "sandbox/win/src/process_mitigations.h" #include "sandbox/win/src/sandbox.h" #include "sandbox/win/src/sandbox_factory.h" -#include "sandbox/win/src/sandbox_utils.h" #include "sandbox/win/src/target_services.h" #include "sandbox/win/src/win_utils.h" #include "sandbox/win/tests/common/controller.h" @@ -89,7 +88,6 @@ SBOX_TESTS_COMMAND int CheckWin8(int argc, wchar_t **argv) { reinterpret_cast<GetProcessMitigationPolicyFunction>( ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "GetProcessMitigationPolicy")); - if (!get_process_mitigation_policy) return SBOX_TEST_NOT_FOUND; @@ -104,9 +102,6 @@ SBOX_TESTS_COMMAND int CheckWin8(int argc, wchar_t **argv) { if (!CheckWin8StrictHandlePolicy()) return SBOX_TEST_THIRD_ERROR; - if (!CheckWin8Win32CallPolicy()) - return SBOX_TEST_FOURTH_ERROR; - if (!CheckWin8DllExtensionPolicy()) return SBOX_TEST_FIFTH_ERROR; @@ -130,8 +125,7 @@ TEST(ProcessMitigationsTest, CheckWin8) { EXPECT_EQ(policy->SetProcessMitigations(mitigations), SBOX_ALL_OK); - mitigations |= MITIGATION_STRICT_HANDLE_CHECKS | - MITIGATION_WIN32K_DISABLE; + mitigations |= MITIGATION_STRICT_HANDLE_CHECKS; EXPECT_EQ(policy->SetDelayedProcessMitigations(mitigations), SBOX_ALL_OK); @@ -188,7 +182,7 @@ SBOX_TESTS_COMMAND int CheckDep(int argc, wchar_t **argv) { #if !defined(_WIN64) // DEP is always enabled on 64-bit. TEST(ProcessMitigationsTest, CheckDep) { - if (!IsXPSP2OrLater() || base::win::GetVersion() > base::win::VERSION_WIN7) + if (base::win::GetVersion() > base::win::VERSION_WIN7) return; TestRunner runner; @@ -203,5 +197,52 @@ TEST(ProcessMitigationsTest, CheckDep) { } #endif +SBOX_TESTS_COMMAND int CheckWin8Lockdown(int argc, wchar_t **argv) { + get_process_mitigation_policy = + reinterpret_cast<GetProcessMitigationPolicyFunction>( + ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), + "GetProcessMitigationPolicy")); + if (!get_process_mitigation_policy) + return SBOX_TEST_NOT_FOUND; + + if (!CheckWin8Win32CallPolicy()) + return SBOX_TEST_FIRST_ERROR; + return SBOX_TEST_SUCCEEDED; +} + +// This test validates that setting the MITIGATION_WIN32K_DISABLE mitigation on +// the target process causes the launch to fail in process initialization. +// The test process itself links against user32/gdi32. +TEST(ProcessMitigationsTest, CheckWin8Win32KLockDownFailure) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + TestRunner runner; + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + EXPECT_EQ(policy->SetProcessMitigations(MITIGATION_WIN32K_DISABLE), + SBOX_ALL_OK); + EXPECT_NE(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckWin8Lockdown")); +} + +// This test validates that setting the MITIGATION_WIN32K_DISABLE mitigation +// along with the policy to fake user32 and gdi32 initialization successfully +// launches the target process. +// The test process itself links against user32/gdi32. +TEST(ProcessMitigationsTest, CheckWin8Win32KLockDownSuccess) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + TestRunner runner; + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + EXPECT_EQ(policy->SetProcessMitigations(MITIGATION_WIN32K_DISABLE), + SBOX_ALL_OK); + EXPECT_EQ(policy->AddRule(sandbox::TargetPolicy::SUBSYS_WIN32K_LOCKDOWN, + sandbox::TargetPolicy::FAKE_USER_GDI_INIT, NULL), + sandbox::SBOX_ALL_OK); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckWin8Lockdown")); +} + } // namespace sandbox diff --git a/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.cc b/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.cc new file mode 100644 index 00000000000..e426084f861 --- /dev/null +++ b/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.cc @@ -0,0 +1,57 @@ +// 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/win/src/process_mitigations_win32k_dispatcher.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/process_mitigations_win32k_interception.h" + +namespace sandbox { + +ProcessMitigationsWin32KDispatcher::ProcessMitigationsWin32KDispatcher( + PolicyBase* policy_base) + : policy_base_(policy_base) { +} + +bool ProcessMitigationsWin32KDispatcher::SetupService( + InterceptionManager* manager, int service) { + if (!(policy_base_->GetProcessMitigations() & + sandbox::MITIGATION_WIN32K_DISABLE)) { + return false; + } + + switch (service) { + case IPC_GDI_GDIDLLINITIALIZE_TAG: { + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GdiDllInitialize, + GDIINITIALIZE_ID, 12)) { + return false; + } + return true; + } + + case IPC_GDI_GETSTOCKOBJECT_TAG: { + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetStockObject, + GETSTOCKOBJECT_ID, 8)) { + return false; + } + return true; + } + + case IPC_USER_REGISTERCLASSW_TAG: { + if (!INTERCEPT_EAT(manager, L"user32.dll", RegisterClassW, + REGISTERCLASSW_ID, 8)) { + return false; + } + return true; + } + + default: + break; + } + return false; +} + +} // namespace sandbox + diff --git a/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.h b/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.h new file mode 100644 index 00000000000..65c9f77e559 --- /dev/null +++ b/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.h @@ -0,0 +1,31 @@ +// 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_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ + +#include "base/basictypes.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class sets up intercepts for the Win32K lockdown policy which is set +// on Windows 8 and beyond. +class ProcessMitigationsWin32KDispatcher : public Dispatcher { + public: + explicit ProcessMitigationsWin32KDispatcher(PolicyBase* policy_base); + ~ProcessMitigationsWin32KDispatcher() {} + + // Dispatcher interface. + virtual bool SetupService(InterceptionManager* manager, int service); + + private: + PolicyBase* policy_base_; + + DISALLOW_COPY_AND_ASSIGN(ProcessMitigationsWin32KDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ diff --git a/chromium/sandbox/win/src/process_mitigations_win32k_interception.cc b/chromium/sandbox/win/src/process_mitigations_win32k_interception.cc new file mode 100644 index 00000000000..ee24fbf434c --- /dev/null +++ b/chromium/sandbox/win/src/process_mitigations_win32k_interception.cc @@ -0,0 +1,29 @@ +// 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/win/src/process_mitigations_win32k_interception.h" + +namespace sandbox { + +BOOL WINAPI TargetGdiDllInitialize( + GdiDllInitializeFunction orig_gdi_dll_initialize, + HANDLE dll, + DWORD reason) { + return TRUE; +} + +HGDIOBJ WINAPI TargetGetStockObject( + GetStockObjectFunction orig_get_stock_object, + int object) { + return reinterpret_cast<HGDIOBJ>(NULL); +} + +ATOM WINAPI TargetRegisterClassW( + RegisterClassWFunction orig_register_class_function, + const WNDCLASS* wnd_class) { + return TRUE; +} + +} // namespace sandbox + diff --git a/chromium/sandbox/win/src/process_mitigations_win32k_interception.h b/chromium/sandbox/win/src/process_mitigations_win32k_interception.h new file mode 100644 index 00000000000..bf7b551227a --- /dev/null +++ b/chromium/sandbox/win/src/process_mitigations_win32k_interception.h @@ -0,0 +1,46 @@ +// 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_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ + +#include <windows.h> +#include "base/basictypes.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +typedef BOOL (WINAPI* GdiDllInitializeFunction) ( + HANDLE dll, + DWORD reason, + LPVOID reserved); + +typedef HGDIOBJ (WINAPI *GetStockObjectFunction) (int object); + +typedef ATOM (WINAPI *RegisterClassWFunction) (const WNDCLASS* wnd_class); + +// Interceptor for the GdiDllInitialize function. +SANDBOX_INTERCEPT BOOL WINAPI TargetGdiDllInitialize( + GdiDllInitializeFunction orig_gdi_dll_initialize, + HANDLE dll, + DWORD reason); + +// Interceptor for the GetStockObject function. +SANDBOX_INTERCEPT HGDIOBJ WINAPI TargetGetStockObject( + GetStockObjectFunction orig_get_stock_object, + int object); + +// Interceptor for the RegisterClassW function. +SANDBOX_INTERCEPT ATOM WINAPI TargetRegisterClassW( + RegisterClassWFunction orig_register_class_function, + const WNDCLASS* wnd_class); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ + diff --git a/chromium/sandbox/win/src/process_mitigations_win32k_policy.cc b/chromium/sandbox/win/src/process_mitigations_win32k_policy.cc new file mode 100644 index 00000000000..af18c5413c2 --- /dev/null +++ b/chromium/sandbox/win/src/process_mitigations_win32k_policy.cc @@ -0,0 +1,24 @@ +// 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/win/src/process_mitigations_win32k_policy.h" + +namespace sandbox { + +bool ProcessMitigationsWin32KLockdownPolicy::GenerateRules( + const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + PolicyRule rule(FAKE_SUCCESS); + if (!policy->AddRule(IPC_GDI_GDIDLLINITIALIZE_TAG, &rule)) + return false; + if (!policy->AddRule(IPC_GDI_GETSTOCKOBJECT_TAG, &rule)) + return false; + if (!policy->AddRule(IPC_USER_REGISTERCLASSW_TAG, &rule)) + return false; + return true; +} + +} // namespace sandbox + diff --git a/chromium/sandbox/win/src/process_mitigations_win32k_policy.h b/chromium/sandbox/win/src/process_mitigations_win32k_policy.h new file mode 100644 index 00000000000..078ed2bee15 --- /dev/null +++ b/chromium/sandbox/win/src/process_mitigations_win32k_policy.h @@ -0,0 +1,35 @@ +// 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_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ + +#include "base/basictypes.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum EvalResult; + +// This class centralizes most of the knowledge related to the process +// mitigations Win32K lockdown policy. +class ProcessMitigationsWin32KLockdownPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for the Win32K process mitigation policy. + // name is the object name, semantics is the desired semantics for the + // open or create and policy is the policy generator to which the rules are + // going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ + + diff --git a/chromium/sandbox/win/src/process_policy_test.cc b/chromium/sandbox/win/src/process_policy_test.cc index a03e0bee5e0..af64f14f791 100644 --- a/chromium/sandbox/win/src/process_policy_test.cc +++ b/chromium/sandbox/win/src/process_policy_test.cc @@ -21,10 +21,10 @@ namespace { // While the shell API provides better calls than this home brew function // we use GetSystemWindowsDirectoryW which does not query the registry so // it is safe to use after revert. -string16 MakeFullPathToSystem32(const wchar_t* name) { +base::string16 MakeFullPathToSystem32(const wchar_t* name) { wchar_t windows_path[MAX_PATH] = {0}; ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH); - string16 full_path(windows_path); + base::string16 full_path(windows_path); if (full_path.empty()) { return full_path; } @@ -35,8 +35,8 @@ string16 MakeFullPathToSystem32(const wchar_t* name) { // Creates a process with the |exe| and |command| parameter using the // unicode and ascii version of the api. -sandbox::SboxTestResult CreateProcessHelper(const string16& exe, - const string16& command) { +sandbox::SboxTestResult CreateProcessHelper(const base::string16& exe, + const base::string16& command) { base::win::ScopedProcessInformation pi; STARTUPINFOW si = {sizeof(si)}; @@ -109,10 +109,10 @@ SBOX_TESTS_COMMAND int Process_RunApp1(int argc, wchar_t **argv) { if ((NULL == argv) || (NULL == argv[0])) { return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; } - string16 path = MakeFullPathToSystem32(argv[0]); + base::string16 path = MakeFullPathToSystem32(argv[0]); // TEST 1: Try with the path in the app_name. - return CreateProcessHelper(path, string16()); + return CreateProcessHelper(path, base::string16()); } SBOX_TESTS_COMMAND int Process_RunApp2(int argc, wchar_t **argv) { @@ -122,13 +122,13 @@ SBOX_TESTS_COMMAND int Process_RunApp2(int argc, wchar_t **argv) { if ((NULL == argv) || (NULL == argv[0])) { return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; } - string16 path = MakeFullPathToSystem32(argv[0]); + base::string16 path = MakeFullPathToSystem32(argv[0]); // TEST 2: Try with the path in the cmd_line. - string16 cmd_line = L"\""; + base::string16 cmd_line = L"\""; cmd_line += path; cmd_line += L"\""; - return CreateProcessHelper(string16(), cmd_line); + return CreateProcessHelper(base::string16(), cmd_line); } SBOX_TESTS_COMMAND int Process_RunApp3(int argc, wchar_t **argv) { @@ -140,7 +140,7 @@ SBOX_TESTS_COMMAND int Process_RunApp3(int argc, wchar_t **argv) { } // TEST 3: Try file name in the cmd_line. - return CreateProcessHelper(string16(), argv[0]); + return CreateProcessHelper(base::string16(), argv[0]); } SBOX_TESTS_COMMAND int Process_RunApp4(int argc, wchar_t **argv) { @@ -152,7 +152,7 @@ SBOX_TESTS_COMMAND int Process_RunApp4(int argc, wchar_t **argv) { } // TEST 4: Try file name in the app_name and current directory sets correctly. - string16 system32 = MakeFullPathToSystem32(L""); + base::string16 system32 = MakeFullPathToSystem32(L""); wchar_t current_directory[MAX_PATH + 1]; int result4; bool test_succeeded = false; @@ -164,7 +164,7 @@ SBOX_TESTS_COMMAND int Process_RunApp4(int argc, wchar_t **argv) { current_directory[ret] = L'\\'; current_directory[ret+1] = L'\0'; if (::SetCurrentDirectory(system32.c_str())) { - result4 = CreateProcessHelper(argv[0], string16()); + result4 = CreateProcessHelper(argv[0], base::string16()); if (::SetCurrentDirectory(current_directory)) { test_succeeded = true; } @@ -185,13 +185,13 @@ SBOX_TESTS_COMMAND int Process_RunApp5(int argc, wchar_t **argv) { if ((NULL == argv) || (NULL == argv[0])) { return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; } - string16 path = MakeFullPathToSystem32(argv[0]); + base::string16 path = MakeFullPathToSystem32(argv[0]); // TEST 5: Try with the path in the cmd_line and arguments. - string16 cmd_line = L"\""; + base::string16 cmd_line = L"\""; cmd_line += path; cmd_line += L"\" /I"; - return CreateProcessHelper(string16(), cmd_line); + return CreateProcessHelper(base::string16(), cmd_line); } SBOX_TESTS_COMMAND int Process_RunApp6(int argc, wchar_t **argv) { @@ -203,9 +203,9 @@ SBOX_TESTS_COMMAND int Process_RunApp6(int argc, wchar_t **argv) { } // TEST 6: Try with the file_name in the cmd_line and arguments. - string16 cmd_line = argv[0]; + base::string16 cmd_line = argv[0]; cmd_line += L" /I"; - return CreateProcessHelper(string16(), cmd_line); + return CreateProcessHelper(base::string16(), cmd_line); } // Creates a process and checks if it's possible to get a handle to it's token. @@ -216,7 +216,7 @@ SBOX_TESTS_COMMAND int Process_GetChildProcessToken(int argc, wchar_t **argv) { if ((NULL == argv) || (NULL == argv[0])) return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; - string16 path = MakeFullPathToSystem32(argv[0]); + base::string16 path = MakeFullPathToSystem32(argv[0]); STARTUPINFOW si = {sizeof(si)}; @@ -284,8 +284,8 @@ TEST(ProcessPolicyTest, TestAllAccess) { TEST(ProcessPolicyTest, CreateProcessAW) { TestRunner runner; - string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); - string16 system32 = MakeFullPathToSystem32(L""); + base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); + base::string16 system32 = MakeFullPathToSystem32(L""); ASSERT_TRUE(!exe_path.empty()); EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, TargetPolicy::PROCESS_MIN_EXEC, @@ -339,7 +339,7 @@ TEST(ProcessPolicyTest, OpenToken) { TEST(ProcessPolicyTest, TestGetProcessTokenMinAccess) { TestRunner runner; - string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); + base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); ASSERT_TRUE(!exe_path.empty()); EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, TargetPolicy::PROCESS_MIN_EXEC, @@ -351,7 +351,7 @@ TEST(ProcessPolicyTest, TestGetProcessTokenMinAccess) { TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccess) { TestRunner runner(JOB_UNPROTECTED, USER_INTERACTIVE, USER_INTERACTIVE); - string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); + base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); ASSERT_TRUE(!exe_path.empty()); EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, TargetPolicy::PROCESS_ALL_EXEC, @@ -363,7 +363,7 @@ TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccess) { TEST(ProcessPolicyTest, TestGetProcessTokenMinAccessNoJob) { TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); - string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); + base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); ASSERT_TRUE(!exe_path.empty()); EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, TargetPolicy::PROCESS_MIN_EXEC, @@ -375,7 +375,7 @@ TEST(ProcessPolicyTest, TestGetProcessTokenMinAccessNoJob) { TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccessNoJob) { TestRunner runner(JOB_NONE, USER_INTERACTIVE, USER_INTERACTIVE); - string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); + base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); ASSERT_TRUE(!exe_path.empty()); EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, TargetPolicy::PROCESS_ALL_EXEC, diff --git a/chromium/sandbox/win/src/process_thread_interception.cc b/chromium/sandbox/win/src/process_thread_interception.cc index d351ee546c8..45926bc5f63 100644 --- a/chromium/sandbox/win/src/process_thread_interception.cc +++ b/chromium/sandbox/win/src/process_thread_interception.cc @@ -400,50 +400,4 @@ BOOL WINAPI TargetCreateProcessA(CreateProcessAFunction orig_CreateProcessA, return FALSE; } -// Creates a thread without registering with CSRSS. This is required if we -// closed the CSRSS ALPC port after lockdown. -HANDLE WINAPI TargetCreateThread(CreateThreadFunction orig_CreateThread, - LPSECURITY_ATTRIBUTES thread_attributes, - SIZE_T stack_size, - LPTHREAD_START_ROUTINE start_address, - PVOID parameter, - DWORD creation_flags, - LPDWORD thread_id) { -// Try the normal CreateThread; switch to RtlCreateUserThread if needed. - static bool use_create_thread = true; - HANDLE thread; - if (use_create_thread) { - thread = orig_CreateThread(thread_attributes, stack_size, start_address, - parameter, creation_flags, thread_id); - if (thread) - return thread; - } - - PSECURITY_DESCRIPTOR sd = - thread_attributes ? thread_attributes->lpSecurityDescriptor : NULL; - CLIENT_ID client_id; - - NTSTATUS result = g_nt.RtlCreateUserThread(NtCurrentProcess, sd, - creation_flags & CREATE_SUSPENDED, - 0, stack_size, 0, start_address, - parameter, &thread, &client_id); - if (!NT_SUCCESS(result)) - return 0; - - // CSRSS is closed if we got here, so use RtlCreateUserThread from here on. - use_create_thread = false; - if (thread_id) - *thread_id = HandleToUlong(client_id.UniqueThread); - return thread; -} - -// Cache the default LCID to avoid pinging CSRSS after lockdown. -// TODO(jschuh): This approach will miss a default locale changes after -// lockdown. In the future we may want to have the broker check instead. -LCID WINAPI TargetGetUserDefaultLCID( - GetUserDefaultLCIDFunction orig_GetUserDefaultLCID) { - static LCID default_lcid = orig_GetUserDefaultLCID(); - return default_lcid; -} - } // namespace sandbox diff --git a/chromium/sandbox/win/src/process_thread_interception.h b/chromium/sandbox/win/src/process_thread_interception.h index 7d2d533dafd..31dc231543d 100644 --- a/chromium/sandbox/win/src/process_thread_interception.h +++ b/chromium/sandbox/win/src/process_thread_interception.h @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. +// Copyright (c) 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. @@ -83,17 +83,6 @@ SANDBOX_INTERCEPT BOOL WINAPI TargetCreateProcessA( LPVOID environment, LPCSTR current_directory, LPSTARTUPINFOA startup_info, LPPROCESS_INFORMATION process_information); -// Interception of CreateThread in kernel32.dll. -SANDBOX_INTERCEPT HANDLE WINAPI TargetCreateThread( - CreateThreadFunction orig_CreateThread, - LPSECURITY_ATTRIBUTES thread_attributes, SIZE_T stack_size, - LPTHREAD_START_ROUTINE start_address, PVOID parameter, - DWORD creation_flags, LPDWORD thread_id); - -// Interception of GetUserDefaultLCID in kernel32.dll. -SANDBOX_INTERCEPT LCID WINAPI TargetGetUserDefaultLCID( - GetUserDefaultLCIDFunction orig_GetUserDefaultLCID); - } // extern "C" } // namespace sandbox diff --git a/chromium/sandbox/win/src/process_thread_policy.cc b/chromium/sandbox/win/src/process_thread_policy.cc index 85a2f978a93..f2029ba6819 100644 --- a/chromium/sandbox/win/src/process_thread_policy.cc +++ b/chromium/sandbox/win/src/process_thread_policy.cc @@ -227,7 +227,8 @@ DWORD ProcessPolicy::CreateProcessWAction(EvalResult eval_result, STARTUPINFO startup_info = {0}; startup_info.cb = sizeof(startup_info); - scoped_ptr_malloc<wchar_t> cmd_line(_wcsdup(command_line.c_str())); + scoped_ptr<wchar_t, base::FreeDeleter> + cmd_line(_wcsdup(command_line.c_str())); BOOL should_give_full_access = (GIVE_ALLACCESS == eval_result); if (!CreateProcessExWHelper(client_info.process, should_give_full_access, diff --git a/chromium/sandbox/win/src/restricted_token.cc b/chromium/sandbox/win/src/restricted_token.cc index 64973e98f32..3960926f4d7 100644 --- a/chromium/sandbox/win/src/restricted_token.cc +++ b/chromium/sandbox/win/src/restricted_token.cc @@ -14,7 +14,6 @@ namespace sandbox { unsigned RestrictedToken::Init(const HANDLE effective_token) { - DCHECK(!init_); if (init_) return ERROR_ALREADY_INITIALIZED; diff --git a/chromium/sandbox/win/src/restricted_token_unittest.cc b/chromium/sandbox/win/src/restricted_token_unittest.cc index 480106e8f35..8186f9c77c4 100644 --- a/chromium/sandbox/win/src/restricted_token_unittest.cc +++ b/chromium/sandbox/win/src/restricted_token_unittest.cc @@ -408,8 +408,8 @@ TEST(RestrictedTokenTest, DeletePrivilege) { // elements in the restricting sids list has to be equal. void CheckRestrictingSid(const ATL::CAccessToken &restricted_token, ATL::CSid sid, int count) { - DWORD length = 1000; - BYTE *memory = new BYTE[1000]; + DWORD length = 8192; + BYTE *memory = new BYTE[length]; TOKEN_GROUPS *groups = reinterpret_cast<TOKEN_GROUPS*>(memory); ASSERT_TRUE(::GetTokenInformation(restricted_token.GetHandle(), TokenRestrictedSids, @@ -530,8 +530,8 @@ TEST(RestrictedTokenTest, AddMultipleRestrictingSids) { ATL::CSid session; restricted_token.GetLogonSid(&session); - DWORD length = 1000; - BYTE *memory = new BYTE[1000]; + DWORD length = 8192; + BYTE *memory = new BYTE[length]; TOKEN_GROUPS *groups = reinterpret_cast<TOKEN_GROUPS*>(memory); ASSERT_TRUE(::GetTokenInformation(restricted_token.GetHandle(), TokenRestrictedSids, @@ -577,9 +577,6 @@ TEST(RestrictedTokenTest, AddAllSidToRestrictingSids) { CheckRestrictingSid(restricted_token, user, -1); } -// Test to be executed only in release because they are triggering DCHECKs. -#ifndef _DEBUG - // Checks the error code when the object is initialized twice. TEST(RestrictedTokenTest, DoubleInit) { RestrictedToken token; @@ -588,6 +585,4 @@ TEST(RestrictedTokenTest, DoubleInit) { ASSERT_EQ(ERROR_ALREADY_INITIALIZED, token.Init(NULL)); } -#endif - } // namespace sandbox diff --git a/chromium/sandbox/win/src/restricted_token_utils.cc b/chromium/sandbox/win/src/restricted_token_utils.cc index f3b18591e8f..93b212efaf3 100644 --- a/chromium/sandbox/win/src/restricted_token_utils.cc +++ b/chromium/sandbox/win/src/restricted_token_utils.cc @@ -146,7 +146,7 @@ DWORD StartRestrictedProcessInJob(wchar_t *command_line, JobLevel job_level, HANDLE *const job_handle_ret) { Job job; - DWORD err_code = job.Init(job_level, NULL, 0); + DWORD err_code = job.Init(job_level, NULL, 0, 0); if (ERROR_SUCCESS != err_code) return err_code; diff --git a/chromium/sandbox/win/src/sandbox.cc b/chromium/sandbox/win/src/sandbox.cc index d26daa434e9..984dfecec8a 100644 --- a/chromium/sandbox/win/src/sandbox.cc +++ b/chromium/sandbox/win/src/sandbox.cc @@ -12,8 +12,7 @@ namespace sandbox { // The section for IPC and policy. SANDBOX_INTERCEPT HANDLE g_shared_section; - -static bool s_is_broker = false; +static bool s_is_broker = false; // GetBrokerServices: the current implementation relies on a shared section // that is created by the broker and opened by the target. @@ -42,3 +41,8 @@ TargetServices* SandboxFactory::GetTargetServices() { } } // namespace sandbox + +// Allows querying for whether the current process has been sandboxed. +extern "C" bool __declspec(dllexport) IsSandboxedProcess() { + return sandbox::g_shared_section != NULL; +} diff --git a/chromium/sandbox/win/src/sandbox_nt_util.cc b/chromium/sandbox/win/src/sandbox_nt_util.cc index 613d4859dd7..ed1d908ad69 100644 --- a/chromium/sandbox/win/src/sandbox_nt_util.cc +++ b/chromium/sandbox/win/src/sandbox_nt_util.cc @@ -525,14 +525,17 @@ bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, DWORD length, if (file_info->RootDirectory) return false; + static const wchar_t kPathPrefix[] = { L'\\', L'?', L'?', L'\\'}; + // Check if it starts with \\??\\. We don't support relative paths. - if (file_info->FileNameLength < 4 || file_info->FileNameLength > kuint16max) + if (file_info->FileNameLength < sizeof(kPathPrefix) || + file_info->FileNameLength > kuint16max) return false; - if (file_info->FileName[0] != L'\\' || - file_info->FileName[1] != L'?' || - file_info->FileName[2] != L'?' || - file_info->FileName[3] != L'\\') + if (file_info->FileName[0] != kPathPrefix[0] || + file_info->FileName[1] != kPathPrefix[1] || + file_info->FileName[2] != kPathPrefix[2] || + file_info->FileName[3] != kPathPrefix[3]) return false; return true; diff --git a/chromium/sandbox/win/src/sandbox_policy.h b/chromium/sandbox/win/src/sandbox_policy.h index a9f12451621..22a2049c2d1 100644 --- a/chromium/sandbox/win/src/sandbox_policy.h +++ b/chromium/sandbox/win/src/sandbox_policy.h @@ -26,7 +26,8 @@ class TargetPolicy { SUBSYS_PROCESS, // Creation of child processes. SUBSYS_REGISTRY, // Creation and opening of registry keys. SUBSYS_SYNC, // Creation of named sync objects. - SUBSYS_HANDLES // Duplication of handles to other processes. + SUBSYS_HANDLES, // Duplication of handles to other processes. + SUBSYS_WIN32K_LOCKDOWN // Win32K Lockdown related policy. }; // Allowable semantics when a rule is matched. @@ -52,7 +53,10 @@ class TargetPolicy { EVENTS_ALLOW_ANY, // Allows the creation of an event with full access. EVENTS_ALLOW_READONLY, // Allows opening an even with synchronize access. REG_ALLOW_READONLY, // Allows readonly access to a registry key. - REG_ALLOW_ANY // Allows read and write access to a registry key. + REG_ALLOW_ANY, // Allows read and write access to a registry key. + FAKE_USER_GDI_INIT // Fakes user32 and gdi32 initialization. This can + // be used to allow the DLLs to load and initialize + // even if the process cannot access that subsystem. }; // Increments the reference count of this object. The reference count must @@ -87,6 +91,12 @@ class TargetPolicy { // as possible. virtual ResultCode SetTokenLevel(TokenLevel initial, TokenLevel lockdown) = 0; + // Returns the initial token level. + virtual TokenLevel GetInitialTokenLevel() const = 0; + + // Returns the lockdown token level. + virtual TokenLevel GetLockdownTokenLevel() const = 0; + // Sets the security level of the Job Object to which the target process will // belong. This setting is permanent and cannot be changed once the target // process is spawned. The job controls the global security settings which @@ -122,6 +132,11 @@ class TargetPolicy { // Note: the recommended level is JOB_RESTRICTED or JOB_LOCKDOWN. virtual ResultCode SetJobLevel(JobLevel job_level, uint32 ui_exceptions) = 0; + // Sets a hard limit on the size of the commit set for the sandboxed process. + // If the limit is reached, the process will be terminated with + // SBOX_FATAL_MEMORY_EXCEEDED (7012). + virtual ResultCode SetJobMemoryLimit(size_t memory_limit) = 0; + // Specifies the desktop on which the application is going to run. If the // desktop does not exist, it will be created. If alternate_winstation is // set to true, the desktop will be created on an alternate window station. @@ -144,6 +159,9 @@ class TargetPolicy { // to start. virtual ResultCode SetIntegrityLevel(IntegrityLevel level) = 0; + // Returns the initial integrity level used. + virtual IntegrityLevel GetIntegrityLevel() const = 0; + // Sets the integrity level of the process in the sandbox. The integrity level // will not take effect before you call LowerToken. User Interface Privilege // Isolation is not affected by this setting and will remain off for the @@ -179,7 +197,7 @@ class TargetPolicy { virtual ResultCode SetDelayedProcessMitigations(MitigationFlags flags) = 0; // Returns the currently set delayed mitigation flags. - virtual MitigationFlags GetDelayedProcessMitigations() = 0; + virtual MitigationFlags GetDelayedProcessMitigations() const = 0; // Sets the interceptions to operate in strict mode. By default, interceptions // are performed in "relaxed" mode, where if something inside NTDLL.DLL is diff --git a/chromium/sandbox/win/src/sandbox_policy_base.cc b/chromium/sandbox/win/src/sandbox_policy_base.cc index 220a0703024..711fafc006a 100644 --- a/chromium/sandbox/win/src/sandbox_policy_base.cc +++ b/chromium/sandbox/win/src/sandbox_policy_base.cc @@ -21,6 +21,8 @@ #include "sandbox/win/src/policy_broker.h" #include "sandbox/win/src/policy_engine_processor.h" #include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/process_mitigations_win32k_dispatcher.h" +#include "sandbox/win/src/process_mitigations_win32k_policy.h" #include "sandbox/win/src/process_thread_dispatcher.h" #include "sandbox/win/src/process_thread_policy.h" #include "sandbox/win/src/registry_dispatcher.h" @@ -80,6 +82,7 @@ PolicyBase::PolicyBase() initial_level_(USER_LOCKDOWN), job_level_(JOB_LOCKDOWN), ui_exceptions_(0), + memory_limit_(0), use_alternate_desktop_(false), use_alternate_winstation_(false), file_system_init_(false), @@ -124,6 +127,11 @@ PolicyBase::PolicyBase() dispatcher = new HandleDispatcher(this); ipc_targets_[IPC_DUPLICATEHANDLEPROXY_TAG] = dispatcher; + + dispatcher = new ProcessMitigationsWin32KDispatcher(this); + ipc_targets_[IPC_GDI_GDIDLLINITIALIZE_TAG] = dispatcher; + ipc_targets_[IPC_GDI_GETSTOCKOBJECT_TAG] = dispatcher; + ipc_targets_[IPC_USER_REGISTERCLASSW_TAG] = dispatcher; } PolicyBase::~PolicyBase() { @@ -161,33 +169,52 @@ ResultCode PolicyBase::SetTokenLevel(TokenLevel initial, TokenLevel lockdown) { return SBOX_ALL_OK; } +TokenLevel PolicyBase::GetInitialTokenLevel() const { + return initial_level_; +} + +TokenLevel PolicyBase::GetLockdownTokenLevel() const{ + return lockdown_level_; +} + ResultCode PolicyBase::SetJobLevel(JobLevel job_level, uint32 ui_exceptions) { + if (memory_limit_ && job_level == JOB_NONE) { + return SBOX_ERROR_BAD_PARAMS; + } job_level_ = job_level; ui_exceptions_ = ui_exceptions; return SBOX_ALL_OK; } +ResultCode PolicyBase::SetJobMemoryLimit(size_t memory_limit) { + if (memory_limit && job_level_ == JOB_NONE) { + return SBOX_ERROR_BAD_PARAMS; + } + memory_limit_ = memory_limit; + return SBOX_ALL_OK; +} + ResultCode PolicyBase::SetAlternateDesktop(bool alternate_winstation) { use_alternate_desktop_ = true; use_alternate_winstation_ = alternate_winstation; return CreateAlternateDesktop(alternate_winstation); } -string16 PolicyBase::GetAlternateDesktop() const { +base::string16 PolicyBase::GetAlternateDesktop() const { // No alternate desktop or winstation. Return an empty string. if (!use_alternate_desktop_ && !use_alternate_winstation_) { - return string16(); + return base::string16(); } // The desktop and winstation should have been created by now. // If we hit this scenario, it means that the user ignored the failure // during SetAlternateDesktop, so we ignore it here too. if (use_alternate_desktop_ && !alternate_desktop_handle_) { - return string16(); + return base::string16(); } if (use_alternate_winstation_ && (!alternate_desktop_handle_ || !alternate_winstation_handle_)) { - return string16(); + return base::string16(); } return GetFullDesktopName(alternate_winstation_handle_, @@ -265,6 +292,10 @@ ResultCode PolicyBase::SetIntegrityLevel(IntegrityLevel integrity_level) { return SBOX_ALL_OK; } +IntegrityLevel PolicyBase::GetIntegrityLevel() const { + return integrity_level_; +} + ResultCode PolicyBase::SetDelayedIntegrityLevel( IntegrityLevel integrity_level) { delayed_integrity_level_ = integrity_level; @@ -316,7 +347,7 @@ ResultCode PolicyBase::SetDelayedProcessMitigations( return SBOX_ALL_OK; } -MitigationFlags PolicyBase::GetDelayedProcessMitigations() { +MitigationFlags PolicyBase::GetDelayedProcessMitigations() const { return delayed_mitigations_; } @@ -401,6 +432,16 @@ ResultCode PolicyBase::AddRule(SubSystem subsystem, Semantics semantics, } break; } + + case SUBSYS_WIN32K_LOCKDOWN: { + if (!ProcessMitigationsWin32KLockdownPolicy::GenerateRules( + pattern, semantics,policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + default: { return SBOX_ERROR_UNSUPPORTED; } @@ -414,8 +455,8 @@ ResultCode PolicyBase::AddDllToUnload(const wchar_t* dll_name) { return SBOX_ALL_OK; } -ResultCode PolicyBase::AddKernelObjectToClose(const char16* handle_type, - const char16* handle_name) { +ResultCode PolicyBase::AddKernelObjectToClose(const base::char16* handle_type, + const base::char16* handle_name) { return handle_closer_.AddHandle(handle_type, handle_name); } @@ -459,7 +500,8 @@ ResultCode PolicyBase::MakeJobObject(HANDLE* job) { if (job_level_ != JOB_NONE) { // Create the windows job object. Job job_obj; - DWORD result = job_obj.Init(job_level_, NULL, ui_exceptions_); + DWORD result = job_obj.Init(job_level_, NULL, ui_exceptions_, + memory_limit_); if (ERROR_SUCCESS != result) { return SBOX_ERROR_GENERIC; } @@ -649,15 +691,12 @@ bool PolicyBase::SetupAllInterceptions(TargetProcess* target) { } if (!blacklisted_dlls_.empty()) { - std::vector<string16>::iterator it = blacklisted_dlls_.begin(); + std::vector<base::string16>::iterator it = blacklisted_dlls_.begin(); for (; it != blacklisted_dlls_.end(); ++it) { manager.AddToUnloadModules(it->c_str()); } } - if (!handle_closer_.SetupHandleInterceptions(&manager)) - return false; - if (!SetupBasicInterceptions(&manager)) return false; diff --git a/chromium/sandbox/win/src/sandbox_policy_base.h b/chromium/sandbox/win/src/sandbox_policy_base.h index d56501d78bc..540f261cdde 100644 --- a/chromium/sandbox/win/src/sandbox_policy_base.h +++ b/chromium/sandbox/win/src/sandbox_policy_base.h @@ -41,13 +41,17 @@ class PolicyBase : public Dispatcher, public TargetPolicy { virtual void Release() OVERRIDE; virtual ResultCode SetTokenLevel(TokenLevel initial, TokenLevel lockdown) OVERRIDE; + virtual TokenLevel GetInitialTokenLevel() const OVERRIDE; + virtual TokenLevel GetLockdownTokenLevel() const OVERRIDE; virtual ResultCode SetJobLevel(JobLevel job_level, uint32 ui_exceptions) OVERRIDE; + virtual ResultCode SetJobMemoryLimit(size_t memory_limit) OVERRIDE; virtual ResultCode SetAlternateDesktop(bool alternate_winstation) OVERRIDE; - virtual string16 GetAlternateDesktop() const OVERRIDE; + virtual base::string16 GetAlternateDesktop() const OVERRIDE; virtual ResultCode CreateAlternateDesktop(bool alternate_winstation) OVERRIDE; virtual void DestroyAlternateDesktop() OVERRIDE; virtual ResultCode SetIntegrityLevel(IntegrityLevel integrity_level) OVERRIDE; + virtual IntegrityLevel GetIntegrityLevel() const OVERRIDE; virtual ResultCode SetDelayedIntegrityLevel( IntegrityLevel integrity_level) OVERRIDE; virtual ResultCode SetAppContainer(const wchar_t* sid) OVERRIDE; @@ -56,15 +60,16 @@ class PolicyBase : public Dispatcher, public TargetPolicy { virtual MitigationFlags GetProcessMitigations() OVERRIDE; virtual ResultCode SetDelayedProcessMitigations( MitigationFlags flags) OVERRIDE; - virtual MitigationFlags GetDelayedProcessMitigations() OVERRIDE; + virtual MitigationFlags GetDelayedProcessMitigations() const OVERRIDE; virtual void SetStrictInterceptions() OVERRIDE; virtual ResultCode SetStdoutHandle(HANDLE handle) OVERRIDE; virtual ResultCode SetStderrHandle(HANDLE handle) OVERRIDE; virtual ResultCode AddRule(SubSystem subsystem, Semantics semantics, const wchar_t* pattern) OVERRIDE; virtual ResultCode AddDllToUnload(const wchar_t* dll_name); - virtual ResultCode AddKernelObjectToClose(const char16* handle_type, - const char16* handle_name) OVERRIDE; + virtual ResultCode AddKernelObjectToClose( + const base::char16* handle_type, + const base::char16* handle_name) OVERRIDE; // Dispatcher: virtual Dispatcher* OnMessageReady(IPCParams* ipc, @@ -123,6 +128,7 @@ class PolicyBase : public Dispatcher, public TargetPolicy { TokenLevel initial_level_; JobLevel job_level_; uint32 ui_exceptions_; + size_t memory_limit_; bool use_alternate_desktop_; bool use_alternate_winstation_; // Helps the file system policy initialization. @@ -141,12 +147,12 @@ class PolicyBase : public Dispatcher, public TargetPolicy { // Memory structure that stores the low level policy. PolicyGlobal* policy_; // The list of dlls to unload in the target process. - std::vector<string16> blacklisted_dlls_; + std::vector<base::string16> blacklisted_dlls_; // This is a map of handle-types to names that we need to close in the // target process. A null set means we need to close all handles of the // given type. HandleCloser handle_closer_; - std::vector<string16> capabilities_; + std::vector<base::string16> capabilities_; scoped_ptr<AppContainerAttributes> appcontainer_list_; static HDESK alternate_desktop_handle_; diff --git a/chromium/sandbox/win/src/sandbox_types.h b/chromium/sandbox/win/src/sandbox_types.h index 48f1b613f1e..22840cef077 100644 --- a/chromium/sandbox/win/src/sandbox_types.h +++ b/chromium/sandbox/win/src/sandbox_types.h @@ -58,6 +58,7 @@ enum TerminationCodes { SBOX_FATAL_CACHEDISABLE = 7009, // Failed to forbid HCKU caching. SBOX_FATAL_CLOSEHANDLES = 7010, // Failed to close pending handles. SBOX_FATAL_MITIGATION = 7011, // Could not set the mitigation policy. + SBOX_FATAL_MEMORY_EXCEEDED = 7012, // Exceeded the job memory limit. SBOX_FATAL_LAST }; diff --git a/chromium/sandbox/win/src/sandbox_utils.cc b/chromium/sandbox/win/src/sandbox_utils.cc index 8631a7c9117..a1b77d65b68 100644 --- a/chromium/sandbox/win/src/sandbox_utils.cc +++ b/chromium/sandbox/win/src/sandbox_utils.cc @@ -7,18 +7,10 @@ #include <windows.h> #include "base/logging.h" -#include "base/win/windows_version.h" #include "sandbox/win/src/internal_types.h" namespace sandbox { -bool IsXPSP2OrLater() { - base::win::Version version = base::win::GetVersion(); - return (version > base::win::VERSION_XP) || - ((version == base::win::VERSION_XP) && - (base::win::OSInfo::GetInstance()->service_pack().major >= 2)); -} - void InitObjectAttribs(const base::string16& name, ULONG attributes, HANDLE root, diff --git a/chromium/sandbox/win/src/sandbox_utils.h b/chromium/sandbox/win/src/sandbox_utils.h index 3043597b185..2a17d63b8f9 100644 --- a/chromium/sandbox/win/src/sandbox_utils.h +++ b/chromium/sandbox/win/src/sandbox_utils.h @@ -14,9 +14,6 @@ namespace sandbox { -// Returns true if the current OS is Windows XP SP2 or later. -bool IsXPSP2OrLater(); - void InitObjectAttribs(const base::string16& name, ULONG attributes, HANDLE root, diff --git a/chromium/sandbox/win/src/security_level.h b/chromium/sandbox/win/src/security_level.h index f8e72b3ce00..da84b75252b 100644 --- a/chromium/sandbox/win/src/security_level.h +++ b/chromium/sandbox/win/src/security_level.h @@ -76,7 +76,8 @@ enum TokenLevel { USER_INTERACTIVE, USER_NON_ADMIN, USER_RESTRICTED_SAME_ACCESS, - USER_UNPROTECTED + USER_UNPROTECTED, + USER_LAST }; // The Job level specifies a set of decreasing security profiles for the diff --git a/chromium/sandbox/win/src/service_resolver.h b/chromium/sandbox/win/src/service_resolver.h index 00896922049..76daee40cb4 100644 --- a/chromium/sandbox/win/src/service_resolver.h +++ b/chromium/sandbox/win/src/service_resolver.h @@ -16,7 +16,7 @@ class ServiceResolverThunk : public ResolverThunk { public: // The service resolver needs a child process to write to. ServiceResolverThunk(HANDLE process, bool relaxed) - : process_(process), ntdll_base_(NULL), win2k_(false), + : process_(process), ntdll_base_(NULL), relaxed_(relaxed), relative_jump_(0) {} virtual ~ServiceResolverThunk() {} @@ -46,6 +46,15 @@ class ServiceResolverThunk : public ResolverThunk { // Call this to set up ntdll_base_ which will allow for local patches. virtual void AllowLocalPatches(); + // Verifies that the function specified by |target_name| in |target_module| is + // a service and copies the data from that function into |thunk_storage|. If + // |storage_bytes| is too small, then the method fails. + virtual NTSTATUS CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used); + protected: // The unit test will use this member to allow local patch on a buffer. HMODULE ntdll_base_; @@ -53,10 +62,6 @@ class ServiceResolverThunk : public ResolverThunk { // Handle of the child process. HANDLE process_; - protected: - // Keeps track of a Windows 2000 resolver. - bool win2k_; - private: // Returns true if the code pointer by target_ corresponds to the expected // type of function. Saves that code on the first part of the thunk pointed @@ -114,23 +119,6 @@ class Wow64W8ResolverThunk : public ServiceResolverThunk { }; // This is the concrete resolver used to perform service-call type functions -// inside ntdll.dll on Windows 2000 and XP pre SP2. -class Win2kResolverThunk : public ServiceResolverThunk { - public: - // The service resolver needs a child process to write to. - Win2kResolverThunk(HANDLE process, bool relaxed) - : ServiceResolverThunk(process, relaxed) { - win2k_ = true; - } - virtual ~Win2kResolverThunk() {} - - private: - virtual bool IsFunctionAService(void* local_thunk) const; - - DISALLOW_COPY_AND_ASSIGN(Win2kResolverThunk); -}; - -// This is the concrete resolver used to perform service-call type functions // inside ntdll.dll on Windows 8. class Win8ResolverThunk : public ServiceResolverThunk { public: diff --git a/chromium/sandbox/win/src/service_resolver_32.cc b/chromium/sandbox/win/src/service_resolver_32.cc index 2e69dbc9e7e..be9de6b9224 100644 --- a/chromium/sandbox/win/src/service_resolver_32.cc +++ b/chromium/sandbox/win/src/service_resolver_32.cc @@ -179,6 +179,32 @@ size_t ServiceResolverThunk::GetThunkSize() const { return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize(); } +NTSTATUS ServiceResolverThunk::CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + if (storage_bytes < thunk_bytes) + return STATUS_UNSUCCESSFUL; + + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(thunk_storage); + + if (!IsFunctionAService(&thunk->original) && + (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) { + return STATUS_UNSUCCESSFUL; + } + + if (NULL != storage_used) + *storage_used = thunk_bytes; + + return ret; +} + bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { ServiceEntry function_code; SIZE_T read; @@ -374,26 +400,6 @@ bool Wow64W8ResolverThunk::IsFunctionAService(void* local_thunk) const { return true; } -bool Win2kResolverThunk::IsFunctionAService(void* local_thunk) const { - ServiceEntry function_code; - SIZE_T read; - if (!::ReadProcessMemory(process_, target_, &function_code, - sizeof(function_code), &read)) - return false; - - if (sizeof(function_code) != read) - return false; - - if (kMovEax != function_code.mov_eax || - function_code.service_id > kMaxService) - return false; - - // Save the verified code - memcpy(local_thunk, &function_code, sizeof(function_code)); - - return true; -} - bool Win8ResolverThunk::IsFunctionAService(void* local_thunk) const { ServiceEntryW8 function_code; SIZE_T read; diff --git a/chromium/sandbox/win/src/service_resolver_64.cc b/chromium/sandbox/win/src/service_resolver_64.cc index 473ddbc7f16..03795f7c9d0 100644 --- a/chromium/sandbox/win/src/service_resolver_64.cc +++ b/chromium/sandbox/win/src/service_resolver_64.cc @@ -56,7 +56,7 @@ struct ServiceEntryW8 { ULONG mov_r10_rcx_mov_eax; // = 4C 8B D1 B8 ULONG service_id; USHORT syscall; // = 0F 05 - BYTE ret; // = C2 + BYTE ret; // = C3 BYTE nop; // = 90 }; @@ -116,6 +116,30 @@ size_t ServiceResolverThunk::GetThunkSize() const { return sizeof(ServiceFullThunk); } +NTSTATUS ServiceResolverThunk::CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + if (storage_bytes < thunk_bytes) + return STATUS_UNSUCCESSFUL; + + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(thunk_storage); + + if (!IsFunctionAService(&thunk->original)) + return STATUS_UNSUCCESSFUL; + + if (NULL != storage_used) + *storage_used = thunk_bytes; + + return ret; +} + bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { ServiceFullThunk function_code; SIZE_T read; @@ -185,9 +209,4 @@ bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { return false; } -bool Win2kResolverThunk::IsFunctionAService(void* local_thunk) const { - NOTREACHED_NT(); - return false; -} - } // namespace sandbox diff --git a/chromium/sandbox/win/src/service_resolver_unittest.cc b/chromium/sandbox/win/src/service_resolver_unittest.cc index b01fedfbf09..c7ac7eab169 100644 --- a/chromium/sandbox/win/src/service_resolver_unittest.cc +++ b/chromium/sandbox/win/src/service_resolver_unittest.cc @@ -58,7 +58,6 @@ class ResolverThunkTest : public T { typedef ResolverThunkTest<sandbox::ServiceResolverThunk> WinXpResolverTest; #if !defined(_WIN64) -typedef ResolverThunkTest<sandbox::Win2kResolverThunk> Win2kResolverTest; typedef ResolverThunkTest<sandbox::Win8ResolverThunk> Win8ResolverTest; typedef ResolverThunkTest<sandbox::Wow64ResolverThunk> Wow64ResolverTest; typedef ResolverThunkTest<sandbox::Wow64W8ResolverThunk> Wow64W8ResolverTest; @@ -141,9 +140,6 @@ sandbox::ServiceResolverThunk* GetTestResolver(bool relaxed) { return new Wow64ResolverTest(relaxed); } - if (!sandbox::IsXPSP2OrLater()) - return new Win2kResolverTest(relaxed); - if (os_info->version() >= base::win::VERSION_WIN8) return new Win8ResolverTest(relaxed); diff --git a/chromium/sandbox/win/src/sharedmem_ipc_client.cc b/chromium/sandbox/win/src/sharedmem_ipc_client.cc index a9eb01f36b2..9d37bbda7d4 100644 --- a/chromium/sandbox/win/src/sharedmem_ipc_client.cc +++ b/chromium/sandbox/win/src/sharedmem_ipc_client.cc @@ -31,7 +31,7 @@ void SharedMemIPCClient::FreeBuffer(void* buffer) { size_t num = ChannelIndexFromBuffer(buffer); ChannelControl* channel = control_->channels; LONG result = ::InterlockedExchange(&channel[num].state, kFreeChannel); - DCHECK(kFreeChannel != result); + DCHECK_NE(kFreeChannel, static_cast<ChannelState>(result)); result; } @@ -145,7 +145,7 @@ size_t SharedMemIPCClient::LockFreeChannel(bool* severe_failure) { size_t SharedMemIPCClient::ChannelIndexFromBuffer(const void* buffer) { ptrdiff_t d = reinterpret_cast<const char*>(buffer) - first_base_; size_t num = d/kIPCChannelSize; - DCHECK(num < control_->channels_count); + DCHECK_LT(num, control_->channels_count); return (num); } diff --git a/chromium/sandbox/win/src/sharedmem_ipc_server.cc b/chromium/sandbox/win/src/sharedmem_ipc_server.cc index bf8761e8853..a1b881eb1b3 100644 --- a/chromium/sandbox/win/src/sharedmem_ipc_server.cc +++ b/chromium/sandbox/win/src/sharedmem_ipc_server.cc @@ -12,6 +12,11 @@ #include "sandbox/win/src/crosscall_params.h" #include "sandbox/win/src/crosscall_server.h" +namespace { +// This handle must not be closed. +volatile HANDLE g_alive_mutex = NULL; +} + namespace sandbox { SharedMemIPCServer::SharedMemIPCServer(HANDLE target_process, @@ -25,6 +30,19 @@ SharedMemIPCServer::SharedMemIPCServer(HANDLE target_process, target_process_id_(target_process_id), target_job_object_(target_job), call_dispatcher_(dispatcher) { + // We create a initially owned mutex. If the server dies unexpectedly, + // the thread that owns it will fail to release the lock and windows will + // report to the target (when it tries to acquire it) that the wait was + // abandoned. Note: We purposely leak the local handle because we want it to + // be closed by Windows itself so it is properly marked as abandoned if the + // server dies. + if (!g_alive_mutex) { + HANDLE mutex = ::CreateMutexW(NULL, TRUE, NULL); + if (::InterlockedCompareExchangePointer(&g_alive_mutex, mutex, NULL)) { + // We lost the race to create the mutex. + ::CloseHandle(mutex); + } + } } SharedMemIPCServer::~SharedMemIPCServer() { @@ -108,15 +126,7 @@ bool SharedMemIPCServer::Init(void* shared_mem, uint32 shared_size, thread_provider_->RegisterWait(this, service_context->ping_event, ThreadPingEventReady, service_context); } - - // We create a mutex that the server locks. If the server dies unexpectedly, - // the thread that owns it will fail to release the lock and windows will - // report to the target (when it tries to acquire it) that the wait was - // abandoned. Note: We purposely leak the local handle because we want it to - // be closed by Windows itself so it is properly marked as abandoned if the - // server dies. - if (!::DuplicateHandle(::GetCurrentProcess(), - ::CreateMutexW(NULL, TRUE, NULL), + if (!::DuplicateHandle(::GetCurrentProcess(), g_alive_mutex, target_process_, &client_control_->server_alive, SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, 0)) { return false; diff --git a/chromium/sandbox/win/src/target_process.cc b/chromium/sandbox/win/src/target_process.cc index a2d630c9452..5f73adcd4f9 100644 --- a/chromium/sandbox/win/src/target_process.cc +++ b/chromium/sandbox/win/src/target_process.cc @@ -116,7 +116,7 @@ DWORD TargetProcess::Create(const wchar_t* exe_path, exe_name_.reset(_wcsdup(exe_path)); // the command line needs to be writable by CreateProcess(). - scoped_ptr_malloc<wchar_t> cmd_line(_wcsdup(command_line)); + scoped_ptr<wchar_t, base::FreeDeleter> cmd_line(_wcsdup(command_line)); // Start the target process suspended. DWORD flags = diff --git a/chromium/sandbox/win/src/target_process.h b/chromium/sandbox/win/src/target_process.h index 0b72d98f231..9a8dded55a2 100644 --- a/chromium/sandbox/win/src/target_process.h +++ b/chromium/sandbox/win/src/target_process.h @@ -113,7 +113,7 @@ class TargetProcess { // Base address of the main executable void* base_address_; // Full name of the target executable. - scoped_ptr_malloc<wchar_t> exe_name_; + scoped_ptr<wchar_t, base::FreeDeleter> exe_name_; // Function used for testing. friend TargetProcess* MakeTestTargetProcess(HANDLE process, diff --git a/chromium/sandbox/win/src/win_utils.cc b/chromium/sandbox/win/src/win_utils.cc index 53a12a4f292..ea68c07f35b 100644 --- a/chromium/sandbox/win/src/win_utils.cc +++ b/chromium/sandbox/win/src/win_utils.cc @@ -7,6 +7,7 @@ #include <map> #include "base/memory/scoped_ptr.h" +#include "base/win/pe_image.h" #include "sandbox/win/src/internal_types.h" #include "sandbox/win/src/nt_internals.h" #include "sandbox/win/src/sandbox_nt_util.h" @@ -114,7 +115,7 @@ DWORD IsReparsePoint(const base::string16& full_path, bool* result) { } last_pos = path.rfind(L'\\'); - } while (last_pos != base::string16::npos); + } while (last_pos > 2); // Skip root dir. *result = false; return ERROR_SUCCESS; @@ -299,26 +300,22 @@ bool WriteProtectedChildMemory(HANDLE child_process, void* address, }; // namespace sandbox -// TODO(jschuh): http://crbug.com/11789 -// I'm guessing we have a race where some "security" software is messing -// with ntdll/imports underneath us. So, we retry a few times, and in the -// worst case we sleep briefly before a few more attempts. (Normally sleeping -// would be very bad, but it's better than crashing in this case.) void ResolveNTFunctionPtr(const char* name, void* ptr) { - const int max_tries = 5; - const int sleep_threshold = 2; + static volatile HMODULE ntdll = NULL; - static HMODULE ntdll = ::GetModuleHandle(sandbox::kNtdllName); + if (!ntdll) { + HMODULE ntdll_local = ::GetModuleHandle(sandbox::kNtdllName); + // Use PEImage to sanity-check that we have a valid ntdll handle. + base::win::PEImage ntdll_peimage(ntdll_local); + CHECK_NT(ntdll_peimage.VerifyMagic()); + // Race-safe way to set static ntdll. + ::InterlockedCompareExchangePointer( + reinterpret_cast<PVOID volatile*>(&ntdll), ntdll_local, NULL); - FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr); - *function_ptr = ::GetProcAddress(ntdll, name); - - for (int tries = 1; !(*function_ptr) && tries < max_tries; ++tries) { - if (tries >= sleep_threshold) - ::Sleep(1); - ntdll = ::GetModuleHandle(sandbox::kNtdllName); - *function_ptr = ::GetProcAddress(ntdll, name); } + CHECK_NT(ntdll); + FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr); + *function_ptr = ::GetProcAddress(ntdll, name); CHECK_NT(*function_ptr); } diff --git a/chromium/sandbox/win/src/window.cc b/chromium/sandbox/win/src/window.cc index 6b5766b325b..ed5a6626338 100644 --- a/chromium/sandbox/win/src/window.cc +++ b/chromium/sandbox/win/src/window.cc @@ -48,9 +48,8 @@ ResultCode CreateAltWindowStation(HWINSTA* winsta) { *winsta = ::CreateWindowStationW(NULL, 0, WINSTA_ALL_ACCESS, &attributes); LocalFree(attributes.lpSecurityDescriptor); - if (*winsta) { + if (*winsta) return SBOX_ALL_OK; - } return SBOX_ERROR_CANNOT_CREATE_WINSTATION; } @@ -100,11 +99,14 @@ ResultCode CreateAltDesktop(HWINSTA winsta, HDESK* desktop) { if (*desktop) { // Replace the DACL on the new Desktop with a reduced privilege version. // We can soft fail on this for now, as it's just an extra mitigation. - static const ACCESS_MASK kDesktopDenyMask = WRITE_DAC | WRITE_OWNER | - DESKTOP_HOOKCONTROL | - DESKTOP_JOURNALPLAYBACK | - DESKTOP_JOURNALRECORD | - DESKTOP_SWITCHDESKTOP; + static const ACCESS_MASK kDesktopDenyMask = WRITE_DAC | WRITE_OWNER | + DELETE | + DESKTOP_CREATEMENU | + DESKTOP_CREATEWINDOW | + DESKTOP_HOOKCONTROL | + DESKTOP_JOURNALPLAYBACK | + DESKTOP_JOURNALRECORD | + DESKTOP_SWITCHDESKTOP; AddKnownSidToObject(*desktop, SE_WINDOW_OBJECT, Sid(WinRestrictedCodeSid), DENY_ACCESS, kDesktopDenyMask); return SBOX_ALL_OK; diff --git a/chromium/sandbox/win/wow_helper/wow_helper.cc b/chromium/sandbox/win/wow_helper/wow_helper.cc index 847190ac8b8..ea73e8487c2 100644 --- a/chromium/sandbox/win/wow_helper/wow_helper.cc +++ b/chromium/sandbox/win/wow_helper/wow_helper.cc @@ -15,43 +15,6 @@ #include "sandbox/win/wow_helper/service64_resolver.h" #include "sandbox/win/wow_helper/target_code.h" -namespace { - -// Grabbed from base/strings/string_util.h -template <class string_type> -inline typename string_type::value_type* WriteInto(string_type* str, - size_t length_with_null) { - str->reserve(length_with_null); - str->resize(length_with_null - 1); - return &((*str)[0]); -} - -// Grabbed from base/string_util.cc -std::string WideToMultiByte(const base::string16& wide, UINT code_page) { - if (wide.length() == 0) - return std::string(); - - // compute the length of the buffer we'll need - int charcount = WideCharToMultiByte(code_page, 0, wide.c_str(), -1, - NULL, 0, NULL, NULL); - if (charcount == 0) - return std::string(); - - // convert - std::string mb; - WideCharToMultiByte(code_page, 0, wide.c_str(), -1, - WriteInto(&mb, charcount), charcount, NULL, NULL); - - return mb; -} - -// Grabbed from base/string_util.cc -std::string WideToUTF8(const base::string16& wide) { - return WideToMultiByte(wide, CP_UTF8); -} - -} // namespace - namespace sandbox { // Performs the interception of NtMapViewOfSection on the 64-bit version of |