summaryrefslogtreecommitdiffstats
path: root/chromium/sandbox/linux
diff options
context:
space:
mode:
authorJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-08 14:30:41 +0200
committerJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-12 13:49:54 +0200
commitab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch)
tree498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/sandbox/linux
parent4ce69f7403811819800e7c5ae1318b2647e778d1 (diff)
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/sandbox/linux')
-rw-r--r--chromium/sandbox/linux/BUILD.gn314
-rw-r--r--chromium/sandbox/linux/DEPS25
-rw-r--r--chromium/sandbox/linux/OWNERS3
-rw-r--r--chromium/sandbox/linux/sandbox_linux.gypi129
-rw-r--r--chromium/sandbox/linux/sandbox_linux_test_sources.gypi13
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/DEPS4
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc92
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h10
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc288
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc80
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h30
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc102
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h28
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc17
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h7
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/DEPS3
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h65
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/bpf_tests.h182
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc139
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/codegen.cc116
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/codegen.h3
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc251
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/demo.cc22
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/die.cc8
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/die.h3
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/errorcode.cc5
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/errorcode.h9
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/errorcode_unittest.cc11
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/linux_seccomp.h97
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc188
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h49
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_compatibility_policy.h43
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.cc17
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h8
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc76
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h59
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc795
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/syscall.cc395
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/syscall.h197
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/syscall_iterator.h3
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc137
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/trap.cc23
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/trap.h19
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/verifier.cc6
-rw-r--r--chromium/sandbox/linux/services/android_futex.h28
-rw-r--r--chromium/sandbox/linux/services/android_ucontext.h2
-rw-r--r--chromium/sandbox/linux/services/android_x86_64_ucontext.h88
-rw-r--r--chromium/sandbox/linux/services/broker_process.cc59
-rw-r--r--chromium/sandbox/linux/services/broker_process.h12
-rw-r--r--chromium/sandbox/linux/services/broker_process_unittest.cc131
-rw-r--r--chromium/sandbox/linux/services/credentials.cc89
-rw-r--r--chromium/sandbox/linux/services/credentials.h15
-rw-r--r--chromium/sandbox/linux/services/credentials_unittest.cc43
-rw-r--r--chromium/sandbox/linux/services/init_process_reaper.h4
-rw-r--r--chromium/sandbox/linux/services/libc_urandom_override.cc12
-rw-r--r--chromium/sandbox/linux/services/scoped_process.cc119
-rw-r--r--chromium/sandbox/linux/services/scoped_process.h55
-rw-r--r--chromium/sandbox/linux/services/scoped_process_unittest.cc130
-rw-r--r--chromium/sandbox/linux/services/thread_helpers.cc20
-rw-r--r--chromium/sandbox/linux/services/thread_helpers.h11
-rw-r--r--chromium/sandbox/linux/services/thread_helpers_unittests.cc24
-rw-r--r--chromium/sandbox/linux/services/unix_domain_socket_unittest.cc267
-rw-r--r--chromium/sandbox/linux/services/yama.cc116
-rw-r--r--chromium/sandbox/linux/services/yama.h58
-rw-r--r--chromium/sandbox/linux/services/yama_unittests.cc152
-rw-r--r--chromium/sandbox/linux/suid/client/DEPS3
-rw-r--r--chromium/sandbox/linux/suid/client/setuid_sandbox_client.cc144
-rw-r--r--chromium/sandbox/linux/suid/client/setuid_sandbox_client.h59
-rw-r--r--chromium/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc13
-rw-r--r--chromium/sandbox/linux/suid/common/suid_unsafe_environment_variables.h2
-rw-r--r--chromium/sandbox/linux/suid/linux_util.c15
-rw-r--r--chromium/sandbox/linux/suid/process_util.h6
-rw-r--r--chromium/sandbox/linux/suid/process_util_linux.c9
-rw-r--r--chromium/sandbox/linux/suid/sandbox.c93
74 files changed, 4590 insertions, 1260 deletions
diff --git a/chromium/sandbox/linux/BUILD.gn b/chromium/sandbox/linux/BUILD.gn
new file mode 100644
index 00000000000..0cc9be4562a
--- /dev/null
+++ b/chromium/sandbox/linux/BUILD.gn
@@ -0,0 +1,314 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/features.gni")
+
+declare_args() {
+ compile_suid_client = is_linux
+
+ compile_credentials = is_linux
+
+ compile_seccomp_bpf_demo =
+ (is_linux && (cpu_arch == "x86" || cpu_arch == "x64"))
+}
+
+# We have two principal targets: sandbox and sandbox_linux_unittests
+# All other targets are listed as dependencies.
+# There is one notable exception: for historical reasons, chrome_sandbox is
+# the setuid sandbox and is its own target.
+
+group("sandbox") {
+ deps = [
+ ":sandbox_services",
+ ]
+
+ if (compile_suid_client) {
+ deps += [ ":suid_sandbox_client" ]
+ }
+ if (use_seccomp_bpf) {
+ deps += [
+ ":seccomp_bpf",
+ ":seccomp_bpf_helpers",
+ ]
+ }
+}
+
+source_set("sandbox_linux_test_utils") {
+ sources = [
+ "tests/sandbox_test_runner.cc",
+ "tests/sandbox_test_runner.h",
+ "tests/sandbox_test_runner_function_pointer.cc",
+ "tests/sandbox_test_runner_function_pointer.h",
+ "tests/test_utils.cc",
+ "tests/test_utils.h",
+ "tests/unit_tests.cc",
+ "tests/unit_tests.h",
+ ]
+
+ deps = [
+ "//testing/gtest",
+ ]
+
+ if (use_seccomp_bpf) {
+ sources += [
+ "seccomp-bpf/bpf_tester_compatibility_delegate.h",
+ "seccomp-bpf/bpf_tests.h",
+ "seccomp-bpf/sandbox_bpf_test_runner.cc",
+ "seccomp-bpf/sandbox_bpf_test_runner.h",
+ ]
+ deps += [
+ ":seccomp_bpf",
+ ]
+ }
+}
+
+# The main sandboxing test target.
+test("sandbox_linux_unittests") {
+ sources = [
+ "tests/main.cc",
+ "tests/unit_tests_unittest.cc",
+ "services/broker_process_unittest.cc",
+ "services/scoped_process_unittest.cc",
+ "services/thread_helpers_unittests.cc",
+ "services/yama_unittests.cc",
+ ]
+
+ deps = [
+ ":sandbox",
+ ":sandbox_linux_test_utils",
+ "//base",
+ "//base/test:test_support",
+ "//testing/gtest",
+ ]
+
+ if (compile_suid_client) {
+ sources += [
+ "suid/client/setuid_sandbox_client_unittest.cc",
+ ]
+ }
+ if (use_seccomp_bpf) {
+ sources += [
+ "seccomp-bpf-helpers/baseline_policy_unittest.cc",
+ "seccomp-bpf/bpf_tests_unittest.cc",
+ "seccomp-bpf/codegen_unittest.cc",
+ "seccomp-bpf/errorcode_unittest.cc",
+ "seccomp-bpf/sandbox_bpf_unittest.cc",
+ "seccomp-bpf/syscall_iterator_unittest.cc",
+ "seccomp-bpf/syscall_unittest.cc",
+ ]
+ }
+ if (compile_credentials) {
+ sources += [
+ "services/credentials_unittest.cc",
+ "services/unix_domain_socket_unittest.cc",
+ ]
+ }
+}
+
+# TODO(GYP) Android version of this test.
+# {
+# # This target is the shared library used by Android APK (i.e.
+# # JNI-friendly) tests.
+# "target_name": "sandbox_linux_jni_unittests",
+# "includes": [
+# "sandbox_linux_test_sources.gypi",
+# ],
+# "type": "shared_library",
+# "conditions": [
+# [ "OS == "android"", {
+# "dependencies": [
+# "../testing/android/native_test.gyp:native_test_native_code",
+# ],
+# }],
+# ],
+# },
+
+component("seccomp_bpf") {
+ sources = [
+ "seccomp-bpf/basicblock.cc",
+ "seccomp-bpf/basicblock.h",
+ "seccomp-bpf/codegen.cc",
+ "seccomp-bpf/codegen.h",
+ "seccomp-bpf/die.cc",
+ "seccomp-bpf/die.h",
+ "seccomp-bpf/errorcode.cc",
+ "seccomp-bpf/errorcode.h",
+ "seccomp-bpf/instruction.h",
+ "seccomp-bpf/linux_seccomp.h",
+ "seccomp-bpf/sandbox_bpf.cc",
+ "seccomp-bpf/sandbox_bpf.h",
+ "seccomp-bpf/sandbox_bpf_compatibility_policy.h",
+ "seccomp-bpf/sandbox_bpf_policy.cc",
+ "seccomp-bpf/sandbox_bpf_policy.h",
+ "seccomp-bpf/syscall.cc",
+ "seccomp-bpf/syscall.h",
+ "seccomp-bpf/syscall_iterator.cc",
+ "seccomp-bpf/syscall_iterator.h",
+ "seccomp-bpf/trap.cc",
+ "seccomp-bpf/trap.h",
+ "seccomp-bpf/verifier.cc",
+ "seccomp-bpf/verifier.h",
+ ]
+ defines = [ "SANDBOX_IMPLEMENTATION" ]
+
+ deps = [
+ ":sandbox_services_headers",
+ "//base",
+ ]
+}
+
+component("seccomp_bpf_helpers") {
+ sources = [
+ "seccomp-bpf-helpers/baseline_policy.cc",
+ "seccomp-bpf-helpers/baseline_policy.h",
+ "seccomp-bpf-helpers/sigsys_handlers.cc",
+ "seccomp-bpf-helpers/sigsys_handlers.h",
+ "seccomp-bpf-helpers/syscall_parameters_restrictions.cc",
+ "seccomp-bpf-helpers/syscall_parameters_restrictions.h",
+ "seccomp-bpf-helpers/syscall_sets.cc",
+ "seccomp-bpf-helpers/syscall_sets.h",
+ ]
+ defines = [ "SANDBOX_IMPLEMENTATION" ]
+
+ deps = [
+ "//base",
+ ":seccomp_bpf",
+ ]
+}
+
+if (compile_seccomp_bpf_demo) {
+ # A demonstration program for the seccomp-bpf sandbox.
+ executable("seccomp_bpf_demo") {
+ sources = [
+ "seccomp-bpf/demo.cc",
+ ]
+ deps = [
+ ":seccomp_bpf",
+ ]
+ }
+}
+
+# The setuid sandbox for Linux.
+executable("chrome_sandbox") {
+ sources = [
+ "suid/common/sandbox.h",
+ "suid/common/suid_unsafe_environment_variables.h",
+ "suid/linux_util.c",
+ "suid/linux_util.h",
+ "suid/process_util.h",
+ "suid/process_util_linux.c",
+ "suid/sandbox.c",
+ ]
+
+ cflags = [
+ # For ULLONG_MAX
+ "-std=gnu99",
+ # These files have a suspicious comparison.
+ # TODO fix this and re-enable this warning.
+ "-Wno-sign-compare",
+ ]
+}
+
+component("sandbox_services") {
+ sources = [
+ "services/broker_process.cc",
+ "services/broker_process.h",
+ "services/init_process_reaper.cc",
+ "services/init_process_reaper.h",
+ "services/scoped_process.cc",
+ "services/scoped_process.h",
+ "services/thread_helpers.cc",
+ "services/thread_helpers.h",
+ "services/yama.h",
+ "services/yama.cc",
+ ]
+
+ defines = [ "SANDBOX_IMPLEMENTATION" ]
+
+ if (compile_credentials) {
+ sources += [
+ "services/credentials.cc",
+ "services/credentials.h",
+ ]
+ # For capabilities.cc.
+ configs += [ "//build/config/linux:libcap" ]
+ }
+
+ deps = [
+ "//base",
+ ]
+}
+
+source_set("sandbox_services_headers") {
+ sources = [
+ "services/android_arm_ucontext.h",
+ "services/android_futex.h",
+ "services/android_ucontext.h",
+ "services/android_i386_ucontext.h",
+ "services/arm_linux_syscalls.h",
+ "services/linux_syscalls.h",
+ "services/x86_32_linux_syscalls.h",
+ "services/x86_64_linux_syscalls.h",
+ ]
+}
+
+# We make this its own target so that it does not interfere with our tests.
+source_set("libc_urandom_override") {
+ sources = [
+ "services/libc_urandom_override.cc",
+ "services/libc_urandom_override.h",
+ ]
+ deps = [
+ "//base",
+ ]
+}
+
+component("suid_sandbox_client") {
+ sources = [
+ "suid/common/sandbox.h",
+ "suid/common/suid_unsafe_environment_variables.h",
+ "suid/client/setuid_sandbox_client.cc",
+ "suid/client/setuid_sandbox_client.h",
+ ]
+ defines = [ "SANDBOX_IMPLEMENTATION" ]
+
+ deps = [
+ ":sandbox_services",
+ "//base",
+ ]
+}
+
+if (is_android) {
+ # TODO(GYP) enable this. Needs an android_strip wrapper python script.
+ #action("sandbox_linux_unittests_stripped") {
+ # script = "android_stip.py"
+ #
+ # in_file = "$root_out_dir/sandbox_linux_unittests"
+ #
+ # out_file = "$root_out_dir/sandbox_linux_unittests_stripped"
+ # outputs = [ out_file ]
+ #
+ # args = [
+ # rebase_path(in_file, root_build_dir),
+ # "-o", rebase_path(out_file, root_build_dir),
+ # ]
+ #
+ # deps = [
+ # ":sandbox_linux_unittests",
+ # ]
+ #}
+
+ # TODO(GYP) convert this.
+ # {
+ # 'target_name': 'sandbox_linux_jni_unittests_apk',
+ # 'type': 'none',
+ # 'variables': {
+ # 'test_suite_name': 'sandbox_linux_jni_unittests',
+ # },
+ # 'dependencies': [
+ # 'sandbox_linux_jni_unittests',
+ # ],
+ # 'includes': [ '../../build/apk_test.gypi' ],
+ # }
+}
diff --git a/chromium/sandbox/linux/DEPS b/chromium/sandbox/linux/DEPS
new file mode 100644
index 00000000000..39128593449
--- /dev/null
+++ b/chromium/sandbox/linux/DEPS
@@ -0,0 +1,25 @@
+include_rules = [
+ # First, exclude everything.
+ # Exclude a few dependencies that are included in the root DEPS and that we
+ # don't need.
+ # Sadly, there is no way to exclude all root DEPS since the root has no name.
+ "-ipc",
+ "-library_loaders",
+ "-third_party",
+ "-url",
+ # Make sure that each subdirectory has to declare its dependencies in
+ # sandbox/ explicitly.
+ "-sandbox/linux",
+
+ # Second, add what we want to allow.
+ # Anything included from sandbox/linux must be declared after this line or in
+ # a more specific DEPS file.
+ # base/, build/ and testing/ are already included in the global DEPS file,
+ # but be explicit.
+ "+base",
+ "+build",
+ "+testing",
+ "+sandbox/sandbox_export.h",
+ # Everyone can use tests/
+ "+sandbox/linux/tests",
+]
diff --git a/chromium/sandbox/linux/OWNERS b/chromium/sandbox/linux/OWNERS
new file mode 100644
index 00000000000..35643d1565f
--- /dev/null
+++ b/chromium/sandbox/linux/OWNERS
@@ -0,0 +1,3 @@
+cevans@chromium.org
+jln@chromium.org
+jorgelo@chromium.org
diff --git a/chromium/sandbox/linux/sandbox_linux.gypi b/chromium/sandbox/linux/sandbox_linux.gypi
index 0e211f6c320..9ddcf0c874c 100644
--- a/chromium/sandbox/linux/sandbox_linux.gypi
+++ b/chromium/sandbox/linux/sandbox_linux.gypi
@@ -12,13 +12,6 @@
'compile_suid_client': 0,
'compile_credentials': 0,
}],
- ['((OS=="linux" or OS=="android") and '
- '(target_arch=="ia32" or target_arch=="x64" or '
- 'target_arch=="arm"))', {
- 'compile_seccomp_bpf': 1,
- }, {
- 'compile_seccomp_bpf': 0,
- }],
['OS=="linux" and (target_arch=="ia32" or target_arch=="x64")', {
'compile_seccomp_bpf_demo': 1,
}, {
@@ -40,8 +33,8 @@
'targets': [
# We have two principal targets: sandbox and sandbox_linux_unittests
# All other targets are listed as dependencies.
- # FIXME(jln): for historial reasons, sandbox_linux is the setuid sandbox
- # and is its own target.
+ # There is one notable exception: for historical reasons, chrome_sandbox is
+ # the setuid sandbox and is its own target.
{
'target_name': 'sandbox',
'type': 'none',
@@ -55,7 +48,7 @@
],
}],
# Compile seccomp BPF when we support it.
- [ 'compile_seccomp_bpf==1', {
+ [ 'use_seccomp_bpf==1', {
'dependencies': [
'seccomp_bpf',
'seccomp_bpf_helpers',
@@ -64,6 +57,39 @@
],
},
{
+ 'target_name': 'sandbox_linux_test_utils',
+ 'type': 'static_library',
+ 'dependencies': [
+ '../testing/gtest.gyp:gtest',
+ ],
+ 'include_dirs': [
+ '../..',
+ ],
+ 'sources': [
+ 'tests/sandbox_test_runner.cc',
+ 'tests/sandbox_test_runner.h',
+ 'tests/sandbox_test_runner_function_pointer.cc',
+ 'tests/sandbox_test_runner_function_pointer.h',
+ 'tests/test_utils.cc',
+ 'tests/test_utils.h',
+ 'tests/unit_tests.cc',
+ 'tests/unit_tests.h',
+ ],
+ 'conditions': [
+ [ 'use_seccomp_bpf==1', {
+ 'sources': [
+ 'seccomp-bpf/bpf_tester_compatibility_delegate.h',
+ 'seccomp-bpf/bpf_tests.h',
+ 'seccomp-bpf/sandbox_bpf_test_runner.cc',
+ 'seccomp-bpf/sandbox_bpf_test_runner.h',
+ ],
+ 'dependencies': [
+ 'seccomp_bpf',
+ ]
+ }],
+ ],
+ },
+ {
# The main sandboxing test target.
'target_name': 'sandbox_linux_unittests',
'includes': [
@@ -80,21 +106,16 @@
],
'type': 'shared_library',
'conditions': [
- [ 'OS == "android" and gtest_target_type == "shared_library"', {
+ [ 'OS == "android"', {
'dependencies': [
'../testing/android/native_test.gyp:native_test_native_code',
],
- 'ldflags!': [
- # Remove warnings about text relocations, to prevent build
- # failure.
- '-Wl,--warn-shared-textrel'
- ],
}],
],
},
{
'target_name': 'seccomp_bpf',
- 'type': 'static_library',
+ 'type': '<(component)',
'sources': [
'seccomp-bpf/basicblock.cc',
'seccomp-bpf/basicblock.h',
@@ -108,6 +129,8 @@
'seccomp-bpf/linux_seccomp.h',
'seccomp-bpf/sandbox_bpf.cc',
'seccomp-bpf/sandbox_bpf.h',
+ 'seccomp-bpf/sandbox_bpf_compatibility_policy.h',
+ 'seccomp-bpf/sandbox_bpf_policy.cc',
'seccomp-bpf/sandbox_bpf_policy.h',
'seccomp-bpf/syscall.cc',
'seccomp-bpf/syscall.h',
@@ -122,13 +145,16 @@
'../base/base.gyp:base',
'sandbox_services_headers',
],
+ 'defines': [
+ 'SANDBOX_IMPLEMENTATION',
+ ],
'include_dirs': [
'../..',
],
},
{
'target_name': 'seccomp_bpf_helpers',
- 'type': 'static_library',
+ 'type': '<(component)',
'sources': [
'seccomp-bpf-helpers/baseline_policy.cc',
'seccomp-bpf-helpers/baseline_policy.h',
@@ -140,6 +166,11 @@
'seccomp-bpf-helpers/syscall_sets.h',
],
'dependencies': [
+ '../base/base.gyp:base',
+ 'seccomp_bpf',
+ ],
+ 'defines': [
+ 'SANDBOX_IMPLEMENTATION',
],
'include_dirs': [
'../..',
@@ -185,20 +216,34 @@
'include_dirs': [
'../..',
],
+ # Do not use any sanitizer tools with this binary. http://crbug.com/382766
+ 'cflags/': [
+ ['exclude', '-fsanitize'],
+ ],
+ 'ldflags/': [
+ ['exclude', '-fsanitize'],
+ ],
},
{ 'target_name': 'sandbox_services',
- 'type': 'static_library',
+ 'type': '<(component)',
'sources': [
'services/broker_process.cc',
'services/broker_process.h',
'services/init_process_reaper.cc',
'services/init_process_reaper.h',
+ 'services/scoped_process.cc',
+ 'services/scoped_process.h',
'services/thread_helpers.cc',
'services/thread_helpers.h',
+ 'services/yama.h',
+ 'services/yama.cc',
],
'dependencies': [
'../base/base.gyp:base',
],
+ 'defines': [
+ 'SANDBOX_IMPLEMENTATION',
+ ],
'conditions': [
['compile_credentials==1', {
'sources': [
@@ -219,6 +264,7 @@
'type': 'none',
'sources': [
'services/android_arm_ucontext.h',
+ 'services/android_futex.h',
'services/android_ucontext.h',
'services/android_i386_ucontext.h',
'services/arm_linux_syscalls.h',
@@ -248,13 +294,16 @@
},
{
'target_name': 'suid_sandbox_client',
- 'type': 'static_library',
+ 'type': '<(component)',
'sources': [
'suid/common/sandbox.h',
'suid/common/suid_unsafe_environment_variables.h',
'suid/client/setuid_sandbox_client.cc',
'suid/client/setuid_sandbox_client.h',
],
+ 'defines': [
+ 'SANDBOX_IMPLEMENTATION',
+ ],
'dependencies': [
'../base/base.gyp:base',
'sandbox_services',
@@ -265,18 +314,28 @@
},
],
'conditions': [
- # Strategy copied from base_unittests_apk in base/base.gyp.
- [ 'OS=="android" and gtest_target_type == "shared_library"', {
+ [ 'OS=="android"', {
+ 'targets': [
+ {
+ 'target_name': 'sandbox_linux_unittests_stripped',
+ 'type': 'none',
+ 'dependencies': [ 'sandbox_linux_unittests' ],
+ 'actions': [{
+ 'action_name': 'strip sandbox_linux_unittests',
+ 'inputs': [ '<(PRODUCT_DIR)/sandbox_linux_unittests' ],
+ 'outputs': [ '<(PRODUCT_DIR)/sandbox_linux_unittests_stripped' ],
+ 'action': [ '<(android_strip)', '<@(_inputs)', '-o', '<@(_outputs)' ],
+ }],
+ }
+ ],
+ }],
+ [ 'OS=="android"', {
'targets': [
{
'target_name': 'sandbox_linux_jni_unittests_apk',
'type': 'none',
'variables': {
'test_suite_name': 'sandbox_linux_jni_unittests',
- 'input_shlib_path':
- '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)'
- 'sandbox_linux_jni_unittests'
- '<(SHARED_LIB_SUFFIX)',
},
'dependencies': [
'sandbox_linux_jni_unittests',
@@ -285,5 +344,23 @@
}
],
}],
+ ['test_isolation_mode != "noop"', {
+ 'targets': [
+ {
+ 'target_name': 'sandbox_linux_unittests_run',
+ 'type': 'none',
+ 'dependencies': [
+ 'sandbox_linux_unittests',
+ ],
+ 'includes': [
+ '../../build/isolate.gypi',
+ '../sandbox_linux_unittests.isolate',
+ ],
+ 'sources': [
+ '../sandbox_linux_unittests.isolate',
+ ],
+ },
+ ],
+ }],
],
}
diff --git a/chromium/sandbox/linux/sandbox_linux_test_sources.gypi b/chromium/sandbox/linux/sandbox_linux_test_sources.gypi
index a6a916fee45..bf414712132 100644
--- a/chromium/sandbox/linux/sandbox_linux_test_sources.gypi
+++ b/chromium/sandbox/linux/sandbox_linux_test_sources.gypi
@@ -7,7 +7,9 @@
{
'dependencies': [
'sandbox',
+ 'sandbox_linux_test_utils',
'../base/base.gyp:base',
+ '../base/base.gyp:test_support_base',
'../testing/gtest.gyp:gtest',
],
'include_dirs': [
@@ -15,10 +17,11 @@
],
'sources': [
'tests/main.cc',
- 'tests/unit_tests.cc',
- 'tests/unit_tests.h',
+ 'tests/unit_tests_unittest.cc',
'services/broker_process_unittest.cc',
+ 'services/scoped_process_unittest.cc',
'services/thread_helpers_unittests.cc',
+ 'services/yama_unittests.cc',
],
'conditions': [
[ 'compile_suid_client==1', {
@@ -26,9 +29,10 @@
'suid/client/setuid_sandbox_client_unittest.cc',
],
}],
- [ 'compile_seccomp_bpf==1', {
+ [ 'use_seccomp_bpf==1', {
'sources': [
- 'seccomp-bpf/bpf_tests.h',
+ 'seccomp-bpf-helpers/baseline_policy_unittest.cc',
+ 'seccomp-bpf/bpf_tests_unittest.cc',
'seccomp-bpf/codegen_unittest.cc',
'seccomp-bpf/errorcode_unittest.cc',
'seccomp-bpf/sandbox_bpf_unittest.cc',
@@ -39,6 +43,7 @@
[ 'compile_credentials==1', {
'sources': [
'services/credentials_unittest.cc',
+ 'services/unix_domain_socket_unittest.cc',
],
}],
],
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/DEPS b/chromium/sandbox/linux/seccomp-bpf-helpers/DEPS
new file mode 100644
index 00000000000..e8000d3b32c
--- /dev/null
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+sandbox/linux/services",
+ "+sandbox/linux/seccomp-bpf",
+]
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc
index d0e53e39bc8..a9fb1044778 100644
--- a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc
@@ -6,8 +6,10 @@
#include <errno.h>
#include <sys/mman.h>
-#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
#include "base/logging.h"
#include "build/build_config.h"
@@ -30,19 +32,17 @@ bool IsBaselinePolicyAllowed(int sysno) {
SyscallSets::IsAllowedBasicScheduler(sysno) ||
SyscallSets::IsAllowedEpoll(sysno) ||
SyscallSets::IsAllowedFileSystemAccessViaFd(sysno) ||
+ SyscallSets::IsAllowedFutex(sysno) ||
SyscallSets::IsAllowedGeneralIo(sysno) ||
SyscallSets::IsAllowedGetOrModifySocket(sysno) ||
SyscallSets::IsAllowedGettime(sysno) ||
- SyscallSets::IsAllowedPrctl(sysno) ||
SyscallSets::IsAllowedProcessStartOrDeath(sysno) ||
SyscallSets::IsAllowedSignalHandling(sysno) ||
- SyscallSets::IsFutex(sysno) ||
SyscallSets::IsGetSimpleId(sysno) ||
SyscallSets::IsKernelInternalApi(sysno) ||
#if defined(__arm__)
SyscallSets::IsArmPrivate(sysno) ||
#endif
- SyscallSets::IsKill(sysno) ||
SyscallSets::IsAllowedOperationOnFd(sysno);
}
@@ -63,14 +63,15 @@ bool IsBaselinePolicyWatched(int sysno) {
SyscallSets::IsInotify(sysno) ||
SyscallSets::IsKernelModule(sysno) ||
SyscallSets::IsKeyManagement(sysno) ||
+ SyscallSets::IsKill(sysno) ||
SyscallSets::IsMessageQueue(sysno) ||
SyscallSets::IsMisc(sysno) ||
#if defined(__x86_64__)
SyscallSets::IsNetworkSocketInformation(sysno) ||
#endif
SyscallSets::IsNuma(sysno) ||
+ SyscallSets::IsPrctl(sysno) ||
SyscallSets::IsProcessGroupOrSession(sysno) ||
- SyscallSets::IsProcessPrivilegeChange(sysno) ||
#if defined(__i386__)
SyscallSets::IsSocketCall(sysno) ||
#endif
@@ -81,22 +82,48 @@ bool IsBaselinePolicyWatched(int sysno) {
}
// |fs_denied_errno| is the errno return for denied filesystem access.
-ErrorCode EvaluateSyscallImpl(int fs_denied_errno, SandboxBPF* sandbox,
+ErrorCode EvaluateSyscallImpl(int fs_denied_errno,
+ pid_t current_pid,
+ SandboxBPF* sandbox,
int sysno) {
+#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \
+ defined(MEMORY_SANITIZER)
+ // TCGETS is required by the sanitizers on failure.
+ if (sysno == __NR_ioctl) {
+ return RestrictIoctl(sandbox);
+ }
+
+ if (sysno == __NR_sched_getaffinity) {
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ }
+
+ if (sysno == __NR_sigaltstack) {
+ // Required for better stack overflow detection in ASan. Disallowed in
+ // non-ASan builds.
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ }
+#endif // defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) ||
+ // defined(MEMORY_SANITIZER)
+
if (IsBaselinePolicyAllowed(sysno)) {
return ErrorCode(ErrorCode::ERR_ALLOWED);
}
-#if defined(__x86_64__) || defined(__arm__)
- if (sysno == __NR_socketpair) {
- // Only allow AF_UNIX, PF_UNIX. Crash if anything else is seen.
- COMPILE_ASSERT(AF_UNIX == PF_UNIX, af_unix_pf_unix_different);
- return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, AF_UNIX,
- ErrorCode(ErrorCode::ERR_ALLOWED),
- sandbox->Trap(CrashSIGSYS_Handler, NULL));
+ if (sysno == __NR_clone) {
+ return RestrictCloneToThreadsAndEPERMFork(sandbox);
}
+
+ if (sysno == __NR_fcntl)
+ return RestrictFcntlCommands(sandbox);
+
+#if defined(__i386__) || defined(__arm__)
+ if (sysno == __NR_fcntl64)
+ return RestrictFcntlCommands(sandbox);
#endif
+ if (sysno == __NR_futex)
+ return RestrictFutex(sandbox);
+
if (sysno == __NR_madvise) {
// Only allow MADV_DONTNEED (aka MADV_FREE).
return sandbox->Cond(2, ErrorCode::TP_32BIT,
@@ -118,14 +145,23 @@ ErrorCode EvaluateSyscallImpl(int fs_denied_errno, SandboxBPF* sandbox,
if (sysno == __NR_mprotect)
return RestrictMprotectFlags(sandbox);
- if (sysno == __NR_fcntl)
- return RestrictFcntlCommands(sandbox);
+ if (sysno == __NR_prctl)
+ return sandbox::RestrictPrctl(sandbox);
-#if defined(__i386__) || defined(__arm__)
- if (sysno == __NR_fcntl64)
- return RestrictFcntlCommands(sandbox);
+#if defined(__x86_64__) || defined(__arm__)
+ if (sysno == __NR_socketpair) {
+ // Only allow AF_UNIX, PF_UNIX. Crash if anything else is seen.
+ COMPILE_ASSERT(AF_UNIX == PF_UNIX, af_unix_pf_unix_different);
+ return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, AF_UNIX,
+ ErrorCode(ErrorCode::ERR_ALLOWED),
+ sandbox->Trap(CrashSIGSYS_Handler, NULL));
+ }
#endif
+ if (SyscallSets::IsKill(sysno)) {
+ return RestrictKillTarget(current_pid, sandbox, sysno);
+ }
+
if (SyscallSets::IsFileSystem(sysno) ||
SyscallSets::IsCurrentDirectory(sysno)) {
return ErrorCode(fs_denied_errno);
@@ -137,7 +173,8 @@ ErrorCode EvaluateSyscallImpl(int fs_denied_errno, SandboxBPF* sandbox,
if (SyscallSets::IsUmask(sysno) ||
SyscallSets::IsDeniedFileSystemAccessViaFd(sysno) ||
- SyscallSets::IsDeniedGetOrModifySocket(sysno)) {
+ SyscallSets::IsDeniedGetOrModifySocket(sysno) ||
+ SyscallSets::IsProcessPrivilegeChange(sysno)) {
return ErrorCode(EPERM);
}
@@ -151,6 +188,7 @@ ErrorCode EvaluateSyscallImpl(int fs_denied_errno, SandboxBPF* sandbox,
// be denied gracefully right away.
return sandbox->Trap(CrashSIGSYS_Handler, NULL);
}
+
// In any other case crash the program with our SIGSYS handler.
return sandbox->Trap(CrashSIGSYS_Handler, NULL);
}
@@ -160,16 +198,24 @@ ErrorCode EvaluateSyscallImpl(int fs_denied_errno, SandboxBPF* sandbox,
// Unfortunately C++03 doesn't allow delegated constructors.
// Call other constructor when C++11 lands.
BaselinePolicy::BaselinePolicy()
- : fs_denied_errno_(EPERM) {}
+ : fs_denied_errno_(EPERM), current_pid_(syscall(__NR_getpid)) {}
BaselinePolicy::BaselinePolicy(int fs_denied_errno)
- : fs_denied_errno_(fs_denied_errno) {}
+ : fs_denied_errno_(fs_denied_errno), current_pid_(syscall(__NR_getpid)) {}
-BaselinePolicy::~BaselinePolicy() {}
+BaselinePolicy::~BaselinePolicy() {
+ // Make sure that this policy is created, used and destroyed by a single
+ // process.
+ DCHECK_EQ(syscall(__NR_getpid), current_pid_);
+}
ErrorCode BaselinePolicy::EvaluateSyscall(SandboxBPF* sandbox,
int sysno) const {
- return EvaluateSyscallImpl(fs_denied_errno_, sandbox, sysno);
+ // Make sure that this policy is used in the creating process.
+ if (1 == sysno) {
+ DCHECK_EQ(syscall(__NR_getpid), current_pid_);
+ }
+ return EvaluateSyscallImpl(fs_denied_errno_, current_pid_, sandbox, sysno);
}
} // namespace sandbox.
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h
index 1dfd137fa3d..edf4c77b3c6 100644
--- a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h
@@ -7,6 +7,7 @@
#include "sandbox/linux/seccomp-bpf/errorcode.h"
#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h"
+#include "sandbox/sandbox_export.h"
namespace sandbox {
@@ -17,12 +18,14 @@ class SandboxBPFPolicy;
// that reduces the Linux kernel's attack surface. Given its nature, it doesn't
// have a clear semantics and is mostly "implementation-defined".
//
-// This returns an object that implements the SandboxBPFPolicy interface with
-// a "baseline" policy within Chromium.
+// This class implements the SandboxBPFPolicy interface with a "baseline"
+// policy for us within Chromium.
// The "baseline" policy is somewhat arbitrary. All Chromium policies are an
// alteration of it, and it represents a reasonable common ground to run most
// code in a sandboxed environment.
-class BaselinePolicy : public SandboxBPFPolicy {
+// A baseline policy is only valid for the process for which this object was
+// instantiated (so do not fork() and use it in a child).
+class SANDBOX_EXPORT BaselinePolicy : public SandboxBPFPolicy {
public:
BaselinePolicy();
// |fs_denied_errno| is the errno returned when a filesystem access system
@@ -35,6 +38,7 @@ class BaselinePolicy : public SandboxBPFPolicy {
private:
int fs_denied_errno_;
+ pid_t current_pid_;
DISALLOW_COPY_AND_ASSIGN(BaselinePolicy);
};
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc
new file mode 100644
index 00000000000..2fa0e93c1f0
--- /dev/null
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc
@@ -0,0 +1,288 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sandbox/linux/seccomp-bpf-helpers/baseline_policy.h"
+
+#include <errno.h>
+#include <linux/futex.h>
+#include <sched.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/files/scoped_file.h"
+#include "base/macros.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h"
+#include "sandbox/linux/seccomp-bpf/bpf_tests.h"
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
+#include "sandbox/linux/services/android_futex.h"
+#include "sandbox/linux/services/linux_syscalls.h"
+#include "sandbox/linux/services/thread_helpers.h"
+#include "sandbox/linux/tests/unit_tests.h"
+
+namespace sandbox {
+
+namespace {
+
+// |pid| is the return value of a fork()-like call. This
+// makes sure that if fork() succeeded the child exits
+// and the parent waits for it.
+void HandlePostForkReturn(pid_t pid) {
+ const int kChildExitCode = 1;
+ if (pid > 0) {
+ int status = 0;
+ PCHECK(pid == HANDLE_EINTR(waitpid(pid, &status, 0)));
+ CHECK(WIFEXITED(status));
+ CHECK_EQ(kChildExitCode, WEXITSTATUS(status));
+ } else if (pid == 0) {
+ _exit(kChildExitCode);
+ }
+}
+
+// Check that HandlePostForkReturn works.
+TEST(BaselinePolicy, HandlePostForkReturn) {
+ pid_t pid = fork();
+ HandlePostForkReturn(pid);
+}
+
+// This also tests that read(), write() and fstat() are allowed.
+void TestPipeOrSocketPair(base::ScopedFD read_end, base::ScopedFD write_end) {
+ BPF_ASSERT_LE(0, read_end.get());
+ BPF_ASSERT_LE(0, write_end.get());
+ struct stat stat_buf;
+ int sys_ret = fstat(read_end.get(), &stat_buf);
+ BPF_ASSERT_EQ(0, sys_ret);
+ BPF_ASSERT(S_ISFIFO(stat_buf.st_mode) || S_ISSOCK(stat_buf.st_mode));
+
+ const ssize_t kTestTransferSize = 4;
+ static const char kTestString[kTestTransferSize] = {'T', 'E', 'S', 'T'};
+ ssize_t transfered = 0;
+
+ transfered =
+ HANDLE_EINTR(write(write_end.get(), kTestString, kTestTransferSize));
+ BPF_ASSERT_EQ(kTestTransferSize, transfered);
+ char read_buf[kTestTransferSize + 1] = {0};
+ transfered = HANDLE_EINTR(read(read_end.get(), read_buf, sizeof(read_buf)));
+ BPF_ASSERT_EQ(kTestTransferSize, transfered);
+ BPF_ASSERT_EQ(0, memcmp(kTestString, read_buf, kTestTransferSize));
+}
+
+// Test that a few easy-to-test system calls are allowed.
+BPF_TEST_C(BaselinePolicy, BaselinePolicyBasicAllowed, BaselinePolicy) {
+ BPF_ASSERT_EQ(0, sched_yield());
+
+ int pipefd[2];
+ int sys_ret = pipe(pipefd);
+ BPF_ASSERT_EQ(0, sys_ret);
+ TestPipeOrSocketPair(base::ScopedFD(pipefd[0]), base::ScopedFD(pipefd[1]));
+
+ BPF_ASSERT_LE(1, getpid());
+ BPF_ASSERT_LE(0, getuid());
+}
+
+BPF_TEST_C(BaselinePolicy, FchmodErrno, BaselinePolicy) {
+ int ret = fchmod(-1, 07777);
+ BPF_ASSERT_EQ(-1, ret);
+ // Without the sandbox, this would EBADF instead.
+ BPF_ASSERT_EQ(EPERM, errno);
+}
+
+BPF_TEST_C(BaselinePolicy, ForkErrno, BaselinePolicy) {
+ errno = 0;
+ pid_t pid = fork();
+ const int fork_errno = errno;
+ HandlePostForkReturn(pid);
+
+ BPF_ASSERT_EQ(-1, pid);
+ BPF_ASSERT_EQ(EPERM, fork_errno);
+}
+
+pid_t ForkX86Glibc() {
+ return syscall(__NR_clone, CLONE_PARENT_SETTID | SIGCHLD);
+}
+
+BPF_TEST_C(BaselinePolicy, ForkX86Eperm, BaselinePolicy) {
+ errno = 0;
+ pid_t pid = ForkX86Glibc();
+ const int fork_errno = errno;
+ HandlePostForkReturn(pid);
+
+ BPF_ASSERT_EQ(-1, pid);
+ BPF_ASSERT_EQ(EPERM, fork_errno);
+}
+
+pid_t ForkARMGlibc() {
+ return syscall(__NR_clone,
+ CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD);
+}
+
+BPF_TEST_C(BaselinePolicy, ForkArmEperm, BaselinePolicy) {
+ errno = 0;
+ pid_t pid = ForkARMGlibc();
+ const int fork_errno = errno;
+ HandlePostForkReturn(pid);
+
+ BPF_ASSERT_EQ(-1, pid);
+ BPF_ASSERT_EQ(EPERM, fork_errno);
+}
+
+BPF_TEST_C(BaselinePolicy, CreateThread, BaselinePolicy) {
+ base::Thread thread("sandbox_tests");
+ BPF_ASSERT(thread.Start());
+}
+
+BPF_DEATH_TEST_C(BaselinePolicy,
+ DisallowedCloneFlagCrashes,
+ DEATH_MESSAGE(GetCloneErrorMessageContentForTests()),
+ BaselinePolicy) {
+ pid_t pid = syscall(__NR_clone, CLONE_THREAD | SIGCHLD);
+ HandlePostForkReturn(pid);
+}
+
+BPF_DEATH_TEST_C(BaselinePolicy,
+ DisallowedKillCrashes,
+ DEATH_MESSAGE(GetKillErrorMessageContentForTests()),
+ BaselinePolicy) {
+ BPF_ASSERT_NE(1, getpid());
+ kill(1, 0);
+ _exit(1);
+}
+
+BPF_TEST_C(BaselinePolicy, CanKillSelf, BaselinePolicy) {
+ int sys_ret = kill(getpid(), 0);
+ BPF_ASSERT_EQ(0, sys_ret);
+}
+
+BPF_TEST_C(BaselinePolicy, Socketpair, BaselinePolicy) {
+ int sv[2];
+ int sys_ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, sv);
+ BPF_ASSERT_EQ(0, sys_ret);
+ TestPipeOrSocketPair(base::ScopedFD(sv[0]), base::ScopedFD(sv[1]));
+
+ sys_ret = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sv);
+ BPF_ASSERT_EQ(0, sys_ret);
+ TestPipeOrSocketPair(base::ScopedFD(sv[0]), base::ScopedFD(sv[1]));
+}
+
+// Not all architectures can restrict the domain for socketpair().
+#if defined(__x86_64__) || defined(__arm__)
+BPF_DEATH_TEST_C(BaselinePolicy,
+ SocketpairWrongDomain,
+ DEATH_MESSAGE(GetErrorMessageContentForTests()),
+ BaselinePolicy) {
+ int sv[2];
+ ignore_result(socketpair(AF_INET, SOCK_STREAM, 0, sv));
+ _exit(1);
+}
+#endif // defined(__x86_64__) || defined(__arm__)
+
+BPF_TEST_C(BaselinePolicy, EPERM_open, BaselinePolicy) {
+ errno = 0;
+ int sys_ret = open("/proc/cpuinfo", O_RDONLY);
+ BPF_ASSERT_EQ(-1, sys_ret);
+ BPF_ASSERT_EQ(EPERM, errno);
+}
+
+BPF_TEST_C(BaselinePolicy, EPERM_access, BaselinePolicy) {
+ errno = 0;
+ int sys_ret = access("/proc/cpuinfo", R_OK);
+ BPF_ASSERT_EQ(-1, sys_ret);
+ BPF_ASSERT_EQ(EPERM, errno);
+}
+
+BPF_TEST_C(BaselinePolicy, EPERM_getcwd, BaselinePolicy) {
+ errno = 0;
+ char buf[1024];
+ char* cwd = getcwd(buf, sizeof(buf));
+ BPF_ASSERT_EQ(NULL, cwd);
+ BPF_ASSERT_EQ(EPERM, errno);
+}
+
+// A failing test using this macro could be problematic since we perform
+// system calls by passing "0" as every argument.
+// The kernel could SIGSEGV the process or the system call itself could reboot
+// the machine. Some thoughts have been given when hand-picking the system
+// calls below to limit any potential side effects outside of the current
+// process.
+#define TEST_BASELINE_SIGSYS(sysno) \
+ BPF_DEATH_TEST_C(BaselinePolicy, \
+ SIGSYS_##sysno, \
+ DEATH_MESSAGE(GetErrorMessageContentForTests()), \
+ BaselinePolicy) { \
+ syscall(sysno, 0, 0, 0, 0, 0, 0); \
+ _exit(1); \
+ }
+
+TEST_BASELINE_SIGSYS(__NR_syslog);
+TEST_BASELINE_SIGSYS(__NR_sched_setaffinity);
+TEST_BASELINE_SIGSYS(__NR_timer_create);
+TEST_BASELINE_SIGSYS(__NR_io_cancel);
+TEST_BASELINE_SIGSYS(__NR_ptrace);
+TEST_BASELINE_SIGSYS(__NR_eventfd);
+TEST_BASELINE_SIGSYS(__NR_fgetxattr);
+TEST_BASELINE_SIGSYS(__NR_fanotify_init);
+TEST_BASELINE_SIGSYS(__NR_swapon);
+TEST_BASELINE_SIGSYS(__NR_chroot);
+TEST_BASELINE_SIGSYS(__NR_acct);
+TEST_BASELINE_SIGSYS(__NR_sysinfo);
+TEST_BASELINE_SIGSYS(__NR_inotify_init);
+TEST_BASELINE_SIGSYS(__NR_init_module);
+TEST_BASELINE_SIGSYS(__NR_keyctl);
+TEST_BASELINE_SIGSYS(__NR_mq_open);
+TEST_BASELINE_SIGSYS(__NR_vserver);
+TEST_BASELINE_SIGSYS(__NR_getcpu);
+TEST_BASELINE_SIGSYS(__NR_setpgid);
+TEST_BASELINE_SIGSYS(__NR_getitimer);
+
+#if !defined(OS_ANDROID)
+BPF_DEATH_TEST_C(BaselinePolicy,
+ FutexWithRequeuePriorityInheritence,
+ DEATH_MESSAGE(GetFutexErrorMessageContentForTests()),
+ BaselinePolicy) {
+ syscall(__NR_futex, NULL, FUTEX_CMP_REQUEUE_PI, 0, NULL, NULL, 0);
+ _exit(1);
+}
+
+BPF_DEATH_TEST_C(BaselinePolicy,
+ FutexWithRequeuePriorityInheritencePrivate,
+ DEATH_MESSAGE(GetFutexErrorMessageContentForTests()),
+ BaselinePolicy) {
+ syscall(__NR_futex, NULL, FUTEX_CMP_REQUEUE_PI_PRIVATE, 0, NULL, NULL, 0);
+ _exit(1);
+}
+#endif // !defined(OS_ANDROID)
+
+BPF_TEST_C(BaselinePolicy, PrctlDumpable, BaselinePolicy) {
+ const int is_dumpable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0);
+ BPF_ASSERT(is_dumpable == 1 || is_dumpable == 0);
+ const int prctl_ret = prctl(PR_SET_DUMPABLE, is_dumpable, 0, 0, 0, 0);
+ BPF_ASSERT_EQ(0, prctl_ret);
+}
+
+// Workaround incomplete Android headers.
+#if !defined(PR_CAPBSET_READ)
+#define PR_CAPBSET_READ 23
+#endif
+
+BPF_DEATH_TEST_C(BaselinePolicy,
+ PrctlSigsys,
+ DEATH_MESSAGE(GetPrctlErrorMessageContentForTests()),
+ BaselinePolicy) {
+ prctl(PR_CAPBSET_READ, 0, 0, 0, 0);
+ _exit(1);
+}
+
+} // namespace
+
+} // namespace sandbox
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc
index 6ff71257526..57dc24e8f88 100644
--- a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc
@@ -9,11 +9,17 @@
#include <unistd.h>
#include "base/basictypes.h"
-#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "build/build_config.h"
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
+#define SECCOMP_MESSAGE_COMMON_CONTENT "seccomp-bpf failure"
+#define SECCOMP_MESSAGE_CLONE_CONTENT "clone() failure"
+#define SECCOMP_MESSAGE_PRCTL_CONTENT "prctl() failure"
+#define SECCOMP_MESSAGE_IOCTL_CONTENT "ioctl() failure"
+#define SECCOMP_MESSAGE_KILL_CONTENT "(tg)kill() failure"
+#define SECCOMP_MESSAGE_FUTEX_CONTENT "futex() failure"
+
namespace {
inline bool IsArchitectureX86_64() {
@@ -55,7 +61,7 @@ void PrintSyscallError(uint32_t sysno) {
sysno_base10[i] = '0' + mod;
}
static const char kSeccompErrorPrefix[] =
- __FILE__":**CRASHING**:seccomp-bpf failure in syscall ";
+ __FILE__":**CRASHING**:" SECCOMP_MESSAGE_COMMON_CONTENT " in syscall ";
static const char kSeccompErrorPostfix[] = "\n";
WriteToStdErr(kSeccompErrorPrefix, sizeof(kSeccompErrorPrefix) - 1);
WriteToStdErr(sysno_base10, sizeof(sysno_base10));
@@ -95,11 +101,11 @@ intptr_t CrashSIGSYS_Handler(const struct arch_seccomp_data& args, void* aux) {
// TODO(jln): refactor the reporting functions.
intptr_t SIGSYSCloneFailure(const struct arch_seccomp_data& args, void* aux) {
+ static const char kSeccompCloneError[] =
+ __FILE__":**CRASHING**:" SECCOMP_MESSAGE_CLONE_CONTENT "\n";
+ WriteToStdErr(kSeccompCloneError, sizeof(kSeccompCloneError) - 1);
// "flags" is the first argument in the kernel's clone().
// Mark as volatile to be able to find the value on the stack in a minidump.
-#if !defined(NDEBUG)
- RAW_LOG(ERROR, __FILE__":**CRASHING**:clone() failure\n");
-#endif
volatile uint64_t clone_flags = args.args[0];
volatile char* addr;
if (IsArchitectureX86_64()) {
@@ -115,10 +121,10 @@ intptr_t SIGSYSCloneFailure(const struct arch_seccomp_data& args, void* aux) {
intptr_t SIGSYSPrctlFailure(const struct arch_seccomp_data& args,
void* /* aux */) {
+ static const char kSeccompPrctlError[] =
+ __FILE__":**CRASHING**:" SECCOMP_MESSAGE_PRCTL_CONTENT "\n";
+ WriteToStdErr(kSeccompPrctlError, sizeof(kSeccompPrctlError) - 1);
// Mark as volatile to be able to find the value on the stack in a minidump.
-#if !defined(NDEBUG)
- RAW_LOG(ERROR, __FILE__":**CRASHING**:prctl() failure\n");
-#endif
volatile uint64_t option = args.args[0];
volatile char* addr =
reinterpret_cast<volatile char*>(option & 0xFFF);
@@ -129,10 +135,10 @@ intptr_t SIGSYSPrctlFailure(const struct arch_seccomp_data& args,
intptr_t SIGSYSIoctlFailure(const struct arch_seccomp_data& args,
void* /* aux */) {
+ static const char kSeccompIoctlError[] =
+ __FILE__":**CRASHING**:" SECCOMP_MESSAGE_IOCTL_CONTENT "\n";
+ WriteToStdErr(kSeccompIoctlError, sizeof(kSeccompIoctlError) - 1);
// Make "request" volatile so that we can see it on the stack in a minidump.
-#if !defined(NDEBUG)
- RAW_LOG(ERROR, __FILE__":**CRASHING**:ioctl() failure\n");
-#endif
volatile uint64_t request = args.args[1];
volatile char* addr = reinterpret_cast<volatile char*>(request & 0xFFFF);
*addr = '\0';
@@ -143,4 +149,56 @@ intptr_t SIGSYSIoctlFailure(const struct arch_seccomp_data& args,
_exit(1);
}
+intptr_t SIGSYSKillFailure(const struct arch_seccomp_data& args,
+ void* /* aux */) {
+ static const char kSeccompKillError[] =
+ __FILE__":**CRASHING**:" SECCOMP_MESSAGE_KILL_CONTENT "\n";
+ WriteToStdErr(kSeccompKillError, sizeof(kSeccompKillError) - 1);
+ // Make "request" volatile so that we can see it on the stack in a minidump.
+ volatile uint64_t pid = args.args[0];
+ volatile char* addr = reinterpret_cast<volatile char*>(pid & 0xFFF);
+ *addr = '\0';
+ // Hit the NULL page if this fails.
+ addr = reinterpret_cast<volatile char*>(pid & 0xFFF);
+ *addr = '\0';
+ for (;;)
+ _exit(1);
+}
+
+intptr_t SIGSYSFutexFailure(const struct arch_seccomp_data& args,
+ void* /* aux */) {
+ static const char kSeccompFutexError[] =
+ __FILE__ ":**CRASHING**:" SECCOMP_MESSAGE_FUTEX_CONTENT "\n";
+ WriteToStdErr(kSeccompFutexError, sizeof(kSeccompFutexError) - 1);
+ volatile int futex_op = args.args[1];
+ volatile char* addr = reinterpret_cast<volatile char*>(futex_op & 0xFFF);
+ *addr = '\0';
+ for (;;)
+ _exit(1);
+}
+
+const char* GetErrorMessageContentForTests() {
+ return SECCOMP_MESSAGE_COMMON_CONTENT;
+}
+
+const char* GetCloneErrorMessageContentForTests() {
+ return SECCOMP_MESSAGE_CLONE_CONTENT;
+}
+
+const char* GetPrctlErrorMessageContentForTests() {
+ return SECCOMP_MESSAGE_PRCTL_CONTENT;
+}
+
+const char* GetIoctlErrorMessageContentForTests() {
+ return SECCOMP_MESSAGE_IOCTL_CONTENT;
+}
+
+const char* GetKillErrorMessageContentForTests() {
+ return SECCOMP_MESSAGE_KILL_CONTENT;
+}
+
+const char* GetFutexErrorMessageContentForTests() {
+ return SECCOMP_MESSAGE_FUTEX_CONTENT;
+}
+
} // namespace sandbox.
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h
index 3bf5c16db1f..280afa7a69f 100644
--- a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h
@@ -7,6 +7,7 @@
#include "base/basictypes.h"
#include "build/build_config.h"
+#include "sandbox/sandbox_export.h"
// The handlers are suitable for use in Trap() error codes. They are
// guaranteed to be async-signal safe.
@@ -19,20 +20,41 @@ struct arch_seccomp_data;
// This handler will crash the currently running process. The crashing address
// will be the number of the current system call, extracted from |args|.
// This handler will also print to stderr the number of the crashing syscall.
-intptr_t CrashSIGSYS_Handler(const struct arch_seccomp_data& args, void* aux);
+SANDBOX_EXPORT intptr_t
+ CrashSIGSYS_Handler(const struct arch_seccomp_data& args, void* aux);
// The following three handlers are suitable to report failures with the
// clone(), prctl() and ioctl() system calls respectively.
// The crashing address will be (clone_flags & 0xFFFFFF), where clone_flags is
// the clone(2) argument, extracted from |args|.
-intptr_t SIGSYSCloneFailure(const struct arch_seccomp_data& args, void* aux);
+SANDBOX_EXPORT intptr_t
+ SIGSYSCloneFailure(const struct arch_seccomp_data& args, void* aux);
// The crashing address will be (option & 0xFFF), where option is the prctl(2)
// argument.
-intptr_t SIGSYSPrctlFailure(const struct arch_seccomp_data& args, void* aux);
+SANDBOX_EXPORT intptr_t
+ SIGSYSPrctlFailure(const struct arch_seccomp_data& args, void* aux);
// The crashing address will be request & 0xFFFF, where request is the ioctl(2)
// argument.
-intptr_t SIGSYSIoctlFailure(const struct arch_seccomp_data& args, void* aux);
+SANDBOX_EXPORT intptr_t
+ SIGSYSIoctlFailure(const struct arch_seccomp_data& args, void* aux);
+// The crashing address will be (pid & 0xFFF), where pid is the first
+// argument (and can be a tid).
+SANDBOX_EXPORT intptr_t
+ SIGSYSKillFailure(const struct arch_seccomp_data& args, void* aux);
+// The crashing address will be (op & 0xFFF), where op is the second
+// argument.
+SANDBOX_EXPORT intptr_t
+ SIGSYSFutexFailure(const struct arch_seccomp_data& args, void* aux);
+
+// Following four functions return substrings of error messages used
+// in the above four functions. They are useful in death tests.
+SANDBOX_EXPORT const char* GetErrorMessageContentForTests();
+SANDBOX_EXPORT const char* GetCloneErrorMessageContentForTests();
+SANDBOX_EXPORT const char* GetPrctlErrorMessageContentForTests();
+SANDBOX_EXPORT const char* GetIoctlErrorMessageContentForTests();
+SANDBOX_EXPORT const char* GetKillErrorMessageContentForTests();
+SANDBOX_EXPORT const char* GetFutexErrorMessageContentForTests();
} // namespace sandbox.
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc
index 9b417ce221f..16c37a06975 100644
--- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc
@@ -7,6 +7,7 @@
#include <errno.h>
#include <fcntl.h>
#include <fcntl.h>
+#include <linux/futex.h>
#include <linux/net.h>
#include <sched.h>
#include <signal.h>
@@ -19,9 +20,12 @@
#include "base/basictypes.h"
#include "base/logging.h"
+#include "base/macros.h"
+#include "build/build_config.h"
#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h"
#include "sandbox/linux/seccomp-bpf/linux_seccomp.h"
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
+#include "sandbox/linux/services/android_futex.h"
#if defined(OS_ANDROID)
#if !defined(F_DUPFD_CLOEXEC)
@@ -35,24 +39,24 @@
namespace {
-inline bool RunningOnASAN() {
-#if defined(ADDRESS_SANITIZER)
+inline bool IsArchitectureX86_64() {
+#if defined(__x86_64__)
return true;
#else
return false;
#endif
}
-inline bool IsArchitectureX86_64() {
-#if defined(__x86_64__)
+inline bool IsArchitectureI386() {
+#if defined(__i386__)
return true;
#else
return false;
#endif
}
-inline bool IsArchitectureI386() {
-#if defined(__i386__)
+inline bool IsAndroid() {
+#if defined(OS_ANDROID)
return true;
#else
return false;
@@ -63,24 +67,40 @@ inline bool IsArchitectureI386() {
namespace sandbox {
+// Allow Glibc's and Android pthread creation flags, crash on any other
+// thread creation attempts and EPERM attempts to use neither
+// CLONE_VM, nor CLONE_THREAD, which includes all fork() implementations.
ErrorCode RestrictCloneToThreadsAndEPERMFork(SandboxBPF* sandbox) {
- // Glibc's pthread.
- if (!RunningOnASAN()) {
+ if (!IsAndroid()) {
+ const uint64_t kGlibcPthreadFlags =
+ CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD |
+ CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID |
+ CLONE_CHILD_CLEARTID;
+
return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
- CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
- CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS |
- CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID,
+ kGlibcPthreadFlags,
ErrorCode(ErrorCode::ERR_ALLOWED),
- sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
- CLONE_PARENT_SETTID | SIGCHLD,
- ErrorCode(EPERM),
- // ARM
- sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
- CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD,
- ErrorCode(EPERM),
- sandbox->Trap(SIGSYSCloneFailure, NULL))));
+ sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS,
+ CLONE_VM | CLONE_THREAD,
+ sandbox->Trap(SIGSYSCloneFailure, NULL),
+ ErrorCode(EPERM)));
} else {
- return ErrorCode(ErrorCode::ERR_ALLOWED);
+ const uint64_t kAndroidCloneMask = CLONE_VM | CLONE_FS | CLONE_FILES |
+ CLONE_SIGHAND | CLONE_THREAD |
+ CLONE_SYSVSEM;
+ const uint64_t kObsoleteAndroidCloneMask =
+ kAndroidCloneMask | CLONE_DETACHED;
+
+ return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
+ kAndroidCloneMask,
+ ErrorCode(ErrorCode::ERR_ALLOWED),
+ sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
+ kObsoleteAndroidCloneMask,
+ ErrorCode(ErrorCode::ERR_ALLOWED),
+ sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS,
+ CLONE_VM | CLONE_THREAD,
+ sandbox->Trap(SIGSYSCloneFailure, NULL),
+ ErrorCode(EPERM))));
}
}
@@ -211,4 +231,46 @@ ErrorCode RestrictSocketcallCommand(SandboxBPF* sandbox) {
}
#endif
+ErrorCode RestrictKillTarget(pid_t target_pid, SandboxBPF* sandbox, int sysno) {
+ switch (sysno) {
+ case __NR_kill:
+ case __NR_tgkill:
+ return sandbox->Cond(0,
+ ErrorCode::TP_32BIT,
+ ErrorCode::OP_EQUAL,
+ target_pid,
+ ErrorCode(ErrorCode::ERR_ALLOWED),
+ sandbox->Trap(SIGSYSKillFailure, NULL));
+ case __NR_tkill:
+ return sandbox->Trap(SIGSYSKillFailure, NULL);
+ default:
+ NOTREACHED();
+ return sandbox->Trap(CrashSIGSYS_Handler, NULL);
+ }
+}
+
+ErrorCode RestrictFutex(SandboxBPF* sandbox) {
+ // In futex.c, the kernel does "int cmd = op & FUTEX_CMD_MASK;". We need to
+ // make sure that the combination below will cover every way to get
+ // FUTEX_CMP_REQUEUE_PI.
+ const int kBannedFutexBits =
+ ~(FUTEX_CMD_MASK | FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME);
+ COMPILE_ASSERT(0 == kBannedFutexBits,
+ need_to_explicitly_blacklist_more_bits);
+
+ return sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
+ FUTEX_CMP_REQUEUE_PI,
+ sandbox->Trap(SIGSYSFutexFailure, NULL),
+ sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
+ FUTEX_CMP_REQUEUE_PI_PRIVATE,
+ sandbox->Trap(SIGSYSFutexFailure, NULL),
+ sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
+ FUTEX_CMP_REQUEUE_PI | FUTEX_CLOCK_REALTIME,
+ sandbox->Trap(SIGSYSFutexFailure, NULL),
+ sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL,
+ FUTEX_CMP_REQUEUE_PI_PRIVATE | FUTEX_CLOCK_REALTIME,
+ sandbox->Trap(SIGSYSFutexFailure, NULL),
+ ErrorCode(ErrorCode::ERR_ALLOWED)))));
+}
+
} // namespace sandbox.
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h
index 65b7c472198..bc5a1c0e76a 100644
--- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h
@@ -5,11 +5,14 @@
#ifndef SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_PARAMETERS_RESTRICTIONS_H_
#define SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_PARAMETERS_RESTRICTIONS_H_
+#include <unistd.h>
+
#include "build/build_config.h"
+#include "sandbox/sandbox_export.h"
// These are helpers to build seccomp-bpf policies, i.e. policies for a
// sandbox that reduces the Linux kernel's attack surface. They return an
-// ErrorCode suitable to restrict certain system call parameters.
+// SANDBOX_EXPORT ErrorCode suitable to restrict certain system call parameters.
namespace sandbox {
@@ -20,39 +23,48 @@ class SandboxBPF;
// Reject fork(2) attempts with EPERM.
// Don't restrict on ASAN.
// Crash if anything else is attempted.
-ErrorCode RestrictCloneToThreadsAndEPERMFork(SandboxBPF* sandbox);
+SANDBOX_EXPORT ErrorCode
+ RestrictCloneToThreadsAndEPERMFork(SandboxBPF* sandbox);
// Allow PR_SET_NAME, PR_SET_DUMPABLE, PR_GET_DUMPABLE.
// Crash if anything else is attempted.
-ErrorCode RestrictPrctl(SandboxBPF* sandbox);
+SANDBOX_EXPORT ErrorCode RestrictPrctl(SandboxBPF* sandbox);
// Allow TCGETS and FIONREAD.
// Crash if anything else is attempted.
-ErrorCode RestrictIoctl(SandboxBPF* sandbox);
+SANDBOX_EXPORT ErrorCode RestrictIoctl(SandboxBPF* sandbox);
// Restrict the flags argument in mmap(2).
// Only allow: MAP_SHARED | MAP_PRIVATE | MAP_ANONYMOUS |
// MAP_STACK | MAP_NORESERVE | MAP_FIXED | MAP_DENYWRITE.
// Crash if any other flag is used.
-ErrorCode RestrictMmapFlags(SandboxBPF* sandbox);
+SANDBOX_EXPORT ErrorCode RestrictMmapFlags(SandboxBPF* sandbox);
// Restrict the prot argument in mprotect(2).
// Only allow: PROT_READ | PROT_WRITE | PROT_EXEC.
-ErrorCode RestrictMprotectFlags(SandboxBPF* sandbox);
+SANDBOX_EXPORT ErrorCode RestrictMprotectFlags(SandboxBPF* sandbox);
// Restrict fcntl(2) cmd argument to:
// We allow F_GETFL, F_SETFL, F_GETFD, F_SETFD, F_DUPFD, F_DUPFD_CLOEXEC,
// F_SETLK, F_SETLKW and F_GETLK.
// Also, in F_SETFL, restrict the allowed flags to: O_ACCMODE | O_APPEND |
// O_NONBLOCK | O_SYNC | O_LARGEFILE | O_CLOEXEC | O_NOATIME.
-ErrorCode RestrictFcntlCommands(SandboxBPF* sandbox);
+SANDBOX_EXPORT ErrorCode RestrictFcntlCommands(SandboxBPF* sandbox);
#if defined(__i386__)
// Restrict socketcall(2) to only allow socketpair(2), send(2), recv(2),
// sendto(2), recvfrom(2), shutdown(2), sendmsg(2) and recvmsg(2).
-ErrorCode RestrictSocketcallCommand(SandboxBPF* sandbox);
+SANDBOX_EXPORT ErrorCode RestrictSocketcallCommand(SandboxBPF* sandbox);
#endif
+// Restrict |sysno| (which must be kill, tkill or tgkill) by allowing tgkill or
+// kill iff the first parameter is |target_pid|, crashing otherwise or if
+// |sysno| is tkill.
+ErrorCode RestrictKillTarget(pid_t target_pid, SandboxBPF* sandbox, int sysno);
+
+// Crash if FUTEX_CMP_REQUEUE_PI is used in the second argument of futex(2).
+ErrorCode RestrictFutex(SandboxBPF* sandbox);
+
} // namespace sandbox.
#endif // SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_PARAMETERS_RESTRICTIONS_H_
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc
index 032f6c3c472..e3db231502d 100644
--- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc
@@ -14,12 +14,11 @@ namespace sandbox {
// The implicitly defined sets form a partition of the sets of
// system calls.
-// TODO(jln) we need to restrict the first parameter!
bool SyscallSets::IsKill(int sysno) {
switch (sysno) {
case __NR_kill:
- case __NR_tkill:
case __NR_tgkill:
+ case __NR_tkill: // Deprecated.
return true;
default:
return false;
@@ -351,7 +350,6 @@ bool SyscallSets::IsKernelInternalApi(int sysno) {
// This should be thought through in conjunction with IsFutex().
bool SyscallSets::IsAllowedProcessStartOrDeath(int sysno) {
switch (sysno) {
- case __NR_clone: // TODO(jln): restrict flags.
case __NR_exit:
case __NR_exit_group:
case __NR_wait4:
@@ -360,6 +358,7 @@ bool SyscallSets::IsAllowedProcessStartOrDeath(int sysno) {
case __NR_waitpid:
#endif
return true;
+ case __NR_clone: // Should be parameter-restricted.
case __NR_setns: // Privileged.
case __NR_fork:
#if defined(__i386__) || defined(__x86_64__)
@@ -375,12 +374,12 @@ bool SyscallSets::IsAllowedProcessStartOrDeath(int sysno) {
}
// It's difficult to restrict those, but there is attack surface here.
-bool SyscallSets::IsFutex(int sysno) {
+bool SyscallSets::IsAllowedFutex(int sysno) {
switch (sysno) {
- case __NR_futex:
case __NR_get_robust_list:
case __NR_set_robust_list:
return true;
+ case __NR_futex:
default:
return false;
}
@@ -547,14 +546,14 @@ bool SyscallSets::IsAllowedGeneralIo(int sysno) {
}
}
-bool SyscallSets::IsAllowedPrctl(int sysno) {
+bool SyscallSets::IsPrctl(int sysno) {
switch (sysno) {
- case __NR_prctl:
- return true;
- default:
#if defined(__x86_64__)
case __NR_arch_prctl:
#endif
+ case __NR_prctl:
+ return true;
+ default:
return false;
}
}
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h
index d2cf1a17801..c1e412e9034 100644
--- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h
@@ -7,6 +7,7 @@
#include "base/basictypes.h"
#include "build/build_config.h"
+#include "sandbox/sandbox_export.h"
// These are helpers to build seccomp-bpf policies, i.e. policies for a
// sandbox that reduces the Linux kernel's attack surface. Given their
@@ -15,7 +16,7 @@
namespace sandbox {
-class SyscallSets {
+class SANDBOX_EXPORT SyscallSets {
public:
static bool IsKill(int sysno);
static bool IsAllowedGettime(int sysno);
@@ -36,7 +37,7 @@ class SyscallSets {
// This should be thought through in conjunction with IsFutex().
static bool IsAllowedProcessStartOrDeath(int sysno);
// It's difficult to restrict those, but there is attack surface here.
- static bool IsFutex(int sysno);
+ static bool IsAllowedFutex(int sysno);
static bool IsAllowedEpoll(int sysno);
static bool IsAllowedGetOrModifySocket(int sysno);
static bool IsDeniedGetOrModifySocket(int sysno);
@@ -52,7 +53,7 @@ class SyscallSets {
static bool IsAllowedAddressSpaceAccess(int sysno);
static bool IsAllowedGeneralIo(int sysno);
- static bool IsAllowedPrctl(int sysno);
+ static bool IsPrctl(int sysno);
static bool IsAllowedBasicScheduler(int sysno);
static bool IsAdminOperation(int sysno);
static bool IsKernelModule(int sysno);
diff --git a/chromium/sandbox/linux/seccomp-bpf/DEPS b/chromium/sandbox/linux/seccomp-bpf/DEPS
new file mode 100644
index 00000000000..15b2b36a3ef
--- /dev/null
+++ b/chromium/sandbox/linux/seccomp-bpf/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+sandbox/linux/services"
+]
diff --git a/chromium/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h b/chromium/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h
new file mode 100644
index 00000000000..8890f798749
--- /dev/null
+++ b/chromium/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTER_COMPATIBILITY_DELEGATE_H_
+#define SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTER_COMPATIBILITY_DELEGATE_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf_compatibility_policy.h"
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h"
+#include "sandbox/linux/tests/sandbox_test_runner.h"
+#include "sandbox/linux/tests/unit_tests.h"
+
+namespace sandbox {
+
+// This templated class allows building a BPFTesterDelegate from a
+// deprecated-style BPF policy (that is a SyscallEvaluator function pointer,
+// instead of a SandboxBPFPolicy class), specified in |policy_function| and a
+// function pointer to a test in |test_function|.
+// This allows both the policy and the test function to take a pointer to an
+// object of type "Aux" as a parameter. This is used to implement the BPF_TEST
+// macro and should generally not be used directly.
+template <class Aux>
+class BPFTesterCompatibilityDelegate : public BPFTesterDelegate {
+ public:
+ typedef Aux AuxType;
+ BPFTesterCompatibilityDelegate(
+ void (*test_function)(AuxType*),
+ typename CompatibilityPolicy<AuxType>::SyscallEvaluator policy_function)
+ : aux_(),
+ test_function_(test_function),
+ policy_function_(policy_function) {}
+
+ virtual ~BPFTesterCompatibilityDelegate() {}
+
+ virtual scoped_ptr<SandboxBPFPolicy> GetSandboxBPFPolicy() OVERRIDE {
+ // The current method is guaranteed to only run in the child process
+ // running the test. In this process, the current object is guaranteed
+ // to live forever. So it's ok to pass aux_pointer_for_policy_ to
+ // the policy, which could in turn pass it to the kernel via Trap().
+ return scoped_ptr<SandboxBPFPolicy>(
+ new CompatibilityPolicy<AuxType>(policy_function_, &aux_));
+ }
+
+ virtual void RunTestFunction() OVERRIDE {
+ // Run the actual test.
+ // The current object is guaranteed to live forever in the child process
+ // where this will run.
+ test_function_(&aux_);
+ }
+
+ private:
+ AuxType aux_;
+ void (*test_function_)(AuxType*);
+ typename CompatibilityPolicy<AuxType>::SyscallEvaluator policy_function_;
+ DISALLOW_COPY_AND_ASSIGN(BPFTesterCompatibilityDelegate);
+};
+
+} // namespace sandbox
+
+#endif // SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTER_COMPATIBILITY_DELEGATE_H_
diff --git a/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h b/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h
index 7095c23b8c1..da92de80b79 100644
--- a/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h
+++ b/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h
@@ -5,110 +5,118 @@
#ifndef SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTS_H__
#define SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTS_H__
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
+#include "base/basictypes.h"
#include "build/build_config.h"
+#include "sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h"
#include "sandbox/linux/tests/unit_tests.h"
-#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
namespace sandbox {
-// A BPF_DEATH_TEST is just the same as a BPF_TEST, but it assumes that the
-// test will fail with a particular known error condition. Use the DEATH_XXX()
-// macros from unit_tests.h to specify the expected error condition.
-// A BPF_DEATH_TEST is always disabled under ThreadSanitizer, see
-// crbug.com/243968.
-#define BPF_DEATH_TEST(test_case_name, test_name, death, policy, aux...) \
- void BPF_TEST_##test_name(sandbox::BPFTests<aux>::AuxType& BPF_AUX); \
- TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \
- sandbox::BPFTests<aux>::TestArgs arg(BPF_TEST_##test_name, policy); \
- sandbox::BPFTests<aux>::RunTestInProcess( \
- sandbox::BPFTests<aux>::TestWrapper, &arg, death); \
- } \
- void BPF_TEST_##test_name(sandbox::BPFTests<aux>::AuxType& BPF_AUX)
+// BPF_TEST_C() is a special version of SANDBOX_TEST(). It runs a test function
+// in a sub-process, under a seccomp-bpf policy specified in
+// |bpf_policy_class_name| without failing on configurations that are allowed
+// to not support seccomp-bpf in their kernels.
+// This is the preferred format for new BPF tests. |bpf_policy_class_name| is a
+// class name (which will be default-constructed) that implements the
+// SandboxBPFPolicy interface.
+// The test function's body can simply follow. Test functions should use
+// the BPF_ASSERT macros defined below, not GTEST's macros. The use of
+// CHECK* macros is supported but less robust.
+#define BPF_TEST_C(test_case_name, test_name, bpf_policy_class_name) \
+ BPF_DEATH_TEST_C( \
+ test_case_name, test_name, DEATH_SUCCESS(), bpf_policy_class_name)
+
+// Identical to BPF_TEST_C but allows to specify the nature of death.
+#define BPF_DEATH_TEST_C( \
+ test_case_name, test_name, death, bpf_policy_class_name) \
+ void BPF_TEST_C_##test_name(); \
+ TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \
+ sandbox::SandboxBPFTestRunner bpf_test_runner( \
+ new sandbox::BPFTesterSimpleDelegate<bpf_policy_class_name>( \
+ BPF_TEST_C_##test_name)); \
+ sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, death); \
+ } \
+ void BPF_TEST_C_##test_name()
+
+// This form of BPF_TEST is a little verbose and should be reserved for complex
+// tests where a lot of control is required.
+// |bpf_tester_delegate_class| must be a classname implementing the
+// BPFTesterDelegate interface.
+#define BPF_TEST_D(test_case_name, test_name, bpf_tester_delegate_class) \
+ BPF_DEATH_TEST_D( \
+ test_case_name, test_name, DEATH_SUCCESS(), bpf_tester_delegate_class)
+
+// Identical to BPF_TEST_D but allows to specify the nature of death.
+#define BPF_DEATH_TEST_D( \
+ test_case_name, test_name, death, bpf_tester_delegate_class) \
+ TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \
+ sandbox::SandboxBPFTestRunner bpf_test_runner( \
+ new bpf_tester_delegate_class()); \
+ sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, death); \
+ }
-// BPF_TEST() is a special version of SANDBOX_TEST(). It turns into a no-op,
-// if the host does not have kernel support for running BPF filters.
-// Also, it takes advantage of the Die class to avoid calling LOG(FATAL), from
-// inside our tests, as we don't need or even want all the error handling that
-// LOG(FATAL) would do.
+// Assertions are handled exactly the same as with a normal SANDBOX_TEST()
+#define BPF_ASSERT SANDBOX_ASSERT
+#define BPF_ASSERT_EQ(x, y) BPF_ASSERT((x) == (y))
+#define BPF_ASSERT_NE(x, y) BPF_ASSERT((x) != (y))
+#define BPF_ASSERT_LT(x, y) BPF_ASSERT((x) < (y))
+#define BPF_ASSERT_GT(x, y) BPF_ASSERT((x) > (y))
+#define BPF_ASSERT_LE(x, y) BPF_ASSERT((x) <= (y))
+#define BPF_ASSERT_GE(x, y) BPF_ASSERT((x) >= (y))
+
+// This form of BPF_TEST is now discouraged (but still allowed) in favor of
+// BPF_TEST_D and BPF_TEST_C.
+// The |policy| parameter should be a SyscallEvaluator function pointer
+// (which is now a deprecated way of expressing policies).
// BPF_TEST() takes a C++ data type as an optional fourth parameter. If
// present, this sets up a variable that can be accessed as "BPF_AUX". This
// variable will be passed as an argument to the "policy" function. Policies
// would typically use it as an argument to SandboxBPF::Trap(), if they want to
-// communicate data between the BPF_TEST() and a Trap() function.
-#define BPF_TEST(test_case_name, test_name, policy, aux...) \
+// communicate data between the BPF_TEST() and a Trap() function. The life-time
+// of this object is the same as the life-time of the process running under the
+// seccomp-bpf policy.
+// The type specified in |aux| and the last parameter of the policy function
+// must be compatible. |aux| must not be void.
+#define BPF_TEST(test_case_name, test_name, policy, aux) \
BPF_DEATH_TEST(test_case_name, test_name, DEATH_SUCCESS(), policy, aux)
-// Assertions are handled exactly the same as with a normal SANDBOX_TEST()
-#define BPF_ASSERT SANDBOX_ASSERT
-
-// The "Aux" type is optional. We use an "empty" type by default, so that if
-// the caller doesn't provide any type, all the BPF_AUX related data compiles
-// to nothing.
-template <class Aux = int[0]>
-class BPFTests : public UnitTests {
+// A BPF_DEATH_TEST is just the same as a BPF_TEST, but it assumes that the
+// test will fail with a particular known error condition. Use the DEATH_XXX()
+// macros from unit_tests.h to specify the expected error condition.
+#define BPF_DEATH_TEST(test_case_name, test_name, death, policy, aux) \
+ void BPF_TEST_##test_name( \
+ sandbox::BPFTesterCompatibilityDelegate<aux>::AuxType* BPF_AUX); \
+ TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \
+ sandbox::SandboxBPFTestRunner bpf_test_runner( \
+ new sandbox::BPFTesterCompatibilityDelegate<aux>(BPF_TEST_##test_name, \
+ policy)); \
+ sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, death); \
+ } \
+ void BPF_TEST_##test_name( \
+ sandbox::BPFTesterCompatibilityDelegate<aux>::AuxType* BPF_AUX)
+
+// This class takes a simple function pointer as a constructor parameter and a
+// class name as a template parameter to implement the BPFTesterDelegate
+// interface which can be used to build BPF unittests with
+// the SandboxBPFTestRunner class.
+template <class PolicyClass>
+class BPFTesterSimpleDelegate : public BPFTesterDelegate {
public:
- typedef Aux AuxType;
-
- class TestArgs {
- public:
- TestArgs(void (*t)(AuxType&), sandbox::SandboxBPF::EvaluateSyscall p)
- : test_(t), policy_(p), aux_() {}
-
- void (*test() const)(AuxType&) { return test_; }
- sandbox::SandboxBPF::EvaluateSyscall policy() const { return policy_; }
+ explicit BPFTesterSimpleDelegate(void (*test_function)(void))
+ : test_function_(test_function) {}
+ virtual ~BPFTesterSimpleDelegate() {}
- private:
- friend class BPFTests;
-
- void (*test_)(AuxType&);
- sandbox::SandboxBPF::EvaluateSyscall policy_;
- AuxType aux_;
- };
-
- static void TestWrapper(void* void_arg) {
- TestArgs* arg = reinterpret_cast<TestArgs*>(void_arg);
- sandbox::Die::EnableSimpleExit();
- if (sandbox::SandboxBPF::SupportsSeccompSandbox(-1) ==
- sandbox::SandboxBPF::STATUS_AVAILABLE) {
- // Ensure the the sandbox is actually available at this time
- int proc_fd;
- BPF_ASSERT((proc_fd = open("/proc", O_RDONLY | O_DIRECTORY)) >= 0);
- BPF_ASSERT(sandbox::SandboxBPF::SupportsSeccompSandbox(proc_fd) ==
- sandbox::SandboxBPF::STATUS_AVAILABLE);
-
- // Initialize and then start the sandbox with our custom policy
- sandbox::SandboxBPF sandbox;
- sandbox.set_proc_fd(proc_fd);
- sandbox.SetSandboxPolicyDeprecated(arg->policy(), &arg->aux_);
- sandbox.SandboxBPF::StartSandbox();
-
- arg->test()(arg->aux_);
- } else {
- printf("This BPF test is not fully running in this configuration!\n");
- // Android, ARM and Valgrind are the three only configurations where we
- // accept not having kernel BPF support.
- // TODO(jln): remote ARM from this list when possible (crbug.com/243478).
- if (!IsAndroid() && !IsRunningOnValgrind() && !IsArchitectureArm()) {
- const bool seccomp_bpf_is_supported = false;
- BPF_ASSERT(seccomp_bpf_is_supported);
- }
- // Call the compiler and verify the policy. That's the least we can do,
- // if we don't have kernel support.
- sandbox::SandboxBPF sandbox;
- sandbox.SetSandboxPolicyDeprecated(arg->policy(), &arg->aux_);
- sandbox::SandboxBPF::Program* program =
- sandbox.AssembleFilter(true /* force_verification */);
- delete program;
- sandbox::UnitTests::IgnoreThisTest();
- }
+ virtual scoped_ptr<SandboxBPFPolicy> GetSandboxBPFPolicy() OVERRIDE {
+ return scoped_ptr<SandboxBPFPolicy>(new PolicyClass());
+ }
+ virtual void RunTestFunction() OVERRIDE {
+ DCHECK(test_function_);
+ test_function_();
}
private:
- DISALLOW_IMPLICIT_CONSTRUCTORS(BPFTests);
+ void (*test_function_)(void);
+ DISALLOW_COPY_AND_ASSIGN(BPFTesterSimpleDelegate);
};
} // namespace sandbox
diff --git a/chromium/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc
new file mode 100644
index 00000000000..bd18412bee6
--- /dev/null
+++ b/chromium/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc
@@ -0,0 +1,139 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sandbox/linux/seccomp-bpf/bpf_tests.h"
+
+#include <errno.h>
+#include <sys/ptrace.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
+#include "sandbox/linux/services/linux_syscalls.h"
+#include "sandbox/linux/tests/unit_tests.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace sandbox {
+
+namespace {
+
+class FourtyTwo {
+ public:
+ static const int kMagicValue = 42;
+ FourtyTwo() : value_(kMagicValue) {}
+ int value() { return value_; }
+
+ private:
+ int value_;
+ DISALLOW_COPY_AND_ASSIGN(FourtyTwo);
+};
+
+ErrorCode EmptyPolicyTakesClass(SandboxBPF* sandbox,
+ int sysno,
+ FourtyTwo* fourty_two) {
+ // |aux| should point to an instance of FourtyTwo.
+ BPF_ASSERT(fourty_two);
+ BPF_ASSERT(FourtyTwo::kMagicValue == fourty_two->value());
+ if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
+ return ErrorCode(ENOSYS);
+ } else {
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ }
+}
+
+BPF_TEST(BPFTest,
+ BPFAUXPointsToClass,
+ EmptyPolicyTakesClass,
+ FourtyTwo /* *BPF_AUX */) {
+ // BPF_AUX should point to an instance of FourtyTwo.
+ BPF_ASSERT(BPF_AUX);
+ BPF_ASSERT(FourtyTwo::kMagicValue == BPF_AUX->value());
+}
+
+void DummyTestFunction(FourtyTwo *fourty_two) {
+}
+
+TEST(BPFTest, BPFTesterCompatibilityDelegateLeakTest) {
+ // Don't do anything, simply gives dynamic tools an opportunity to detect
+ // leaks.
+ {
+ BPFTesterCompatibilityDelegate<FourtyTwo> simple_delegate(
+ DummyTestFunction, EmptyPolicyTakesClass);
+ }
+ {
+ // Test polymorphism.
+ scoped_ptr<BPFTesterDelegate> simple_delegate(
+ new BPFTesterCompatibilityDelegate<FourtyTwo>(DummyTestFunction,
+ EmptyPolicyTakesClass));
+ }
+}
+
+class EnosysPtracePolicy : public SandboxBPFPolicy {
+ public:
+ EnosysPtracePolicy() {
+ my_pid_ = syscall(__NR_getpid);
+ }
+ virtual ~EnosysPtracePolicy() {
+ // Policies should be able to bind with the process on which they are
+ // created. They should never be created in a parent process.
+ BPF_ASSERT_EQ(my_pid_, syscall(__NR_getpid));
+ }
+
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler,
+ int system_call_number) const OVERRIDE {
+ if (!SandboxBPF::IsValidSyscallNumber(system_call_number)) {
+ return ErrorCode(ENOSYS);
+ } else if (system_call_number == __NR_ptrace) {
+ // The EvaluateSyscall function should run in the process that created
+ // the current object.
+ BPF_ASSERT_EQ(my_pid_, syscall(__NR_getpid));
+ return ErrorCode(ENOSYS);
+ } else {
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ }
+ }
+
+ private:
+ pid_t my_pid_;
+ DISALLOW_COPY_AND_ASSIGN(EnosysPtracePolicy);
+};
+
+class BasicBPFTesterDelegate : public BPFTesterDelegate {
+ public:
+ BasicBPFTesterDelegate() {}
+ virtual ~BasicBPFTesterDelegate() {}
+
+ virtual scoped_ptr<SandboxBPFPolicy> GetSandboxBPFPolicy() OVERRIDE {
+ return scoped_ptr<SandboxBPFPolicy>(new EnosysPtracePolicy());
+ }
+ virtual void RunTestFunction() OVERRIDE {
+ errno = 0;
+ int ret = ptrace(PTRACE_TRACEME, -1, NULL, NULL);
+ BPF_ASSERT(-1 == ret);
+ BPF_ASSERT(ENOSYS == errno);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BasicBPFTesterDelegate);
+};
+
+// This is the most powerful and complex way to create a BPF test, but it
+// requires a full class definition (BasicBPFTesterDelegate).
+BPF_TEST_D(BPFTest, BPFTestWithDelegateClass, BasicBPFTesterDelegate);
+
+// This is the simplest form of BPF tests.
+BPF_TEST_C(BPFTest, BPFTestWithInlineTest, EnosysPtracePolicy) {
+ errno = 0;
+ int ret = ptrace(PTRACE_TRACEME, -1, NULL, NULL);
+ BPF_ASSERT(-1 == ret);
+ BPF_ASSERT(ENOSYS == errno);
+}
+
+} // namespace
+
+} // namespace sandbox
diff --git a/chromium/sandbox/linux/seccomp-bpf/codegen.cc b/chromium/sandbox/linux/seccomp-bpf/codegen.cc
index 8fb1701179e..c90bffcad30 100644
--- a/chromium/sandbox/linux/seccomp-bpf/codegen.cc
+++ b/chromium/sandbox/linux/seccomp-bpf/codegen.cc
@@ -4,6 +4,7 @@
#include <stdio.h>
+#include "base/logging.h"
#include "sandbox/linux/seccomp-bpf/codegen.h"
namespace {
@@ -105,6 +106,8 @@ void CodeGen::PrintProgram(const SandboxBPF::Program& program) {
fprintf(stderr, "Trap #%d\n", iter->k & SECCOMP_RET_DATA);
} else if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) {
fprintf(stderr, "errno = %d\n", iter->k & SECCOMP_RET_DATA);
+ } else if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE) {
+ fprintf(stderr, "Trace #%d\n", iter->k & SECCOMP_RET_DATA);
} else if (iter->k == SECCOMP_RET_ALLOW) {
fprintf(stderr, "Allowed\n");
} else {
@@ -432,67 +435,84 @@ static int PointerCompare(const BasicBlock* block1,
// We compare the sequence of instructions in both basic blocks.
const Instructions& insns1 = block1->instructions;
const Instructions& insns2 = block2->instructions;
+ // Basic blocks should never be empty.
+ CHECK(!insns1.empty());
+ CHECK(!insns2.empty());
+
Instructions::const_iterator iter1 = insns1.begin();
Instructions::const_iterator iter2 = insns2.begin();
for (;; ++iter1, ++iter2) {
// If we have reached the end of the sequence of instructions in one or
// both basic blocks, we know the relative ordering between the two blocks
// and can return.
- if (iter1 == insns1.end()) {
- return iter2 == insns2.end() ? 0 : -1;
- } else if (iter2 == insns2.end()) {
- return 1;
+ if (iter1 == insns1.end() || iter2 == insns2.end()) {
+ if (iter1 != insns1.end()) {
+ return 1;
+ }
+ if (iter2 != insns2.end()) {
+ return -1;
+ }
+
+ // If the two blocks are the same length (and have elementwise-equal code
+ // and k fields) and their last instructions are neither a JMP nor a RET
+ // (which is the only way we can reach this point), then we must compare
+ // their successors.
+ Instruction* const insns1_last = insns1.back();
+ Instruction* const insns2_last = insns2.back();
+ CHECK(BPF_CLASS(insns1_last->code) != BPF_JMP &&
+ BPF_CLASS(insns1_last->code) != BPF_RET);
+
+ // Non jumping instructions will always have a valid next instruction.
+ CHECK(insns1_last->next);
+ CHECK(insns2_last->next);
+ return PointerCompare(blocks.find(insns1_last->next)->second,
+ blocks.find(insns2_last->next)->second,
+ blocks);
}
// Compare the individual fields for both instructions.
const Instruction& insn1 = **iter1;
const Instruction& insn2 = **iter2;
- if (insn1.code == insn2.code) {
- if (insn1.k == insn2.k) {
- // Only conditional jump instructions use the jt_ptr and jf_ptr
- // fields.
- if (BPF_CLASS(insn1.code) == BPF_JMP) {
- if (BPF_OP(insn1.code) != BPF_JA) {
- // Recursively compare the "true" and "false" branches.
- // A well-formed BPF program can't have any cycles, so we know
- // that our recursive algorithm will ultimately terminate.
- // In the unlikely event that the programmer made a mistake and
- // went out of the way to give us a cyclic program, we will crash
- // with a stack overflow. We are OK with that.
- int c = PointerCompare(blocks.find(insn1.jt_ptr)->second,
- blocks.find(insn2.jt_ptr)->second,
- blocks);
- if (c == 0) {
- c = PointerCompare(blocks.find(insn1.jf_ptr)->second,
- blocks.find(insn2.jf_ptr)->second,
- blocks);
- if (c == 0) {
- continue;
- } else {
- return c;
- }
- } else {
- return c;
- }
- } else {
- int c = PointerCompare(blocks.find(insn1.jt_ptr)->second,
- blocks.find(insn2.jt_ptr)->second,
- blocks);
- if (c == 0) {
- continue;
- } else {
- return c;
- }
- }
- } else {
- continue;
- }
- } else {
- return insn1.k - insn2.k;
- }
- } else {
+ if (insn1.code != insn2.code) {
return insn1.code - insn2.code;
}
+ if (insn1.k != insn2.k) {
+ return insn1.k - insn2.k;
+ }
+
+ // Sanity check: If we're looking at a JMP or RET instruction, by definition
+ // it should be the last instruction of the basic block.
+ if (BPF_CLASS(insn1.code) == BPF_JMP || BPF_CLASS(insn1.code) == BPF_RET) {
+ CHECK_EQ(insns1.back(), &insn1);
+ CHECK_EQ(insns2.back(), &insn2);
+ }
+
+ // RET instructions terminate execution, and only JMP instructions use the
+ // jt_ptr and jf_ptr fields. Anything else can continue to the next
+ // instruction in the basic block.
+ if (BPF_CLASS(insn1.code) == BPF_RET) {
+ return 0;
+ } else if (BPF_CLASS(insn1.code) != BPF_JMP) {
+ continue;
+ }
+
+ // Recursively compare the "true" and "false" branches.
+ // A well-formed BPF program can't have any cycles, so we know
+ // that our recursive algorithm will ultimately terminate.
+ // In the unlikely event that the programmer made a mistake and
+ // went out of the way to give us a cyclic program, we will crash
+ // with a stack overflow. We are OK with that.
+ if (BPF_OP(insn1.code) != BPF_JA) {
+ int c = PointerCompare(blocks.find(insn1.jf_ptr)->second,
+ blocks.find(insn2.jf_ptr)->second,
+ blocks);
+ if (c != 0) {
+ return c;
+ }
+ }
+ return PointerCompare(blocks.find(insn1.jt_ptr)->second,
+ blocks.find(insn2.jt_ptr)->second,
+ blocks);
}
}
diff --git a/chromium/sandbox/linux/seccomp-bpf/codegen.h b/chromium/sandbox/linux/seccomp-bpf/codegen.h
index 2745e51194e..1c4cd46a5a9 100644
--- a/chromium/sandbox/linux/seccomp-bpf/codegen.h
+++ b/chromium/sandbox/linux/seccomp-bpf/codegen.h
@@ -12,6 +12,7 @@
#include "sandbox/linux/seccomp-bpf/basicblock.h"
#include "sandbox/linux/seccomp-bpf/instruction.h"
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
+#include "sandbox/sandbox_export.h"
namespace sandbox {
@@ -52,7 +53,7 @@ typedef std::map<const BasicBlock*, int> IncomingBranches;
// static_cast<unsigned short>(program->size()), &program[0] };
// prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
//
-class CodeGen {
+class SANDBOX_EXPORT CodeGen {
public:
CodeGen();
~CodeGen();
diff --git a/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc
index 0539a0d4337..52fc24c6927 100644
--- a/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc
+++ b/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc
@@ -24,47 +24,44 @@ class SandboxUnittestHelper : public SandboxBPF {
class CodeGenUnittestHelper : public CodeGen {
public:
void FindBranchTargets(const Instruction& instructions,
- BranchTargets *branch_targets) {
+ BranchTargets* branch_targets) {
CodeGen::FindBranchTargets(instructions, branch_targets);
}
- BasicBlock *CutGraphIntoBasicBlocks(Instruction *insns,
+ BasicBlock* CutGraphIntoBasicBlocks(Instruction* insns,
const BranchTargets& branch_targets,
- TargetsToBlocks *blocks) {
+ TargetsToBlocks* blocks) {
return CodeGen::CutGraphIntoBasicBlocks(insns, branch_targets, blocks);
}
- void MergeTails(TargetsToBlocks *blocks) {
- CodeGen::MergeTails(blocks);
- }
+ void MergeTails(TargetsToBlocks* blocks) { CodeGen::MergeTails(blocks); }
};
-enum { NO_FLAGS = 0x0000,
- HAS_MERGEABLE_TAILS = 0x0001,
-};
+enum { NO_FLAGS = 0x0000, HAS_MERGEABLE_TAILS = 0x0001, };
-Instruction *SampleProgramOneInstruction(CodeGen *codegen, int *flags) {
+Instruction* SampleProgramOneInstruction(CodeGen* codegen, int* flags) {
// Create the most basic valid BPF program:
// RET ERR_ALLOWED
*flags = NO_FLAGS;
- return codegen->MakeInstruction(BPF_RET+BPF_K,
+ return codegen->MakeInstruction(BPF_RET + BPF_K,
ErrorCode(ErrorCode::ERR_ALLOWED));
}
-Instruction *SampleProgramSimpleBranch(CodeGen *codegen, int *flags) {
+Instruction* SampleProgramSimpleBranch(CodeGen* codegen, int* flags) {
// Create a program with a single branch:
// JUMP if eq 42 then $0 else $1
// 0: RET EPERM
// 1: RET ERR_ALLOWED
*flags = NO_FLAGS;
- return codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42,
- codegen->MakeInstruction(BPF_RET+BPF_K,
- ErrorCode(EPERM)),
- codegen->MakeInstruction(BPF_RET+BPF_K,
- ErrorCode(ErrorCode::ERR_ALLOWED)));
+ return codegen->MakeInstruction(
+ BPF_JMP + BPF_JEQ + BPF_K,
+ 42,
+ codegen->MakeInstruction(BPF_RET + BPF_K, ErrorCode(EPERM)),
+ codegen->MakeInstruction(BPF_RET + BPF_K,
+ ErrorCode(ErrorCode::ERR_ALLOWED)));
}
-Instruction *SampleProgramAtypicalBranch(CodeGen *codegen, int *flags) {
+Instruction* SampleProgramAtypicalBranch(CodeGen* codegen, int* flags) {
// Create a program with a single branch:
// JUMP if eq 42 then $0 else $0
// 0: RET ERR_ALLOWED
@@ -74,13 +71,12 @@ Instruction *SampleProgramAtypicalBranch(CodeGen *codegen, int *flags) {
// This needs to be reflected in our choice of "flags".
*flags = NO_FLAGS;
- Instruction *ret =
- codegen->MakeInstruction(BPF_RET+BPF_K,
- ErrorCode(ErrorCode::ERR_ALLOWED));
- return codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42, ret, ret);
+ Instruction* ret = codegen->MakeInstruction(
+ BPF_RET + BPF_K, ErrorCode(ErrorCode::ERR_ALLOWED));
+ return codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, ret, ret);
}
-Instruction *SampleProgramComplex(CodeGen *codegen, int *flags) {
+Instruction* SampleProgramComplex(CodeGen* codegen, int* flags) {
// Creates a basic BPF program that we'll use to test some of the code:
// JUMP if eq 42 the $0 else $1 (insn6)
// 0: LD 23 (insn5)
@@ -92,31 +88,33 @@ Instruction *SampleProgramComplex(CodeGen *codegen, int *flags) {
// RET ErrorCode(42) (insn3+)
*flags = HAS_MERGEABLE_TAILS;
- Instruction *insn0 = codegen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, 42);
+ Instruction* insn0 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 42);
SANDBOX_ASSERT(insn0);
- SANDBOX_ASSERT(insn0->code == BPF_LD+BPF_W+BPF_ABS);
+ SANDBOX_ASSERT(insn0->code == BPF_LD + BPF_W + BPF_ABS);
SANDBOX_ASSERT(insn0->k == 42);
SANDBOX_ASSERT(insn0->next == NULL);
- Instruction *insn1 = codegen->MakeInstruction(BPF_JMP+BPF_JA, 0, insn0);
+ Instruction* insn1 = codegen->MakeInstruction(BPF_JMP + BPF_JA, 0, insn0);
SANDBOX_ASSERT(insn1);
- SANDBOX_ASSERT(insn1->code == BPF_JMP+BPF_JA);
+ SANDBOX_ASSERT(insn1->code == BPF_JMP + BPF_JA);
SANDBOX_ASSERT(insn1->jt_ptr == insn0);
- Instruction *insn2 = codegen->MakeInstruction(BPF_RET+BPF_K, ErrorCode(42));
+ Instruction* insn2 = codegen->MakeInstruction(BPF_RET + BPF_K, ErrorCode(42));
SANDBOX_ASSERT(insn2);
- SANDBOX_ASSERT(insn2->code == BPF_RET+BPF_K);
+ SANDBOX_ASSERT(insn2->code == BPF_RET + BPF_K);
SANDBOX_ASSERT(insn2->next == NULL);
// We explicitly duplicate instructions so that MergeTails() can coalesce
// them later.
- Instruction *insn3 = codegen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, 42,
- codegen->MakeInstruction(BPF_RET+BPF_K, ErrorCode(42)));
+ Instruction* insn3 = codegen->MakeInstruction(
+ BPF_LD + BPF_W + BPF_ABS,
+ 42,
+ codegen->MakeInstruction(BPF_RET + BPF_K, ErrorCode(42)));
- Instruction *insn4 = codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42,
- insn1, insn3);
+ Instruction* insn4 =
+ codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, insn1, insn3);
SANDBOX_ASSERT(insn4);
- SANDBOX_ASSERT(insn4->code == BPF_JMP+BPF_JEQ+BPF_K);
+ SANDBOX_ASSERT(insn4->code == BPF_JMP + BPF_JEQ + BPF_K);
SANDBOX_ASSERT(insn4->k == 42);
SANDBOX_ASSERT(insn4->jt_ptr == insn1);
SANDBOX_ASSERT(insn4->jf_ptr == insn3);
@@ -124,10 +122,10 @@ Instruction *SampleProgramComplex(CodeGen *codegen, int *flags) {
codegen->JoinInstructions(insn0, insn2);
SANDBOX_ASSERT(insn0->next == insn2);
- Instruction *insn5 = codegen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS,
- 23, insn4);
+ Instruction* insn5 =
+ codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 23, insn4);
SANDBOX_ASSERT(insn5);
- SANDBOX_ASSERT(insn5->code == BPF_LD+BPF_W+BPF_ABS);
+ SANDBOX_ASSERT(insn5->code == BPF_LD + BPF_W + BPF_ABS);
SANDBOX_ASSERT(insn5->k == 23);
SANDBOX_ASSERT(insn5->next == insn4);
@@ -137,18 +135,113 @@ Instruction *SampleProgramComplex(CodeGen *codegen, int *flags) {
// This also gives us a diamond-shaped pattern in our graph, which stresses
// another aspect of the topo-sort algorithm (namely, the ability to
// correctly count the incoming branches for subtrees that are not disjunct).
- Instruction *insn6 = codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42,
- insn5, insn4);
+ Instruction* insn6 =
+ codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, insn5, insn4);
return insn6;
}
-void ForAllPrograms(void (*test)(CodeGenUnittestHelper *, Instruction *, int)){
- Instruction *(*function_table[])(CodeGen *codegen, int *flags) = {
+Instruction* SampleProgramConfusingTails(CodeGen* codegen, int* flags) {
+ // This simple program demonstrates https://crbug.com/351103/
+ // The two "LOAD 0" instructions are blocks of their own. MergeTails() could
+ // be tempted to merge them since they are the same. However, they are
+ // not mergeable because they fall-through to non semantically equivalent
+ // blocks.
+ // Without the fix for this bug, this program should trigger the check in
+ // CompileAndCompare: the serialized graphs from the program and its compiled
+ // version will differ.
+ //
+ // 0) LOAD 1 // ???
+ // 1) if A == 0x1; then JMP 2 else JMP 3
+ // 2) LOAD 0 // System call number
+ // 3) if A == 0x2; then JMP 4 else JMP 5
+ // 4) LOAD 0 // System call number
+ // 5) if A == 0x1; then JMP 6 else JMP 7
+ // 6) RET 0x50000 // errno = 0
+ // 7) RET 0x50001 // errno = 1
+ *flags = NO_FLAGS;
+
+ Instruction* i7 = codegen->MakeInstruction(BPF_RET, ErrorCode(1));
+ Instruction* i6 = codegen->MakeInstruction(BPF_RET, ErrorCode(0));
+ Instruction* i5 =
+ codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i6, i7);
+ Instruction* i4 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i5);
+ Instruction* i3 =
+ codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5);
+ Instruction* i2 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i3);
+ Instruction* i1 =
+ codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3);
+ Instruction* i0 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1);
+
+ return i0;
+}
+
+Instruction* SampleProgramConfusingTailsBasic(CodeGen* codegen, int* flags) {
+ // Without the fix for https://crbug.com/351103/, (see
+ // SampleProgramConfusingTails()), this would generate a cyclic graph and
+ // crash as the two "LOAD 0" instructions would get merged.
+ //
+ // 0) LOAD 1 // ???
+ // 1) if A == 0x1; then JMP 2 else JMP 3
+ // 2) LOAD 0 // System call number
+ // 3) if A == 0x2; then JMP 4 else JMP 5
+ // 4) LOAD 0 // System call number
+ // 5) RET 0x50001 // errno = 1
+ *flags = NO_FLAGS;
+
+ Instruction* i5 = codegen->MakeInstruction(BPF_RET, ErrorCode(1));
+ Instruction* i4 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i5);
+ Instruction* i3 =
+ codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5);
+ Instruction* i2 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i3);
+ Instruction* i1 =
+ codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3);
+ Instruction* i0 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1);
+
+ return i0;
+}
+
+Instruction* SampleProgramConfusingTailsMergeable(CodeGen* codegen,
+ int* flags) {
+ // This is similar to SampleProgramConfusingTails(), except that
+ // instructions 2 and 4 are now RET instructions.
+ // In PointerCompare(), this exercises the path where two blocks are of the
+ // same length and identical and the last instruction is a JMP or RET, so the
+ // following blocks don't need to be looked at and the blocks are mergeable.
+ //
+ // 0) LOAD 1 // ???
+ // 1) if A == 0x1; then JMP 2 else JMP 3
+ // 2) RET 0x5002a // errno = 42
+ // 3) if A == 0x2; then JMP 4 else JMP 5
+ // 4) RET 0x5002a // errno = 42
+ // 5) if A == 0x1; then JMP 6 else JMP 7
+ // 6) RET 0x50000 // errno = 0
+ // 7) RET 0x50001 // errno = 1
+ *flags = HAS_MERGEABLE_TAILS;
+
+ Instruction* i7 = codegen->MakeInstruction(BPF_RET, ErrorCode(1));
+ Instruction* i6 = codegen->MakeInstruction(BPF_RET, ErrorCode(0));
+ Instruction* i5 =
+ codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i6, i7);
+ Instruction* i4 = codegen->MakeInstruction(BPF_RET, ErrorCode(42));
+ Instruction* i3 =
+ codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5);
+ Instruction* i2 = codegen->MakeInstruction(BPF_RET, ErrorCode(42));
+ Instruction* i1 =
+ codegen->MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3);
+ Instruction* i0 = codegen->MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1);
+
+ return i0;
+}
+void ForAllPrograms(void (*test)(CodeGenUnittestHelper*, Instruction*, int)) {
+ Instruction* (*function_table[])(CodeGen* codegen, int* flags) = {
SampleProgramOneInstruction,
SampleProgramSimpleBranch,
SampleProgramAtypicalBranch,
SampleProgramComplex,
+ SampleProgramConfusingTails,
+ SampleProgramConfusingTailsBasic,
+ SampleProgramConfusingTailsMergeable,
};
for (size_t i = 0; i < arraysize(function_table); ++i) {
@@ -159,8 +252,8 @@ void ForAllPrograms(void (*test)(CodeGenUnittestHelper *, Instruction *, int)){
}
}
-void MakeInstruction(CodeGenUnittestHelper *codegen,
- Instruction *program, int) {
+void MakeInstruction(CodeGenUnittestHelper* codegen,
+ Instruction* program, int) {
// Nothing to do here
}
@@ -168,7 +261,7 @@ SANDBOX_TEST(CodeGen, MakeInstruction) {
ForAllPrograms(MakeInstruction);
}
-void FindBranchTargets(CodeGenUnittestHelper *codegen, Instruction *prg, int) {
+void FindBranchTargets(CodeGenUnittestHelper* codegen, Instruction* prg, int) {
BranchTargets branch_targets;
codegen->FindBranchTargets(*prg, &branch_targets);
@@ -178,11 +271,11 @@ void FindBranchTargets(CodeGenUnittestHelper *codegen, Instruction *prg, int) {
// targets of BPF_JMP instructions are represented in the "branch_targets".
// At the same time, compute a set of both the branch targets and all the
// instructions in the program.
- std::vector<Instruction *> stack;
- std::set<Instruction *> all_instructions;
- std::set<Instruction *> target_instructions;
+ std::vector<Instruction*> stack;
+ std::set<Instruction*> all_instructions;
+ std::set<Instruction*> target_instructions;
BranchTargets::const_iterator end = branch_targets.end();
- for (Instruction *insn = prg;;) {
+ for (Instruction* insn = prg;;) {
all_instructions.insert(insn);
if (BPF_CLASS(insn->code) == BPF_JMP) {
target_instructions.insert(insn->jt_ptr);
@@ -215,8 +308,10 @@ void FindBranchTargets(CodeGenUnittestHelper *codegen, Instruction *prg, int) {
// "branch_targets" that FindBranchTargets() computed for us.
Instructions non_target_instructions(all_instructions.size() -
target_instructions.size());
- set_difference(all_instructions.begin(), all_instructions.end(),
- target_instructions.begin(), target_instructions.end(),
+ set_difference(all_instructions.begin(),
+ all_instructions.end(),
+ target_instructions.begin(),
+ target_instructions.end(),
non_target_instructions.begin());
for (Instructions::const_iterator iter = non_target_instructions.begin();
iter != non_target_instructions.end();
@@ -225,20 +320,19 @@ void FindBranchTargets(CodeGenUnittestHelper *codegen, Instruction *prg, int) {
}
}
-SANDBOX_TEST(CodeGen, FindBranchTargets) {
- ForAllPrograms(FindBranchTargets);
-}
+SANDBOX_TEST(CodeGen, FindBranchTargets) { ForAllPrograms(FindBranchTargets); }
-void CutGraphIntoBasicBlocks(CodeGenUnittestHelper *codegen,
- Instruction *prg, int) {
+void CutGraphIntoBasicBlocks(CodeGenUnittestHelper* codegen,
+ Instruction* prg,
+ int) {
BranchTargets branch_targets;
codegen->FindBranchTargets(*prg, &branch_targets);
TargetsToBlocks all_blocks;
- BasicBlock *first_block =
- codegen->CutGraphIntoBasicBlocks(prg, branch_targets, &all_blocks);
+ BasicBlock* first_block =
+ codegen->CutGraphIntoBasicBlocks(prg, branch_targets, &all_blocks);
SANDBOX_ASSERT(first_block != NULL);
SANDBOX_ASSERT(first_block->instructions.size() > 0);
- Instruction *first_insn = first_block->instructions[0];
+ Instruction* first_insn = first_block->instructions[0];
// Basic blocks are supposed to start with a branch target and end with
// either a jump or a return instruction. It can also end, if the next
@@ -247,13 +341,13 @@ void CutGraphIntoBasicBlocks(CodeGenUnittestHelper *codegen,
for (TargetsToBlocks::const_iterator bb_iter = all_blocks.begin();
bb_iter != all_blocks.end();
++bb_iter) {
- BasicBlock *bb = bb_iter->second;
+ BasicBlock* bb = bb_iter->second;
SANDBOX_ASSERT(bb != NULL);
SANDBOX_ASSERT(bb->instructions.size() > 0);
- Instruction *insn = bb->instructions[0];
+ Instruction* insn = bb->instructions[0];
SANDBOX_ASSERT(insn == first_insn ||
branch_targets.find(insn) != branch_targets.end());
- for (Instructions::const_iterator insn_iter = bb->instructions.begin();;){
+ for (Instructions::const_iterator insn_iter = bb->instructions.begin();;) {
insn = *insn_iter;
if (++insn_iter != bb->instructions.end()) {
SANDBOX_ASSERT(BPF_CLASS(insn->code) != BPF_JMP);
@@ -261,8 +355,7 @@ void CutGraphIntoBasicBlocks(CodeGenUnittestHelper *codegen,
} else {
SANDBOX_ASSERT(BPF_CLASS(insn->code) == BPF_JMP ||
BPF_CLASS(insn->code) == BPF_RET ||
- branch_targets.find(insn->next) !=
- branch_targets.end());
+ branch_targets.find(insn->next) != branch_targets.end());
break;
}
SANDBOX_ASSERT(branch_targets.find(*insn_iter) == branch_targets.end());
@@ -274,13 +367,12 @@ SANDBOX_TEST(CodeGen, CutGraphIntoBasicBlocks) {
ForAllPrograms(CutGraphIntoBasicBlocks);
}
-void MergeTails(CodeGenUnittestHelper *codegen, Instruction *prg,
- int flags) {
+void MergeTails(CodeGenUnittestHelper* codegen, Instruction* prg, int flags) {
BranchTargets branch_targets;
codegen->FindBranchTargets(*prg, &branch_targets);
TargetsToBlocks all_blocks;
- BasicBlock *first_block =
- codegen->CutGraphIntoBasicBlocks(prg, branch_targets, &all_blocks);
+ BasicBlock* first_block =
+ codegen->CutGraphIntoBasicBlocks(prg, branch_targets, &all_blocks);
// The shape of our graph and thus the function of our program should
// still be unchanged after we run MergeTails(). We verify this by
@@ -294,8 +386,8 @@ void MergeTails(CodeGenUnittestHelper *codegen, Instruction *prg,
// our graph.
for (int i = 0;;) {
// Traverse the entire program in depth-first order.
- std::vector<BasicBlock *> stack;
- for (BasicBlock *bb = first_block;;) {
+ std::vector<BasicBlock*> stack;
+ for (BasicBlock* bb = first_block;;) {
// Serialize the instructions in this basic block. In general, we only
// need to serialize "code" and "k"; except for a BPF_JA instruction
// where "k" isn't set.
@@ -303,23 +395,23 @@ void MergeTails(CodeGenUnittestHelper *codegen, Instruction *prg,
for (Instructions::const_iterator iter = bb->instructions.begin();
iter != bb->instructions.end();
++iter) {
- graph[i].append(reinterpret_cast<char *>(&(*iter)->code),
+ graph[i].append(reinterpret_cast<char*>(&(*iter)->code),
sizeof((*iter)->code));
if (BPF_CLASS((*iter)->code) != BPF_JMP ||
BPF_OP((*iter)->code) != BPF_JA) {
- graph[i].append(reinterpret_cast<char *>(&(*iter)->k),
+ graph[i].append(reinterpret_cast<char*>(&(*iter)->k),
sizeof((*iter)->k));
}
}
// Also serialize the addresses the basic blocks as we encounter them.
// This will change as basic blocks are coalesed by MergeTails().
- edges[i].append(reinterpret_cast<char *>(&bb), sizeof(bb));
+ edges[i].append(reinterpret_cast<char*>(&bb), sizeof(bb));
// Depth-first traversal of the graph. We only ever need to look at the
// very last instruction in the basic block, as that is the only one that
// can change code flow.
- Instruction *insn = bb->instructions.back();
+ Instruction* insn = bb->instructions.back();
if (BPF_CLASS(insn->code) == BPF_JMP) {
// For jump instructions, we need to remember the "false" branch while
// traversing the "true" branch. This is not necessary for BPF_JA which
@@ -359,7 +451,7 @@ SANDBOX_TEST(CodeGen, MergeTails) {
ForAllPrograms(MergeTails);
}
-void CompileAndCompare(CodeGenUnittestHelper *codegen, Instruction *prg, int) {
+void CompileAndCompare(CodeGenUnittestHelper* codegen, Instruction* prg, int) {
// TopoSortBasicBlocks() has internal checks that cause it to fail, if it
// detects a problem. Typically, if anything goes wrong, this looks to the
// TopoSort algorithm as if there had been cycles in the input data.
@@ -375,7 +467,7 @@ void CompileAndCompare(CodeGenUnittestHelper *codegen, Instruction *prg, int) {
// before calling Compile().
std::string source;
Instructions source_stack;
- for (const Instruction *insn = prg, *next; insn; insn = next) {
+ for (const Instruction* insn = prg, *next; insn; insn = next) {
if (BPF_CLASS(insn->code) == BPF_JMP) {
if (BPF_OP(insn->code) == BPF_JA) {
// Do not serialize BPF_JA instructions (see above).
@@ -398,10 +490,9 @@ void CompileAndCompare(CodeGenUnittestHelper *codegen, Instruction *prg, int) {
// Only serialize "code" and "k". That's all the information we need to
// compare. The rest of the information is encoded in the order of
// instructions.
- source.append(reinterpret_cast<const char *>(&insn->code),
+ source.append(reinterpret_cast<const char*>(&insn->code),
sizeof(insn->code));
- source.append(reinterpret_cast<const char *>(&insn->k),
- sizeof(insn->k));
+ source.append(reinterpret_cast<const char*>(&insn->k), sizeof(insn->k));
}
// Compile the program
@@ -434,8 +525,8 @@ void CompileAndCompare(CodeGenUnittestHelper *codegen, Instruction *prg, int) {
++idx;
}
// Serialize the same information that we serialized before compilation.
- assembly.append(reinterpret_cast<char *>(&insn.code), sizeof(insn.code));
- assembly.append(reinterpret_cast<char *>(&insn.k), sizeof(insn.k));
+ assembly.append(reinterpret_cast<char*>(&insn.code), sizeof(insn.code));
+ assembly.append(reinterpret_cast<char*>(&insn.k), sizeof(insn.k));
}
SANDBOX_ASSERT(source == assembly);
}
diff --git a/chromium/sandbox/linux/seccomp-bpf/demo.cc b/chromium/sandbox/linux/seccomp-bpf/demo.cc
index 14180181a6e..d9fd3423eee 100644
--- a/chromium/sandbox/linux/seccomp-bpf/demo.cc
+++ b/chromium/sandbox/linux/seccomp-bpf/demo.cc
@@ -26,12 +26,15 @@
#include <time.h>
#include <unistd.h>
+#include "base/macros.h"
#include "base/posix/eintr_wrapper.h"
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h"
#include "sandbox/linux/services/linux_syscalls.h"
using sandbox::ErrorCode;
using sandbox::SandboxBPF;
+using sandbox::SandboxBPFPolicy;
using sandbox::arch_seccomp_data;
#define ERR EPERM
@@ -237,7 +240,17 @@ intptr_t DefaultHandler(const struct arch_seccomp_data& data, void *) {
return -ERR;
}
-ErrorCode Evaluator(SandboxBPF* sandbox, int sysno, void *) {
+class DemoPolicy : public SandboxBPFPolicy {
+ public:
+ DemoPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DemoPolicy);
+};
+
+ErrorCode DemoPolicy::EvaluateSyscall(SandboxBPF* sandbox, int sysno) const {
switch (sysno) {
#if defined(__NR_accept)
case __NR_accept: case __NR_accept4:
@@ -420,8 +433,11 @@ int main(int argc, char *argv[]) {
}
SandboxBPF sandbox;
sandbox.set_proc_fd(proc_fd);
- sandbox.SetSandboxPolicyDeprecated(Evaluator, NULL);
- sandbox.StartSandbox();
+ sandbox.SetSandboxPolicy(new DemoPolicy());
+ if (!sandbox.StartSandbox(SandboxBPF::PROCESS_SINGLE_THREADED)) {
+ fprintf(stderr, "StartSandbox() failed");
+ _exit(1);
+ }
// Check that we can create threads
pthread_t thr;
diff --git a/chromium/sandbox/linux/seccomp-bpf/die.cc b/chromium/sandbox/linux/seccomp-bpf/die.cc
index 533e2e9c353..e5bc7c93847 100644
--- a/chromium/sandbox/linux/seccomp-bpf/die.cc
+++ b/chromium/sandbox/linux/seccomp-bpf/die.cc
@@ -22,7 +22,7 @@ void Die::ExitGroup() {
// Especially, since we are dealing with system call filters. Continuing
// execution would be very bad in most cases where ExitGroup() gets called.
// So, we'll try a few other strategies too.
- SandboxSyscall(__NR_exit_group, 1);
+ Syscall::Call(__NR_exit_group, 1);
// We have no idea what our run-time environment looks like. So, signal
// handlers might or might not do the right thing. Try to reset settings
@@ -30,7 +30,7 @@ void Die::ExitGroup() {
// succeeded in doing so. Nonetheless, triggering a fatal signal could help
// us terminate.
signal(SIGSEGV, SIG_DFL);
- SandboxSyscall(__NR_prctl, PR_SET_DUMPABLE, (void*)0, (void*)0, (void*)0);
+ Syscall::Call(__NR_prctl, PR_SET_DUMPABLE, (void*)0, (void*)0, (void*)0);
if (*(volatile char*)0) {
}
@@ -40,7 +40,7 @@ void Die::ExitGroup() {
// We in fact retry the system call inside of our loop so that it will
// stand out when somebody tries to diagnose the problem by using "strace".
for (;;) {
- SandboxSyscall(__NR_exit_group, 1);
+ Syscall::Call(__NR_exit_group, 1);
}
}
@@ -75,7 +75,7 @@ void Die::LogToStderr(const char* msg, const char* file, int line) {
// No need to loop. Short write()s are unlikely and if they happen we
// probably prefer them over a loop that blocks.
ignore_result(
- HANDLE_EINTR(SandboxSyscall(__NR_write, 2, s.c_str(), s.length())));
+ HANDLE_EINTR(Syscall::Call(__NR_write, 2, s.c_str(), s.length())));
}
}
diff --git a/chromium/sandbox/linux/seccomp-bpf/die.h b/chromium/sandbox/linux/seccomp-bpf/die.h
index 5dcfda0f2b4..3ac31cc6c41 100644
--- a/chromium/sandbox/linux/seccomp-bpf/die.h
+++ b/chromium/sandbox/linux/seccomp-bpf/die.h
@@ -6,6 +6,7 @@
#define SANDBOX_LINUX_SECCOMP_BPF_DIE_H__
#include "base/basictypes.h"
+#include "sandbox/sandbox_export.h"
namespace sandbox {
@@ -20,7 +21,7 @@ namespace sandbox {
// Adds an informational message to the log file or stderr as appropriate.
#define SANDBOX_INFO(m) sandbox::Die::SandboxInfo(m, __FILE__, __LINE__)
-class Die {
+class SANDBOX_EXPORT Die {
public:
// Terminate the program, even if the current sandbox policy prevents some
// of the more commonly used functions used for exiting.
diff --git a/chromium/sandbox/linux/seccomp-bpf/errorcode.cc b/chromium/sandbox/linux/seccomp-bpf/errorcode.cc
index 64848528202..5a45e4c8cf3 100644
--- a/chromium/sandbox/linux/seccomp-bpf/errorcode.cc
+++ b/chromium/sandbox/linux/seccomp-bpf/errorcode.cc
@@ -18,6 +18,11 @@ ErrorCode::ErrorCode(int err) {
error_type_ = ET_SIMPLE;
break;
default:
+ if ((err & ~SECCOMP_RET_DATA) == ERR_TRACE) {
+ err_ = SECCOMP_RET_TRACE + (err & SECCOMP_RET_DATA);
+ error_type_ = ET_SIMPLE;
+ break;
+ }
SANDBOX_DIE("Invalid use of ErrorCode object");
}
}
diff --git a/chromium/sandbox/linux/seccomp-bpf/errorcode.h b/chromium/sandbox/linux/seccomp-bpf/errorcode.h
index 625d123513f..2e513818d81 100644
--- a/chromium/sandbox/linux/seccomp-bpf/errorcode.h
+++ b/chromium/sandbox/linux/seccomp-bpf/errorcode.h
@@ -7,6 +7,7 @@
#include "sandbox/linux/seccomp-bpf/linux_seccomp.h"
#include "sandbox/linux/seccomp-bpf/trap.h"
+#include "sandbox/sandbox_export.h"
namespace sandbox {
@@ -20,7 +21,7 @@ struct arch_seccomp_data;
// All of the commonly used values are stored in the "err_" field. So, code
// that is using the ErrorCode class typically operates on a single 32bit
// field.
-class ErrorCode {
+class SANDBOX_EXPORT ErrorCode {
public:
enum {
// Allow this system call. The value of ERR_ALLOWED is pretty much
@@ -29,6 +30,12 @@ class ErrorCode {
// "errno" (see below) value instead.
ERR_ALLOWED = 0x04000000,
+ // If the progress is being ptraced with PTRACE_O_TRACESECCOMP, then the
+ // tracer will be notified of a PTRACE_EVENT_SECCOMP and allowed to change
+ // or skip the system call. The lower 16 bits of err will be available to
+ // the tracer via PTRACE_GETEVENTMSG.
+ ERR_TRACE = 0x08000000,
+
// Deny the system call with a particular "errno" value.
// N.B.: It is also possible to return "0" here. That would normally
// indicate success, but it won't actually run the system call.
diff --git a/chromium/sandbox/linux/seccomp-bpf/errorcode_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/errorcode_unittest.cc
index ef04a5f61f8..f3b77483355 100644
--- a/chromium/sandbox/linux/seccomp-bpf/errorcode_unittest.cc
+++ b/chromium/sandbox/linux/seccomp-bpf/errorcode_unittest.cc
@@ -24,6 +24,17 @@ SANDBOX_TEST(ErrorCode, ErrnoConstructor) {
SandboxBPF sandbox;
ErrorCode e3 = sandbox.Trap(NULL, NULL);
SANDBOX_ASSERT((e3.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP);
+
+ uint16_t data = 0xdead;
+ ErrorCode e4(ErrorCode::ERR_TRACE + data);
+ SANDBOX_ASSERT(e4.err() == SECCOMP_RET_TRACE + data);
+}
+
+SANDBOX_DEATH_TEST(ErrorCode,
+ InvalidSeccompRetTrace,
+ DEATH_MESSAGE("Invalid use of ErrorCode object")) {
+ // Should die if the trace data does not fit in 16 bits.
+ ErrorCode e(ErrorCode::ERR_TRACE + (1 << 16));
}
SANDBOX_TEST(ErrorCode, Trap) {
diff --git a/chromium/sandbox/linux/seccomp-bpf/linux_seccomp.h b/chromium/sandbox/linux/seccomp-bpf/linux_seccomp.h
index 0de0259da39..270e11c1f8f 100644
--- a/chromium/sandbox/linux/seccomp-bpf/linux_seccomp.h
+++ b/chromium/sandbox/linux/seccomp-bpf/linux_seccomp.h
@@ -16,6 +16,14 @@
#include <asm/unistd.h>
#include <linux/filter.h>
+#include <sys/cdefs.h>
+// Old Bionic versions do not have sys/user.h. The if can be removed once we no
+// longer need to support these old Bionic versions.
+// All x86_64 builds use a new enough bionic to have sys/user.h.
+#if !defined(__BIONIC__) || defined(__x86_64__)
+#include <sys/user.h>
+#endif
+
// For audit.h
#ifndef EM_ARM
#define EM_ARM 40
@@ -124,6 +132,44 @@
#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \
8*(nr) + 0)
+
+#if defined(__BIONIC__)
+// Old Bionic versions don't have sys/user.h, so we just define regs_struct
+// directly. This can be removed once we no longer need to support these old
+// Bionic versions.
+struct regs_struct {
+ long int ebx;
+ long int ecx;
+ long int edx;
+ long int esi;
+ long int edi;
+ long int ebp;
+ long int eax;
+ long int xds;
+ long int xes;
+ long int xfs;
+ long int xgs;
+ long int orig_eax;
+ long int eip;
+ long int xcs;
+ long int eflags;
+ long int esp;
+ long int xss;
+};
+#else
+typedef user_regs_struct regs_struct;
+#endif
+
+#define SECCOMP_PT_RESULT(_regs) (_regs).eax
+#define SECCOMP_PT_SYSCALL(_regs) (_regs).orig_eax
+#define SECCOMP_PT_IP(_regs) (_regs).eip
+#define SECCOMP_PT_PARM1(_regs) (_regs).ebx
+#define SECCOMP_PT_PARM2(_regs) (_regs).ecx
+#define SECCOMP_PT_PARM3(_regs) (_regs).edx
+#define SECCOMP_PT_PARM4(_regs) (_regs).esi
+#define SECCOMP_PT_PARM5(_regs) (_regs).edi
+#define SECCOMP_PT_PARM6(_regs) (_regs).ebp
+
#elif defined(__x86_64__)
#define MIN_SYSCALL 0u
#define MAX_PUBLIC_SYSCALL 1024u
@@ -151,6 +197,17 @@
#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \
8*(nr) + 0)
+typedef user_regs_struct regs_struct;
+#define SECCOMP_PT_RESULT(_regs) (_regs).rax
+#define SECCOMP_PT_SYSCALL(_regs) (_regs).orig_rax
+#define SECCOMP_PT_IP(_regs) (_regs).rip
+#define SECCOMP_PT_PARM1(_regs) (_regs).rdi
+#define SECCOMP_PT_PARM2(_regs) (_regs).rsi
+#define SECCOMP_PT_PARM3(_regs) (_regs).rdx
+#define SECCOMP_PT_PARM4(_regs) (_regs).r10
+#define SECCOMP_PT_PARM5(_regs) (_regs).r8
+#define SECCOMP_PT_PARM6(_regs) (_regs).r9
+
#elif defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__))
// ARM EABI includes "ARM private" system calls starting at |__ARM_NR_BASE|,
// and a "ghost syscall private to the kernel", cmpxchg,
@@ -189,6 +246,46 @@
#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \
8*(nr) + 0)
+#if defined(__BIONIC__)
+// Old Bionic versions don't have sys/user.h, so we just define regs_struct
+// directly. This can be removed once we no longer need to support these old
+// Bionic versions.
+struct regs_struct {
+ unsigned long uregs[18];
+};
+#else
+typedef user_regs regs_struct;
+#endif
+
+#define REG_cpsr uregs[16]
+#define REG_pc uregs[15]
+#define REG_lr uregs[14]
+#define REG_sp uregs[13]
+#define REG_ip uregs[12]
+#define REG_fp uregs[11]
+#define REG_r10 uregs[10]
+#define REG_r9 uregs[9]
+#define REG_r8 uregs[8]
+#define REG_r7 uregs[7]
+#define REG_r6 uregs[6]
+#define REG_r5 uregs[5]
+#define REG_r4 uregs[4]
+#define REG_r3 uregs[3]
+#define REG_r2 uregs[2]
+#define REG_r1 uregs[1]
+#define REG_r0 uregs[0]
+#define REG_ORIG_r0 uregs[17]
+
+#define SECCOMP_PT_RESULT(_regs) (_regs).REG_r0
+#define SECCOMP_PT_SYSCALL(_regs) (_regs).REG_r7
+#define SECCOMP_PT_IP(_regs) (_regs).REG_pc
+#define SECCOMP_PT_PARM1(_regs) (_regs).REG_r0
+#define SECCOMP_PT_PARM2(_regs) (_regs).REG_r1
+#define SECCOMP_PT_PARM3(_regs) (_regs).REG_r2
+#define SECCOMP_PT_PARM4(_regs) (_regs).REG_r3
+#define SECCOMP_PT_PARM5(_regs) (_regs).REG_r4
+#define SECCOMP_PT_PARM6(_regs) (_regs).REG_r5
+
#else
#error Unsupported target platform
diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
index 6b2327e5452..6ecbca99a5f 100644
--- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
+++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
@@ -22,6 +22,7 @@
#include "base/compiler_specific.h"
#include "base/logging.h"
+#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/posix/eintr_wrapper.h"
#include "sandbox/linux/seccomp-bpf/codegen.h"
@@ -56,20 +57,26 @@ void WriteFailedStderrSetupMessage(int out_fd) {
// We define a really simple sandbox policy. It is just good enough for us
// to tell that the sandbox has actually been activated.
-ErrorCode ProbeEvaluator(SandboxBPF*, int sysnum, void*) __attribute__((const));
-ErrorCode ProbeEvaluator(SandboxBPF*, int sysnum, void*) {
- switch (sysnum) {
- case __NR_getpid:
- // Return EPERM so that we can check that the filter actually ran.
- return ErrorCode(EPERM);
- case __NR_exit_group:
- // Allow exit() with a non-default return code.
- return ErrorCode(ErrorCode::ERR_ALLOWED);
- default:
- // Make everything else fail in an easily recognizable way.
- return ErrorCode(EINVAL);
+class ProbePolicy : public SandboxBPFPolicy {
+ public:
+ ProbePolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysnum) const OVERRIDE {
+ switch (sysnum) {
+ case __NR_getpid:
+ // Return EPERM so that we can check that the filter actually ran.
+ return ErrorCode(EPERM);
+ case __NR_exit_group:
+ // Allow exit() with a non-default return code.
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ default:
+ // Make everything else fail in an easily recognizable way.
+ return ErrorCode(EINVAL);
+ }
}
-}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProbePolicy);
+};
void ProbeProcess(void) {
if (syscall(__NR_getpid) < 0 && errno == EPERM) {
@@ -77,12 +84,17 @@ void ProbeProcess(void) {
}
}
-ErrorCode AllowAllEvaluator(SandboxBPF*, int sysnum, void*) {
- if (!SandboxBPF::IsValidSyscallNumber(sysnum)) {
- return ErrorCode(ENOSYS);
+class AllowAllPolicy : public SandboxBPFPolicy {
+ public:
+ AllowAllPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysnum) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysnum));
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
}
- return ErrorCode(ErrorCode::ERR_ALLOWED);
-}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AllowAllPolicy);
+};
void TryVsyscallProcess(void) {
time_t current_time;
@@ -187,13 +199,21 @@ class RedirectToUserSpacePolicyWrapper : public SandboxBPFPolicy {
ErrorCode err =
wrapped_policy_->EvaluateSyscall(sandbox_compiler, system_call_number);
if ((err.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) {
- return sandbox_compiler->Trap(
- ReturnErrno, reinterpret_cast<void*>(err.err() & SECCOMP_RET_DATA));
+ return ReturnErrnoViaTrap(sandbox_compiler, err.err() & SECCOMP_RET_DATA);
}
return err;
}
+ virtual ErrorCode InvalidSyscall(
+ SandboxBPF* sandbox_compiler) const OVERRIDE {
+ return ReturnErrnoViaTrap(sandbox_compiler, ENOSYS);
+ }
+
private:
+ ErrorCode ReturnErrnoViaTrap(SandboxBPF* sandbox_compiler, int err) const {
+ return sandbox_compiler->Trap(ReturnErrno, reinterpret_cast<void*>(err));
+ }
+
const SandboxBPFPolicy* wrapped_policy_;
DISALLOW_COPY_AND_ASSIGN(RedirectToUserSpacePolicyWrapper);
};
@@ -202,25 +222,6 @@ intptr_t BPFFailure(const struct arch_seccomp_data&, void* aux) {
SANDBOX_DIE(static_cast<char*>(aux));
}
-// This class allows compatibility with the old, deprecated SetSandboxPolicy.
-class CompatibilityPolicy : public SandboxBPFPolicy {
- public:
- CompatibilityPolicy(SandboxBPF::EvaluateSyscall syscall_evaluator, void* aux)
- : syscall_evaluator_(syscall_evaluator), aux_(aux) {
- DCHECK(syscall_evaluator_);
- }
-
- virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler,
- int system_call_number) const OVERRIDE {
- return syscall_evaluator_(sandbox_compiler, system_call_number, aux_);
- }
-
- private:
- SandboxBPF::EvaluateSyscall syscall_evaluator_;
- void* aux_;
- DISALLOW_COPY_AND_ASSIGN(CompatibilityPolicy);
-};
-
} // namespace
SandboxBPF::SandboxBPF()
@@ -251,8 +252,7 @@ bool SandboxBPF::IsValidSyscallNumber(int sysnum) {
}
bool SandboxBPF::RunFunctionInPolicy(void (*code_in_sandbox)(),
- EvaluateSyscall syscall_evaluator,
- void* aux) {
+ scoped_ptr<SandboxBPFPolicy> policy) {
// Block all signals before forking a child process. This prevents an
// attacker from manipulating our test by sending us an unexpected signal.
sigset_t old_mask, new_mask;
@@ -322,8 +322,10 @@ bool SandboxBPF::RunFunctionInPolicy(void (*code_in_sandbox)(),
#endif
}
- SetSandboxPolicyDeprecated(syscall_evaluator, aux);
- StartSandbox();
+ SetSandboxPolicy(policy.release());
+ if (!StartSandbox(PROCESS_SINGLE_THREADED)) {
+ SANDBOX_DIE(NULL);
+ }
// Run our code in the sandbox.
code_in_sandbox();
@@ -369,8 +371,11 @@ bool SandboxBPF::RunFunctionInPolicy(void (*code_in_sandbox)(),
}
bool SandboxBPF::KernelSupportSeccompBPF() {
- return RunFunctionInPolicy(ProbeProcess, ProbeEvaluator, 0) &&
- RunFunctionInPolicy(TryVsyscallProcess, AllowAllEvaluator, 0);
+ return RunFunctionInPolicy(ProbeProcess,
+ scoped_ptr<SandboxBPFPolicy>(new ProbePolicy())) &&
+ RunFunctionInPolicy(
+ TryVsyscallProcess,
+ scoped_ptr<SandboxBPFPolicy>(new AllowAllPolicy()));
}
SandboxBPF::SandboxStatus SandboxBPF::SupportsSeccompSandbox(int proc_fd) {
@@ -430,15 +435,20 @@ SandboxBPF::SandboxStatus SandboxBPF::SupportsSeccompSandbox(int proc_fd) {
void SandboxBPF::set_proc_fd(int proc_fd) { proc_fd_ = proc_fd; }
-void SandboxBPF::StartSandbox() {
+bool SandboxBPF::StartSandbox(SandboxThreadState thread_state) {
+ CHECK(thread_state == PROCESS_SINGLE_THREADED ||
+ thread_state == PROCESS_MULTI_THREADED);
+
if (status_ == STATUS_UNSUPPORTED || status_ == STATUS_UNAVAILABLE) {
SANDBOX_DIE(
"Trying to start sandbox, even though it is known to be "
"unavailable");
+ return false;
} else if (sandbox_has_started_ || !conds_) {
SANDBOX_DIE(
"Cannot repeatedly start sandbox. Create a separate Sandbox "
"object instead.");
+ return false;
}
if (proc_fd_ < 0) {
proc_fd_ = open("/proc", O_RDONLY | O_DIRECTORY);
@@ -447,8 +457,10 @@ void SandboxBPF::StartSandbox() {
// For now, continue in degraded mode, if we can't access /proc.
// In the future, we might want to tighten this requirement.
}
- if (!IsSingleThreaded(proc_fd_)) {
+
+ if (thread_state == PROCESS_SINGLE_THREADED && !IsSingleThreaded(proc_fd_)) {
SANDBOX_DIE("Cannot start sandbox, if process is already multi-threaded");
+ return false;
}
// We no longer need access to any files in /proc. We want to do this
@@ -457,38 +469,27 @@ void SandboxBPF::StartSandbox() {
if (proc_fd_ >= 0) {
if (IGNORE_EINTR(close(proc_fd_))) {
SANDBOX_DIE("Failed to close file descriptor for /proc");
+ return false;
}
proc_fd_ = -1;
}
// Install the filters.
- InstallFilter();
+ InstallFilter(thread_state);
// We are now inside the sandbox.
status_ = STATUS_ENABLED;
+
+ return true;
}
void SandboxBPF::PolicySanityChecks(SandboxBPFPolicy* policy) {
- for (SyscallIterator iter(true); !iter.Done();) {
- uint32_t sysnum = iter.Next();
- if (!IsDenied(policy->EvaluateSyscall(this, sysnum))) {
- SANDBOX_DIE(
- "Policies should deny system calls that are outside the "
- "expected range (typically MIN_SYSCALL..MAX_SYSCALL)");
- }
+ if (!IsDenied(policy->InvalidSyscall(this))) {
+ SANDBOX_DIE("Policies should deny invalid system calls.");
}
return;
}
-// Deprecated API, supported with a wrapper to the new API.
-void SandboxBPF::SetSandboxPolicyDeprecated(EvaluateSyscall syscall_evaluator,
- void* aux) {
- if (sandbox_has_started_ || !conds_) {
- SANDBOX_DIE("Cannot change policy after sandbox has started");
- }
- SetSandboxPolicy(new CompatibilityPolicy(syscall_evaluator, aux));
-}
-
// Don't take a scoped_ptr here, polymorphism make their use awkward.
void SandboxBPF::SetSandboxPolicy(SandboxBPFPolicy* policy) {
DCHECK(!policy_);
@@ -499,7 +500,7 @@ void SandboxBPF::SetSandboxPolicy(SandboxBPFPolicy* policy) {
policy_.reset(policy);
}
-void SandboxBPF::InstallFilter() {
+void SandboxBPF::InstallFilter(SandboxThreadState thread_state) {
// We want to be very careful in not imposing any requirements on the
// policies that are set with SetSandboxPolicy(). This means, as soon as
// the sandbox is active, we shouldn't be relying on libraries that could
@@ -535,9 +536,23 @@ void SandboxBPF::InstallFilter() {
}
}
- sandbox_has_started_ = true;
+ // TODO(rsesek): Always try to engage the sandbox with the
+ // PROCESS_MULTI_THREADED path first, and if that fails, assert that the
+ // process IsSingleThreaded() or SANDBOX_DIE.
+
+ if (thread_state == PROCESS_MULTI_THREADED) {
+ // TODO(rsesek): Move these to a more reasonable place once the kernel
+ // patch has landed upstream and these values are formalized.
+ #define PR_SECCOMP_EXT 41
+ #define SECCOMP_EXT_ACT 1
+ #define SECCOMP_EXT_ACT_TSYNC 1
+ if (prctl(PR_SECCOMP_EXT, SECCOMP_EXT_ACT, SECCOMP_EXT_ACT_TSYNC, 0, 0)) {
+ SANDBOX_DIE(quiet_ ? NULL : "Kernel refuses to synchronize threadgroup "
+ "BPF filters.");
+ }
+ }
- return;
+ sandbox_has_started_ = true;
}
SandboxBPF::Program* SandboxBPF::AssembleFilter(bool force_verification) {
@@ -598,7 +613,7 @@ SandboxBPF::Program* SandboxBPF::AssembleFilter(bool force_verification) {
// and of course, we make sure to only ever enable this feature if it
// is actually requested by the sandbox policy.
if (has_unsafe_traps) {
- if (SandboxSyscall(-1) == -1 && errno == ENOSYS) {
+ if (Syscall::Call(-1) == -1 && errno == ENOSYS) {
SANDBOX_DIE(
"Support for UnsafeTrap() has not yet been ported to this "
"architecture");
@@ -635,9 +650,8 @@ SandboxBPF::Program* SandboxBPF::AssembleFilter(bool force_verification) {
gen->Traverse(jumptable, RedirectToUserspace, this);
// Allow system calls, if they originate from our magic return address
- // (which we can query by calling SandboxSyscall(-1)).
- uintptr_t syscall_entry_point =
- static_cast<uintptr_t>(SandboxSyscall(-1));
+ // (which we can query by calling Syscall::Call(-1)).
+ uintptr_t syscall_entry_point = static_cast<uintptr_t>(Syscall::Call(-1));
uint32_t low = static_cast<uint32_t>(syscall_entry_point);
#if __SIZEOF_POINTER__ > 4
uint32_t hi = static_cast<uint32_t>(syscall_entry_point >> 32);
@@ -737,20 +751,18 @@ void SandboxBPF::FindRanges(Ranges* ranges) {
// deal with this disparity by enumerating from MIN_SYSCALL to MAX_SYSCALL,
// and then verifying that the rest of the number range (both positive and
// negative) all return the same ErrorCode.
+ const ErrorCode invalid_err = policy_->InvalidSyscall(this);
uint32_t old_sysnum = 0;
- ErrorCode old_err = policy_->EvaluateSyscall(this, old_sysnum);
- ErrorCode invalid_err = policy_->EvaluateSyscall(this, MIN_SYSCALL - 1);
+ ErrorCode old_err = IsValidSyscallNumber(old_sysnum)
+ ? policy_->EvaluateSyscall(this, old_sysnum)
+ : invalid_err;
for (SyscallIterator iter(false); !iter.Done();) {
uint32_t sysnum = iter.Next();
- ErrorCode err = policy_->EvaluateSyscall(this, static_cast<int>(sysnum));
- if (!iter.IsValid(sysnum) && !invalid_err.Equals(err)) {
- // A proper sandbox policy should always treat system calls outside of
- // the range MIN_SYSCALL..MAX_SYSCALL (i.e. anything that returns
- // "false" for SyscallIterator::IsValid()) identically. Typically, all
- // of these system calls would be denied with the same ErrorCode.
- SANDBOX_DIE("Invalid seccomp policy");
- }
+ ErrorCode err =
+ IsValidSyscallNumber(sysnum)
+ ? policy_->EvaluateSyscall(this, static_cast<int>(sysnum))
+ : invalid_err;
if (!err.Equals(old_err) || iter.Done()) {
ranges->push_back(Range(old_sysnum, sysnum - 1, old_err));
old_sysnum = sysnum;
@@ -990,13 +1002,13 @@ ErrorCode SandboxBPF::UnsafeTrap(Trap::TrapFnc fnc, const void* aux) {
}
intptr_t SandboxBPF::ForwardSyscall(const struct arch_seccomp_data& args) {
- return SandboxSyscall(args.nr,
- static_cast<intptr_t>(args.args[0]),
- static_cast<intptr_t>(args.args[1]),
- static_cast<intptr_t>(args.args[2]),
- static_cast<intptr_t>(args.args[3]),
- static_cast<intptr_t>(args.args[4]),
- static_cast<intptr_t>(args.args[5]));
+ return Syscall::Call(args.nr,
+ static_cast<intptr_t>(args.args[0]),
+ static_cast<intptr_t>(args.args[1]),
+ static_cast<intptr_t>(args.args[2]),
+ static_cast<intptr_t>(args.args[3]),
+ static_cast<intptr_t>(args.args[4]),
+ static_cast<intptr_t>(args.args[5]));
}
ErrorCode SandboxBPF::Cond(int argno,
diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h
index d626e4c74ca..32fe2a7d748 100644
--- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h
+++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h
@@ -16,13 +16,16 @@
#include <utility>
#include <vector>
+#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "sandbox/linux/seccomp-bpf/die.h"
#include "sandbox/linux/seccomp-bpf/errorcode.h"
#include "sandbox/linux/seccomp-bpf/linux_seccomp.h"
+#include "sandbox/sandbox_export.h"
namespace sandbox {
+// This must match the kernel's seccomp_data structure.
struct arch_seccomp_data {
int nr;
uint32_t arch;
@@ -41,7 +44,7 @@ class SandboxBPFPolicy;
class SandboxUnittestHelper;
struct Instruction;
-class SandboxBPF {
+class SANDBOX_EXPORT SandboxBPF {
public:
enum SandboxStatus {
STATUS_UNKNOWN, // Status prior to calling supportsSeccompSandbox()
@@ -51,15 +54,18 @@ class SandboxBPF {
STATUS_ENABLED // The sandbox is now active
};
- // When calling setSandboxPolicy(), the caller can provide an arbitrary
- // pointer in |aux|. This pointer will then be forwarded to the sandbox
- // policy each time a call is made through an EvaluateSyscall function
- // pointer. One common use case would be to pass the "aux" pointer as an
- // argument to Trap() functions.
- typedef ErrorCode (*EvaluateSyscall)(SandboxBPF* sandbox_compiler,
- int system_call_number,
- void* aux);
- typedef std::vector<std::pair<EvaluateSyscall, void*> > Evaluators;
+ // Depending on the level of kernel support, seccomp-bpf may require the
+ // process to be single-threaded in order to enable it. When calling
+ // StartSandbox(), the program should indicate whether or not the sandbox
+ // should try and engage with multi-thread support.
+ enum SandboxThreadState {
+ PROCESS_INVALID,
+ PROCESS_SINGLE_THREADED, // The program is currently single-threaded.
+ // Note: PROCESS_MULTI_THREADED requires experimental kernel support that
+ // has not been contributed to upstream Linux.
+ PROCESS_MULTI_THREADED, // The program may be multi-threaded.
+ };
+
// A vector of BPF instructions that need to be installed as a filter
// program in the kernel.
typedef std::vector<struct sock_filter> Program;
@@ -96,20 +102,6 @@ class SandboxBPF {
// eventually close it when "StartSandbox()" executes.
void set_proc_fd(int proc_fd);
- // The system call evaluator function is called with the system
- // call number. It can decide to allow the system call unconditionally
- // by returning ERR_ALLOWED; it can deny the system call unconditionally by
- // returning an appropriate "errno" value; or it can request inspection
- // of system call argument(s) by returning a suitable ErrorCode.
- // The "aux" parameter can be used to pass optional data to the system call
- // evaluator. There are different possible uses for this data, but one of the
- // use cases would be for the policy to then forward this pointer to a Trap()
- // handler. In this case, of course, the data that is pointed to must remain
- // valid for the entire time that Trap() handlers can be called; typically,
- // this would be the lifetime of the program.
- // DEPRECATED: use the policy interface below.
- void SetSandboxPolicyDeprecated(EvaluateSyscall syscallEvaluator, void* aux);
-
// Set the BPF policy as |policy|. Ownership of |policy| is transfered here
// to the sandbox object.
void SetSandboxPolicy(SandboxBPFPolicy* policy);
@@ -167,6 +159,8 @@ class SandboxBPF {
// This is the main public entry point. It finds all system calls that
// need rewriting, sets up the resources needed by the sandbox, and
// enters Seccomp mode.
+ // The calling process must specify its current SandboxThreadState, as a way
+ // to tell the sandbox which type of kernel support it should engage.
// It is possible to stack multiple sandboxes by creating separate "Sandbox"
// objects and calling "StartSandbox()" on each of them. Please note, that
// this requires special care, though, as newly stacked sandboxes can never
@@ -175,7 +169,7 @@ class SandboxBPF {
// disallowed.
// Finally, stacking does add more kernel overhead than having a single
// combined policy. So, it should only be used if there are no alternatives.
- void StartSandbox();
+ bool StartSandbox(SandboxThreadState thread_state) WARN_UNUSED_RESULT;
// Assembles a BPF filter program from the current policy. After calling this
// function, you must not call any other sandboxing function.
@@ -214,8 +208,7 @@ class SandboxBPF {
// policy. The caller has to make sure that "this" has not yet been
// initialized with any other policies.
bool RunFunctionInPolicy(void (*code_in_sandbox)(),
- EvaluateSyscall syscall_evaluator,
- void* aux);
+ scoped_ptr<SandboxBPFPolicy> policy);
// Performs a couple of sanity checks to verify that the kernel supports the
// features that we need for successful sandboxing.
@@ -228,7 +221,7 @@ class SandboxBPF {
// Assembles and installs a filter based on the policy that has previously
// been configured with SetSandboxPolicy().
- void InstallFilter();
+ void InstallFilter(SandboxThreadState thread_state);
// Verify the correctness of a compiled program by comparing it against the
// current policy. This function should only ever be called by unit tests and
diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_compatibility_policy.h b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_compatibility_policy.h
new file mode 100644
index 00000000000..d4b8ab8b15a
--- /dev/null
+++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_compatibility_policy.h
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_COMPATIBILITY_POLICY_H_
+#define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_COMPATIBILITY_POLICY_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h"
+
+namespace sandbox {
+
+// This class allows compatibility with the old, deprecated
+// policies that were designed for SetSandboxPolicyDeprecated().
+template <class AuxType>
+class CompatibilityPolicy : public SandboxBPFPolicy {
+ public:
+ typedef ErrorCode (*SyscallEvaluator)(SandboxBPF* sandbox_compiler,
+ int system_call_number,
+ AuxType* aux);
+ CompatibilityPolicy(SyscallEvaluator syscall_evaluator, AuxType* aux)
+ : syscall_evaluator_(syscall_evaluator), aux_(aux) {}
+
+ virtual ~CompatibilityPolicy() {}
+
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler,
+ int system_call_number) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(system_call_number));
+ return syscall_evaluator_(sandbox_compiler, system_call_number, aux_);
+ }
+
+ private:
+ SyscallEvaluator syscall_evaluator_;
+ AuxType* aux_;
+ DISALLOW_COPY_AND_ASSIGN(CompatibilityPolicy);
+};
+
+} // namespace sandbox
+
+#endif // SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_COMPATIBILITY_POLICY_H_
diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.cc
new file mode 100644
index 00000000000..962c8036f8a
--- /dev/null
+++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h"
+
+#include <errno.h>
+
+#include "sandbox/linux/seccomp-bpf/errorcode.h"
+
+namespace sandbox {
+
+ErrorCode SandboxBPFPolicy::InvalidSyscall(SandboxBPF* sandbox_compiler) const {
+ return ErrorCode(ENOSYS);
+}
+
+} // namespace sandbox
diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h
index 1ac5daba5d9..fc6fdf6fe4f 100644
--- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h
+++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h
@@ -6,6 +6,7 @@
#define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_POLICY_H_
#include "base/basictypes.h"
+#include "sandbox/sandbox_export.h"
namespace sandbox {
@@ -13,7 +14,7 @@ class ErrorCode;
class SandboxBPF;
// This is the interface to implement to define a BPF sandbox policy.
-class SandboxBPFPolicy {
+class SANDBOX_EXPORT SandboxBPFPolicy {
public:
SandboxBPFPolicy() {}
virtual ~SandboxBPFPolicy() {}
@@ -23,9 +24,14 @@ class SandboxBPFPolicy {
// it can deny the system call unconditionally by returning an appropriate
// "errno" value; or it can request inspection of system call argument(s) by
// returning a suitable ErrorCode.
+ // Will only be called for valid system call numbers.
virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler,
int system_call_number) const = 0;
+ // The InvalidSyscall method specifies the behavior used for invalid
+ // system calls. The default implementation is to return ENOSYS.
+ virtual ErrorCode InvalidSyscall(SandboxBPF* sandbox_compiler) const;
+
private:
DISALLOW_COPY_AND_ASSIGN(SandboxBPFPolicy);
};
diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc
new file mode 100644
index 00000000000..ff659ab7f80
--- /dev/null
+++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
+#include "sandbox/linux/tests/unit_tests.h"
+
+namespace sandbox {
+
+SandboxBPFTestRunner::SandboxBPFTestRunner(
+ BPFTesterDelegate* bpf_tester_delegate)
+ : bpf_tester_delegate_(bpf_tester_delegate) {
+}
+
+SandboxBPFTestRunner::~SandboxBPFTestRunner() {
+}
+
+void SandboxBPFTestRunner::Run() {
+ DCHECK(bpf_tester_delegate_);
+ sandbox::Die::EnableSimpleExit();
+
+ scoped_ptr<SandboxBPFPolicy> policy =
+ bpf_tester_delegate_->GetSandboxBPFPolicy();
+
+ if (sandbox::SandboxBPF::SupportsSeccompSandbox(-1) ==
+ sandbox::SandboxBPF::STATUS_AVAILABLE) {
+ // Ensure the the sandbox is actually available at this time
+ int proc_fd;
+ SANDBOX_ASSERT((proc_fd = open("/proc", O_RDONLY | O_DIRECTORY)) >= 0);
+ SANDBOX_ASSERT(sandbox::SandboxBPF::SupportsSeccompSandbox(proc_fd) ==
+ sandbox::SandboxBPF::STATUS_AVAILABLE);
+
+ // Initialize and then start the sandbox with our custom policy
+ sandbox::SandboxBPF sandbox;
+ sandbox.set_proc_fd(proc_fd);
+ sandbox.SetSandboxPolicy(policy.release());
+ SANDBOX_ASSERT(
+ sandbox.StartSandbox(sandbox::SandboxBPF::PROCESS_SINGLE_THREADED));
+
+ // Run the actual test.
+ bpf_tester_delegate_->RunTestFunction();
+ } else {
+ printf("This BPF test is not fully running in this configuration!\n");
+ // Android and Valgrind are the only configurations where we accept not
+ // having kernel BPF support.
+ if (!IsAndroid() && !IsRunningOnValgrind()) {
+ const bool seccomp_bpf_is_supported = false;
+ SANDBOX_ASSERT(seccomp_bpf_is_supported);
+ }
+ // Call the compiler and verify the policy. That's the least we can do,
+ // if we don't have kernel support.
+ sandbox::SandboxBPF sandbox;
+ sandbox.SetSandboxPolicy(policy.release());
+ sandbox::SandboxBPF::Program* program =
+ sandbox.AssembleFilter(true /* force_verification */);
+ delete program;
+ sandbox::UnitTests::IgnoreThisTest();
+ }
+}
+
+bool SandboxBPFTestRunner::ShouldCheckForLeaks() const {
+ // LSAN requires being able to use ptrace() and other system calls that could
+ // be denied.
+ return false;
+}
+
+} // namespace sandbox
diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h
new file mode 100644
index 00000000000..77210330414
--- /dev/null
+++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_TEST_RUNNER_H_
+#define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_TEST_RUNNER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h"
+#include "sandbox/linux/tests/sandbox_test_runner.h"
+
+namespace sandbox {
+
+// To create a SandboxBPFTestRunner object, one needs to implement this
+// interface and pass an instance to the SandboxBPFTestRunner constructor.
+// In the child process running the test, the BPFTesterDelegate object is
+// guaranteed to not be destroyed until the child process terminates.
+class BPFTesterDelegate {
+ public:
+ BPFTesterDelegate() {}
+ virtual ~BPFTesterDelegate() {}
+
+ // This will instanciate a policy suitable for the test we want to run. It is
+ // guaranteed to only be called from the child process that will run the
+ // test.
+ virtual scoped_ptr<SandboxBPFPolicy> GetSandboxBPFPolicy() = 0;
+ // This will be called from a child process with the BPF sandbox turned on.
+ virtual void RunTestFunction() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BPFTesterDelegate);
+};
+
+// This class implements the SandboxTestRunner interface and Run() will
+// initialize a seccomp-bpf sandbox (specified by |bpf_tester_delegate|) and
+// run a test function (via |bpf_tester_delegate|) if the current kernel
+// configuration allows it. If it can not run the test under seccomp-bpf,
+// Run() will still compile the policy which should allow to get some coverage
+// under tools such as Valgrind.
+class SandboxBPFTestRunner : public SandboxTestRunner {
+ public:
+ // This constructor takes ownership of the |bpf_tester_delegate| object.
+ // (It doesn't take a scoped_ptr since they make polymorphism verbose).
+ explicit SandboxBPFTestRunner(BPFTesterDelegate* bpf_tester_delegate);
+ virtual ~SandboxBPFTestRunner();
+
+ virtual void Run() OVERRIDE;
+
+ virtual bool ShouldCheckForLeaks() const OVERRIDE;
+
+ private:
+ scoped_ptr<BPFTesterDelegate> bpf_tester_delegate_;
+ DISALLOW_COPY_AND_ASSIGN(SandboxBPFTestRunner);
+};
+
+} // namespace sandbox
+
+#endif // SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_TEST_RUNNER_H_
diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc
index 988e29544b0..06ba2090c91 100644
--- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc
+++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc
@@ -5,7 +5,9 @@
#include <errno.h>
#include <pthread.h>
#include <sched.h>
+#include <signal.h>
#include <sys/prctl.h>
+#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
@@ -20,7 +22,11 @@
#include <ostream>
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
+#include "base/posix/eintr_wrapper.h"
#include "build/build_config.h"
#include "sandbox/linux/seccomp-bpf/bpf_tests.h"
#include "sandbox/linux/seccomp-bpf/syscall.h"
@@ -49,7 +55,7 @@ const char kSandboxDebuggingEnv[] = "CHROME_SANDBOX_DEBUGGING";
// This test should execute no matter whether we have kernel support. So,
// we make it a TEST() instead of a BPF_TEST().
-TEST(SandboxBPF, CallSupports) {
+TEST(SandboxBPF, DISABLE_ON_TSAN(CallSupports)) {
// We check that we don't crash, but it's ok if the kernel doesn't
// support it.
bool seccomp_bpf_supported =
@@ -64,7 +70,7 @@ TEST(SandboxBPF, CallSupports) {
std::cout << "Pointer size: " << sizeof(void*) << "\n";
}
-SANDBOX_TEST(SandboxBPF, CallSupportsTwice) {
+SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(CallSupportsTwice)) {
SandboxBPF::SupportsSeccompSandbox(-1);
SandboxBPF::SupportsSeccompSandbox(-1);
}
@@ -76,59 +82,67 @@ SANDBOX_TEST(SandboxBPF, CallSupportsTwice) {
// setting up the sandbox. But it wouldn't hurt to have at least one test
// that explicitly walks through all these steps.
-intptr_t FakeGetPid(const struct arch_seccomp_data& args, void* aux) {
+intptr_t IncreaseCounter(const struct arch_seccomp_data& args, void* aux) {
BPF_ASSERT(aux);
- pid_t* pid_ptr = static_cast<pid_t*>(aux);
- return (*pid_ptr)++;
+ int* counter = static_cast<int*>(aux);
+ return (*counter)++;
}
-ErrorCode VerboseAPITestingPolicy(SandboxBPF* sandbox, int sysno, void* aux) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- return ErrorCode(ENOSYS);
- } else if (sysno == __NR_getpid) {
- return sandbox->Trap(FakeGetPid, aux);
- } else {
+class VerboseAPITestingPolicy : public SandboxBPFPolicy {
+ public:
+ VerboseAPITestingPolicy(int* counter_ptr) : counter_ptr_(counter_ptr) {}
+
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
+ if (sysno == __NR_uname) {
+ return sandbox->Trap(IncreaseCounter, counter_ptr_);
+ }
return ErrorCode(ErrorCode::ERR_ALLOWED);
}
-}
+
+ private:
+ int* counter_ptr_;
+ DISALLOW_COPY_AND_ASSIGN(VerboseAPITestingPolicy);
+};
SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(VerboseAPITesting)) {
if (SandboxBPF::SupportsSeccompSandbox(-1) ==
sandbox::SandboxBPF::STATUS_AVAILABLE) {
- pid_t test_var = 0;
+ static int counter = 0;
+
SandboxBPF sandbox;
- sandbox.SetSandboxPolicyDeprecated(VerboseAPITestingPolicy, &test_var);
- sandbox.StartSandbox();
-
- BPF_ASSERT(test_var == 0);
- BPF_ASSERT(syscall(__NR_getpid) == 0);
- BPF_ASSERT(test_var == 1);
- BPF_ASSERT(syscall(__NR_getpid) == 1);
- BPF_ASSERT(test_var == 2);
-
- // N.B.: Any future call to getpid() would corrupt the stack.
- // This is OK. The SANDBOX_TEST() macro is guaranteed to
- // only ever call _exit() after the test completes.
+ sandbox.SetSandboxPolicy(new VerboseAPITestingPolicy(&counter));
+ BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::PROCESS_SINGLE_THREADED));
+
+ BPF_ASSERT_EQ(0, counter);
+ BPF_ASSERT_EQ(0, syscall(__NR_uname, 0));
+ BPF_ASSERT_EQ(1, counter);
+ BPF_ASSERT_EQ(1, syscall(__NR_uname, 0));
+ BPF_ASSERT_EQ(2, counter);
}
}
// A simple blacklist test
-ErrorCode BlacklistNanosleepPolicy(SandboxBPF*, int sysno, void*) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
+class BlacklistNanosleepPolicy : public SandboxBPFPolicy {
+ public:
+ BlacklistNanosleepPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysno) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
+ switch (sysno) {
+ case __NR_nanosleep:
+ return ErrorCode(EACCES);
+ default:
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ }
}
- switch (sysno) {
- case __NR_nanosleep:
- return ErrorCode(EACCES);
- default:
- return ErrorCode(ErrorCode::ERR_ALLOWED);
- }
-}
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BlacklistNanosleepPolicy);
+};
-BPF_TEST(SandboxBPF, ApplyBasicBlacklistPolicy, BlacklistNanosleepPolicy) {
+BPF_TEST_C(SandboxBPF, ApplyBasicBlacklistPolicy, BlacklistNanosleepPolicy) {
// nanosleep() should be denied
const struct timespec ts = {0, 0};
errno = 0;
@@ -138,17 +152,25 @@ BPF_TEST(SandboxBPF, ApplyBasicBlacklistPolicy, BlacklistNanosleepPolicy) {
// Now do a simple whitelist test
-ErrorCode WhitelistGetpidPolicy(SandboxBPF*, int sysno, void*) {
- switch (sysno) {
- case __NR_getpid:
- case __NR_exit_group:
- return ErrorCode(ErrorCode::ERR_ALLOWED);
- default:
- return ErrorCode(ENOMEM);
+class WhitelistGetpidPolicy : public SandboxBPFPolicy {
+ public:
+ WhitelistGetpidPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysno) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
+ switch (sysno) {
+ case __NR_getpid:
+ case __NR_exit_group:
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ default:
+ return ErrorCode(ENOMEM);
+ }
}
-}
-BPF_TEST(SandboxBPF, ApplyBasicWhitelistPolicy, WhitelistGetpidPolicy) {
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WhitelistGetpidPolicy);
+};
+
+BPF_TEST_C(SandboxBPF, ApplyBasicWhitelistPolicy, WhitelistGetpidPolicy) {
// getpid() should be allowed
errno = 0;
BPF_ASSERT(syscall(__NR_getpid) > 0);
@@ -160,7 +182,6 @@ BPF_TEST(SandboxBPF, ApplyBasicWhitelistPolicy, WhitelistGetpidPolicy) {
}
// A simple blacklist policy, with a SIGSYS handler
-
intptr_t EnomemHandler(const struct arch_seccomp_data& args, void* aux) {
// We also check that the auxiliary data is correct
SANDBOX_ASSERT(aux);
@@ -170,12 +191,8 @@ intptr_t EnomemHandler(const struct arch_seccomp_data& args, void* aux) {
ErrorCode BlacklistNanosleepPolicySigsys(SandboxBPF* sandbox,
int sysno,
- void* aux) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
- }
-
+ int* aux) {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
switch (sysno) {
case __NR_nanosleep:
return sandbox->Trap(EnomemHandler, aux);
@@ -187,32 +204,41 @@ ErrorCode BlacklistNanosleepPolicySigsys(SandboxBPF* sandbox,
BPF_TEST(SandboxBPF,
BasicBlacklistWithSigsys,
BlacklistNanosleepPolicySigsys,
- int /* BPF_AUX */) {
+ int /* (*BPF_AUX) */) {
// getpid() should work properly
errno = 0;
BPF_ASSERT(syscall(__NR_getpid) > 0);
BPF_ASSERT(errno == 0);
// Our Auxiliary Data, should be reset by the signal handler
- BPF_AUX = -1;
+ *BPF_AUX = -1;
const struct timespec ts = {0, 0};
BPF_ASSERT(syscall(__NR_nanosleep, &ts, NULL) == -1);
BPF_ASSERT(errno == ENOMEM);
// We expect the signal handler to modify AuxData
- BPF_ASSERT(BPF_AUX == kExpectedReturnValue);
+ BPF_ASSERT(*BPF_AUX == kExpectedReturnValue);
}
// A simple test that verifies we can return arbitrary errno values.
-ErrorCode ErrnoTestPolicy(SandboxBPF*, int sysno, void*) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
- }
+class ErrnoTestPolicy : public SandboxBPFPolicy {
+ public:
+ ErrnoTestPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysno) const OVERRIDE;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ErrnoTestPolicy);
+};
+
+ErrorCode ErrnoTestPolicy::EvaluateSyscall(SandboxBPF*, int sysno) const {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
switch (sysno) {
+#if defined(ANDROID)
+ case __NR_dup3: // dup2 is a wrapper of dup3 in android
+#else
case __NR_dup2:
+#endif
// Pretend that dup2() worked, but don't actually do anything.
return ErrorCode(0);
case __NR_setuid:
@@ -235,7 +261,7 @@ ErrorCode ErrnoTestPolicy(SandboxBPF*, int sysno, void*) {
}
}
-BPF_TEST(SandboxBPF, ErrnoTest, ErrnoTestPolicy) {
+BPF_TEST_C(SandboxBPF, ErrnoTest, ErrnoTestPolicy) {
// Verify that dup2() returns success, but doesn't actually run.
int fds[4];
BPF_ASSERT(pipe(fds) == 0);
@@ -277,43 +303,53 @@ BPF_TEST(SandboxBPF, ErrnoTest, ErrnoTestPolicy) {
// Testing the stacking of two sandboxes
-ErrorCode StackingPolicyPartOne(SandboxBPF* sandbox, int sysno, void*) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- return ErrorCode(ENOSYS);
+class StackingPolicyPartOne : public SandboxBPFPolicy {
+ public:
+ StackingPolicyPartOne() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
+ switch (sysno) {
+ case __NR_getppid:
+ return sandbox->Cond(0,
+ ErrorCode::TP_32BIT,
+ ErrorCode::OP_EQUAL,
+ 0,
+ ErrorCode(ErrorCode::ERR_ALLOWED),
+ ErrorCode(EPERM));
+ default:
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ }
}
- switch (sysno) {
- case __NR_getppid:
- return sandbox->Cond(0,
- ErrorCode::TP_32BIT,
- ErrorCode::OP_EQUAL,
- 0,
- ErrorCode(ErrorCode::ERR_ALLOWED),
- ErrorCode(EPERM));
- default:
- return ErrorCode(ErrorCode::ERR_ALLOWED);
- }
-}
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StackingPolicyPartOne);
+};
-ErrorCode StackingPolicyPartTwo(SandboxBPF* sandbox, int sysno, void*) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- return ErrorCode(ENOSYS);
+class StackingPolicyPartTwo : public SandboxBPFPolicy {
+ public:
+ StackingPolicyPartTwo() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
+ switch (sysno) {
+ case __NR_getppid:
+ return sandbox->Cond(0,
+ ErrorCode::TP_32BIT,
+ ErrorCode::OP_EQUAL,
+ 0,
+ ErrorCode(EINVAL),
+ ErrorCode(ErrorCode::ERR_ALLOWED));
+ default:
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ }
}
- switch (sysno) {
- case __NR_getppid:
- return sandbox->Cond(0,
- ErrorCode::TP_32BIT,
- ErrorCode::OP_EQUAL,
- 0,
- ErrorCode(EINVAL),
- ErrorCode(ErrorCode::ERR_ALLOWED));
- default:
- return ErrorCode(ErrorCode::ERR_ALLOWED);
- }
-}
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StackingPolicyPartTwo);
+};
-BPF_TEST(SandboxBPF, StackingPolicy, StackingPolicyPartOne) {
+BPF_TEST_C(SandboxBPF, StackingPolicy, StackingPolicyPartOne) {
errno = 0;
BPF_ASSERT(syscall(__NR_getppid, 0) > 0);
BPF_ASSERT(errno == 0);
@@ -324,8 +360,8 @@ BPF_TEST(SandboxBPF, StackingPolicy, StackingPolicyPartOne) {
// Stack a second sandbox with its own policy. Verify that we can further
// restrict filters, but we cannot relax existing filters.
SandboxBPF sandbox;
- sandbox.SetSandboxPolicyDeprecated(StackingPolicyPartTwo, NULL);
- sandbox.StartSandbox();
+ sandbox.SetSandboxPolicy(new StackingPolicyPartTwo());
+ BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::PROCESS_SINGLE_THREADED));
errno = 0;
BPF_ASSERT(syscall(__NR_getppid, 0) == -1);
@@ -350,29 +386,24 @@ int SysnoToRandomErrno(int sysno) {
return ((sysno & ~3) >> 2) % 29 + 1;
}
-ErrorCode SyntheticPolicy(SandboxBPF*, int sysno, void*) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
- }
-
-// TODO(jorgelo): remove this once the new code generator lands.
-#if defined(__arm__)
- if (sysno > static_cast<int>(MAX_PUBLIC_SYSCALL)) {
- return ErrorCode(ENOSYS);
- }
-#endif
-
- if (sysno == __NR_exit_group || sysno == __NR_write) {
- // exit_group() is special, we really need it to work.
- // write() is needed for BPF_ASSERT() to report a useful error message.
- return ErrorCode(ErrorCode::ERR_ALLOWED);
- } else {
+class SyntheticPolicy : public SandboxBPFPolicy {
+ public:
+ SyntheticPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysno) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
+ if (sysno == __NR_exit_group || sysno == __NR_write) {
+ // exit_group() is special, we really need it to work.
+ // write() is needed for BPF_ASSERT() to report a useful error message.
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ }
return ErrorCode(SysnoToRandomErrno(sysno));
}
-}
-BPF_TEST(SandboxBPF, SyntheticPolicy, SyntheticPolicy) {
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SyntheticPolicy);
+};
+
+BPF_TEST_C(SandboxBPF, SyntheticPolicy, SyntheticPolicy) {
// Ensure that that kExpectedReturnValue + syscallnumber + 1 does not int
// overflow.
BPF_ASSERT(std::numeric_limits<int>::max() - kExpectedReturnValue - 1 >=
@@ -406,23 +437,25 @@ int ArmPrivateSysnoToErrno(int sysno) {
}
}
-ErrorCode ArmPrivatePolicy(SandboxBPF*, int sysno, void*) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy.
- return ErrorCode(ENOSYS);
- }
-
- // Start from |__ARM_NR_set_tls + 1| so as not to mess with actual
- // ARM private system calls.
- if (sysno >= static_cast<int>(__ARM_NR_set_tls + 1) &&
- sysno <= static_cast<int>(MAX_PRIVATE_SYSCALL)) {
- return ErrorCode(ArmPrivateSysnoToErrno(sysno));
- } else {
+class ArmPrivatePolicy : public SandboxBPFPolicy {
+ public:
+ ArmPrivatePolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF*, int sysno) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
+ // Start from |__ARM_NR_set_tls + 1| so as not to mess with actual
+ // ARM private system calls.
+ if (sysno >= static_cast<int>(__ARM_NR_set_tls + 1) &&
+ sysno <= static_cast<int>(MAX_PRIVATE_SYSCALL)) {
+ return ErrorCode(ArmPrivateSysnoToErrno(sysno));
+ }
return ErrorCode(ErrorCode::ERR_ALLOWED);
}
-}
-BPF_TEST(SandboxBPF, ArmPrivatePolicy, ArmPrivatePolicy) {
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ArmPrivatePolicy);
+};
+
+BPF_TEST_C(SandboxBPF, ArmPrivatePolicy, ArmPrivatePolicy) {
for (int syscall_number = static_cast<int>(__ARM_NR_set_tls + 1);
syscall_number <= static_cast<int>(MAX_PRIVATE_SYSCALL);
++syscall_number) {
@@ -446,7 +479,7 @@ intptr_t CountSyscalls(const struct arch_seccomp_data& args, void* aux) {
return SandboxBPF::ForwardSyscall(args);
}
-ErrorCode GreyListedPolicy(SandboxBPF* sandbox, int sysno, void* aux) {
+ErrorCode GreyListedPolicy(SandboxBPF* sandbox, int sysno, int* aux) {
// The use of UnsafeTrap() causes us to print a warning message. This is
// generally desirable, but it results in the unittest failing, as it doesn't
// expect any messages on "stderr". So, temporarily disable messages. The
@@ -479,12 +512,12 @@ ErrorCode GreyListedPolicy(SandboxBPF* sandbox, int sysno, void* aux) {
}
}
-BPF_TEST(SandboxBPF, GreyListedPolicy, GreyListedPolicy, int /* BPF_AUX */) {
+BPF_TEST(SandboxBPF, GreyListedPolicy, GreyListedPolicy, int /* (*BPF_AUX) */) {
BPF_ASSERT(syscall(__NR_getpid) == -1);
BPF_ASSERT(errno == EPERM);
- BPF_ASSERT(BPF_AUX == 0);
+ BPF_ASSERT(*BPF_AUX == 0);
BPF_ASSERT(syscall(__NR_geteuid) == syscall(__NR_getuid));
- BPF_ASSERT(BPF_AUX == 2);
+ BPF_ASSERT(*BPF_AUX == 2);
char name[17] = {};
BPF_ASSERT(!syscall(__NR_prctl,
PR_GET_NAME,
@@ -492,7 +525,7 @@ BPF_TEST(SandboxBPF, GreyListedPolicy, GreyListedPolicy, int /* BPF_AUX */) {
(void*)NULL,
(void*)NULL,
(void*)NULL));
- BPF_ASSERT(BPF_AUX == 3);
+ BPF_ASSERT(*BPF_AUX == 3);
BPF_ASSERT(*name);
}
@@ -519,22 +552,29 @@ intptr_t PrctlHandler(const struct arch_seccomp_data& args, void*) {
}
}
-ErrorCode PrctlPolicy(SandboxBPF* sandbox, int sysno, void* aux) {
- setenv(kSandboxDebuggingEnv, "t", 0);
- Die::SuppressInfoMessages(true);
+class PrctlPolicy : public SandboxBPFPolicy {
+ public:
+ PrctlPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
+ setenv(kSandboxDebuggingEnv, "t", 0);
+ Die::SuppressInfoMessages(true);
+
+ if (sysno == __NR_prctl) {
+ // Handle prctl() inside an UnsafeTrap()
+ return sandbox->UnsafeTrap(PrctlHandler, NULL);
+ }
- if (sysno == __NR_prctl) {
- // Handle prctl() inside an UnsafeTrap()
- return sandbox->UnsafeTrap(PrctlHandler, NULL);
- } else if (SandboxBPF::IsValidSyscallNumber(sysno)) {
// Allow all other system calls.
return ErrorCode(ErrorCode::ERR_ALLOWED);
- } else {
- return ErrorCode(ENOSYS);
}
-}
-BPF_TEST(SandboxBPF, ForwardSyscall, PrctlPolicy) {
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PrctlPolicy);
+};
+
+BPF_TEST_C(SandboxBPF, ForwardSyscall, PrctlPolicy) {
// This call should never be allowed. But our policy will intercept it and
// let it pass successfully.
BPF_ASSERT(
@@ -565,7 +605,19 @@ intptr_t AllowRedirectedSyscall(const struct arch_seccomp_data& args, void*) {
return SandboxBPF::ForwardSyscall(args);
}
-ErrorCode RedirectAllSyscallsPolicy(SandboxBPF* sandbox, int sysno, void* aux) {
+class RedirectAllSyscallsPolicy : public SandboxBPFPolicy {
+ public:
+ RedirectAllSyscallsPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RedirectAllSyscallsPolicy);
+};
+
+ErrorCode RedirectAllSyscallsPolicy::EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
setenv(kSandboxDebuggingEnv, "t", 0);
Die::SuppressInfoMessages(true);
@@ -582,11 +634,8 @@ ErrorCode RedirectAllSyscallsPolicy(SandboxBPF* sandbox, int sysno, void* aux) {
#endif
) {
return ErrorCode(ErrorCode::ERR_ALLOWED);
- } else if (SandboxBPF::IsValidSyscallNumber(sysno)) {
- return sandbox->UnsafeTrap(AllowRedirectedSyscall, aux);
- } else {
- return ErrorCode(ENOSYS);
}
+ return sandbox->UnsafeTrap(AllowRedirectedSyscall, NULL);
}
int bus_handler_fd_ = -1;
@@ -595,7 +644,7 @@ void SigBusHandler(int, siginfo_t* info, void* void_context) {
BPF_ASSERT(write(bus_handler_fd_, "\x55", 1) == 1);
}
-BPF_TEST(SandboxBPF, SigBus, RedirectAllSyscallsPolicy) {
+BPF_TEST_C(SandboxBPF, SigBus, RedirectAllSyscallsPolicy) {
// We use the SIGBUS bit in the signal mask as a thread-local boolean
// value in the implementation of UnsafeTrap(). This is obviously a bit
// of a hack that could conceivably interfere with code that uses SIGBUS
@@ -618,7 +667,7 @@ BPF_TEST(SandboxBPF, SigBus, RedirectAllSyscallsPolicy) {
BPF_ASSERT(c == 0x55);
}
-BPF_TEST(SandboxBPF, SigMask, RedirectAllSyscallsPolicy) {
+BPF_TEST_C(SandboxBPF, SigMask, RedirectAllSyscallsPolicy) {
// Signal masks are potentially tricky to handle. For instance, if we
// ever tried to update them from inside a Trap() or UnsafeTrap() handler,
// the call to sigreturn() at the end of the signal handler would undo
@@ -645,7 +694,7 @@ BPF_TEST(SandboxBPF, SigMask, RedirectAllSyscallsPolicy) {
BPF_ASSERT(sigismember(&mask2, SIGUSR2));
}
-BPF_TEST(SandboxBPF, UnsafeTrapWithErrno, RedirectAllSyscallsPolicy) {
+BPF_TEST_C(SandboxBPF, UnsafeTrapWithErrno, RedirectAllSyscallsPolicy) {
// An UnsafeTrap() (or for that matter, a Trap()) has to report error
// conditions by returning an exit code in the range -1..-4096. This
// should happen automatically if using ForwardSyscall(). If the TrapFnc()
@@ -670,6 +719,8 @@ BPF_TEST(SandboxBPF, UnsafeTrapWithErrno, RedirectAllSyscallsPolicy) {
BPF_ASSERT(errno == 0);
}
+bool NoOpCallback() { return true; }
+
// Test a trap handler that makes use of a broker process to open().
class InitializedOpenBroker {
@@ -682,7 +733,7 @@ class InitializedOpenBroker {
broker_process_.reset(
new BrokerProcess(EPERM, allowed_files, std::vector<std::string>()));
BPF_ASSERT(broker_process() != NULL);
- BPF_ASSERT(broker_process_->Init(NULL));
+ BPF_ASSERT(broker_process_->Init(base::Bind(&NoOpCallback)));
initialized_ = true;
}
@@ -700,9 +751,15 @@ intptr_t BrokerOpenTrapHandler(const struct arch_seccomp_data& args,
BPF_ASSERT(aux);
BrokerProcess* broker_process = static_cast<BrokerProcess*>(aux);
switch (args.nr) {
+#if defined(ANDROID)
+ case __NR_faccessat: // access is a wrapper of faccessat in android
+ return broker_process->Access(reinterpret_cast<const char*>(args.args[1]),
+ static_cast<int>(args.args[2]));
+#else
case __NR_access:
return broker_process->Access(reinterpret_cast<const char*>(args.args[0]),
static_cast<int>(args.args[1]));
+#endif
case __NR_open:
return broker_process->Open(reinterpret_cast<const char*>(args.args[0]),
static_cast<int>(args.args[1]));
@@ -718,14 +775,19 @@ intptr_t BrokerOpenTrapHandler(const struct arch_seccomp_data& args,
}
}
-ErrorCode DenyOpenPolicy(SandboxBPF* sandbox, int sysno, void* aux) {
- InitializedOpenBroker* iob = static_cast<InitializedOpenBroker*>(aux);
+ErrorCode DenyOpenPolicy(SandboxBPF* sandbox,
+ int sysno,
+ InitializedOpenBroker* iob) {
if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
return ErrorCode(ENOSYS);
}
switch (sysno) {
+#if defined(ANDROID)
+ case __NR_faccessat:
+#else
case __NR_access:
+#endif
case __NR_open:
case __NR_openat:
// We get a InitializedOpenBroker class, but our trap handler wants
@@ -742,9 +804,9 @@ ErrorCode DenyOpenPolicy(SandboxBPF* sandbox, int sysno, void* aux) {
BPF_TEST(SandboxBPF,
UseOpenBroker,
DenyOpenPolicy,
- InitializedOpenBroker /* BPF_AUX */) {
- BPF_ASSERT(BPF_AUX.initialized());
- BrokerProcess* broker_process = BPF_AUX.broker_process();
+ InitializedOpenBroker /* (*BPF_AUX) */) {
+ BPF_ASSERT(BPF_AUX->initialized());
+ BrokerProcess* broker_process = BPF_AUX->broker_process();
BPF_ASSERT(broker_process != NULL);
// First, use the broker "manually"
@@ -786,16 +848,35 @@ BPF_TEST(SandboxBPF,
// Simple test demonstrating how to use SandboxBPF::Cond()
-ErrorCode SimpleCondTestPolicy(SandboxBPF* sandbox, int sysno, void*) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
- }
+class SimpleCondTestPolicy : public SandboxBPFPolicy {
+ public:
+ SimpleCondTestPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SimpleCondTestPolicy);
+};
+
+ErrorCode SimpleCondTestPolicy::EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
// We deliberately return unusual errno values upon failure, so that we
// can uniquely test for these values. In a "real" policy, you would want
// to return more traditional values.
switch (sysno) {
+#if defined(ANDROID)
+ case __NR_openat: // open is a wrapper of openat in android
+ // Allow opening files for reading, but don't allow writing.
+ COMPILE_ASSERT(O_RDONLY == 0, O_RDONLY_must_be_all_zero_bits);
+ return sandbox->Cond(2,
+ ErrorCode::TP_32BIT,
+ ErrorCode::OP_HAS_ANY_BITS,
+ O_ACCMODE /* 0x3 */,
+ ErrorCode(EROFS),
+ ErrorCode(ErrorCode::ERR_ALLOWED));
+#else
case __NR_open:
// Allow opening files for reading, but don't allow writing.
COMPILE_ASSERT(O_RDONLY == 0, O_RDONLY_must_be_all_zero_bits);
@@ -805,6 +886,7 @@ ErrorCode SimpleCondTestPolicy(SandboxBPF* sandbox, int sysno, void*) {
O_ACCMODE /* 0x3 */,
ErrorCode(EROFS),
ErrorCode(ErrorCode::ERR_ALLOWED));
+#endif
case __NR_prctl:
// Allow prctl(PR_SET_DUMPABLE) and prctl(PR_GET_DUMPABLE), but
// disallow everything else.
@@ -824,7 +906,7 @@ ErrorCode SimpleCondTestPolicy(SandboxBPF* sandbox, int sysno, void*) {
}
}
-BPF_TEST(SandboxBPF, SimpleCondTest, SimpleCondTestPolicy) {
+BPF_TEST_C(SandboxBPF, SimpleCondTest, SimpleCondTestPolicy) {
int fd;
BPF_ASSERT((fd = open("/proc/self/comm", O_RDWR)) == -1);
BPF_ASSERT(errno == EROFS);
@@ -1122,7 +1204,7 @@ class EqualityStressTest {
// based on the system call number and the parameters that we decided
// to pass in. Verify that this condition holds true.
BPF_ASSERT(
- SandboxSyscall(
+ Syscall::Call(
sysno, args[0], args[1], args[2], args[3], args[4], args[5]) ==
-err);
}
@@ -1139,22 +1221,34 @@ class EqualityStressTest {
static const int kMaxArgs = 6;
};
-ErrorCode EqualityStressTestPolicy(SandboxBPF* sandbox, int sysno, void* aux) {
- return reinterpret_cast<EqualityStressTest*>(aux)->Policy(sandbox, sysno);
+ErrorCode EqualityStressTestPolicy(SandboxBPF* sandbox,
+ int sysno,
+ EqualityStressTest* aux) {
+ DCHECK(aux);
+ return aux->Policy(sandbox, sysno);
}
BPF_TEST(SandboxBPF,
EqualityTests,
EqualityStressTestPolicy,
- EqualityStressTest /* BPF_AUX */) {
- BPF_AUX.VerifyFilter();
+ EqualityStressTest /* (*BPF_AUX) */) {
+ BPF_AUX->VerifyFilter();
}
-ErrorCode EqualityArgumentWidthPolicy(SandboxBPF* sandbox, int sysno, void*) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
- } else if (sysno == __NR_uname) {
+class EqualityArgumentWidthPolicy : public SandboxBPFPolicy {
+ public:
+ EqualityArgumentWidthPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EqualityArgumentWidthPolicy);
+};
+
+ErrorCode EqualityArgumentWidthPolicy::EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
+ if (sysno == __NR_uname) {
return sandbox->Cond(
0,
ErrorCode::TP_32BIT,
@@ -1180,24 +1274,23 @@ ErrorCode EqualityArgumentWidthPolicy(SandboxBPF* sandbox, int sysno, void*) {
0x55555555AAAAAAAAULL,
ErrorCode(1),
ErrorCode(2)));
- } else {
- return ErrorCode(ErrorCode::ERR_ALLOWED);
}
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
}
-BPF_TEST(SandboxBPF, EqualityArgumentWidth, EqualityArgumentWidthPolicy) {
- BPF_ASSERT(SandboxSyscall(__NR_uname, 0, 0x55555555) == -1);
- BPF_ASSERT(SandboxSyscall(__NR_uname, 0, 0xAAAAAAAA) == -2);
+BPF_TEST_C(SandboxBPF, EqualityArgumentWidth, EqualityArgumentWidthPolicy) {
+ BPF_ASSERT(Syscall::Call(__NR_uname, 0, 0x55555555) == -1);
+ BPF_ASSERT(Syscall::Call(__NR_uname, 0, 0xAAAAAAAA) == -2);
#if __SIZEOF_POINTER__ > 4
// On 32bit machines, there is no way to pass a 64bit argument through the
// syscall interface. So, we have to skip the part of the test that requires
// 64bit arguments.
- BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x55555555AAAAAAAAULL) == -1);
- BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x5555555500000000ULL) == -2);
- BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x5555555511111111ULL) == -2);
- BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x11111111AAAAAAAAULL) == -2);
+ BPF_ASSERT(Syscall::Call(__NR_uname, 1, 0x55555555AAAAAAAAULL) == -1);
+ BPF_ASSERT(Syscall::Call(__NR_uname, 1, 0x5555555500000000ULL) == -2);
+ BPF_ASSERT(Syscall::Call(__NR_uname, 1, 0x5555555511111111ULL) == -2);
+ BPF_ASSERT(Syscall::Call(__NR_uname, 1, 0x11111111AAAAAAAAULL) == -2);
#else
- BPF_ASSERT(SandboxSyscall(__NR_uname, 1, 0x55555555) == -2);
+ BPF_ASSERT(Syscall::Call(__NR_uname, 1, 0x55555555) == -2);
#endif
}
@@ -1205,62 +1298,74 @@ BPF_TEST(SandboxBPF, EqualityArgumentWidth, EqualityArgumentWidthPolicy) {
// On 32bit machines, there is no way to pass a 64bit argument through the
// syscall interface. So, we have to skip the part of the test that requires
// 64bit arguments.
-BPF_DEATH_TEST(SandboxBPF,
- EqualityArgumentUnallowed64bit,
- DEATH_MESSAGE("Unexpected 64bit argument detected"),
- EqualityArgumentWidthPolicy) {
- SandboxSyscall(__NR_uname, 0, 0x5555555555555555ULL);
+BPF_DEATH_TEST_C(SandboxBPF,
+ EqualityArgumentUnallowed64bit,
+ DEATH_MESSAGE("Unexpected 64bit argument detected"),
+ EqualityArgumentWidthPolicy) {
+ Syscall::Call(__NR_uname, 0, 0x5555555555555555ULL);
}
#endif
-ErrorCode EqualityWithNegativeArgumentsPolicy(SandboxBPF* sandbox,
- int sysno,
- void*) {
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
- } else if (sysno == __NR_uname) {
- return sandbox->Cond(0,
- ErrorCode::TP_32BIT,
- ErrorCode::OP_EQUAL,
- 0xFFFFFFFF,
- ErrorCode(1),
- ErrorCode(2));
- } else {
+class EqualityWithNegativeArgumentsPolicy : public SandboxBPFPolicy {
+ public:
+ EqualityWithNegativeArgumentsPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
+ if (sysno == __NR_uname) {
+ return sandbox->Cond(0,
+ ErrorCode::TP_32BIT,
+ ErrorCode::OP_EQUAL,
+ 0xFFFFFFFF,
+ ErrorCode(1),
+ ErrorCode(2));
+ }
return ErrorCode(ErrorCode::ERR_ALLOWED);
}
-}
-BPF_TEST(SandboxBPF,
- EqualityWithNegativeArguments,
- EqualityWithNegativeArgumentsPolicy) {
- BPF_ASSERT(SandboxSyscall(__NR_uname, 0xFFFFFFFF) == -1);
- BPF_ASSERT(SandboxSyscall(__NR_uname, -1) == -1);
- BPF_ASSERT(SandboxSyscall(__NR_uname, -1LL) == -1);
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EqualityWithNegativeArgumentsPolicy);
+};
+
+BPF_TEST_C(SandboxBPF,
+ EqualityWithNegativeArguments,
+ EqualityWithNegativeArgumentsPolicy) {
+ BPF_ASSERT(Syscall::Call(__NR_uname, 0xFFFFFFFF) == -1);
+ BPF_ASSERT(Syscall::Call(__NR_uname, -1) == -1);
+ BPF_ASSERT(Syscall::Call(__NR_uname, -1LL) == -1);
}
#if __SIZEOF_POINTER__ > 4
-BPF_DEATH_TEST(SandboxBPF,
- EqualityWithNegative64bitArguments,
- DEATH_MESSAGE("Unexpected 64bit argument detected"),
- EqualityWithNegativeArgumentsPolicy) {
+BPF_DEATH_TEST_C(SandboxBPF,
+ EqualityWithNegative64bitArguments,
+ DEATH_MESSAGE("Unexpected 64bit argument detected"),
+ EqualityWithNegativeArgumentsPolicy) {
// When expecting a 32bit system call argument, we look at the MSB of the
// 64bit value and allow both "0" and "-1". But the latter is allowed only
// iff the LSB was negative. So, this death test should error out.
- BPF_ASSERT(SandboxSyscall(__NR_uname, 0xFFFFFFFF00000000LL) == -1);
+ BPF_ASSERT(Syscall::Call(__NR_uname, 0xFFFFFFFF00000000LL) == -1);
}
#endif
-ErrorCode AllBitTestPolicy(SandboxBPF* sandbox, int sysno, void *) {
+class AllBitTestPolicy : public SandboxBPFPolicy {
+ public:
+ AllBitTestPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AllBitTestPolicy);
+};
+
+ErrorCode AllBitTestPolicy::EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
// Test the OP_HAS_ALL_BITS conditional test operator with a couple of
// different bitmasks. We try to find bitmasks that could conceivably
// touch corner cases.
// For all of these tests, we override the uname(). We can make use with
// a single system call number, as we use the first system call argument to
// select the different bit masks that we want to test against.
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
- } else if (sysno == __NR_uname) {
+ if (sysno == __NR_uname) {
return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 0,
sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ALL_BITS,
0x0,
@@ -1316,9 +1421,8 @@ ErrorCode AllBitTestPolicy(SandboxBPF* sandbox, int sysno, void *) {
ErrorCode(1), ErrorCode(0)),
sandbox->Kill("Invalid test case number"))))))))))));
- } else {
- return ErrorCode(ErrorCode::ERR_ALLOWED);
}
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
}
// Define a macro that performs tests using our test policy.
@@ -1329,10 +1433,10 @@ ErrorCode AllBitTestPolicy(SandboxBPF* sandbox, int sysno, void *) {
// to make changes to these values, you will have to edit the
// test policy instead.
#define BITMASK_TEST(testcase, arg, op, mask, expected_value) \
- BPF_ASSERT(SandboxSyscall(__NR_uname, (testcase), (arg)) == (expected_value))
+ BPF_ASSERT(Syscall::Call(__NR_uname, (testcase), (arg)) == (expected_value))
// Our uname() system call returns ErrorCode(1) for success and
-// ErrorCode(0) for failure. SandboxSyscall() turns this into an
+// ErrorCode(0) for failure. Syscall::Call() turns this into an
// exit code of -1 or 0.
#define EXPECT_FAILURE 0
#define EXPECT_SUCCESS -1
@@ -1343,7 +1447,7 @@ ErrorCode AllBitTestPolicy(SandboxBPF* sandbox, int sysno, void *) {
// We expect these tests to succeed on 64bit systems, but to tail on 32bit
// systems.
#define EXPT64_SUCCESS (sizeof(void*) > 4 ? EXPECT_SUCCESS : EXPECT_FAILURE)
-BPF_TEST(SandboxBPF, AllBitTests, AllBitTestPolicy) {
+BPF_TEST_C(SandboxBPF, AllBitTests, AllBitTestPolicy) {
// 32bit test: all of 0x0 (should always be true)
BITMASK_TEST( 0, 0, ALLBITS32, 0, EXPECT_SUCCESS);
BITMASK_TEST( 0, 1, ALLBITS32, 0, EXPECT_SUCCESS);
@@ -1446,17 +1550,26 @@ BPF_TEST(SandboxBPF, AllBitTests, AllBitTestPolicy) {
BITMASK_TEST(10, -1L, ALLBITS64,0x100000001, EXPT64_SUCCESS);
}
-ErrorCode AnyBitTestPolicy(SandboxBPF* sandbox, int sysno, void*) {
+class AnyBitTestPolicy : public SandboxBPFPolicy {
+ public:
+ AnyBitTestPolicy() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AnyBitTestPolicy);
+};
+
+ErrorCode AnyBitTestPolicy::EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
// Test the OP_HAS_ANY_BITS conditional test operator with a couple of
// different bitmasks. We try to find bitmasks that could conceivably
// touch corner cases.
// For all of these tests, we override the uname(). We can make use with
// a single system call number, as we use the first system call argument to
// select the different bit masks that we want to test against.
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
- } else if (sysno == __NR_uname) {
+ if (sysno == __NR_uname) {
return sandbox->Cond(0, ErrorCode::TP_32BIT, ErrorCode::OP_EQUAL, 0,
sandbox->Cond(1, ErrorCode::TP_32BIT, ErrorCode::OP_HAS_ANY_BITS,
0x0,
@@ -1515,12 +1628,11 @@ ErrorCode AnyBitTestPolicy(SandboxBPF* sandbox, int sysno, void*) {
ErrorCode(1), ErrorCode(0)),
sandbox->Kill("Invalid test case number"))))))))))));
- } else {
- return ErrorCode(ErrorCode::ERR_ALLOWED);
}
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
}
-BPF_TEST(SandboxBPF, AnyBitTests, AnyBitTestPolicy) {
+BPF_TEST_C(SandboxBPF, AnyBitTests, AnyBitTestPolicy) {
// 32bit test: any of 0x0 (should always be false)
BITMASK_TEST( 0, 0, ANYBITS32, 0x0, EXPECT_FAILURE);
BITMASK_TEST( 0, 1, ANYBITS32, 0x0, EXPECT_FAILURE);
@@ -1650,15 +1762,25 @@ intptr_t PthreadTrapHandler(const struct arch_seccomp_data& args, void* aux) {
}
return -EPERM;
}
-ErrorCode PthreadPolicyEquality(SandboxBPF* sandbox, int sysno, void* aux) {
+
+class PthreadPolicyEquality : public SandboxBPFPolicy {
+ public:
+ PthreadPolicyEquality() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PthreadPolicyEquality);
+};
+
+ErrorCode PthreadPolicyEquality::EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
// This policy allows creating threads with pthread_create(). But it
// doesn't allow any other uses of clone(). Most notably, it does not
// allow callers to implement fork() or vfork() by passing suitable flags
// to the clone() system call.
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
- } else if (sysno == __NR_clone) {
+ if (sysno == __NR_clone) {
// We have seen two different valid combinations of flags. Glibc
// uses the more modern flags, sets the TLS from the call to clone(), and
// uses futexes to monitor threads. Android's C run-time library, doesn't
@@ -1685,20 +1807,28 @@ ErrorCode PthreadPolicyEquality(SandboxBPF* sandbox, int sysno, void* aux) {
kBaseAndroidCloneMask,
ErrorCode(ErrorCode::ERR_ALLOWED),
sandbox->Trap(PthreadTrapHandler, "Unknown mask"))));
- } else {
- return ErrorCode(ErrorCode::ERR_ALLOWED);
}
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
}
-ErrorCode PthreadPolicyBitMask(SandboxBPF* sandbox, int sysno, void* aux) {
+class PthreadPolicyBitMask : public SandboxBPFPolicy {
+ public:
+ PthreadPolicyBitMask() {}
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PthreadPolicyBitMask);
+};
+
+ErrorCode PthreadPolicyBitMask::EvaluateSyscall(SandboxBPF* sandbox,
+ int sysno) const {
+ DCHECK(SandboxBPF::IsValidSyscallNumber(sysno));
// This policy allows creating threads with pthread_create(). But it
// doesn't allow any other uses of clone(). Most notably, it does not
// allow callers to implement fork() or vfork() by passing suitable flags
// to the clone() system call.
- if (!SandboxBPF::IsValidSyscallNumber(sysno)) {
- // FIXME: we should really not have to do that in a trivial policy
- return ErrorCode(ENOSYS);
- } else if (sysno == __NR_clone) {
+ if (sysno == __NR_clone) {
// We have seen two different valid combinations of flags. Glibc
// uses the more modern flags, sets the TLS from the call to clone(), and
// uses futexes to monitor threads. Android's C run-time library, doesn't
@@ -1730,14 +1860,13 @@ ErrorCode PthreadPolicyBitMask(SandboxBPF* sandbox, int sysno, void* aux) {
sandbox->Trap(PthreadTrapHandler,
"Missing mandatory CLONE_XXX flags "
"when creating new thread")));
- } else {
- return ErrorCode(ErrorCode::ERR_ALLOWED);
}
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
}
static void* ThreadFnc(void* arg) {
++*reinterpret_cast<int*>(arg);
- SandboxSyscall(__NR_futex, arg, FUTEX_WAKE, 1, 0, 0, 0);
+ Syscall::Call(__NR_futex, arg, FUTEX_WAKE, 1, 0, 0, 0);
return NULL;
}
@@ -1756,7 +1885,7 @@ static void PthreadTest() {
BPF_ASSERT(!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
BPF_ASSERT(!pthread_create(&thread, &attr, ThreadFnc, &thread_ran));
BPF_ASSERT(!pthread_attr_destroy(&attr));
- while (SandboxSyscall(__NR_futex, &thread_ran, FUTEX_WAIT, 0, 0, 0, 0) ==
+ while (Syscall::Call(__NR_futex, &thread_ran, FUTEX_WAIT, 0, 0, 0, 0) ==
-EINTR) {
}
BPF_ASSERT(thread_ran);
@@ -1767,16 +1896,168 @@ static void PthreadTest() {
// run-time libraries other than glibc might call __NR_fork instead of
// __NR_clone, and that would introduce a bogus test failure.
int pid;
- BPF_ASSERT(SandboxSyscall(__NR_clone,
- CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | SIGCHLD,
- 0,
- 0,
- &pid) == -EPERM);
+ BPF_ASSERT(Syscall::Call(__NR_clone,
+ CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | SIGCHLD,
+ 0,
+ 0,
+ &pid) == -EPERM);
}
-BPF_TEST(SandboxBPF, PthreadEquality, PthreadPolicyEquality) { PthreadTest(); }
+BPF_TEST_C(SandboxBPF, PthreadEquality, PthreadPolicyEquality) {
+ PthreadTest();
+}
+
+BPF_TEST_C(SandboxBPF, PthreadBitMask, PthreadPolicyBitMask) {
+ PthreadTest();
+}
+
+// libc might not define these even though the kernel supports it.
+#ifndef PTRACE_O_TRACESECCOMP
+#define PTRACE_O_TRACESECCOMP 0x00000080
+#endif
+
+#ifdef PTRACE_EVENT_SECCOMP
+#define IS_SECCOMP_EVENT(status) ((status >> 16) == PTRACE_EVENT_SECCOMP)
+#else
+// When Debian/Ubuntu backported seccomp-bpf support into earlier kernels, they
+// changed the value of PTRACE_EVENT_SECCOMP from 7 to 8, since 7 was taken by
+// PTRACE_EVENT_STOP (upstream chose to renumber PTRACE_EVENT_STOP to 128). If
+// PTRACE_EVENT_SECCOMP isn't defined, we have no choice but to consider both
+// values here.
+#define IS_SECCOMP_EVENT(status) ((status >> 16) == 7 || (status >> 16) == 8)
+#endif
-BPF_TEST(SandboxBPF, PthreadBitMask, PthreadPolicyBitMask) { PthreadTest(); }
+#if defined(__arm__)
+#ifndef PTRACE_SET_SYSCALL
+#define PTRACE_SET_SYSCALL 23
+#endif
+#endif
+
+// Changes the syscall to run for a child being sandboxed using seccomp-bpf with
+// PTRACE_O_TRACESECCOMP. Should only be called when the child is stopped on
+// PTRACE_EVENT_SECCOMP.
+//
+// regs should contain the current set of registers of the child, obtained using
+// PTRACE_GETREGS.
+//
+// Depending on the architecture, this may modify regs, so the caller is
+// responsible for committing these changes using PTRACE_SETREGS.
+long SetSyscall(pid_t pid, regs_struct* regs, int syscall_number) {
+#if defined(__arm__)
+ // On ARM, the syscall is changed using PTRACE_SET_SYSCALL. We cannot use the
+ // libc ptrace call as the request parameter is an enum, and
+ // PTRACE_SET_SYSCALL may not be in the enum.
+ return syscall(__NR_ptrace, PTRACE_SET_SYSCALL, pid, NULL, syscall_number);
+#endif
+
+ SECCOMP_PT_SYSCALL(*regs) = syscall_number;
+ return 0;
+}
+
+const uint16_t kTraceData = 0xcc;
+
+class TraceAllPolicy : public SandboxBPFPolicy {
+ public:
+ TraceAllPolicy() {}
+ virtual ~TraceAllPolicy() {}
+
+ virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler,
+ int system_call_number) const OVERRIDE {
+ return ErrorCode(ErrorCode::ERR_TRACE + kTraceData);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TraceAllPolicy);
+};
+
+SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(SeccompRetTrace)) {
+ if (SandboxBPF::SupportsSeccompSandbox(-1) !=
+ sandbox::SandboxBPF::STATUS_AVAILABLE) {
+ return;
+ }
+
+#if defined(__arm__)
+ printf("This test is currently disabled on ARM due to a kernel bug.");
+ return;
+#endif
+
+ pid_t pid = fork();
+ BPF_ASSERT_NE(-1, pid);
+ if (pid == 0) {
+ pid_t my_pid = getpid();
+ BPF_ASSERT_NE(-1, ptrace(PTRACE_TRACEME, -1, NULL, NULL));
+ BPF_ASSERT_EQ(0, raise(SIGSTOP));
+ SandboxBPF sandbox;
+ sandbox.SetSandboxPolicy(new TraceAllPolicy);
+ BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::PROCESS_SINGLE_THREADED));
+
+ // getpid is allowed.
+ BPF_ASSERT_EQ(my_pid, syscall(__NR_getpid));
+
+ // write to stdout is skipped and returns a fake value.
+ BPF_ASSERT_EQ(kExpectedReturnValue,
+ syscall(__NR_write, STDOUT_FILENO, "A", 1));
+
+ // kill is rewritten to exit(kExpectedReturnValue).
+ syscall(__NR_kill, my_pid, SIGKILL);
+
+ // Should not be reached.
+ BPF_ASSERT(false);
+ }
+
+ int status;
+ BPF_ASSERT(HANDLE_EINTR(waitpid(pid, &status, WUNTRACED)) != -1);
+ BPF_ASSERT(WIFSTOPPED(status));
+
+ BPF_ASSERT_NE(-1, ptrace(PTRACE_SETOPTIONS, pid, NULL,
+ reinterpret_cast<void*>(PTRACE_O_TRACESECCOMP)));
+ BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL));
+ while (true) {
+ BPF_ASSERT(HANDLE_EINTR(waitpid(pid, &status, 0)) != -1);
+ if (WIFEXITED(status) || WIFSIGNALED(status)) {
+ BPF_ASSERT(WIFEXITED(status));
+ BPF_ASSERT_EQ(kExpectedReturnValue, WEXITSTATUS(status));
+ break;
+ }
+
+ if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP ||
+ !IS_SECCOMP_EVENT(status)) {
+ BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL));
+ continue;
+ }
+
+ unsigned long data;
+ BPF_ASSERT_NE(-1, ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data));
+ BPF_ASSERT_EQ(kTraceData, data);
+
+ regs_struct regs;
+ BPF_ASSERT_NE(-1, ptrace(PTRACE_GETREGS, pid, NULL, &regs));
+ 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, &regs, -1));
+ SECCOMP_PT_RESULT(regs) = kExpectedReturnValue;
+ BPF_ASSERT_NE(-1, ptrace(PTRACE_SETREGS, pid, NULL, &regs));
+ }
+ break;
+
+ case __NR_kill:
+ // Rewrite to exit(kExpectedReturnValue).
+ BPF_ASSERT_NE(-1, SetSyscall(pid, &regs, __NR_exit));
+ SECCOMP_PT_PARM1(regs) = kExpectedReturnValue;
+ BPF_ASSERT_NE(-1, ptrace(PTRACE_SETREGS, pid, NULL, &regs));
+ 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())