diff options
author | Daniel Erat <derat@google.com> | 2015-08-24 12:50:25 -0600 |
---|---|---|
committer | Daniel Erat <derat@google.com> | 2015-08-24 12:50:25 -0600 |
commit | 59c5f4b0fb104e8e4806e4934a3d5d112ad695ab (patch) | |
tree | a392cfe92bfebf2f2ee13d6bd8ca3e4a09cafa51 /sandbox | |
parent | db7c5941371d7639578632d11f31fa6698198c36 (diff) | |
download | libchrome-59c5f4b0fb104e8e4806e4934a3d5d112ad695ab.tar.gz |
Pull in upstream crypto/ and sandbox/ dirs at r334380.
Add code from
https://chromium.googlesource.com/chromium/src/crypto at
3b5d1294 (r333554) and
https://chromium.googlesource.com/chromium/src/sandbox at
50337f60 (r334108).
These won't be built in AOSP, but they correspond to the
versions checked out by Chrome OS.
BUG=chromium:521005
Change-Id: Id82858f3a870d8ab9e3e8fe1c3bb598ba065dd14
Diffstat (limited to 'sandbox')
418 files changed, 73146 insertions, 0 deletions
diff --git a/sandbox/BUILD.gn b/sandbox/BUILD.gn new file mode 100644 index 0000000000..15fb62092f --- /dev/null +++ b/sandbox/BUILD.gn @@ -0,0 +1,23 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Meta-target that forwards to the proper platform one. +group("sandbox") { + if (is_win) { + deps = [ + "//sandbox/win:sandbox", + ] + } else if (is_mac) { + # TODO(GYP): Make sandbox compile w/ 10.6 SDK. + if (false) { + deps = [ + "//sandbox/mac:sandbox", + ] + } + } else if (is_linux || is_android) { + deps = [ + "//sandbox/linux:sandbox", + ] + } +} diff --git a/sandbox/OWNERS b/sandbox/OWNERS new file mode 100644 index 0000000000..5d3f6ff9e1 --- /dev/null +++ b/sandbox/OWNERS @@ -0,0 +1,3 @@ +cpu@chromium.org +jln@chromium.org +jschuh@chromium.org diff --git a/sandbox/linux/BUILD.gn b/sandbox/linux/BUILD.gn new file mode 100644 index 0000000000..a1a77204f5 --- /dev/null +++ b/sandbox/linux/BUILD.gn @@ -0,0 +1,382 @@ +# 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") +import("//testing/test.gni") + +declare_args() { + compile_suid_client = is_linux + + compile_credentials = is_linux + + # On Android, use plain GTest. + use_base_test_suite = is_linux +} + +# 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") { + testonly = true + 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" ] + } + + if (use_base_test_suite) { + deps += [ "//base/test:test_support" ] + defines = [ "SANDBOX_USES_BASE_TEST_SUITE" ] + } +} + +# Sources shared by sandbox_linux_unittests and sandbox_linux_jni_unittests. +source_set("sandbox_linux_unittests_sources") { + testonly = true + + sources = [ + "services/proc_util_unittest.cc", + "services/resource_limits_unittests.cc", + "services/scoped_process_unittest.cc", + "services/syscall_wrappers_unittest.cc", + "services/thread_helpers_unittests.cc", + "services/yama_unittests.cc", + "syscall_broker/broker_file_permission_unittest.cc", + "syscall_broker/broker_process_unittest.cc", + "tests/main.cc", + "tests/scoped_temporary_file.cc", + "tests/scoped_temporary_file.h", + "tests/scoped_temporary_file_unittest.cc", + "tests/test_utils_unittest.cc", + "tests/unit_tests_unittest.cc", + ] + + deps = [ + ":sandbox", + ":sandbox_linux_test_utils", + "//base", + "//testing/gtest", + ] + + if (use_base_test_suite) { + deps += [ "//base/test:test_support" ] + defines = [ "SANDBOX_USES_BASE_TEST_SUITE" ] + } + + if (is_linux) { + # Don't use this on Android. + libs = [ "rt" ] + } + + if (compile_suid_client) { + sources += [ + "suid/client/setuid_sandbox_client_unittest.cc", + "suid/client/setuid_sandbox_host_unittest.cc", + ] + } + if (use_seccomp_bpf) { + sources += [ + "bpf_dsl/bpf_dsl_unittest.cc", + "bpf_dsl/codegen_unittest.cc", + "bpf_dsl/cons_unittest.cc", + "bpf_dsl/syscall_set_unittest.cc", + "integration_tests/bpf_dsl_seccomp_unittest.cc", + "integration_tests/seccomp_broker_process_unittest.cc", + "seccomp-bpf-helpers/baseline_policy_unittest.cc", + "seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc", + "seccomp-bpf/bpf_tests_unittest.cc", + "seccomp-bpf/errorcode_unittest.cc", + "seccomp-bpf/sandbox_bpf_unittest.cc", + "seccomp-bpf/syscall_unittest.cc", + "seccomp-bpf/trap_unittest.cc", + ] + } + if (compile_credentials) { + sources += [ + "integration_tests/namespace_unix_domain_socket_unittest.cc", + "services/credentials_unittest.cc", + "services/namespace_utils_unittest.cc", + ] + + if (use_base_test_suite) { + # Tests that use advanced features not available in stock GTest. + sources += [ "services/namespace_sandbox_unittest.cc" ] + } + + # For credentials_unittest.cc + configs += [ "//build/config/linux:libcap" ] + } +} + +# The main sandboxing test target. +test("sandbox_linux_unittests") { + deps = [ + ":sandbox_linux_unittests_sources", + ] +} + +# This target is the shared library used by Android APK (i.e. +# JNI-friendly) tests. +shared_library("sandbox_linux_jni_unittests") { + testonly = true + deps = [ + ":sandbox_linux_unittests_sources", + ] + if (is_android) { + deps += [ "//testing/android/native_test:native_test_native_code" ] + } +} + +component("seccomp_bpf") { + sources = [ + "bpf_dsl/bpf_dsl.cc", + "bpf_dsl/bpf_dsl.h", + "bpf_dsl/bpf_dsl_forward.h", + "bpf_dsl/bpf_dsl_impl.h", + "bpf_dsl/codegen.cc", + "bpf_dsl/codegen.h", + "bpf_dsl/cons.h", + "bpf_dsl/dump_bpf.cc", + "bpf_dsl/dump_bpf.h", + "bpf_dsl/linux_syscall_ranges.h", + "bpf_dsl/policy.cc", + "bpf_dsl/policy.h", + "bpf_dsl/policy_compiler.cc", + "bpf_dsl/policy_compiler.h", + "bpf_dsl/seccomp_macros.h", + "bpf_dsl/syscall_set.cc", + "bpf_dsl/syscall_set.h", + "bpf_dsl/trap_registry.h", + "bpf_dsl/verifier.cc", + "bpf_dsl/verifier.h", + "seccomp-bpf/die.cc", + "seccomp-bpf/die.h", + "seccomp-bpf/errorcode.cc", + "seccomp-bpf/errorcode.h", + "seccomp-bpf/sandbox_bpf.cc", + "seccomp-bpf/sandbox_bpf.h", + "seccomp-bpf/syscall.cc", + "seccomp-bpf/syscall.h", + "seccomp-bpf/trap.cc", + "seccomp-bpf/trap.h", + ] + defines = [ "SANDBOX_IMPLEMENTATION" ] + + deps = [ + ":sandbox_services", + ":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", + ":sandbox_services", + ":seccomp_bpf", + ] +} + +if (is_linux) { + # The setuid sandbox for Linux. + executable("chrome_sandbox") { + sources = [ + "suid/common/sandbox.h", + "suid/common/suid_unsafe_environment_variables.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/init_process_reaper.cc", + "services/init_process_reaper.h", + "services/proc_util.cc", + "services/proc_util.h", + "services/resource_limits.cc", + "services/resource_limits.h", + "services/scoped_process.cc", + "services/scoped_process.h", + "services/syscall_wrappers.cc", + "services/syscall_wrappers.h", + "services/thread_helpers.cc", + "services/thread_helpers.h", + "services/yama.cc", + "services/yama.h", + "syscall_broker/broker_channel.cc", + "syscall_broker/broker_channel.h", + "syscall_broker/broker_client.cc", + "syscall_broker/broker_client.h", + "syscall_broker/broker_common.h", + "syscall_broker/broker_file_permission.cc", + "syscall_broker/broker_file_permission.h", + "syscall_broker/broker_host.cc", + "syscall_broker/broker_host.h", + "syscall_broker/broker_policy.cc", + "syscall_broker/broker_policy.h", + "syscall_broker/broker_process.cc", + "syscall_broker/broker_process.h", + ] + + defines = [ "SANDBOX_IMPLEMENTATION" ] + + deps = [ + "//base", + ] + + if (compile_credentials) { + sources += [ + "services/credentials.cc", + "services/credentials.h", + "services/namespace_sandbox.cc", + "services/namespace_sandbox.h", + "services/namespace_utils.cc", + "services/namespace_utils.h", + ] + + deps += [ ":sandbox_services_headers" ] + } +} + +source_set("sandbox_services_headers") { + sources = [ + "system_headers/arm64_linux_syscalls.h", + "system_headers/arm64_linux_ucontext.h", + "system_headers/arm_linux_syscalls.h", + "system_headers/arm_linux_ucontext.h", + "system_headers/i386_linux_ucontext.h", + "system_headers/linux_futex.h", + "system_headers/linux_seccomp.h", + "system_headers/linux_signal.h", + "system_headers/linux_syscalls.h", + "system_headers/linux_time.h", + "system_headers/linux_ucontext.h", + "system_headers/x86_32_linux_syscalls.h", + "system_headers/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", + ] +} + +if (compile_suid_client) { + component("suid_sandbox_client") { + sources = [ + "suid/client/setuid_sandbox_client.cc", + "suid/client/setuid_sandbox_client.h", + "suid/client/setuid_sandbox_host.cc", + "suid/client/setuid_sandbox_host.h", + "suid/common/sandbox.h", + "suid/common/suid_unsafe_environment_variables.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/sandbox/linux/DEPS b/sandbox/linux/DEPS new file mode 100644 index 0000000000..3912859344 --- /dev/null +++ b/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/sandbox/linux/OWNERS b/sandbox/linux/OWNERS new file mode 100644 index 0000000000..f39e96736d --- /dev/null +++ b/sandbox/linux/OWNERS @@ -0,0 +1,3 @@ +jln@chromium.org +jorgelo@chromium.org +mdempsky@chromium.org diff --git a/sandbox/linux/bpf_dsl/DEPS b/sandbox/linux/bpf_dsl/DEPS new file mode 100644 index 0000000000..be37a129c2 --- /dev/null +++ b/sandbox/linux/bpf_dsl/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + # TODO(mdempsky): Eliminate cyclic dependency on seccomp-bpf. + "+sandbox/linux/seccomp-bpf", + "+sandbox/linux/system_headers", +] diff --git a/sandbox/linux/bpf_dsl/bpf_dsl.cc b/sandbox/linux/bpf_dsl/bpf_dsl.cc new file mode 100644 index 0000000000..3a35903ec9 --- /dev/null +++ b/sandbox/linux/bpf_dsl/bpf_dsl.cc @@ -0,0 +1,363 @@ +// 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/bpf_dsl/bpf_dsl.h" + +#include <limits> + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" + +namespace sandbox { +namespace bpf_dsl { +namespace { + +intptr_t BPFFailure(const struct arch_seccomp_data&, void* aux) { + SANDBOX_DIE(static_cast<char*>(aux)); +} + +class AllowResultExprImpl : public internal::ResultExprImpl { + public: + AllowResultExprImpl() {} + + ErrorCode Compile(PolicyCompiler* pc) const override { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } + + bool IsAllow() const override { return true; } + + private: + ~AllowResultExprImpl() override {} + + DISALLOW_COPY_AND_ASSIGN(AllowResultExprImpl); +}; + +class ErrorResultExprImpl : public internal::ResultExprImpl { + public: + explicit ErrorResultExprImpl(int err) : err_(err) { + CHECK(err_ >= ErrorCode::ERR_MIN_ERRNO && err_ <= ErrorCode::ERR_MAX_ERRNO); + } + + ErrorCode Compile(PolicyCompiler* pc) const override { + return pc->Error(err_); + } + + bool IsDeny() const override { return true; } + + private: + ~ErrorResultExprImpl() override {} + + int err_; + + DISALLOW_COPY_AND_ASSIGN(ErrorResultExprImpl); +}; + +class TraceResultExprImpl : public internal::ResultExprImpl { + public: + TraceResultExprImpl(uint16_t aux) : aux_(aux) {} + + ErrorCode Compile(PolicyCompiler* pc) const override { + return ErrorCode(ErrorCode::ERR_TRACE + aux_); + } + + private: + ~TraceResultExprImpl() override {} + + uint16_t aux_; + + DISALLOW_COPY_AND_ASSIGN(TraceResultExprImpl); +}; + +class TrapResultExprImpl : public internal::ResultExprImpl { + public: + TrapResultExprImpl(TrapRegistry::TrapFnc func, const void* arg, bool safe) + : func_(func), arg_(arg), safe_(safe) { + DCHECK(func_); + } + + ErrorCode Compile(PolicyCompiler* pc) const override { + return pc->Trap(func_, arg_, safe_); + } + + bool HasUnsafeTraps() const override { return safe_ == false; } + + bool IsDeny() const override { return true; } + + private: + ~TrapResultExprImpl() override {} + + TrapRegistry::TrapFnc func_; + const void* arg_; + bool safe_; + + DISALLOW_COPY_AND_ASSIGN(TrapResultExprImpl); +}; + +class IfThenResultExprImpl : public internal::ResultExprImpl { + public: + IfThenResultExprImpl(const BoolExpr& cond, + const ResultExpr& then_result, + const ResultExpr& else_result) + : cond_(cond), then_result_(then_result), else_result_(else_result) {} + + ErrorCode Compile(PolicyCompiler* pc) const override { + return cond_->Compile( + pc, then_result_->Compile(pc), else_result_->Compile(pc)); + } + + bool HasUnsafeTraps() const override { + return then_result_->HasUnsafeTraps() || else_result_->HasUnsafeTraps(); + } + + private: + ~IfThenResultExprImpl() override {} + + BoolExpr cond_; + ResultExpr then_result_; + ResultExpr else_result_; + + DISALLOW_COPY_AND_ASSIGN(IfThenResultExprImpl); +}; + +class ConstBoolExprImpl : public internal::BoolExprImpl { + public: + ConstBoolExprImpl(bool value) : value_(value) {} + + ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const override { + return value_ ? true_ec : false_ec; + } + + private: + ~ConstBoolExprImpl() override {} + + bool value_; + + DISALLOW_COPY_AND_ASSIGN(ConstBoolExprImpl); +}; + +class PrimitiveBoolExprImpl : public internal::BoolExprImpl { + public: + PrimitiveBoolExprImpl(int argno, + ErrorCode::ArgType is_32bit, + uint64_t mask, + uint64_t value) + : argno_(argno), is_32bit_(is_32bit), mask_(mask), value_(value) {} + + ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const override { + return pc->CondMaskedEqual( + argno_, is_32bit_, mask_, value_, true_ec, false_ec); + } + + private: + ~PrimitiveBoolExprImpl() override {} + + int argno_; + ErrorCode::ArgType is_32bit_; + uint64_t mask_; + uint64_t value_; + + DISALLOW_COPY_AND_ASSIGN(PrimitiveBoolExprImpl); +}; + +class NegateBoolExprImpl : public internal::BoolExprImpl { + public: + explicit NegateBoolExprImpl(const BoolExpr& cond) : cond_(cond) {} + + ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const override { + return cond_->Compile(pc, false_ec, true_ec); + } + + private: + ~NegateBoolExprImpl() override {} + + BoolExpr cond_; + + DISALLOW_COPY_AND_ASSIGN(NegateBoolExprImpl); +}; + +class AndBoolExprImpl : public internal::BoolExprImpl { + public: + AndBoolExprImpl(const BoolExpr& lhs, const BoolExpr& rhs) + : lhs_(lhs), rhs_(rhs) {} + + ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const override { + return lhs_->Compile(pc, rhs_->Compile(pc, true_ec, false_ec), false_ec); + } + + private: + ~AndBoolExprImpl() override {} + + BoolExpr lhs_; + BoolExpr rhs_; + + DISALLOW_COPY_AND_ASSIGN(AndBoolExprImpl); +}; + +class OrBoolExprImpl : public internal::BoolExprImpl { + public: + OrBoolExprImpl(const BoolExpr& lhs, const BoolExpr& rhs) + : lhs_(lhs), rhs_(rhs) {} + + ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const override { + return lhs_->Compile(pc, true_ec, rhs_->Compile(pc, true_ec, false_ec)); + } + + private: + ~OrBoolExprImpl() override {} + + BoolExpr lhs_; + BoolExpr rhs_; + + DISALLOW_COPY_AND_ASSIGN(OrBoolExprImpl); +}; + +} // namespace + +namespace internal { + +bool ResultExprImpl::HasUnsafeTraps() const { + return false; +} + +bool ResultExprImpl::IsAllow() const { + return false; +} + +bool ResultExprImpl::IsDeny() const { + return false; +} + +uint64_t DefaultMask(size_t size) { + switch (size) { + case 4: + return std::numeric_limits<uint32_t>::max(); + case 8: + return std::numeric_limits<uint64_t>::max(); + default: + CHECK(false) << "Unimplemented DefaultMask case"; + return 0; + } +} + +BoolExpr ArgEq(int num, size_t size, uint64_t mask, uint64_t val) { + CHECK(size == 4 || size == 8); + + // TODO(mdempsky): Should we just always use TP_64BIT? + const ErrorCode::ArgType arg_type = + (size == 4) ? ErrorCode::TP_32BIT : ErrorCode::TP_64BIT; + + return BoolExpr(new const PrimitiveBoolExprImpl(num, arg_type, mask, val)); +} + +} // namespace internal + +ResultExpr Allow() { + return ResultExpr(new const AllowResultExprImpl()); +} + +ResultExpr Error(int err) { + return ResultExpr(new const ErrorResultExprImpl(err)); +} + +ResultExpr Kill(const char* msg) { + return Trap(BPFFailure, msg); +} + +ResultExpr Trace(uint16_t aux) { + return ResultExpr(new const TraceResultExprImpl(aux)); +} + +ResultExpr Trap(TrapRegistry::TrapFnc trap_func, const void* aux) { + return ResultExpr( + new const TrapResultExprImpl(trap_func, aux, true /* safe */)); +} + +ResultExpr UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux) { + return ResultExpr( + new const TrapResultExprImpl(trap_func, aux, false /* unsafe */)); +} + +BoolExpr BoolConst(bool value) { + return BoolExpr(new const ConstBoolExprImpl(value)); +} + +BoolExpr operator!(const BoolExpr& cond) { + return BoolExpr(new const NegateBoolExprImpl(cond)); +} + +BoolExpr operator&&(const BoolExpr& lhs, const BoolExpr& rhs) { + return BoolExpr(new const AndBoolExprImpl(lhs, rhs)); +} + +BoolExpr operator||(const BoolExpr& lhs, const BoolExpr& rhs) { + return BoolExpr(new const OrBoolExprImpl(lhs, rhs)); +} + +Elser If(const BoolExpr& cond, const ResultExpr& then_result) { + return Elser(nullptr).ElseIf(cond, then_result); +} + +Elser::Elser(cons::List<Clause> clause_list) : clause_list_(clause_list) { +} + +Elser::Elser(const Elser& elser) : clause_list_(elser.clause_list_) { +} + +Elser::~Elser() { +} + +Elser Elser::ElseIf(const BoolExpr& cond, const ResultExpr& then_result) const { + return Elser(Cons(std::make_pair(cond, then_result), clause_list_)); +} + +ResultExpr Elser::Else(const ResultExpr& else_result) const { + // We finally have the default result expression for this + // if/then/else sequence. Also, we've already accumulated all + // if/then pairs into a list of reverse order (i.e., lower priority + // conditions are listed before higher priority ones). E.g., an + // expression like + // + // If(b1, e1).ElseIf(b2, e2).ElseIf(b3, e3).Else(e4) + // + // will have built up a list like + // + // [(b3, e3), (b2, e2), (b1, e1)]. + // + // Now that we have e4, we can walk the list and create a ResultExpr + // tree like: + // + // expr = e4 + // expr = (b3 ? e3 : expr) = (b3 ? e3 : e4) + // expr = (b2 ? e2 : expr) = (b2 ? e2 : (b3 ? e3 : e4)) + // expr = (b1 ? e1 : expr) = (b1 ? e1 : (b2 ? e2 : (b3 ? e3 : e4))) + // + // and end up with an appropriately chained tree. + + ResultExpr expr = else_result; + for (const Clause& clause : clause_list_) { + expr = ResultExpr( + new const IfThenResultExprImpl(clause.first, clause.second, expr)); + } + return expr; +} + +} // namespace bpf_dsl +} // namespace sandbox + +template class scoped_refptr<const sandbox::bpf_dsl::internal::BoolExprImpl>; +template class scoped_refptr<const sandbox::bpf_dsl::internal::ResultExprImpl>; diff --git a/sandbox/linux/bpf_dsl/bpf_dsl.h b/sandbox/linux/bpf_dsl/bpf_dsl.h new file mode 100644 index 0000000000..365e9b5466 --- /dev/null +++ b/sandbox/linux/bpf_dsl/bpf_dsl.h @@ -0,0 +1,317 @@ +// 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_BPF_DSL_BPF_DSL_H_ +#define SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ + +#include <stdint.h> + +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/linux/bpf_dsl/cons.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/sandbox_export.h" + +// The sandbox::bpf_dsl namespace provides a domain-specific language +// to make writing BPF policies more expressive. In general, the +// object types all have value semantics (i.e., they can be copied +// around, returned from or passed to function calls, etc. without any +// surprising side effects), though not all support assignment. +// +// An idiomatic and demonstrative (albeit silly) example of this API +// would be: +// +// #include "sandbox/linux/bpf_dsl/bpf_dsl.h" +// +// using namespace sandbox::bpf_dsl; +// +// class SillyPolicy : public Policy { +// public: +// SillyPolicy() {} +// ~SillyPolicy() override {} +// ResultExpr EvaluateSyscall(int sysno) const override { +// if (sysno == __NR_fcntl) { +// Arg<int> fd(0), cmd(1); +// Arg<unsigned long> flags(2); +// const uint64_t kGoodFlags = O_ACCMODE | O_NONBLOCK; +// return If(fd == 0 && cmd == F_SETFL && (flags & ~kGoodFlags) == 0, +// Allow()) +// .ElseIf(cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC, +// Error(EMFILE)) +// .Else(Trap(SetFlagHandler, NULL)); +// } else { +// return Allow(); +// } +// } +// +// private: +// DISALLOW_COPY_AND_ASSIGN(SillyPolicy); +// }; +// +// More generally, the DSL currently supports the following grammar: +// +// result = Allow() | Error(errno) | Kill(msg) | Trace(aux) +// | Trap(trap_func, aux) | UnsafeTrap(trap_func, aux) +// | If(bool, result)[.ElseIf(bool, result)].Else(result) +// | Switch(arg)[.Case(val, result)].Default(result) +// bool = BoolConst(boolean) | !bool | bool && bool | bool || bool +// | arg == val | arg != val +// arg = Arg<T>(num) | arg & mask +// +// The semantics of each function and operator are intended to be +// intuitive, but are described in more detail below. +// +// (Credit to Sean Parent's "Inheritance is the Base Class of Evil" +// talk at Going Native 2013 for promoting value semantics via shared +// pointers to immutable state.) + +namespace sandbox { +namespace bpf_dsl { + +// ResultExpr is an opaque reference to an immutable result expression tree. +typedef scoped_refptr<const internal::ResultExprImpl> ResultExpr; + +// BoolExpr is an opaque reference to an immutable boolean expression tree. +typedef scoped_refptr<const internal::BoolExprImpl> BoolExpr; + +// Allow specifies a result that the system call should be allowed to +// execute normally. +SANDBOX_EXPORT ResultExpr Allow(); + +// Error specifies a result that the system call should fail with +// error number |err|. As a special case, Error(0) will result in the +// system call appearing to have succeeded, but without having any +// side effects. +SANDBOX_EXPORT ResultExpr Error(int err); + +// Kill specifies a result to kill the program and print an error message. +SANDBOX_EXPORT ResultExpr Kill(const char* msg); + +// Trace specifies a result to notify a tracing process via the +// PTRACE_EVENT_SECCOMP event and allow it to change or skip the system call. +// The value of |aux| will be available to the tracer via PTRACE_GETEVENTMSG. +SANDBOX_EXPORT ResultExpr Trace(uint16_t aux); + +// Trap specifies a result that the system call should be handled by +// trapping back into userspace and invoking |trap_func|, passing +// |aux| as the second parameter. +SANDBOX_EXPORT ResultExpr + Trap(TrapRegistry::TrapFnc trap_func, const void* aux); + +// UnsafeTrap is like Trap, except the policy is marked as "unsafe" +// and allowed to use SandboxSyscall to invoke any system call. +// +// NOTE: This feature, by definition, disables all security features of +// the sandbox. It should never be used in production, but it can be +// very useful to diagnose code that is incompatible with the sandbox. +// If even a single system call returns "UnsafeTrap", the security of +// entire sandbox should be considered compromised. +SANDBOX_EXPORT ResultExpr + UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux); + +// BoolConst converts a bool value into a BoolExpr. +SANDBOX_EXPORT BoolExpr BoolConst(bool value); + +// Various ways to combine boolean expressions into more complex expressions. +// They follow standard boolean algebra laws. +SANDBOX_EXPORT BoolExpr operator!(const BoolExpr& cond); +SANDBOX_EXPORT BoolExpr operator&&(const BoolExpr& lhs, const BoolExpr& rhs); +SANDBOX_EXPORT BoolExpr operator||(const BoolExpr& lhs, const BoolExpr& rhs); + +template <typename T> +class SANDBOX_EXPORT Arg { + public: + // Initializes the Arg to represent the |num|th system call + // argument (indexed from 0), which is of type |T|. + explicit Arg(int num); + + Arg(const Arg& arg) : num_(arg.num_), mask_(arg.mask_) {} + + // Returns an Arg representing the current argument, but after + // bitwise-and'ing it with |rhs|. + friend Arg operator&(const Arg& lhs, uint64_t rhs) { + return Arg(lhs.num_, lhs.mask_ & rhs); + } + + // Returns a boolean expression comparing whether the system call argument + // (after applying any bitmasks, if appropriate) equals |rhs|. + friend BoolExpr operator==(const Arg& lhs, T rhs) { return lhs.EqualTo(rhs); } + + // Returns a boolean expression comparing whether the system call argument + // (after applying any bitmasks, if appropriate) does not equal |rhs|. + friend BoolExpr operator!=(const Arg& lhs, T rhs) { return !(lhs == rhs); } + + private: + Arg(int num, uint64_t mask) : num_(num), mask_(mask) {} + + BoolExpr EqualTo(T val) const; + + int num_; + uint64_t mask_; + + DISALLOW_ASSIGN(Arg); +}; + +// If begins a conditional result expression predicated on the +// specified boolean expression. +SANDBOX_EXPORT Elser If(const BoolExpr& cond, const ResultExpr& then_result); + +class SANDBOX_EXPORT Elser { + public: + Elser(const Elser& elser); + ~Elser(); + + // ElseIf extends the conditional result expression with another + // "if then" clause, predicated on the specified boolean expression. + Elser ElseIf(const BoolExpr& cond, const ResultExpr& then_result) const; + + // Else terminates a conditional result expression using |else_result| as + // the default fallback result expression. + ResultExpr Else(const ResultExpr& else_result) const; + + private: + typedef std::pair<BoolExpr, ResultExpr> Clause; + + explicit Elser(cons::List<Clause> clause_list); + + cons::List<Clause> clause_list_; + + friend Elser If(const BoolExpr&, const ResultExpr&); + template <typename T> + friend Caser<T> Switch(const Arg<T>&); + DISALLOW_ASSIGN(Elser); +}; + +// Switch begins a switch expression dispatched according to the +// specified argument value. +template <typename T> +SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg); + +template <typename T> +class SANDBOX_EXPORT Caser { + public: + Caser(const Caser<T>& caser) : arg_(caser.arg_), elser_(caser.elser_) {} + ~Caser() {} + + // Case adds a single-value "case" clause to the switch. + Caser<T> Case(T value, ResultExpr result) const; + + // Cases adds a multiple-value "case" clause to the switch. + // See also the SANDBOX_BPF_DSL_CASES macro below for a more idiomatic way + // of using this function. + Caser<T> Cases(const std::vector<T>& values, ResultExpr result) const; + + // Terminate the switch with a "default" clause. + ResultExpr Default(ResultExpr result) const; + + private: + Caser(const Arg<T>& arg, Elser elser) : arg_(arg), elser_(elser) {} + + Arg<T> arg_; + Elser elser_; + + template <typename U> + friend Caser<U> Switch(const Arg<U>&); + DISALLOW_ASSIGN(Caser); +}; + +// Recommended usage is to put +// #define CASES SANDBOX_BPF_DSL_CASES +// near the top of the .cc file (e.g., nearby any "using" statements), then +// use like: +// Switch(arg).CASES((3, 5, 7), result)...; +#define SANDBOX_BPF_DSL_CASES(values, result) \ + Cases(SANDBOX_BPF_DSL_CASES_HELPER values, result) + +// Helper macro to construct a std::vector from an initializer list. +// TODO(mdempsky): Convert to use C++11 initializer lists instead. +#define SANDBOX_BPF_DSL_CASES_HELPER(value, ...) \ + ({ \ + const __typeof__(value) bpf_dsl_cases_values[] = {value, __VA_ARGS__}; \ + std::vector<__typeof__(value)>( \ + bpf_dsl_cases_values, \ + bpf_dsl_cases_values + arraysize(bpf_dsl_cases_values)); \ + }) + +// ===================================================================== +// Official API ends here. +// ===================================================================== + +namespace internal { + +// Make argument-dependent lookup work. This is necessary because although +// BoolExpr is defined in bpf_dsl, since it's merely a typedef for +// scoped_refptr<const internal::BoolExplImpl>, argument-dependent lookup only +// searches the "internal" nested namespace. +using bpf_dsl::operator!; +using bpf_dsl::operator||; +using bpf_dsl::operator&&; + +// Returns a boolean expression that represents whether system call +// argument |num| of size |size| is equal to |val|, when masked +// according to |mask|. Users should use the Arg template class below +// instead of using this API directly. +SANDBOX_EXPORT BoolExpr + ArgEq(int num, size_t size, uint64_t mask, uint64_t val); + +// Returns the default mask for a system call argument of the specified size. +SANDBOX_EXPORT uint64_t DefaultMask(size_t size); + +} // namespace internal + +template <typename T> +Arg<T>::Arg(int num) + : num_(num), mask_(internal::DefaultMask(sizeof(T))) { +} + +// Definition requires ArgEq to have been declared. Moved out-of-line +// to minimize how much internal clutter users have to ignore while +// reading the header documentation. +// +// Additionally, we use this helper member function to avoid linker errors +// caused by defining operator== out-of-line. For a more detailed explanation, +// see http://www.parashift.com/c++-faq-lite/template-friends.html. +template <typename T> +BoolExpr Arg<T>::EqualTo(T val) const { + return internal::ArgEq(num_, sizeof(T), mask_, static_cast<uint64_t>(val)); +} + +template <typename T> +SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg) { + return Caser<T>(arg, Elser(nullptr)); +} + +template <typename T> +Caser<T> Caser<T>::Case(T value, ResultExpr result) const { + return SANDBOX_BPF_DSL_CASES((value), result); +} + +template <typename T> +Caser<T> Caser<T>::Cases(const std::vector<T>& values, + ResultExpr result) const { + // Theoretically we could evaluate arg_ just once and emit a more efficient + // dispatch table, but for now we simply translate into an equivalent + // If/ElseIf/Else chain. + + typedef typename std::vector<T>::const_iterator Iter; + BoolExpr test = BoolConst(false); + for (Iter i = values.begin(), end = values.end(); i != end; ++i) { + test = test || (arg_ == *i); + } + return Caser<T>(arg_, elser_.ElseIf(test, result)); +} + +template <typename T> +ResultExpr Caser<T>::Default(ResultExpr result) const { + return elser_.Else(result); +} + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ diff --git a/sandbox/linux/bpf_dsl/bpf_dsl_forward.h b/sandbox/linux/bpf_dsl/bpf_dsl_forward.h new file mode 100644 index 0000000000..183038990a --- /dev/null +++ b/sandbox/linux/bpf_dsl/bpf_dsl_forward.h @@ -0,0 +1,42 @@ +// 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_BPF_DSL_BPF_DSL_FORWARD_H_ +#define SANDBOX_LINUX_BPF_DSL_BPF_DSL_FORWARD_H_ + +#include "base/memory/ref_counted.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { + +// The bpf_dsl_forward.h header provides forward declarations for the +// types defined in bpf_dsl.h. It's intended for use in user headers +// that need to reference bpf_dsl types, but don't require definitions. + +namespace internal { +class ResultExprImpl; +class BoolExprImpl; +} + +typedef scoped_refptr<const internal::ResultExprImpl> ResultExpr; +typedef scoped_refptr<const internal::BoolExprImpl> BoolExpr; + +template <typename T> +class Arg; + +class Elser; + +template <typename T> +class Caser; + +} // namespace bpf_dsl +} // namespace sandbox + +extern template class SANDBOX_EXPORT + scoped_refptr<const sandbox::bpf_dsl::internal::BoolExprImpl>; +extern template class SANDBOX_EXPORT + scoped_refptr<const sandbox::bpf_dsl::internal::ResultExprImpl>; + +#endif // SANDBOX_LINUX_BPF_DSL_BPF_DSL_FORWARD_H_ diff --git a/sandbox/linux/bpf_dsl/bpf_dsl_impl.h b/sandbox/linux/bpf_dsl/bpf_dsl_impl.h new file mode 100644 index 0000000000..2ffaf79c2f --- /dev/null +++ b/sandbox/linux/bpf_dsl/bpf_dsl_impl.h @@ -0,0 +1,69 @@ +// 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_BPF_DSL_BPF_DSL_IMPL_H_ +#define SANDBOX_LINUX_BPF_DSL_BPF_DSL_IMPL_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +class ErrorCode; + +namespace bpf_dsl { +class PolicyCompiler; + +namespace internal { + +// Internal interface implemented by BoolExpr implementations. +class BoolExprImpl : public base::RefCounted<BoolExprImpl> { + public: + // Compile uses |pc| to construct an ErrorCode that conditionally continues + // to either |true_ec| or |false_ec|, depending on whether the represented + // boolean expression is true or false. + virtual ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const = 0; + + protected: + BoolExprImpl() {} + virtual ~BoolExprImpl() {} + + private: + friend class base::RefCounted<BoolExprImpl>; + DISALLOW_COPY_AND_ASSIGN(BoolExprImpl); +}; + +// Internal interface implemented by ResultExpr implementations. +class ResultExprImpl : public base::RefCounted<ResultExprImpl> { + public: + // Compile uses |pc| to construct an ErrorCode analogous to the represented + // result expression. + virtual ErrorCode Compile(PolicyCompiler* pc) const = 0; + + // HasUnsafeTraps returns whether the result expression is or recursively + // contains an unsafe trap expression. + virtual bool HasUnsafeTraps() const; + + // IsAllow returns whether the result expression is an "allow" result. + virtual bool IsAllow() const; + + // IsAllow returns whether the result expression is a "deny" result. + virtual bool IsDeny() const; + + protected: + ResultExprImpl() {} + virtual ~ResultExprImpl() {} + + private: + friend class base::RefCounted<ResultExprImpl>; + DISALLOW_COPY_AND_ASSIGN(ResultExprImpl); +}; + +} // namespace internal +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_BPF_DSL_IMPL_H_ diff --git a/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc b/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc new file mode 100644 index 0000000000..398ec59ef1 --- /dev/null +++ b/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc @@ -0,0 +1,486 @@ +// 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/bpf_dsl/bpf_dsl.h" + +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <sys/utsname.h> +#include <unistd.h> + +#include <map> +#include <utility> + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/linux/bpf_dsl/verifier.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define CASES SANDBOX_BPF_DSL_CASES + +namespace sandbox { +namespace bpf_dsl { +namespace { + +// Helper function to construct fake arch_seccomp_data objects. +struct arch_seccomp_data FakeSyscall(int nr, + uint64_t p0 = 0, + uint64_t p1 = 0, + uint64_t p2 = 0, + uint64_t p3 = 0, + uint64_t p4 = 0, + uint64_t p5 = 0) { + // Made up program counter for syscall address. + const uint64_t kFakePC = 0x543210; + + struct arch_seccomp_data data = { + nr, + SECCOMP_ARCH, + kFakePC, + { + p0, p1, p2, p3, p4, p5, + }, + }; + + return data; +} + +class FakeTrapRegistry : public TrapRegistry { + public: + FakeTrapRegistry() : map_() {} + virtual ~FakeTrapRegistry() {} + + uint16_t Add(TrapFnc fnc, const void* aux, bool safe) override { + EXPECT_TRUE(safe); + + const uint16_t next_id = map_.size() + 1; + return map_.insert(std::make_pair(Key(fnc, aux), next_id)).first->second; + } + + bool EnableUnsafeTraps() override { + ADD_FAILURE() << "Unimplemented"; + return false; + } + + private: + using Key = std::pair<TrapFnc, const void*>; + + std::map<Key, uint16_t> map_; + + DISALLOW_COPY_AND_ASSIGN(FakeTrapRegistry); +}; + +intptr_t FakeTrapFuncOne(const arch_seccomp_data& data, void* aux) { return 1; } +intptr_t FakeTrapFuncTwo(const arch_seccomp_data& data, void* aux) { return 2; } + +// Test that FakeTrapRegistry correctly assigns trap IDs to trap handlers. +TEST(FakeTrapRegistry, TrapIDs) { + struct { + TrapRegistry::TrapFnc fnc; + const void* aux; + } funcs[] = { + {FakeTrapFuncOne, nullptr}, + {FakeTrapFuncTwo, nullptr}, + {FakeTrapFuncOne, funcs}, + {FakeTrapFuncTwo, funcs}, + }; + + FakeTrapRegistry traps; + + // Add traps twice to test that IDs are reused correctly. + for (int i = 0; i < 2; ++i) { + for (size_t j = 0; j < arraysize(funcs); ++j) { + // Trap IDs start at 1. + EXPECT_EQ(j + 1, traps.Add(funcs[j].fnc, funcs[j].aux, true)); + } + } +} + +class PolicyEmulator { + public: + explicit PolicyEmulator(const Policy* policy) : program_(), traps_() { + program_ = *PolicyCompiler(policy, &traps_).Compile(true /* verify */); + } + ~PolicyEmulator() {} + + uint32_t Emulate(const struct arch_seccomp_data& data) const { + const char* err = nullptr; + uint32_t res = Verifier::EvaluateBPF(program_, data, &err); + if (err) { + ADD_FAILURE() << err; + return 0; + } + return res; + } + + void ExpectAllow(const struct arch_seccomp_data& data) const { + EXPECT_EQ(SECCOMP_RET_ALLOW, Emulate(data)); + } + + void ExpectErrno(uint16_t err, const struct arch_seccomp_data& data) const { + EXPECT_EQ(SECCOMP_RET_ERRNO | err, Emulate(data)); + } + + private: + CodeGen::Program program_; + FakeTrapRegistry traps_; + + DISALLOW_COPY_AND_ASSIGN(PolicyEmulator); +}; + +class BasicPolicy : public Policy { + public: + BasicPolicy() {} + ~BasicPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_getpgid) { + const Arg<pid_t> pid(0); + return If(pid == 0, Error(EPERM)).Else(Error(EINVAL)); + } + if (sysno == __NR_setuid) { + const Arg<uid_t> uid(0); + return If(uid != 42, Error(ESRCH)).Else(Error(ENOMEM)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicPolicy); +}; + +TEST(BPFDSL, Basic) { + BasicPolicy policy; + PolicyEmulator emulator(&policy); + + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_getpgid, 0)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_getpgid, 1)); + + emulator.ExpectErrno(ENOMEM, FakeSyscall(__NR_setuid, 42)); + emulator.ExpectErrno(ESRCH, FakeSyscall(__NR_setuid, 43)); +} + +/* On IA-32, socketpair() is implemented via socketcall(). :-( */ +#if !defined(ARCH_CPU_X86) +class BooleanLogicPolicy : public Policy { + public: + BooleanLogicPolicy() {} + ~BooleanLogicPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_socketpair) { + const Arg<int> domain(0), type(1), protocol(2); + return If(domain == AF_UNIX && + (type == SOCK_STREAM || type == SOCK_DGRAM) && + protocol == 0, + Error(EPERM)).Else(Error(EINVAL)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BooleanLogicPolicy); +}; + +TEST(BPFDSL, BooleanLogic) { + BooleanLogicPolicy policy; + PolicyEmulator emulator(&policy); + + const intptr_t kFakeSV = 0x12345; + + // Acceptable combinations that should return EPERM. + emulator.ExpectErrno( + EPERM, FakeSyscall(__NR_socketpair, AF_UNIX, SOCK_STREAM, 0, kFakeSV)); + emulator.ExpectErrno( + EPERM, FakeSyscall(__NR_socketpair, AF_UNIX, SOCK_DGRAM, 0, kFakeSV)); + + // Combinations that are invalid for only one reason; should return EINVAL. + emulator.ExpectErrno( + EINVAL, FakeSyscall(__NR_socketpair, AF_INET, SOCK_STREAM, 0, kFakeSV)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_socketpair, AF_UNIX, + SOCK_SEQPACKET, 0, kFakeSV)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_socketpair, AF_UNIX, + SOCK_STREAM, IPPROTO_TCP, kFakeSV)); + + // Completely unacceptable combination; should also return EINVAL. + emulator.ExpectErrno( + EINVAL, FakeSyscall(__NR_socketpair, AF_INET, SOCK_SEQPACKET, IPPROTO_UDP, + kFakeSV)); +} +#endif // !ARCH_CPU_X86 + +class MoreBooleanLogicPolicy : public Policy { + public: + MoreBooleanLogicPolicy() {} + ~MoreBooleanLogicPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_setresuid) { + const Arg<uid_t> ruid(0), euid(1), suid(2); + return If(ruid == 0 || euid == 0 || suid == 0, Error(EPERM)) + .ElseIf(ruid == 1 && euid == 1 && suid == 1, Error(EAGAIN)) + .Else(Error(EINVAL)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MoreBooleanLogicPolicy); +}; + +TEST(BPFDSL, MoreBooleanLogic) { + MoreBooleanLogicPolicy policy; + PolicyEmulator emulator(&policy); + + // Expect EPERM if any set to 0. + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_setresuid, 0, 5, 5)); + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_setresuid, 5, 0, 5)); + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_setresuid, 5, 5, 0)); + + // Expect EAGAIN if all set to 1. + emulator.ExpectErrno(EAGAIN, FakeSyscall(__NR_setresuid, 1, 1, 1)); + + // Expect EINVAL for anything else. + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 5, 1, 1)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 1, 5, 1)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 1, 1, 5)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 3, 4, 5)); +} + +static const uintptr_t kDeadBeefAddr = + static_cast<uintptr_t>(0xdeadbeefdeadbeefULL); + +class ArgSizePolicy : public Policy { + public: + ArgSizePolicy() {} + ~ArgSizePolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_uname) { + const Arg<uintptr_t> addr(0); + return If(addr == kDeadBeefAddr, Error(EPERM)).Else(Allow()); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ArgSizePolicy); +}; + +TEST(BPFDSL, ArgSizeTest) { + ArgSizePolicy policy; + PolicyEmulator emulator(&policy); + + emulator.ExpectAllow(FakeSyscall(__NR_uname, 0)); + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_uname, kDeadBeefAddr)); +} + +#if 0 +// TODO(mdempsky): This is really an integration test. + +class TrappingPolicy : public Policy { + public: + TrappingPolicy() {} + ~TrappingPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_uname) { + return Trap(UnameTrap, &count_); + } + return Allow(); + } + + private: + static intptr_t count_; + + static intptr_t UnameTrap(const struct arch_seccomp_data& data, void* aux) { + BPF_ASSERT_EQ(&count_, aux); + return ++count_; + } + + DISALLOW_COPY_AND_ASSIGN(TrappingPolicy); +}; + +intptr_t TrappingPolicy::count_; + +BPF_TEST_C(BPFDSL, TrapTest, TrappingPolicy) { + ASSERT_SYSCALL_RESULT(1, uname, NULL); + ASSERT_SYSCALL_RESULT(2, uname, NULL); + ASSERT_SYSCALL_RESULT(3, uname, NULL); +} +#endif + +class MaskingPolicy : public Policy { + public: + MaskingPolicy() {} + ~MaskingPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_setuid) { + const Arg<uid_t> uid(0); + return If((uid & 0xf) == 0, Error(EINVAL)).Else(Error(EACCES)); + } + if (sysno == __NR_setgid) { + const Arg<gid_t> gid(0); + return If((gid & 0xf0) == 0xf0, Error(EINVAL)).Else(Error(EACCES)); + } + if (sysno == __NR_setpgid) { + const Arg<pid_t> pid(0); + return If((pid & 0xa5) == 0xa0, Error(EINVAL)).Else(Error(EACCES)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MaskingPolicy); +}; + +TEST(BPFDSL, MaskTest) { + MaskingPolicy policy; + PolicyEmulator emulator(&policy); + + for (uid_t uid = 0; uid < 0x100; ++uid) { + const int expect_errno = (uid & 0xf) == 0 ? EINVAL : EACCES; + emulator.ExpectErrno(expect_errno, FakeSyscall(__NR_setuid, uid)); + } + + for (gid_t gid = 0; gid < 0x100; ++gid) { + const int expect_errno = (gid & 0xf0) == 0xf0 ? EINVAL : EACCES; + emulator.ExpectErrno(expect_errno, FakeSyscall(__NR_setgid, gid)); + } + + for (pid_t pid = 0; pid < 0x100; ++pid) { + const int expect_errno = (pid & 0xa5) == 0xa0 ? EINVAL : EACCES; + emulator.ExpectErrno(expect_errno, FakeSyscall(__NR_setpgid, pid, 0)); + } +} + +class ElseIfPolicy : public Policy { + public: + ElseIfPolicy() {} + ~ElseIfPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_setuid) { + const Arg<uid_t> uid(0); + return If((uid & 0xfff) == 0, Error(0)) + .ElseIf((uid & 0xff0) == 0, Error(EINVAL)) + .ElseIf((uid & 0xf00) == 0, Error(EEXIST)) + .Else(Error(EACCES)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ElseIfPolicy); +}; + +TEST(BPFDSL, ElseIfTest) { + ElseIfPolicy policy; + PolicyEmulator emulator(&policy); + + emulator.ExpectErrno(0, FakeSyscall(__NR_setuid, 0)); + + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setuid, 0x0001)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setuid, 0x0002)); + + emulator.ExpectErrno(EEXIST, FakeSyscall(__NR_setuid, 0x0011)); + emulator.ExpectErrno(EEXIST, FakeSyscall(__NR_setuid, 0x0022)); + + emulator.ExpectErrno(EACCES, FakeSyscall(__NR_setuid, 0x0111)); + emulator.ExpectErrno(EACCES, FakeSyscall(__NR_setuid, 0x0222)); +} + +class SwitchPolicy : public Policy { + public: + SwitchPolicy() {} + ~SwitchPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_fcntl) { + const Arg<int> cmd(1); + const Arg<unsigned long> long_arg(2); + return Switch(cmd) + .CASES((F_GETFL, F_GETFD), Error(ENOENT)) + .Case(F_SETFD, If(long_arg == O_CLOEXEC, Allow()).Else(Error(EINVAL))) + .Case(F_SETFL, Error(EPERM)) + .Default(Error(EACCES)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(SwitchPolicy); +}; + +TEST(BPFDSL, SwitchTest) { + SwitchPolicy policy; + PolicyEmulator emulator(&policy); + + const int kFakeSockFD = 42; + + emulator.ExpectErrno(ENOENT, FakeSyscall(__NR_fcntl, kFakeSockFD, F_GETFD)); + emulator.ExpectErrno(ENOENT, FakeSyscall(__NR_fcntl, kFakeSockFD, F_GETFL)); + + emulator.ExpectAllow( + FakeSyscall(__NR_fcntl, kFakeSockFD, F_SETFD, O_CLOEXEC)); + emulator.ExpectErrno(EINVAL, + FakeSyscall(__NR_fcntl, kFakeSockFD, F_SETFD, 0)); + + emulator.ExpectErrno(EPERM, + FakeSyscall(__NR_fcntl, kFakeSockFD, F_SETFL, O_RDONLY)); + + emulator.ExpectErrno(EACCES, + FakeSyscall(__NR_fcntl, kFakeSockFD, F_DUPFD, 0)); +} + +static intptr_t DummyTrap(const struct arch_seccomp_data& data, void* aux) { + return 0; +} + +TEST(BPFDSL, IsAllowDeny) { + ResultExpr allow = Allow(); + EXPECT_TRUE(allow->IsAllow()); + EXPECT_FALSE(allow->IsDeny()); + + ResultExpr error = Error(ENOENT); + EXPECT_FALSE(error->IsAllow()); + EXPECT_TRUE(error->IsDeny()); + + ResultExpr trace = Trace(42); + EXPECT_FALSE(trace->IsAllow()); + EXPECT_FALSE(trace->IsDeny()); + + ResultExpr trap = Trap(DummyTrap, nullptr); + EXPECT_FALSE(trap->IsAllow()); + EXPECT_TRUE(trap->IsDeny()); + + const Arg<int> arg(0); + ResultExpr maybe = If(arg == 0, Allow()).Else(Error(EPERM)); + EXPECT_FALSE(maybe->IsAllow()); + EXPECT_FALSE(maybe->IsDeny()); +} + +TEST(BPFDSL, HasUnsafeTraps) { + ResultExpr allow = Allow(); + EXPECT_FALSE(allow->HasUnsafeTraps()); + + ResultExpr safe = Trap(DummyTrap, nullptr); + EXPECT_FALSE(safe->HasUnsafeTraps()); + + ResultExpr unsafe = UnsafeTrap(DummyTrap, nullptr); + EXPECT_TRUE(unsafe->HasUnsafeTraps()); + + const Arg<int> arg(0); + ResultExpr maybe = If(arg == 0, allow).Else(unsafe); + EXPECT_TRUE(maybe->HasUnsafeTraps()); +} + +} // namespace +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/codegen.cc b/sandbox/linux/bpf_dsl/codegen.cc new file mode 100644 index 0000000000..2d5c8e406e --- /dev/null +++ b/sandbox/linux/bpf_dsl/codegen.cc @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/codegen.h" + +#include <limits> +#include <utility> + +#include "base/logging.h" +#include "sandbox/linux/system_headers/linux_filter.h" + +// This CodeGen implementation strives for simplicity while still +// generating acceptable BPF programs under typical usage patterns +// (e.g., by PolicyCompiler). +// +// The key to its simplicity is that BPF programs only support forward +// jumps/branches, which allows constraining the DAG construction API +// to make instruction nodes immutable. Immutable nodes admits a +// simple greedy approach of emitting new instructions as needed and +// then reusing existing ones that have already been emitted. This +// cleanly avoids any need to compute basic blocks or apply +// topological sorting because the API effectively sorts instructions +// for us (e.g., before MakeInstruction() can be called to emit a +// branch instruction, it must have already been called for each +// branch path). +// +// This greedy algorithm is not without (theoretical) weakness though: +// +// 1. In the general case, we don't eliminate dead code. If needed, +// we could trace back through the program in Compile() and elide +// any unneeded instructions, but in practice we only emit live +// instructions anyway. +// +// 2. By not dividing instructions into basic blocks and sorting, we +// lose an opportunity to move non-branch/non-return instructions +// adjacent to their successor instructions, which means we might +// need to emit additional jumps. But in practice, they'll +// already be nearby as long as callers don't go out of their way +// to interleave MakeInstruction() calls for unrelated code +// sequences. + +namespace sandbox { + +// kBranchRange is the maximum value that can be stored in +// sock_filter's 8-bit jt and jf fields. +const size_t kBranchRange = std::numeric_limits<uint8_t>::max(); + +const CodeGen::Node CodeGen::kNullNode; + +CodeGen::CodeGen() : program_(), equivalent_(), memos_() { +} + +CodeGen::~CodeGen() { +} + +void CodeGen::Compile(CodeGen::Node head, Program* out) { + DCHECK(out); + out->assign(program_.rbegin() + Offset(head), program_.rend()); +} + +CodeGen::Node CodeGen::MakeInstruction(uint16_t code, + uint32_t k, + Node jt, + Node jf) { + // To avoid generating redundant code sequences, we memoize the + // results from AppendInstruction(). + auto res = memos_.insert(std::make_pair(MemoKey(code, k, jt, jf), kNullNode)); + CodeGen::Node* node = &res.first->second; + if (res.second) { // Newly inserted memo entry. + *node = AppendInstruction(code, k, jt, jf); + } + return *node; +} + +CodeGen::Node CodeGen::AppendInstruction(uint16_t code, + uint32_t k, + Node jt, + Node jf) { + if (BPF_CLASS(code) == BPF_JMP) { + CHECK_NE(BPF_JA, BPF_OP(code)) << "CodeGen inserts JAs as needed"; + + // Optimally adding jumps is rather tricky, so we use a quick + // approximation: by artificially reducing |jt|'s range, |jt| will + // stay within its true range even if we add a jump for |jf|. + jt = WithinRange(jt, kBranchRange - 1); + jf = WithinRange(jf, kBranchRange); + return Append(code, k, Offset(jt), Offset(jf)); + } + + CHECK_EQ(kNullNode, jf) << "Non-branch instructions shouldn't provide jf"; + if (BPF_CLASS(code) == BPF_RET) { + CHECK_EQ(kNullNode, jt) << "Return instructions shouldn't provide jt"; + } else { + // For non-branch/non-return instructions, execution always + // proceeds to the next instruction; so we need to arrange for + // that to be |jt|. + jt = WithinRange(jt, 0); + CHECK_EQ(0U, Offset(jt)) << "ICE: Failed to setup next instruction"; + } + return Append(code, k, 0, 0); +} + +CodeGen::Node CodeGen::WithinRange(Node target, size_t range) { + // Just use |target| if it's already within range. + if (Offset(target) <= range) { + return target; + } + + // Alternatively, look for an equivalent instruction within range. + if (Offset(equivalent_.at(target)) <= range) { + return equivalent_.at(target); + } + + // Otherwise, fall back to emitting a jump instruction. + Node jump = Append(BPF_JMP | BPF_JA, Offset(target), 0, 0); + equivalent_.at(target) = jump; + return jump; +} + +CodeGen::Node CodeGen::Append(uint16_t code, uint32_t k, size_t jt, size_t jf) { + if (BPF_CLASS(code) == BPF_JMP && BPF_OP(code) != BPF_JA) { + CHECK_LE(jt, kBranchRange); + CHECK_LE(jf, kBranchRange); + } else { + CHECK_EQ(0U, jt); + CHECK_EQ(0U, jf); + } + + CHECK_LT(program_.size(), static_cast<size_t>(BPF_MAXINSNS)); + CHECK_EQ(program_.size(), equivalent_.size()); + + Node res = program_.size(); + program_.push_back(sock_filter{ + code, static_cast<uint8_t>(jt), static_cast<uint8_t>(jf), k}); + equivalent_.push_back(res); + return res; +} + +size_t CodeGen::Offset(Node target) const { + CHECK_LT(target, program_.size()) << "Bogus offset target node"; + return (program_.size() - 1) - target; +} + +// TODO(mdempsky): Move into a general base::Tuple helper library. +bool CodeGen::MemoKeyLess::operator()(const MemoKey& lhs, + const MemoKey& rhs) const { + if (base::get<0>(lhs) != base::get<0>(rhs)) + return base::get<0>(lhs) < base::get<0>(rhs); + if (base::get<1>(lhs) != base::get<1>(rhs)) + return base::get<1>(lhs) < base::get<1>(rhs); + if (base::get<2>(lhs) != base::get<2>(rhs)) + return base::get<2>(lhs) < base::get<2>(rhs); + if (base::get<3>(lhs) != base::get<3>(rhs)) + return base::get<3>(lhs) < base::get<3>(rhs); + return false; +} + +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/codegen.h b/sandbox/linux/bpf_dsl/codegen.h new file mode 100644 index 0000000000..9d898030b9 --- /dev/null +++ b/sandbox/linux/bpf_dsl/codegen.h @@ -0,0 +1,123 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_CODEGEN_H__ +#define SANDBOX_LINUX_BPF_DSL_CODEGEN_H__ + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <vector> + +#include "base/macros.h" +#include "base/tuple.h" +#include "sandbox/sandbox_export.h" + +struct sock_filter; + +namespace sandbox { + +// The code generator implements a basic assembler that can convert a +// graph of BPF instructions into a well-formed array of BPF +// instructions. Most notably, it ensures that jumps are always +// forward and don't exceed the limit of 255 instructions imposed by +// the instruction set. +// +// Callers would typically create a new CodeGen object and then use it +// to build a DAG of instruction nodes. They'll eventually call +// Compile() to convert this DAG to a Program. +// +// CodeGen gen; +// CodeGen::Node allow, branch, dag; +// +// allow = +// gen.MakeInstruction(BPF_RET+BPF_K, +// ErrorCode(ErrorCode::ERR_ALLOWED).err())); +// branch = +// gen.MakeInstruction(BPF_JMP+BPF_EQ+BPF_K, __NR_getpid, +// Trap(GetPidHandler, NULL), allow); +// dag = +// gen.MakeInstruction(BPF_LD+BPF_W+BPF_ABS, +// offsetof(struct arch_seccomp_data, nr), branch); +// +// // Simplified code follows; in practice, it is important to avoid calling +// // any C++ destructors after starting the sandbox. +// CodeGen::Program program; +// gen.Compile(dag, program); +// const struct sock_fprog prog = { +// static_cast<unsigned short>(program->size()), &program[0] }; +// prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog); +// +class SANDBOX_EXPORT CodeGen { + public: + // A vector of BPF instructions that need to be installed as a filter + // program in the kernel. + typedef std::vector<struct sock_filter> Program; + + // Node represents a node within the instruction DAG being compiled. + using Node = Program::size_type; + + // kNullNode represents the "null" node; i.e., the reserved node + // value guaranteed to not equal any actual nodes. + static const Node kNullNode = -1; + + CodeGen(); + ~CodeGen(); + + // MakeInstruction creates a node representing the specified + // instruction, or returns and existing equivalent node if one + // exists. For details on the possible parameters refer to + // https://www.kernel.org/doc/Documentation/networking/filter.txt. + // TODO(mdempsky): Reconsider using default arguments here. + Node MakeInstruction(uint16_t code, + uint32_t k, + Node jt = kNullNode, + Node jf = kNullNode); + + // Compile linearizes the instruction DAG rooted at |head| into a + // program that can be executed by a BPF virtual machine. + void Compile(Node head, Program* program); + + private: + using MemoKey = base::Tuple<uint16_t, uint32_t, Node, Node>; + struct MemoKeyLess { + bool operator()(const MemoKey& lhs, const MemoKey& rhs) const; + }; + + // AppendInstruction adds a new instruction, ensuring that |jt| and + // |jf| are within range as necessary for |code|. + Node AppendInstruction(uint16_t code, uint32_t k, Node jt, Node jf); + + // WithinRange returns a node equivalent to |next| that is at most + // |range| instructions away from the (logical) beginning of the + // program. + Node WithinRange(Node next, size_t range); + + // Append appends a new instruction to the physical end (i.e., + // logical beginning) of |program_|. + Node Append(uint16_t code, uint32_t k, size_t jt, size_t jf); + + // Offset returns how many instructions exist in |program_| after |target|. + size_t Offset(Node target) const; + + // NOTE: program_ is the compiled program in *reverse*, so that + // indices remain stable as we add instructions. + Program program_; + + // equivalent_ stores the most recent semantically-equivalent node for each + // instruction in program_. A node is defined as semantically-equivalent to N + // if it has the same instruction code and constant as N and its successor + // nodes (if any) are semantically-equivalent to N's successor nodes, or + // if it's an unconditional jump to a node semantically-equivalent to N. + std::vector<Node> equivalent_; + + std::map<MemoKey, Node, MemoKeyLess> memos_; + + DISALLOW_COPY_AND_ASSIGN(CodeGen); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_CODEGEN_H__ diff --git a/sandbox/linux/bpf_dsl/codegen_unittest.cc b/sandbox/linux/bpf_dsl/codegen_unittest.cc new file mode 100644 index 0000000000..5961822123 --- /dev/null +++ b/sandbox/linux/bpf_dsl/codegen_unittest.cc @@ -0,0 +1,402 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/codegen.h" + +#include <map> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/md5.h" +#include "base/strings/string_piece.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { +namespace { + +// Hash provides an abstraction for building "hash trees" from BPF +// control flow graphs, and efficiently identifying equivalent graphs. +// +// For simplicity, we use MD5, because base happens to provide a +// convenient API for its use. However, any collision-resistant hash +// should suffice. +class Hash { + public: + static const Hash kZero; + + Hash() : digest_() {} + + Hash(uint16_t code, + uint32_t k, + const Hash& jt = kZero, + const Hash& jf = kZero) + : digest_() { + base::MD5Context ctx; + base::MD5Init(&ctx); + HashValue(&ctx, code); + HashValue(&ctx, k); + HashValue(&ctx, jt); + HashValue(&ctx, jf); + base::MD5Final(&digest_, &ctx); + } + + Hash(const Hash& hash) = default; + Hash& operator=(const Hash& rhs) = default; + + friend bool operator==(const Hash& lhs, const Hash& rhs) { + return lhs.Base16() == rhs.Base16(); + } + friend bool operator!=(const Hash& lhs, const Hash& rhs) { + return !(lhs == rhs); + } + + private: + template <typename T> + void HashValue(base::MD5Context* ctx, const T& value) { + base::MD5Update(ctx, + base::StringPiece(reinterpret_cast<const char*>(&value), + sizeof(value))); + } + + std::string Base16() const { + return base::MD5DigestToBase16(digest_); + } + + base::MD5Digest digest_; +}; + +const Hash Hash::kZero; + +// Sanity check that equality and inequality work on Hash as required. +TEST(CodeGen, HashSanity) { + std::vector<Hash> hashes; + + // Push a bunch of logically distinct hashes. + hashes.push_back(Hash::kZero); + for (int i = 0; i < 4; ++i) { + hashes.push_back(Hash(i & 1, i & 2)); + } + for (int i = 0; i < 16; ++i) { + hashes.push_back(Hash(i & 1, i & 2, Hash(i & 4, i & 8))); + } + for (int i = 0; i < 64; ++i) { + hashes.push_back( + Hash(i & 1, i & 2, Hash(i & 4, i & 8), Hash(i & 16, i & 32))); + } + + for (const Hash& a : hashes) { + for (const Hash& b : hashes) { + // Hashes should equal themselves, but not equal all others. + if (&a == &b) { + EXPECT_EQ(a, b); + } else { + EXPECT_NE(a, b); + } + } + } +} + +// ProgramTest provides a fixture for writing compiling sample +// programs with CodeGen and verifying the linearized output matches +// the input DAG. +class ProgramTest : public ::testing::Test { + protected: + ProgramTest() : gen_(), node_hashes_() {} + + // MakeInstruction calls CodeGen::MakeInstruction() and associated + // the returned address with a hash of the instruction. + CodeGen::Node MakeInstruction(uint16_t code, + uint32_t k, + CodeGen::Node jt = CodeGen::kNullNode, + CodeGen::Node jf = CodeGen::kNullNode) { + CodeGen::Node res = gen_.MakeInstruction(code, k, jt, jf); + EXPECT_NE(CodeGen::kNullNode, res); + + Hash digest(code, k, Lookup(jt), Lookup(jf)); + auto it = node_hashes_.insert(std::make_pair(res, digest)); + EXPECT_EQ(digest, it.first->second); + + return res; + } + + // RunTest compiles the program and verifies that the output matches + // what is expected. It should be called at the end of each program + // test case. + void RunTest(CodeGen::Node head) { + // Compile the program + CodeGen::Program program; + gen_.Compile(head, &program); + + // Walk the program backwards, and compute the hash for each instruction. + std::vector<Hash> prog_hashes(program.size()); + for (size_t i = program.size(); i > 0; --i) { + const sock_filter& insn = program.at(i - 1); + Hash& hash = prog_hashes.at(i - 1); + + if (BPF_CLASS(insn.code) == BPF_JMP) { + if (BPF_OP(insn.code) == BPF_JA) { + // The compiler adds JA instructions as needed, so skip them. + hash = prog_hashes.at(i + insn.k); + } else { + hash = Hash(insn.code, insn.k, prog_hashes.at(i + insn.jt), + prog_hashes.at(i + insn.jf)); + } + } else if (BPF_CLASS(insn.code) == BPF_RET) { + hash = Hash(insn.code, insn.k); + } else { + hash = Hash(insn.code, insn.k, prog_hashes.at(i)); + } + } + + EXPECT_EQ(Lookup(head), prog_hashes.at(0)); + } + + private: + const Hash& Lookup(CodeGen::Node next) const { + if (next == CodeGen::kNullNode) { + return Hash::kZero; + } + auto it = node_hashes_.find(next); + if (it == node_hashes_.end()) { + ADD_FAILURE() << "No hash found for node " << next; + return Hash::kZero; + } + return it->second; + } + + CodeGen gen_; + std::map<CodeGen::Node, Hash> node_hashes_; + + DISALLOW_COPY_AND_ASSIGN(ProgramTest); +}; + +TEST_F(ProgramTest, OneInstruction) { + // Create the most basic valid BPF program: + // RET 0 + CodeGen::Node head = MakeInstruction(BPF_RET + BPF_K, 0); + RunTest(head); +} + +TEST_F(ProgramTest, SimpleBranch) { + // Create a program with a single branch: + // JUMP if eq 42 then $0 else $1 + // 0: RET 1 + // 1: RET 0 + CodeGen::Node head = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, + MakeInstruction(BPF_RET + BPF_K, 1), + MakeInstruction(BPF_RET + BPF_K, 0)); + RunTest(head); +} + +TEST_F(ProgramTest, AtypicalBranch) { + // Create a program with a single branch: + // JUMP if eq 42 then $0 else $0 + // 0: RET 0 + + CodeGen::Node ret = MakeInstruction(BPF_RET + BPF_K, 0); + CodeGen::Node head = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, ret, ret); + + // N.B.: As the instructions in both sides of the branch are already + // the same object, we do not actually have any "mergeable" branches. + // This needs to be reflected in our choice of "flags". + RunTest(head); +} + +TEST_F(ProgramTest, Complex) { + // 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) + // 1: JUMP if eq 42 then $2 else $4 (insn4) + // 2: JUMP to $3 (insn2) + // 3: LD 42 (insn1) + // RET 42 (insn0) + // 4: LD 42 (insn3) + // RET 42 (insn3+) + CodeGen::Node insn0 = MakeInstruction(BPF_RET + BPF_K, 42); + CodeGen::Node insn1 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 42, insn0); + CodeGen::Node insn2 = insn1; // Implicit JUMP + + // We explicitly duplicate instructions to test that they're merged. + CodeGen::Node insn3 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 42, + MakeInstruction(BPF_RET + BPF_K, 42)); + EXPECT_EQ(insn2, insn3); + + CodeGen::Node insn4 = + MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, insn2, insn3); + CodeGen::Node insn5 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 23, insn4); + + // Force a basic block that ends in neither a jump instruction nor a return + // instruction. It only contains "insn5". This exercises one of the less + // common code paths in the topo-sort algorithm. + // 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). + CodeGen::Node insn6 = + MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, insn5, insn4); + + RunTest(insn6); +} + +TEST_F(ProgramTest, ConfusingTails) { + // 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 0 + // 7) RET 1 + + CodeGen::Node i7 = MakeInstruction(BPF_RET + BPF_K, 1); + CodeGen::Node i6 = MakeInstruction(BPF_RET + BPF_K, 0); + CodeGen::Node i5 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i6, i7); + CodeGen::Node i4 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i5); + CodeGen::Node i3 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5); + CodeGen::Node i2 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i3); + CodeGen::Node i1 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3); + CodeGen::Node i0 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1); + + RunTest(i0); +} + +TEST_F(ProgramTest, ConfusingTailsBasic) { + // 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 1 + + CodeGen::Node i5 = MakeInstruction(BPF_RET + BPF_K, 1); + CodeGen::Node i4 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i5); + CodeGen::Node i3 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5); + CodeGen::Node i2 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i3); + CodeGen::Node i1 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3); + CodeGen::Node i0 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1); + + RunTest(i0); +} + +TEST_F(ProgramTest, ConfusingTailsMergeable) { + // 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 42 + // 3) if A == 0x2; then JMP 4 else JMP 5 + // 4) RET 42 + // 5) if A == 0x1; then JMP 6 else JMP 7 + // 6) RET 0 + // 7) RET 1 + + CodeGen::Node i7 = MakeInstruction(BPF_RET + BPF_K, 1); + CodeGen::Node i6 = MakeInstruction(BPF_RET + BPF_K, 0); + CodeGen::Node i5 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i6, i7); + CodeGen::Node i4 = MakeInstruction(BPF_RET + BPF_K, 42); + CodeGen::Node i3 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5); + CodeGen::Node i2 = MakeInstruction(BPF_RET + BPF_K, 42); + CodeGen::Node i1 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3); + CodeGen::Node i0 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1); + + RunTest(i0); +} + +TEST_F(ProgramTest, InstructionFolding) { + // Check that simple instructions are folded as expected. + CodeGen::Node a = MakeInstruction(BPF_RET + BPF_K, 0); + EXPECT_EQ(a, MakeInstruction(BPF_RET + BPF_K, 0)); + CodeGen::Node b = MakeInstruction(BPF_RET + BPF_K, 1); + EXPECT_EQ(a, MakeInstruction(BPF_RET + BPF_K, 0)); + EXPECT_EQ(b, MakeInstruction(BPF_RET + BPF_K, 1)); + EXPECT_EQ(b, MakeInstruction(BPF_RET + BPF_K, 1)); + + // Check that complex sequences are folded too. + CodeGen::Node c = + MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, + MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, 0x100, a, b)); + EXPECT_EQ(c, MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, 0, + MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, 0x100, a, b))); + + RunTest(c); +} + +TEST_F(ProgramTest, FarBranches) { + // BPF instructions use 8-bit fields for branch offsets, which means + // branch targets must be within 255 instructions of the branch + // instruction. CodeGen abstracts away this detail by inserting jump + // instructions as needed, which we test here by generating programs + // that should trigger any interesting boundary conditions. + + // Populate with 260 initial instruction nodes. + std::vector<CodeGen::Node> nodes; + nodes.push_back(MakeInstruction(BPF_RET + BPF_K, 0)); + for (size_t i = 1; i < 260; ++i) { + nodes.push_back( + MakeInstruction(BPF_ALU + BPF_ADD + BPF_K, i, nodes.back())); + } + + // Exhaustively test branch offsets near BPF's limits. + for (size_t jt = 250; jt < 260; ++jt) { + for (size_t jf = 250; jf < 260; ++jf) { + nodes.push_back(MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 0, + nodes.rbegin()[jt], nodes.rbegin()[jf])); + RunTest(nodes.back()); + } + } +} + +TEST_F(ProgramTest, JumpReuse) { + // As a code size optimization, we try to reuse jumps when possible + // instead of emitting new ones. Here we make sure that optimization + // is working as intended. + // + // NOTE: To simplify testing, we rely on implementation details + // about what CodeGen::Node values indicate (i.e., vector indices), + // but CodeGen users should treat them as opaque values. + + // Populate with 260 initial instruction nodes. + std::vector<CodeGen::Node> nodes; + nodes.push_back(MakeInstruction(BPF_RET + BPF_K, 0)); + for (size_t i = 1; i < 260; ++i) { + nodes.push_back( + MakeInstruction(BPF_ALU + BPF_ADD + BPF_K, i, nodes.back())); + } + + // Branching to nodes[0] and nodes[1] should require 3 new + // instructions: two far jumps plus the branch itself. + CodeGen::Node one = + MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 0, nodes[0], nodes[1]); + EXPECT_EQ(nodes.back() + 3, one); // XXX: Implementation detail! + RunTest(one); + + // Branching again to the same target nodes should require only one + // new instruction, as we can reuse the previous branch's jumps. + CodeGen::Node two = + MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, nodes[0], nodes[1]); + EXPECT_EQ(one + 1, two); // XXX: Implementation detail! + RunTest(two); +} + +} // namespace +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/cons.h b/sandbox/linux/bpf_dsl/cons.h new file mode 100644 index 0000000000..fa47c140ff --- /dev/null +++ b/sandbox/linux/bpf_dsl/cons.h @@ -0,0 +1,138 @@ +// 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_BPF_DSL_CONS_H_ +#define SANDBOX_LINUX_BPF_DSL_CONS_H_ + +#include "base/memory/ref_counted.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace cons { + +// Namespace cons provides an abstraction for immutable "cons list" +// data structures as commonly provided in functional programming +// languages like Lisp or Haskell. +// +// A cons list is a linked list consisting of "cells", each of which +// have a "head" and a "tail" element. A cell's head element contains +// a user specified value, while the tail element contains a (possibly +// null) pointer to another cell. +// +// An empty list (idiomatically referred to as "nil") can be +// constructed as "cons::List<Foo>()" or simply as "nullptr" if Foo +// can be inferred from context (e.g., calling a function that has a +// "cons::List<Foo>" parameter). +// +// Existing lists (including empty lists) can be extended by +// prepending new values to the front using the "Cons(head, tail)" +// function, which will allocate a new cons cell. Notably, cons lists +// support creating multiple lists that share a common tail sequence. +// +// Lastly, lists support iteration via C++11's range-based for loop +// construct. +// +// Examples: +// +// // basic construction +// const cons::List<char> kNil = nullptr; +// cons::List<char> ba = Cons('b', Cons('a', kNil)); +// +// // common tail sequence +// cons::List<char> cba = Cons('c', ba); +// cons::List<char> dba = Cons('d', ba); +// +// // iteration +// for (const char& ch : cba) { +// // iterates 'c', 'b', 'a' +// } +// for (const char& ch : dba) { +// // iterates 'd', 'b', 'a' +// } + +// Forward declarations. +template <typename T> +class Cell; +template <typename T> +class ListIterator; + +// List represents a (possibly null) pointer to a cons cell. +template <typename T> +using List = scoped_refptr<const Cell<T>>; + +// Cons extends a cons list by prepending a new value to the front. +template <typename T> +List<T> Cons(const T& head, const List<T>& tail) { + return List<T>(new const Cell<T>(head, tail)); +} + +// Cell represents an individual "cons cell" within a cons list. +template <typename T> +class Cell : public base::RefCounted<Cell<T>> { + public: + Cell(const T& head, const List<T>& tail) : head_(head), tail_(tail) {} + + // Head returns this cell's head element. + const T& head() const { return head_; } + + // Tail returns this cell's tail element. + const List<T>& tail() const { return tail_; } + + private: + virtual ~Cell() {} + + T head_; + List<T> tail_; + + friend class base::RefCounted<Cell<T>>; + DISALLOW_COPY_AND_ASSIGN(Cell); +}; + +// Begin returns a list iterator pointing to the first element of the +// cons list. It's provided to support range-based for loops. +template <typename T> +ListIterator<T> begin(const List<T>& list) { + return ListIterator<T>(list); +} + +// End returns a list iterator pointing to the "past-the-end" element +// of the cons list (i.e., nil). It's provided to support range-based +// for loops. +template <typename T> +ListIterator<T> end(const List<T>& list) { + return ListIterator<T>(); +} + +// ListIterator provides C++ forward iterator semantics for traversing +// a cons list. +template <typename T> +class ListIterator { + public: + ListIterator() : list_() {} + explicit ListIterator(const List<T>& list) : list_(list) {} + + const T& operator*() const { return list_->head(); } + + ListIterator& operator++() { + list_ = list_->tail(); + return *this; + } + + friend bool operator==(const ListIterator& lhs, const ListIterator& rhs) { + return lhs.list_ == rhs.list_; + } + + private: + List<T> list_; +}; + +template <typename T> +bool operator!=(const ListIterator<T>& lhs, const ListIterator<T>& rhs) { + return !(lhs == rhs); +} + +} // namespace cons +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_CONS_H_ diff --git a/sandbox/linux/bpf_dsl/cons_unittest.cc b/sandbox/linux/bpf_dsl/cons_unittest.cc new file mode 100644 index 0000000000..ea2ba2f8dc --- /dev/null +++ b/sandbox/linux/bpf_dsl/cons_unittest.cc @@ -0,0 +1,33 @@ +// 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/bpf_dsl/cons.h" + +#include <string> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { +namespace { + +std::string Join(cons::List<char> char_list) { + std::string res; + for (const char& ch : char_list) { + res.push_back(ch); + } + return res; +} + +TEST(ConsListTest, Basic) { + cons::List<char> ba = Cons('b', Cons('a', cons::List<char>())); + EXPECT_EQ("ba", Join(ba)); + + cons::List<char> cba = Cons('c', ba); + cons::List<char> dba = Cons('d', ba); + EXPECT_EQ("cba", Join(cba)); + EXPECT_EQ("dba", Join(dba)); +} + +} // namespace +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/dump_bpf.cc b/sandbox/linux/bpf_dsl/dump_bpf.cc new file mode 100644 index 0000000000..d0c8f75073 --- /dev/null +++ b/sandbox/linux/bpf_dsl/dump_bpf.cc @@ -0,0 +1,109 @@ +// 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/bpf_dsl/dump_bpf.h" + +#include <stdio.h> + +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" + +namespace sandbox { +namespace bpf_dsl { + +void DumpBPF::PrintProgram(const CodeGen::Program& program) { + for (CodeGen::Program::const_iterator iter = program.begin(); + iter != program.end(); + ++iter) { + int ip = (int)(iter - program.begin()); + fprintf(stderr, "%3d) ", ip); + switch (BPF_CLASS(iter->code)) { + case BPF_LD: + if (iter->code == BPF_LD + BPF_W + BPF_ABS) { + fprintf(stderr, "LOAD %d // ", (int)iter->k); + if (iter->k == offsetof(struct arch_seccomp_data, nr)) { + fprintf(stderr, "System call number\n"); + } else if (iter->k == offsetof(struct arch_seccomp_data, arch)) { + fprintf(stderr, "Architecture\n"); + } else if (iter->k == + offsetof(struct arch_seccomp_data, instruction_pointer)) { + fprintf(stderr, "Instruction pointer (LSB)\n"); + } else if (iter->k == + offsetof(struct arch_seccomp_data, instruction_pointer) + + 4) { + fprintf(stderr, "Instruction pointer (MSB)\n"); + } else if (iter->k >= offsetof(struct arch_seccomp_data, args) && + iter->k < offsetof(struct arch_seccomp_data, args) + 48 && + (iter->k - offsetof(struct arch_seccomp_data, args)) % 4 == + 0) { + fprintf( + stderr, + "Argument %d (%cSB)\n", + (int)(iter->k - offsetof(struct arch_seccomp_data, args)) / 8, + (iter->k - offsetof(struct arch_seccomp_data, args)) % 8 ? 'M' + : 'L'); + } else { + fprintf(stderr, "???\n"); + } + } else { + fprintf(stderr, "LOAD ???\n"); + } + break; + case BPF_JMP: + if (BPF_OP(iter->code) == BPF_JA) { + fprintf(stderr, "JMP %d\n", ip + iter->k + 1); + } else { + fprintf(stderr, "if A %s 0x%x; then JMP %d else JMP %d\n", + BPF_OP(iter->code) == BPF_JSET ? "&" : + BPF_OP(iter->code) == BPF_JEQ ? "==" : + BPF_OP(iter->code) == BPF_JGE ? ">=" : + BPF_OP(iter->code) == BPF_JGT ? ">" : "???", + (int)iter->k, + ip + iter->jt + 1, ip + iter->jf + 1); + } + break; + case BPF_RET: + fprintf(stderr, "RET 0x%x // ", iter->k); + if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP) { + fprintf(stderr, "Trap #%d\n", iter->k & SECCOMP_RET_DATA); + } else if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) { + fprintf(stderr, "errno = %d\n", iter->k & SECCOMP_RET_DATA); + } else if ((iter->k & SECCOMP_RET_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 { + fprintf(stderr, "???\n"); + } + break; + case BPF_ALU: + if (BPF_OP(iter->code) == BPF_NEG) { + fprintf(stderr, "A := -A\n"); + } else { + fprintf(stderr, "A := A %s 0x%x\n", + BPF_OP(iter->code) == BPF_ADD ? "+" : + BPF_OP(iter->code) == BPF_SUB ? "-" : + BPF_OP(iter->code) == BPF_MUL ? "*" : + BPF_OP(iter->code) == BPF_DIV ? "/" : + BPF_OP(iter->code) == BPF_MOD ? "%" : + BPF_OP(iter->code) == BPF_OR ? "|" : + BPF_OP(iter->code) == BPF_XOR ? "^" : + BPF_OP(iter->code) == BPF_AND ? "&" : + BPF_OP(iter->code) == BPF_LSH ? "<<" : + BPF_OP(iter->code) == BPF_RSH ? ">>" : "???", + (int)iter->k); + } + break; + default: + fprintf(stderr, "???\n"); + break; + } + } + return; +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/dump_bpf.h b/sandbox/linux/bpf_dsl/dump_bpf.h new file mode 100644 index 0000000000..cd12be793d --- /dev/null +++ b/sandbox/linux/bpf_dsl/dump_bpf.h @@ -0,0 +1,18 @@ +// 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/bpf_dsl/codegen.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { + +class SANDBOX_EXPORT DumpBPF { + public: + // PrintProgram writes |program| in a human-readable format to stderr. + static void PrintProgram(const CodeGen::Program& program); +}; + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/linux_syscall_ranges.h b/sandbox/linux/bpf_dsl/linux_syscall_ranges.h new file mode 100644 index 0000000000..a747770c78 --- /dev/null +++ b/sandbox/linux/bpf_dsl/linux_syscall_ranges.h @@ -0,0 +1,57 @@ +// Copyright 2015 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_BPF_DSL_LINUX_SYSCALL_RANGES_H_ +#define SANDBOX_LINUX_BPF_DSL_LINUX_SYSCALL_RANGES_H_ + +#if defined(__x86_64__) + +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL 1024u +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL + +#elif defined(__i386__) + +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL 1024u +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL + +#elif defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__)) + +// ARM EABI includes "ARM private" system calls starting at |__ARM_NR_BASE|, +// and a "ghost syscall private to the kernel", cmpxchg, +// at |__ARM_NR_BASE+0x00fff0|. +// See </arch/arm/include/asm/unistd.h> in the Linux kernel. + +// __NR_SYSCALL_BASE is 0 in thumb and ARM EABI. +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL (MIN_SYSCALL + 1024u) +// __ARM_NR_BASE is __NR_SYSCALL_BASE + 0xf0000u +#define MIN_PRIVATE_SYSCALL 0xf0000u +#define MAX_PRIVATE_SYSCALL (MIN_PRIVATE_SYSCALL + 16u) +#define MIN_GHOST_SYSCALL (MIN_PRIVATE_SYSCALL + 0xfff0u) +#define MAX_SYSCALL (MIN_GHOST_SYSCALL + 4u) + +#elif defined(__mips__) && (_MIPS_SIM == _ABIO32) + +#include <asm/unistd.h> // for __NR_O32_Linux and __NR_Linux_syscalls +#define MIN_SYSCALL __NR_O32_Linux +#define MAX_PUBLIC_SYSCALL (MIN_SYSCALL + __NR_Linux_syscalls) +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL + +#elif defined(__mips__) && (_MIPS_SIM == _ABI64) + +#error "Add support to header file" + +#elif defined(__aarch64__) + +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL 279u +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL + +#else +#error "Unsupported architecture" +#endif + +#endif // SANDBOX_LINUX_BPF_DSL_LINUX_SYSCALL_RANGES_H_ diff --git a/sandbox/linux/bpf_dsl/policy.cc b/sandbox/linux/bpf_dsl/policy.cc new file mode 100644 index 0000000000..c20edc6da8 --- /dev/null +++ b/sandbox/linux/bpf_dsl/policy.cc @@ -0,0 +1,19 @@ +// 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/bpf_dsl/policy.h" + +#include <errno.h> + +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" + +namespace sandbox { +namespace bpf_dsl { + +ResultExpr Policy::InvalidSyscall() const { + return Error(ENOSYS); +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/policy.h b/sandbox/linux/bpf_dsl/policy.h new file mode 100644 index 0000000000..6c67589456 --- /dev/null +++ b/sandbox/linux/bpf_dsl/policy.h @@ -0,0 +1,37 @@ +// 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_BPF_DSL_POLICY_H_ +#define SANDBOX_LINUX_BPF_DSL_POLICY_H_ + +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { + +// Interface to implement to define a BPF sandbox policy. +class SANDBOX_EXPORT Policy { + public: + Policy() {} + virtual ~Policy() {} + + // User extension point for writing custom sandbox policies. + // The returned ResultExpr will control how the kernel responds to the + // specified system call number. + virtual ResultExpr EvaluateSyscall(int sysno) const = 0; + + // Optional overload for specifying alternate behavior for invalid + // system calls. The default is to return ENOSYS. + virtual ResultExpr InvalidSyscall() const; + + private: + DISALLOW_COPY_AND_ASSIGN(Policy); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_POLICY_H_ diff --git a/sandbox/linux/bpf_dsl/policy_compiler.cc b/sandbox/linux/bpf_dsl/policy_compiler.cc new file mode 100644 index 0000000000..f38232f85f --- /dev/null +++ b/sandbox/linux/bpf_dsl/policy_compiler.cc @@ -0,0 +1,499 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/policy_compiler.h" + +#include <errno.h> +#include <sys/syscall.h> + +#include <limits> + +#include "base/logging.h" +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/dump_bpf.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/syscall_set.h" +#include "sandbox/linux/bpf_dsl/verifier.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { +namespace bpf_dsl { + +namespace { + +#if defined(__i386__) || defined(__x86_64__) +const bool kIsIntel = true; +#else +const bool kIsIntel = false; +#endif +#if defined(__x86_64__) && defined(__ILP32__) +const bool kIsX32 = true; +#else +const bool kIsX32 = false; +#endif + +const int kSyscallsRequiredForUnsafeTraps[] = { + __NR_rt_sigprocmask, + __NR_rt_sigreturn, +#if defined(__NR_sigprocmask) + __NR_sigprocmask, +#endif +#if defined(__NR_sigreturn) + __NR_sigreturn, +#endif +}; + +bool HasExactlyOneBit(uint64_t x) { + // Common trick; e.g., see http://stackoverflow.com/a/108329. + return x != 0 && (x & (x - 1)) == 0; +} + +// A Trap() handler that returns an "errno" value. The value is encoded +// in the "aux" parameter. +intptr_t ReturnErrno(const struct arch_seccomp_data&, void* aux) { + // TrapFnc functions report error by following the native kernel convention + // of returning an exit code in the range of -1..-4096. They do not try to + // set errno themselves. The glibc wrapper that triggered the SIGSYS will + // ultimately do so for us. + int err = reinterpret_cast<intptr_t>(aux) & SECCOMP_RET_DATA; + return -err; +} + +bool HasUnsafeTraps(const Policy* policy) { + DCHECK(policy); + for (uint32_t sysnum : SyscallSet::ValidOnly()) { + if (policy->EvaluateSyscall(sysnum)->HasUnsafeTraps()) { + return true; + } + } + return policy->InvalidSyscall()->HasUnsafeTraps(); +} + +} // namespace + +struct PolicyCompiler::Range { + uint32_t from; + CodeGen::Node node; +}; + +PolicyCompiler::PolicyCompiler(const Policy* policy, TrapRegistry* registry) + : policy_(policy), + registry_(registry), + escapepc_(0), + conds_(), + gen_(), + has_unsafe_traps_(HasUnsafeTraps(policy_)) { + DCHECK(policy); +} + +PolicyCompiler::~PolicyCompiler() { +} + +scoped_ptr<CodeGen::Program> PolicyCompiler::Compile(bool verify) { + CHECK(policy_->InvalidSyscall()->IsDeny()) + << "Policies should deny invalid system calls"; + + // If our BPF program has unsafe traps, enable support for them. + if (has_unsafe_traps_) { + CHECK_NE(0U, escapepc_) << "UnsafeTrap() requires a valid escape PC"; + + for (int sysnum : kSyscallsRequiredForUnsafeTraps) { + CHECK(policy_->EvaluateSyscall(sysnum)->IsAllow()) + << "Policies that use UnsafeTrap() must unconditionally allow all " + "required system calls"; + } + + CHECK(registry_->EnableUnsafeTraps()) + << "We'd rather die than enable unsafe traps"; + } + + // Assemble the BPF filter program. + scoped_ptr<CodeGen::Program> program(new CodeGen::Program()); + gen_.Compile(AssemblePolicy(), program.get()); + + // Make sure compilation resulted in a BPF program that executes + // correctly. Otherwise, there is an internal error in our BPF compiler. + // There is really nothing the caller can do until the bug is fixed. + if (verify) { + const char* err = nullptr; + if (!Verifier::VerifyBPF(this, *program, *policy_, &err)) { + DumpBPF::PrintProgram(*program); + LOG(FATAL) << err; + } + } + + return program.Pass(); +} + +void PolicyCompiler::DangerousSetEscapePC(uint64_t escapepc) { + escapepc_ = escapepc; +} + +CodeGen::Node PolicyCompiler::AssemblePolicy() { + // A compiled policy consists of three logical parts: + // 1. Check that the "arch" field matches the expected architecture. + // 2. If the policy involves unsafe traps, check if the syscall was + // invoked by Syscall::Call, and then allow it unconditionally. + // 3. Check the system call number and jump to the appropriate compiled + // system call policy number. + return CheckArch(MaybeAddEscapeHatch(DispatchSyscall())); +} + +CodeGen::Node PolicyCompiler::CheckArch(CodeGen::Node passed) { + // If the architecture doesn't match SECCOMP_ARCH, disallow the + // system call. + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_ARCH_IDX, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, SECCOMP_ARCH, passed, + CompileResult(Kill("Invalid audit architecture in BPF filter")))); +} + +CodeGen::Node PolicyCompiler::MaybeAddEscapeHatch(CodeGen::Node rest) { + // If no unsafe traps, then simply return |rest|. + if (!has_unsafe_traps_) { + return rest; + } + + // We already enabled unsafe traps in Compile, but enable them again to give + // the trap registry a second chance to complain before we add the backdoor. + CHECK(registry_->EnableUnsafeTraps()); + + // Allow system calls, if they originate from our magic return address. + const uint32_t lopc = static_cast<uint32_t>(escapepc_); + const uint32_t hipc = static_cast<uint32_t>(escapepc_ >> 32); + + // BPF cannot do native 64-bit comparisons, so we have to compare + // both 32-bit halves of the instruction pointer. If they match what + // we expect, we return ERR_ALLOWED. If either or both don't match, + // we continue evalutating the rest of the sandbox policy. + // + // For simplicity, we check the full 64-bit instruction pointer even + // on 32-bit architectures. + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_LSB_IDX, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, lopc, + gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_MSB_IDX, + gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, hipc, + CompileResult(Allow()), rest)), + rest)); +} + +CodeGen::Node PolicyCompiler::DispatchSyscall() { + // Evaluate all possible system calls and group their ErrorCodes into + // ranges of identical codes. + Ranges ranges; + FindRanges(&ranges); + + // Compile the system call ranges to an optimized BPF jumptable + CodeGen::Node jumptable = AssembleJumpTable(ranges.begin(), ranges.end()); + + // Grab the system call number, so that we can check it and then + // execute the jump table. + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_NR_IDX, CheckSyscallNumber(jumptable)); +} + +CodeGen::Node PolicyCompiler::CheckSyscallNumber(CodeGen::Node passed) { + if (kIsIntel) { + // On Intel architectures, verify that system call numbers are in the + // expected number range. + CodeGen::Node invalidX32 = + CompileResult(Kill("Illegal mixing of system call ABIs")); + if (kIsX32) { + // The newer x32 API always sets bit 30. + return gen_.MakeInstruction( + BPF_JMP + BPF_JSET + BPF_K, 0x40000000, passed, invalidX32); + } else { + // The older i386 and x86-64 APIs clear bit 30 on all system calls. + return gen_.MakeInstruction( + BPF_JMP + BPF_JSET + BPF_K, 0x40000000, invalidX32, passed); + } + } + + // TODO(mdempsky): Similar validation for other architectures? + return passed; +} + +void PolicyCompiler::FindRanges(Ranges* ranges) { + // Please note that "struct seccomp_data" defines system calls as a signed + // int32_t, but BPF instructions always operate on unsigned quantities. We + // deal with this disparity by enumerating from MIN_SYSCALL to MAX_SYSCALL, + // and then verifying that the rest of the number range (both positive and + // negative) all return the same ErrorCode. + const CodeGen::Node invalid_node = CompileResult(policy_->InvalidSyscall()); + uint32_t old_sysnum = 0; + CodeGen::Node old_node = + SyscallSet::IsValid(old_sysnum) + ? CompileResult(policy_->EvaluateSyscall(old_sysnum)) + : invalid_node; + + for (uint32_t sysnum : SyscallSet::All()) { + CodeGen::Node node = + SyscallSet::IsValid(sysnum) + ? CompileResult(policy_->EvaluateSyscall(static_cast<int>(sysnum))) + : invalid_node; + // N.B., here we rely on CodeGen folding (i.e., returning the same + // node value for) identical code sequences, otherwise our jump + // table will blow up in size. + if (node != old_node) { + ranges->push_back(Range{old_sysnum, old_node}); + old_sysnum = sysnum; + old_node = node; + } + } + ranges->push_back(Range{old_sysnum, old_node}); +} + +CodeGen::Node PolicyCompiler::AssembleJumpTable(Ranges::const_iterator start, + Ranges::const_iterator stop) { + // We convert the list of system call ranges into jump table that performs + // a binary search over the ranges. + // As a sanity check, we need to have at least one distinct ranges for us + // to be able to build a jump table. + CHECK(start < stop) << "Invalid iterator range"; + const auto n = stop - start; + if (n == 1) { + // If we have narrowed things down to a single range object, we can + // return from the BPF filter program. + return start->node; + } + + // Pick the range object that is located at the mid point of our list. + // We compare our system call number against the lowest valid system call + // number in this range object. If our number is lower, it is outside of + // this range object. If it is greater or equal, it might be inside. + Ranges::const_iterator mid = start + n / 2; + + // Sub-divide the list of ranges and continue recursively. + CodeGen::Node jf = AssembleJumpTable(start, mid); + CodeGen::Node jt = AssembleJumpTable(mid, stop); + return gen_.MakeInstruction(BPF_JMP + BPF_JGE + BPF_K, mid->from, jt, jf); +} + +CodeGen::Node PolicyCompiler::CompileResult(const ResultExpr& res) { + return RetExpression(res->Compile(this)); +} + +CodeGen::Node PolicyCompiler::RetExpression(const ErrorCode& err) { + switch (err.error_type()) { + case ErrorCode::ET_COND: + return CondExpression(err); + case ErrorCode::ET_SIMPLE: + case ErrorCode::ET_TRAP: + return gen_.MakeInstruction(BPF_RET + BPF_K, err.err()); + default: + LOG(FATAL) + << "ErrorCode is not suitable for returning from a BPF program"; + return CodeGen::kNullNode; + } +} + +CodeGen::Node PolicyCompiler::CondExpression(const ErrorCode& cond) { + // Sanity check that |cond| makes sense. + CHECK(cond.argno_ >= 0 && cond.argno_ < 6) << "Invalid argument number " + << cond.argno_; + CHECK(cond.width_ == ErrorCode::TP_32BIT || + cond.width_ == ErrorCode::TP_64BIT) + << "Invalid argument width " << cond.width_; + CHECK_NE(0U, cond.mask_) << "Zero mask is invalid"; + CHECK_EQ(cond.value_, cond.value_ & cond.mask_) + << "Value contains masked out bits"; + if (sizeof(void*) == 4) { + CHECK_EQ(ErrorCode::TP_32BIT, cond.width_) + << "Invalid width on 32-bit platform"; + } + if (cond.width_ == ErrorCode::TP_32BIT) { + CHECK_EQ(0U, cond.mask_ >> 32) << "Mask exceeds argument size"; + CHECK_EQ(0U, cond.value_ >> 32) << "Value exceeds argument size"; + } + + CodeGen::Node passed = RetExpression(*cond.passed_); + CodeGen::Node failed = RetExpression(*cond.failed_); + + // We want to emit code to check "(arg & mask) == value" where arg, mask, and + // value are 64-bit values, but the BPF machine is only 32-bit. We implement + // this by independently testing the upper and lower 32-bits and continuing to + // |passed| if both evaluate true, or to |failed| if either evaluate false. + return CondExpressionHalf(cond, + UpperHalf, + CondExpressionHalf(cond, LowerHalf, passed, failed), + failed); +} + +CodeGen::Node PolicyCompiler::CondExpressionHalf(const ErrorCode& cond, + ArgHalf half, + CodeGen::Node passed, + CodeGen::Node failed) { + if (cond.width_ == ErrorCode::TP_32BIT && half == UpperHalf) { + // Special logic for sanity checking the upper 32-bits of 32-bit system + // call arguments. + + // TODO(mdempsky): Compile Unexpected64bitArgument() just per program. + CodeGen::Node invalid_64bit = RetExpression(Unexpected64bitArgument()); + + const uint32_t upper = SECCOMP_ARG_MSB_IDX(cond.argno_); + const uint32_t lower = SECCOMP_ARG_LSB_IDX(cond.argno_); + + if (sizeof(void*) == 4) { + // On 32-bit platforms, the upper 32-bits should always be 0: + // LDW [upper] + // JEQ 0, passed, invalid + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + upper, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, 0, passed, invalid_64bit)); + } + + // On 64-bit platforms, the upper 32-bits may be 0 or ~0; but we only allow + // ~0 if the sign bit of the lower 32-bits is set too: + // LDW [upper] + // JEQ 0, passed, (next) + // JEQ ~0, (next), invalid + // LDW [lower] + // JSET (1<<31), passed, invalid + // + // TODO(mdempsky): The JSET instruction could perhaps jump to passed->next + // instead, as the first instruction of passed should be "LDW [lower]". + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + upper, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, + 0, + passed, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, + std::numeric_limits<uint32_t>::max(), + gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + lower, + gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, + 1U << 31, + passed, + invalid_64bit)), + invalid_64bit))); + } + + const uint32_t idx = (half == UpperHalf) ? SECCOMP_ARG_MSB_IDX(cond.argno_) + : SECCOMP_ARG_LSB_IDX(cond.argno_); + const uint32_t mask = (half == UpperHalf) ? cond.mask_ >> 32 : cond.mask_; + const uint32_t value = (half == UpperHalf) ? cond.value_ >> 32 : cond.value_; + + // Emit a suitable instruction sequence for (arg & mask) == value. + + // For (arg & 0) == 0, just return passed. + if (mask == 0) { + CHECK_EQ(0U, value); + return passed; + } + + // For (arg & ~0) == value, emit: + // LDW [idx] + // JEQ value, passed, failed + if (mask == std::numeric_limits<uint32_t>::max()) { + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, value, passed, failed)); + } + + // For (arg & mask) == 0, emit: + // LDW [idx] + // JSET mask, failed, passed + // (Note: failed and passed are intentionally swapped.) + if (value == 0) { + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, mask, failed, passed)); + } + + // For (arg & x) == x where x is a single-bit value, emit: + // LDW [idx] + // JSET mask, passed, failed + if (mask == value && HasExactlyOneBit(mask)) { + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, mask, passed, failed)); + } + + // Generic fallback: + // LDW [idx] + // AND mask + // JEQ value, passed, failed + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction( + BPF_ALU + BPF_AND + BPF_K, + mask, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, value, passed, failed))); +} + +ErrorCode PolicyCompiler::Unexpected64bitArgument() { + return Kill("Unexpected 64bit argument detected")->Compile(this); +} + +ErrorCode PolicyCompiler::Error(int err) { + if (has_unsafe_traps_) { + // When inside an UnsafeTrap() callback, we want to allow all system calls. + // This means, we must conditionally disable the sandbox -- and that's not + // something that kernel-side BPF filters can do, as they cannot inspect + // any state other than the syscall arguments. + // But if we redirect all error handlers to user-space, then we can easily + // make this decision. + // The performance penalty for this extra round-trip to user-space is not + // actually that bad, as we only ever pay it for denied system calls; and a + // typical program has very few of these. + return Trap(ReturnErrno, reinterpret_cast<void*>(err), true); + } + + return ErrorCode(err); +} + +ErrorCode PolicyCompiler::Trap(TrapRegistry::TrapFnc fnc, + const void* aux, + bool safe) { + uint16_t trap_id = registry_->Add(fnc, aux, safe); + return ErrorCode(trap_id, fnc, aux, safe); +} + +bool PolicyCompiler::IsRequiredForUnsafeTrap(int sysno) { + for (size_t i = 0; i < arraysize(kSyscallsRequiredForUnsafeTraps); ++i) { + if (sysno == kSyscallsRequiredForUnsafeTraps[i]) { + return true; + } + } + return false; +} + +ErrorCode PolicyCompiler::CondMaskedEqual(int argno, + ErrorCode::ArgType width, + uint64_t mask, + uint64_t value, + const ErrorCode& passed, + const ErrorCode& failed) { + return ErrorCode(argno, + width, + mask, + value, + &*conds_.insert(passed).first, + &*conds_.insert(failed).first); +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/policy_compiler.h b/sandbox/linux/bpf_dsl/policy_compiler.h new file mode 100644 index 0000000000..df38d4ccbc --- /dev/null +++ b/sandbox/linux/bpf_dsl/policy_compiler.h @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_POLICY_COMPILER_H_ +#define SANDBOX_LINUX_BPF_DSL_POLICY_COMPILER_H_ + +#include <stdint.h> + +#include <map> +#include <set> +#include <vector> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { +class Policy; + +// PolicyCompiler implements the bpf_dsl compiler, allowing users to +// transform bpf_dsl policies into BPF programs to be executed by the +// Linux kernel. +class SANDBOX_EXPORT PolicyCompiler { + public: + PolicyCompiler(const Policy* policy, TrapRegistry* registry); + ~PolicyCompiler(); + + // Compile registers any trap handlers needed by the policy and + // compiles the policy to a BPF program, which it returns. + scoped_ptr<CodeGen::Program> Compile(bool verify); + + // DangerousSetEscapePC sets the "escape PC" that is allowed to issue any + // system calls, regardless of policy. + void DangerousSetEscapePC(uint64_t escapepc); + + // Error returns an ErrorCode to indicate the system call should fail with + // the specified error number. + ErrorCode Error(int err); + + // Trap returns an ErrorCode to indicate the system call should + // instead invoke a trap handler. + ErrorCode Trap(TrapRegistry::TrapFnc fnc, const void* aux, bool safe); + + // UnsafeTraps require some syscalls to always be allowed. + // This helper function returns true for these calls. + static bool IsRequiredForUnsafeTrap(int sysno); + + // We can also use ErrorCode to request evaluation of a conditional + // statement based on inspection of system call parameters. + // This method wrap an ErrorCode object around the conditional statement. + // Argument "argno" (1..6) will be bitwise-AND'd with "mask" and compared + // to "value"; if equal, then "passed" will be returned, otherwise "failed". + // If "is32bit" is set, the argument must in the range of 0x0..(1u << 32 - 1) + // If it is outside this range, the sandbox treats the system call just + // the same as any other ABI violation (i.e. it aborts with an error + // message). + ErrorCode CondMaskedEqual(int argno, + ErrorCode::ArgType is_32bit, + uint64_t mask, + uint64_t value, + const ErrorCode& passed, + const ErrorCode& failed); + + // Returns the fatal ErrorCode that is used to indicate that somebody + // attempted to pass a 64bit value in a 32bit system call argument. + // This method is primarily needed for testing purposes. + ErrorCode Unexpected64bitArgument(); + + private: + struct Range; + typedef std::vector<Range> Ranges; + typedef std::set<ErrorCode, struct ErrorCode::LessThan> Conds; + + // Used by CondExpressionHalf to track which half of the argument it's + // emitting instructions for. + enum ArgHalf { + LowerHalf, + UpperHalf, + }; + + // Compile the configured policy into a complete instruction sequence. + CodeGen::Node AssemblePolicy(); + + // Return an instruction sequence that checks the + // arch_seccomp_data's "arch" field is valid, and then passes + // control to |passed| if so. + CodeGen::Node CheckArch(CodeGen::Node passed); + + // If |has_unsafe_traps_| is true, returns an instruction sequence + // that allows all system calls from |escapepc_|, and otherwise + // passes control to |rest|. Otherwise, simply returns |rest|. + CodeGen::Node MaybeAddEscapeHatch(CodeGen::Node rest); + + // Return an instruction sequence that loads and checks the system + // call number, performs a binary search, and then dispatches to an + // appropriate instruction sequence compiled from the current + // policy. + CodeGen::Node DispatchSyscall(); + + // Return an instruction sequence that checks the system call number + // (expected to be loaded in register A) and if valid, passes + // control to |passed| (with register A still valid). + CodeGen::Node CheckSyscallNumber(CodeGen::Node passed); + + // Finds all the ranges of system calls that need to be handled. Ranges are + // sorted in ascending order of system call numbers. There are no gaps in the + // ranges. System calls with identical ErrorCodes are coalesced into a single + // range. + void FindRanges(Ranges* ranges); + + // Returns a BPF program snippet that implements a jump table for the + // given range of system call numbers. This function runs recursively. + CodeGen::Node AssembleJumpTable(Ranges::const_iterator start, + Ranges::const_iterator stop); + + // CompileResult compiles an individual result expression into a + // CodeGen node. + CodeGen::Node CompileResult(const ResultExpr& res); + + // Returns a BPF program snippet that makes the BPF filter program exit + // with the given ErrorCode "err". N.B. the ErrorCode may very well be a + // conditional expression; if so, this function will recursively call + // CondExpression() and possibly RetExpression() to build a complex set of + // instructions. + CodeGen::Node RetExpression(const ErrorCode& err); + + // Returns a BPF program that evaluates the conditional expression in + // "cond" and returns the appropriate value from the BPF filter program. + // This function recursively calls RetExpression(); it should only ever be + // called from RetExpression(). + CodeGen::Node CondExpression(const ErrorCode& cond); + + // Returns a BPF program that evaluates half of a conditional expression; + // it should only ever be called from CondExpression(). + CodeGen::Node CondExpressionHalf(const ErrorCode& cond, + ArgHalf half, + CodeGen::Node passed, + CodeGen::Node failed); + + const Policy* policy_; + TrapRegistry* registry_; + uint64_t escapepc_; + + Conds conds_; + CodeGen gen_; + bool has_unsafe_traps_; + + DISALLOW_COPY_AND_ASSIGN(PolicyCompiler); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_POLICY_COMPILER_H_ diff --git a/sandbox/linux/bpf_dsl/seccomp_macros.h b/sandbox/linux/bpf_dsl/seccomp_macros.h new file mode 100644 index 0000000000..ca28c1d7cd --- /dev/null +++ b/sandbox/linux/bpf_dsl/seccomp_macros.h @@ -0,0 +1,295 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_SECCOMP_MACROS_H_ +#define SANDBOX_LINUX_BPF_DSL_SECCOMP_MACROS_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/types.h> // Fix for gcc 4.7, make sure __uint16_t is defined. +#if !defined(__native_client_nonsfi__) +#include <sys/user.h> +#endif +#if defined(__mips__) +// sys/user.h in eglibc misses size_t definition +#include <stddef.h> +#endif +#endif + +#include "sandbox/linux/system_headers/linux_seccomp.h" // For AUDIT_ARCH_* + +// Impose some reasonable maximum BPF program size. Realistically, the +// kernel probably has much lower limits. But by limiting to less than +// 30 bits, we can ease requirements on some of our data types. +#define SECCOMP_MAX_PROGRAM_SIZE (1<<30) + +#if defined(__i386__) +#define SECCOMP_ARCH AUDIT_ARCH_I386 + +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)]) +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, REG_EAX) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, REG_EAX) +#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, REG_EIP) +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, REG_EBX) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, REG_ECX) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, REG_EDX) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, REG_ESI) +#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, REG_EDI) +#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, REG_EBP) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) + + +#if defined(__BIONIC__) || defined(__native_client_nonsfi__) +// Old Bionic versions and PNaCl toolchain 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 and PNaCl toolchain. +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 SECCOMP_ARCH AUDIT_ARCH_X86_64 + +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)]) +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, REG_RAX) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, REG_RAX) +#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, REG_RIP) +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, REG_RDI) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, REG_RSI) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, REG_RDX) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, REG_R10) +#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, REG_R8) +#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, REG_R9) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) + +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__)) +#define SECCOMP_ARCH AUDIT_ARCH_ARM + +// ARM sigcontext_t is different from i386/x86_64. +// See </arch/arm/include/asm/sigcontext.h> in the Linux kernel. +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.arm_##_reg) +// ARM EABI syscall convention. +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, r0) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, r7) +#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, pc) +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, r0) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, r1) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, r2) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, r3) +#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, r4) +#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, r5) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) + +#if defined(__BIONIC__) || defined(__native_client_nonsfi__) +// Old Bionic versions and PNaCl toolchain 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 and PNaCl toolchain. +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 + +#elif defined(__mips__) && (_MIPS_SIM == _MIPS_SIM_ABI32) +#define SECCOMP_ARCH AUDIT_ARCH_MIPSEL +#define SYSCALL_EIGHT_ARGS +// MIPS sigcontext_t is different from i386/x86_64 and ARM. +// See </arch/mips/include/uapi/asm/sigcontext.h> in the Linux kernel. +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[_reg]) +// Based on MIPS o32 ABI syscall convention. +// On MIPS, when indirect syscall is being made (syscall(__NR_foo)), +// real identificator (__NR_foo) is not in v0, but in a0 +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, 2) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, 2) +#define SECCOMP_IP(_ctx) (_ctx)->uc_mcontext.pc +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, 4) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, 5) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, 6) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, 7) +// Only the first 4 arguments of syscall are in registers. +// The rest are on the stack. +#define SECCOMP_STACKPARM(_ctx, n) (((long *)SECCOMP_REG(_ctx, 29))[(n)]) +#define SECCOMP_PARM5(_ctx) SECCOMP_STACKPARM(_ctx, 4) +#define SECCOMP_PARM6(_ctx) SECCOMP_STACKPARM(_ctx, 5) +#define SECCOMP_PARM7(_ctx) SECCOMP_STACKPARM(_ctx, 6) +#define SECCOMP_PARM8(_ctx) SECCOMP_STACKPARM(_ctx, 7) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) + +// On Mips we don't have structures like user_regs or user_regs_struct in +// sys/user.h that we could use, so we just define regs_struct directly. +struct regs_struct { + unsigned long long regs[32]; +}; + +#define REG_a3 regs[7] +#define REG_a2 regs[6] +#define REG_a1 regs[5] +#define REG_a0 regs[4] +#define REG_v1 regs[3] +#define REG_v0 regs[2] + +#define SECCOMP_PT_RESULT(_regs) (_regs).REG_v0 +#define SECCOMP_PT_SYSCALL(_regs) (_regs).REG_v0 +#define SECCOMP_PT_PARM1(_regs) (_regs).REG_a0 +#define SECCOMP_PT_PARM2(_regs) (_regs).REG_a1 +#define SECCOMP_PT_PARM3(_regs) (_regs).REG_a2 +#define SECCOMP_PT_PARM4(_regs) (_regs).REG_a3 + +#elif defined(__aarch64__) +struct regs_struct { + unsigned long long regs[31]; + unsigned long long sp; + unsigned long long pc; + unsigned long long pstate; +}; + +#define SECCOMP_ARCH AUDIT_ARCH_AARCH64 + +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.regs[_reg]) + +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, 0) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, 8) +#define SECCOMP_IP(_ctx) (_ctx)->uc_mcontext.pc +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, 0) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, 1) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, 2) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, 3) +#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, 4) +#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, 5) + +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX \ + (offsetof(struct arch_seccomp_data, instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX \ + (offsetof(struct arch_seccomp_data, instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) \ + (offsetof(struct arch_seccomp_data, args) + 8 * (nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) \ + (offsetof(struct arch_seccomp_data, args) + 8 * (nr) + 0) + +#define SECCOMP_PT_RESULT(_regs) (_regs).regs[0] +#define SECCOMP_PT_SYSCALL(_regs) (_regs).regs[8] +#define SECCOMP_PT_IP(_regs) (_regs).pc +#define SECCOMP_PT_PARM1(_regs) (_regs).regs[0] +#define SECCOMP_PT_PARM2(_regs) (_regs).regs[1] +#define SECCOMP_PT_PARM3(_regs) (_regs).regs[2] +#define SECCOMP_PT_PARM4(_regs) (_regs).regs[3] +#define SECCOMP_PT_PARM5(_regs) (_regs).regs[4] +#define SECCOMP_PT_PARM6(_regs) (_regs).regs[5] +#else +#error Unsupported target platform + +#endif + +#endif // SANDBOX_LINUX_BPF_DSL_SECCOMP_MACROS_H_ diff --git a/sandbox/linux/bpf_dsl/syscall_set.cc b/sandbox/linux/bpf_dsl/syscall_set.cc new file mode 100644 index 0000000000..47810e99ac --- /dev/null +++ b/sandbox/linux/bpf_dsl/syscall_set.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/syscall_set.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/linux_syscall_ranges.h" + +namespace sandbox { + +namespace { + +#if defined(__mips__) && (_MIPS_SIM == _MIPS_SIM_ABI32) +// This is true for Mips O32 ABI. +static_assert(MIN_SYSCALL == __NR_Linux, "min syscall number should be 4000"); +#else +// This true for supported architectures (Intel and ARM EABI). +static_assert(MIN_SYSCALL == 0u, + "min syscall should always be zero"); +#endif + +// SyscallRange represents an inclusive range of system call numbers. +struct SyscallRange { + uint32_t first; + uint32_t last; +}; + +const SyscallRange kValidSyscallRanges[] = { + // First we iterate up to MAX_PUBLIC_SYSCALL, which is equal to MAX_SYSCALL + // on Intel architectures, but leaves room for private syscalls on ARM. + {MIN_SYSCALL, MAX_PUBLIC_SYSCALL}, +#if defined(__arm__) + // ARM EABI includes "ARM private" system calls starting at + // MIN_PRIVATE_SYSCALL, and a "ghost syscall private to the kernel" at + // MIN_GHOST_SYSCALL. + {MIN_PRIVATE_SYSCALL, MAX_PRIVATE_SYSCALL}, + {MIN_GHOST_SYSCALL, MAX_SYSCALL}, +#endif +}; + +} // namespace + +SyscallSet::Iterator SyscallSet::begin() const { + return Iterator(set_, false); +} + +SyscallSet::Iterator SyscallSet::end() const { + return Iterator(set_, true); +} + +bool SyscallSet::IsValid(uint32_t num) { + for (const SyscallRange& range : kValidSyscallRanges) { + if (num >= range.first && num <= range.last) { + return true; + } + } + return false; +} + +bool operator==(const SyscallSet& lhs, const SyscallSet& rhs) { + return (lhs.set_ == rhs.set_); +} + +SyscallSet::Iterator::Iterator(Set set, bool done) + : set_(set), done_(done), num_(0) { + // If the set doesn't contain 0, we need to skip to the next element. + if (!done && set_ == (IsValid(num_) ? Set::INVALID_ONLY : Set::VALID_ONLY)) { + ++*this; + } +} + +uint32_t SyscallSet::Iterator::operator*() const { + DCHECK(!done_); + return num_; +} + +SyscallSet::Iterator& SyscallSet::Iterator::operator++() { + DCHECK(!done_); + + num_ = NextSyscall(); + if (num_ == 0) { + done_ = true; + } + + return *this; +} + +// NextSyscall returns the next system call in the iterated system +// call set after |num_|, or 0 if no such system call exists. +uint32_t SyscallSet::Iterator::NextSyscall() const { + const bool want_valid = (set_ != Set::INVALID_ONLY); + const bool want_invalid = (set_ != Set::VALID_ONLY); + + for (const SyscallRange& range : kValidSyscallRanges) { + if (want_invalid && range.first > 0 && num_ < range.first - 1) { + // Even when iterating invalid syscalls, we only include the end points; + // so skip directly to just before the next (valid) range. + return range.first - 1; + } + if (want_valid && num_ < range.first) { + return range.first; + } + if (want_valid && num_ < range.last) { + return num_ + 1; + } + if (want_invalid && num_ <= range.last) { + return range.last + 1; + } + } + + if (want_invalid) { + // BPF programs only ever operate on unsigned quantities. So, + // that's how we iterate; we return values from + // 0..0xFFFFFFFFu. But there are places, where the kernel might + // interpret system call numbers as signed quantities, so the + // boundaries between signed and unsigned values are potential + // problem cases. We want to explicitly return these values from + // our iterator. + if (num_ < 0x7FFFFFFFu) + return 0x7FFFFFFFu; + if (num_ < 0x80000000u) + return 0x80000000u; + + if (num_ < 0xFFFFFFFFu) + return 0xFFFFFFFFu; + } + + return 0; +} + +bool operator==(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs) { + DCHECK(lhs.set_ == rhs.set_); + return (lhs.done_ == rhs.done_) && (lhs.num_ == rhs.num_); +} + +bool operator!=(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs) { + return !(lhs == rhs); +} + +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/syscall_set.h b/sandbox/linux/bpf_dsl/syscall_set.h new file mode 100644 index 0000000000..b9f076d932 --- /dev/null +++ b/sandbox/linux/bpf_dsl/syscall_set.h @@ -0,0 +1,103 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_SYSCALL_SET_H__ +#define SANDBOX_LINUX_BPF_DSL_SYSCALL_SET_H__ + +#include <stdint.h> + +#include <iterator> + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Iterates over the entire system call range from 0..0xFFFFFFFFu. This +// iterator is aware of how system calls look like and will skip quickly +// over ranges that can't contain system calls. It iterates more slowly +// whenever it reaches a range that is potentially problematic, returning +// the last invalid value before a valid range of system calls, and the +// first invalid value after a valid range of syscalls. It iterates over +// individual values whenever it is in the normal range for system calls +// (typically MIN_SYSCALL..MAX_SYSCALL). +// +// Example usage: +// for (uint32_t sysnum : SyscallSet::All()) { +// // Do something with sysnum. +// } +class SANDBOX_EXPORT SyscallSet { + public: + class Iterator; + + SyscallSet(const SyscallSet& ss) : set_(ss.set_) {} + ~SyscallSet() {} + + Iterator begin() const; + Iterator end() const; + + // All returns a SyscallSet that contains both valid and invalid + // system call numbers. + static SyscallSet All() { return SyscallSet(Set::ALL); } + + // ValidOnly returns a SyscallSet that contains only valid system + // call numbers. + static SyscallSet ValidOnly() { return SyscallSet(Set::VALID_ONLY); } + + // InvalidOnly returns a SyscallSet that contains only invalid + // system call numbers, but still omits numbers in the middle of a + // range of invalid system call numbers. + static SyscallSet InvalidOnly() { return SyscallSet(Set::INVALID_ONLY); } + + // IsValid returns whether |num| specifies a valid system call + // number. + static bool IsValid(uint32_t num); + + private: + enum class Set { ALL, VALID_ONLY, INVALID_ONLY }; + + explicit SyscallSet(Set set) : set_(set) {} + + Set set_; + + friend bool operator==(const SyscallSet&, const SyscallSet&); + DISALLOW_ASSIGN(SyscallSet); +}; + +SANDBOX_EXPORT bool operator==(const SyscallSet& lhs, const SyscallSet& rhs); + +// Iterator provides C++ input iterator semantics for traversing a +// SyscallSet. +class SyscallSet::Iterator + : public std::iterator<std::input_iterator_tag, uint32_t> { + public: + Iterator(const Iterator& it) + : set_(it.set_), done_(it.done_), num_(it.num_) {} + ~Iterator() {} + + uint32_t operator*() const; + Iterator& operator++(); + + private: + Iterator(Set set, bool done); + + uint32_t NextSyscall() const; + + Set set_; + bool done_; + uint32_t num_; + + friend SyscallSet; + friend bool operator==(const Iterator&, const Iterator&); + DISALLOW_ASSIGN(Iterator); +}; + +SANDBOX_EXPORT bool operator==(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs); +SANDBOX_EXPORT bool operator!=(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs); + +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_SYSCALL_SET_H__ diff --git a/sandbox/linux/bpf_dsl/syscall_set_unittest.cc b/sandbox/linux/bpf_dsl/syscall_set_unittest.cc new file mode 100644 index 0000000000..fafb6f6f73 --- /dev/null +++ b/sandbox/linux/bpf_dsl/syscall_set_unittest.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/syscall_set.h" + +#include <stdint.h> + +#include "sandbox/linux/bpf_dsl/linux_syscall_ranges.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +namespace { + +const SyscallSet kSyscallSets[] = { + SyscallSet::All(), + SyscallSet::InvalidOnly(), +}; + +SANDBOX_TEST(SyscallSet, Monotonous) { + for (const SyscallSet& set : kSyscallSets) { + uint32_t prev = 0; + bool have_prev = false; + for (uint32_t sysnum : set) { + if (have_prev) { + SANDBOX_ASSERT(sysnum > prev); + } else if (set == SyscallSet::All()) { + // The iterator should start at 0. + SANDBOX_ASSERT(sysnum == 0); + } + + prev = sysnum; + have_prev = true; + } + + // The iterator should always return 0xFFFFFFFFu as the last value. + SANDBOX_ASSERT(have_prev); + SANDBOX_ASSERT(prev == 0xFFFFFFFFu); + } +} + +// AssertRange checks that SyscallIterator produces all system call +// numbers in the inclusive range [min, max]. +void AssertRange(uint32_t min, uint32_t max) { + SANDBOX_ASSERT(min < max); + uint32_t prev = min - 1; + for (uint32_t sysnum : SyscallSet::All()) { + if (sysnum >= min && sysnum <= max) { + SANDBOX_ASSERT(prev == sysnum - 1); + prev = sysnum; + } + } + SANDBOX_ASSERT(prev == max); +} + +SANDBOX_TEST(SyscallSet, ValidSyscallRanges) { + AssertRange(MIN_SYSCALL, MAX_PUBLIC_SYSCALL); +#if defined(__arm__) + AssertRange(MIN_PRIVATE_SYSCALL, MAX_PRIVATE_SYSCALL); + AssertRange(MIN_GHOST_SYSCALL, MAX_SYSCALL); +#endif +} + +SANDBOX_TEST(SyscallSet, InvalidSyscalls) { + static const uint32_t kExpected[] = { +#if defined(__mips__) + 0, + MIN_SYSCALL - 1, +#endif + MAX_PUBLIC_SYSCALL + 1, +#if defined(__arm__) + MIN_PRIVATE_SYSCALL - 1, + MAX_PRIVATE_SYSCALL + 1, + MIN_GHOST_SYSCALL - 1, + MAX_SYSCALL + 1, +#endif + 0x7FFFFFFFu, + 0x80000000u, + 0xFFFFFFFFu, + }; + + for (const SyscallSet& set : kSyscallSets) { + size_t i = 0; + for (uint32_t sysnum : set) { + if (!SyscallSet::IsValid(sysnum)) { + SANDBOX_ASSERT(i < arraysize(kExpected)); + SANDBOX_ASSERT(kExpected[i] == sysnum); + ++i; + } + } + SANDBOX_ASSERT(i == arraysize(kExpected)); + } +} + +SANDBOX_TEST(SyscallSet, ValidOnlyIsOnlyValid) { + for (uint32_t sysnum : SyscallSet::ValidOnly()) { + SANDBOX_ASSERT(SyscallSet::IsValid(sysnum)); + } +} + +SANDBOX_TEST(SyscallSet, InvalidOnlyIsOnlyInvalid) { + for (uint32_t sysnum : SyscallSet::InvalidOnly()) { + SANDBOX_ASSERT(!SyscallSet::IsValid(sysnum)); + } +} + +SANDBOX_TEST(SyscallSet, AllIsValidOnlyPlusInvalidOnly) { + std::vector<uint32_t> merged; + const SyscallSet valid_only = SyscallSet::ValidOnly(); + const SyscallSet invalid_only = SyscallSet::InvalidOnly(); + std::merge(valid_only.begin(), + valid_only.end(), + invalid_only.begin(), + invalid_only.end(), + std::back_inserter(merged)); + + const SyscallSet all = SyscallSet::All(); + SANDBOX_ASSERT(merged == std::vector<uint32_t>(all.begin(), all.end())); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/trap_registry.h b/sandbox/linux/bpf_dsl/trap_registry.h new file mode 100644 index 0000000000..0a5d2f14cc --- /dev/null +++ b/sandbox/linux/bpf_dsl/trap_registry.h @@ -0,0 +1,73 @@ +// 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_BPF_DSL_TRAP_REGISTRY_H_ +#define SANDBOX_LINUX_BPF_DSL_TRAP_REGISTRY_H_ + +#include <stdint.h> + +#include "base/macros.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; + uint64_t instruction_pointer; + uint64_t args[6]; +}; + +namespace bpf_dsl { + +// TrapRegistry provides an interface for registering "trap handlers" +// by associating them with non-zero 16-bit trap IDs. Trap IDs should +// remain valid for the lifetime of the trap registry. +class SANDBOX_EXPORT TrapRegistry { + public: + // TrapFnc is a pointer to a function that fulfills the trap handler + // function signature. + // + // Trap handlers follow the calling convention of native system + // calls; e.g., to report an error, they return an exit code in the + // range -1..-4096 instead of directly modifying errno. However, + // modifying errno is harmless, as the original value will be + // restored afterwards. + // + // Trap handlers are executed from signal context and possibly an + // async-signal context, so they must be async-signal safe: + // http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html + typedef intptr_t (*TrapFnc)(const struct arch_seccomp_data& args, void* aux); + + // Add registers the specified trap handler tuple and returns a + // non-zero trap ID that uniquely identifies the tuple for the life + // time of the trap registry. If the same tuple is registered + // multiple times, the same value will be returned each time. + virtual uint16_t Add(TrapFnc fnc, const void* aux, bool safe) = 0; + + // EnableUnsafeTraps tries to enable unsafe traps and returns + // whether it was successful. This is a one-way operation. + // + // CAUTION: Enabling unsafe traps effectively defeats the security + // guarantees provided by the sandbox policy. TrapRegistry + // implementations should ensure unsafe traps are only enabled + // during testing. + virtual bool EnableUnsafeTraps() = 0; + + protected: + TrapRegistry() {} + + // TrapRegistry's destructor is intentionally non-virtual so that + // implementations can omit their destructor. Instead we protect against + // misuse by marking it protected. + ~TrapRegistry() {} + + DISALLOW_COPY_AND_ASSIGN(TrapRegistry); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_TRAP_REGISTRY_H_ diff --git a/sandbox/linux/bpf_dsl/verifier.cc b/sandbox/linux/bpf_dsl/verifier.cc new file mode 100644 index 0000000000..417c663e30 --- /dev/null +++ b/sandbox/linux/bpf_dsl/verifier.cc @@ -0,0 +1,396 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/verifier.h" + +#include <string.h> + +#include <limits> + +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/syscall_set.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" + +namespace sandbox { +namespace bpf_dsl { + +namespace { + +const uint64_t kLower32Bits = std::numeric_limits<uint32_t>::max(); +const uint64_t kUpper32Bits = static_cast<uint64_t>(kLower32Bits) << 32; + +struct State { + State(const std::vector<struct sock_filter>& p, + const struct arch_seccomp_data& d) + : program(p), data(d), ip(0), accumulator(0), acc_is_valid(false) {} + const std::vector<struct sock_filter>& program; + const struct arch_seccomp_data& data; + unsigned int ip; + uint32_t accumulator; + bool acc_is_valid; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(State); +}; + +uint32_t EvaluateErrorCode(bpf_dsl::PolicyCompiler* compiler, + const ErrorCode& code, + const struct arch_seccomp_data& data) { + if (code.error_type() == ErrorCode::ET_SIMPLE || + code.error_type() == ErrorCode::ET_TRAP) { + return code.err(); + } else if (code.error_type() == ErrorCode::ET_COND) { + if (code.width() == ErrorCode::TP_32BIT && + (data.args[code.argno()] >> 32) && + (data.args[code.argno()] & 0xFFFFFFFF80000000ull) != + 0xFFFFFFFF80000000ull) { + return compiler->Unexpected64bitArgument().err(); + } + bool equal = (data.args[code.argno()] & code.mask()) == code.value(); + return EvaluateErrorCode(compiler, equal ? *code.passed() : *code.failed(), + data); + } else { + return SECCOMP_RET_INVALID; + } +} + +bool VerifyErrorCode(bpf_dsl::PolicyCompiler* compiler, + const std::vector<struct sock_filter>& program, + struct arch_seccomp_data* data, + const ErrorCode& root_code, + const ErrorCode& code, + const char** err) { + if (code.error_type() == ErrorCode::ET_SIMPLE || + code.error_type() == ErrorCode::ET_TRAP) { + const uint32_t computed_ret = Verifier::EvaluateBPF(program, *data, err); + if (*err) { + return false; + } + const uint32_t policy_ret = EvaluateErrorCode(compiler, root_code, *data); + if (computed_ret != policy_ret) { + // For efficiency's sake, we'd much rather compare "computed_ret" + // against "code.err()". This works most of the time, but it doesn't + // always work for nested conditional expressions. The test values + // that we generate on the fly to probe expressions can trigger + // code flow decisions in multiple nodes of the decision tree, and the + // only way to compute the correct error code in that situation is by + // calling EvaluateErrorCode(). + *err = "Exit code from BPF program doesn't match"; + return false; + } + } else if (code.error_type() == ErrorCode::ET_COND) { + if (code.argno() < 0 || code.argno() >= 6) { + *err = "Invalid argument number in error code"; + return false; + } + + // TODO(mdempsky): The test values generated here try to provide good + // coverage for generated BPF instructions while avoiding combinatorial + // explosion on large policies. Ideally we would instead take a fuzzing-like + // approach and generate a bounded number of test cases regardless of policy + // size. + + // Verify that we can check a value for simple equality. + data->args[code.argno()] = code.value(); + if (!VerifyErrorCode(compiler, program, data, root_code, *code.passed(), + err)) { + return false; + } + + // If mask ignores any bits, verify that setting those bits is still + // detected as equality. + uint64_t ignored_bits = ~code.mask(); + if (code.width() == ErrorCode::TP_32BIT) { + ignored_bits = static_cast<uint32_t>(ignored_bits); + } + if ((ignored_bits & kLower32Bits) != 0) { + data->args[code.argno()] = code.value() | (ignored_bits & kLower32Bits); + if (!VerifyErrorCode(compiler, program, data, root_code, *code.passed(), + err)) { + return false; + } + } + if ((ignored_bits & kUpper32Bits) != 0) { + data->args[code.argno()] = code.value() | (ignored_bits & kUpper32Bits); + if (!VerifyErrorCode(compiler, program, data, root_code, *code.passed(), + err)) { + return false; + } + } + + // Verify that changing bits included in the mask is detected as inequality. + if ((code.mask() & kLower32Bits) != 0) { + data->args[code.argno()] = code.value() ^ (code.mask() & kLower32Bits); + if (!VerifyErrorCode(compiler, program, data, root_code, *code.failed(), + err)) { + return false; + } + } + if ((code.mask() & kUpper32Bits) != 0) { + data->args[code.argno()] = code.value() ^ (code.mask() & kUpper32Bits); + if (!VerifyErrorCode(compiler, program, data, root_code, *code.failed(), + err)) { + return false; + } + } + + if (code.width() == ErrorCode::TP_32BIT) { + // For 32-bit system call arguments, we emit additional instructions to + // validate the upper 32-bits. Here we test that validation. + + // Arbitrary 64-bit values should be rejected. + data->args[code.argno()] = 1ULL << 32; + if (!VerifyErrorCode(compiler, program, data, root_code, + compiler->Unexpected64bitArgument(), err)) { + return false; + } + + // Upper 32-bits set without the MSB of the lower 32-bits set should be + // rejected too. + data->args[code.argno()] = kUpper32Bits; + if (!VerifyErrorCode(compiler, program, data, root_code, + compiler->Unexpected64bitArgument(), err)) { + return false; + } + } + } else { + *err = "Attempting to return invalid error code from BPF program"; + return false; + } + return true; +} + +void Ld(State* state, const struct sock_filter& insn, const char** err) { + if (BPF_SIZE(insn.code) != BPF_W || BPF_MODE(insn.code) != BPF_ABS || + insn.jt != 0 || insn.jf != 0) { + *err = "Invalid BPF_LD instruction"; + return; + } + if (insn.k < sizeof(struct arch_seccomp_data) && (insn.k & 3) == 0) { + // We only allow loading of properly aligned 32bit quantities. + memcpy(&state->accumulator, + reinterpret_cast<const char*>(&state->data) + insn.k, 4); + } else { + *err = "Invalid operand in BPF_LD instruction"; + return; + } + state->acc_is_valid = true; + return; +} + +void Jmp(State* state, const struct sock_filter& insn, const char** err) { + if (BPF_OP(insn.code) == BPF_JA) { + if (state->ip + insn.k + 1 >= state->program.size() || + state->ip + insn.k + 1 <= state->ip) { + compilation_failure: + *err = "Invalid BPF_JMP instruction"; + return; + } + state->ip += insn.k; + } else { + if (BPF_SRC(insn.code) != BPF_K || !state->acc_is_valid || + state->ip + insn.jt + 1 >= state->program.size() || + state->ip + insn.jf + 1 >= state->program.size()) { + goto compilation_failure; + } + switch (BPF_OP(insn.code)) { + case BPF_JEQ: + if (state->accumulator == insn.k) { + state->ip += insn.jt; + } else { + state->ip += insn.jf; + } + break; + case BPF_JGT: + if (state->accumulator > insn.k) { + state->ip += insn.jt; + } else { + state->ip += insn.jf; + } + break; + case BPF_JGE: + if (state->accumulator >= insn.k) { + state->ip += insn.jt; + } else { + state->ip += insn.jf; + } + break; + case BPF_JSET: + if (state->accumulator & insn.k) { + state->ip += insn.jt; + } else { + state->ip += insn.jf; + } + break; + default: + goto compilation_failure; + } + } +} + +uint32_t Ret(State*, const struct sock_filter& insn, const char** err) { + if (BPF_SRC(insn.code) != BPF_K) { + *err = "Invalid BPF_RET instruction"; + return 0; + } + return insn.k; +} + +void Alu(State* state, const struct sock_filter& insn, const char** err) { + if (BPF_OP(insn.code) == BPF_NEG) { + state->accumulator = -state->accumulator; + return; + } else { + if (BPF_SRC(insn.code) != BPF_K) { + *err = "Unexpected source operand in arithmetic operation"; + return; + } + switch (BPF_OP(insn.code)) { + case BPF_ADD: + state->accumulator += insn.k; + break; + case BPF_SUB: + state->accumulator -= insn.k; + break; + case BPF_MUL: + state->accumulator *= insn.k; + break; + case BPF_DIV: + if (!insn.k) { + *err = "Illegal division by zero"; + break; + } + state->accumulator /= insn.k; + break; + case BPF_MOD: + if (!insn.k) { + *err = "Illegal division by zero"; + break; + } + state->accumulator %= insn.k; + break; + case BPF_OR: + state->accumulator |= insn.k; + break; + case BPF_XOR: + state->accumulator ^= insn.k; + break; + case BPF_AND: + state->accumulator &= insn.k; + break; + case BPF_LSH: + if (insn.k > 32) { + *err = "Illegal shift operation"; + break; + } + state->accumulator <<= insn.k; + break; + case BPF_RSH: + if (insn.k > 32) { + *err = "Illegal shift operation"; + break; + } + state->accumulator >>= insn.k; + break; + default: + *err = "Invalid operator in arithmetic operation"; + break; + } + } +} + +} // namespace + +bool Verifier::VerifyBPF(bpf_dsl::PolicyCompiler* compiler, + const std::vector<struct sock_filter>& program, + const bpf_dsl::Policy& policy, + const char** err) { + *err = NULL; + for (uint32_t sysnum : SyscallSet::All()) { + // We ideally want to iterate over the full system call range and values + // just above and just below this range. This gives us the full result set + // of the "evaluators". + // On Intel systems, this can fail in a surprising way, as a cleared bit 30 + // indicates either i386 or x86-64; and a set bit 30 indicates x32. And + // unless we pay attention to setting this bit correctly, an early check in + // our BPF program will make us fail with a misleading error code. + struct arch_seccomp_data data = {static_cast<int>(sysnum), + static_cast<uint32_t>(SECCOMP_ARCH)}; +#if defined(__i386__) || defined(__x86_64__) +#if defined(__x86_64__) && defined(__ILP32__) + if (!(sysnum & 0x40000000u)) { + continue; + } +#else + if (sysnum & 0x40000000u) { + continue; + } +#endif +#endif + ErrorCode code = SyscallSet::IsValid(sysnum) + ? policy.EvaluateSyscall(sysnum)->Compile(compiler) + : policy.InvalidSyscall()->Compile(compiler); + if (!VerifyErrorCode(compiler, program, &data, code, code, err)) { + return false; + } + } + return true; +} + +uint32_t Verifier::EvaluateBPF(const std::vector<struct sock_filter>& program, + const struct arch_seccomp_data& data, + const char** err) { + *err = NULL; + if (program.size() < 1 || program.size() >= SECCOMP_MAX_PROGRAM_SIZE) { + *err = "Invalid program length"; + return 0; + } + for (State state(program, data); !*err; ++state.ip) { + if (state.ip >= program.size()) { + *err = "Invalid instruction pointer in BPF program"; + break; + } + const struct sock_filter& insn = program[state.ip]; + switch (BPF_CLASS(insn.code)) { + case BPF_LD: + Ld(&state, insn, err); + break; + case BPF_JMP: + Jmp(&state, insn, err); + break; + case BPF_RET: { + uint32_t r = Ret(&state, insn, err); + switch (r & SECCOMP_RET_ACTION) { + case SECCOMP_RET_TRAP: + case SECCOMP_RET_ERRNO: + case SECCOMP_RET_TRACE: + case SECCOMP_RET_ALLOW: + break; + case SECCOMP_RET_KILL: // We don't ever generate this + case SECCOMP_RET_INVALID: // Should never show up in BPF program + default: + *err = "Unexpected return code found in BPF program"; + return 0; + } + return r; + } + case BPF_ALU: + Alu(&state, insn, err); + break; + default: + *err = "Unexpected instruction in BPF program"; + break; + } + } + return 0; +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/verifier.h b/sandbox/linux/bpf_dsl/verifier.h new file mode 100644 index 0000000000..b0435d1aa1 --- /dev/null +++ b/sandbox/linux/bpf_dsl/verifier.h @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_VERIFIER_H__ +#define SANDBOX_LINUX_BPF_DSL_VERIFIER_H__ + +#include <stdint.h> + +#include <vector> + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +struct sock_filter; + +namespace sandbox { +struct arch_seccomp_data; + +namespace bpf_dsl { +class Policy; +class PolicyCompiler; + +class SANDBOX_EXPORT Verifier { + public: + // Evaluate the BPF program for all possible inputs and verify that it + // computes the correct result. We use the "evaluators" to determine + // the full set of possible inputs that we have to iterate over. + // Returns success, if the BPF filter accurately reflects the rules + // set by the "evaluators". + // Upon success, "err" is set to NULL. Upon failure, it contains a static + // error message that does not need to be free()'d. + static bool VerifyBPF(bpf_dsl::PolicyCompiler* compiler, + const std::vector<struct sock_filter>& program, + const bpf_dsl::Policy& policy, + const char** err); + + // Evaluate a given BPF program for a particular set of system call + // parameters. If evaluation failed for any reason, "err" will be set to + // a non-NULL error string. Otherwise, the BPF program's result will be + // returned by the function and "err" is NULL. + // We do not actually implement the full BPF state machine, but only the + // parts that can actually be generated by our BPF compiler. If this code + // is used for purposes other than verifying the output of the sandbox's + // BPF compiler, we might have to extend this BPF interpreter. + static uint32_t EvaluateBPF(const std::vector<struct sock_filter>& program, + const struct arch_seccomp_data& data, + const char** err); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Verifier); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_VERIFIER_H__ diff --git a/sandbox/linux/integration_tests/DEPS b/sandbox/linux/integration_tests/DEPS new file mode 100644 index 0000000000..d50729cea3 --- /dev/null +++ b/sandbox/linux/integration_tests/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+sandbox/linux/bpf_dsl", + "+sandbox/linux/seccomp-bpf", + "+sandbox/linux/services", + "+sandbox/linux/syscall_broker", + "+sandbox/linux/system_headers", +] diff --git a/sandbox/linux/integration_tests/bpf_dsl_seccomp_unittest.cc b/sandbox/linux/integration_tests/bpf_dsl_seccomp_unittest.cc new file mode 100644 index 0000000000..e884774146 --- /dev/null +++ b/sandbox/linux/integration_tests/bpf_dsl_seccomp_unittest.cc @@ -0,0 +1,2259 @@ +// Copyright 2015 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 <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> +#include <sys/utsname.h> +#include <unistd.h> +#include <sys/socket.h> + +#if defined(ANDROID) +// Work-around for buggy headers in Android's NDK +#define __user +#endif +#include <linux/futex.h> + +#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 "base/synchronization/waitable_event.h" +#include "base/sys_info.h" +#include "base/threading/thread.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/linux_syscall_ranges.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/seccomp-bpf/trap.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/services/thread_helpers.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/tests/scoped_temporary_file.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Workaround for Android's prctl.h file. +#ifndef PR_GET_ENDIAN +#define PR_GET_ENDIAN 19 +#endif +#ifndef PR_CAPBSET_READ +#define PR_CAPBSET_READ 23 +#define PR_CAPBSET_DROP 24 +#endif + +namespace sandbox { +namespace bpf_dsl { + +namespace { + +const int kExpectedReturnValue = 42; +const char kSandboxDebuggingEnv[] = "CHROME_SANDBOX_DEBUGGING"; + +// Set the global environment to allow the use of UnsafeTrap() policies. +void EnableUnsafeTraps() { + // The use of UnsafeTrap() causes us to print a warning message. This is + // generally desirable, but it results in the unittest failing, as it doesn't + // expect any messages on "stderr". So, temporarily disable messages. The + // BPF_TEST() is guaranteed to turn messages back on, after the policy + // function has completed. + setenv(kSandboxDebuggingEnv, "t", 0); + Die::SuppressInfoMessages(true); +} + +// BPF_TEST does a lot of the boiler-plate code around setting up a +// policy and optional passing data between the caller, the policy and +// any Trap() handlers. This is great for writing short and concise tests, +// and it helps us accidentally forgetting any of the crucial steps in +// setting up the sandbox. But it wouldn't hurt to have at least one test +// that explicitly walks through all these steps. + +intptr_t IncreaseCounter(const struct arch_seccomp_data& args, void* aux) { + BPF_ASSERT(aux); + int* counter = static_cast<int*>(aux); + return (*counter)++; +} + +class VerboseAPITestingPolicy : public Policy { + public: + explicit VerboseAPITestingPolicy(int* counter_ptr) + : counter_ptr_(counter_ptr) {} + ~VerboseAPITestingPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno == __NR_uname) { + return Trap(IncreaseCounter, counter_ptr_); + } + return Allow(); + } + + private: + int* counter_ptr_; + + DISALLOW_COPY_AND_ASSIGN(VerboseAPITestingPolicy); +}; + +SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(VerboseAPITesting)) { + if (SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::SINGLE_THREADED)) { + static int counter = 0; + + SandboxBPF sandbox(new VerboseAPITestingPolicy(&counter)); + BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::SeccompLevel::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 + +class BlacklistNanosleepPolicy : public Policy { + public: + BlacklistNanosleepPolicy() {} + ~BlacklistNanosleepPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + switch (sysno) { + case __NR_nanosleep: + return Error(EACCES); + default: + return Allow(); + } + } + + static void AssertNanosleepFails() { + const struct timespec ts = {0, 0}; + errno = 0; + BPF_ASSERT_EQ(-1, HANDLE_EINTR(syscall(__NR_nanosleep, &ts, NULL))); + BPF_ASSERT_EQ(EACCES, errno); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BlacklistNanosleepPolicy); +}; + +BPF_TEST_C(SandboxBPF, ApplyBasicBlacklistPolicy, BlacklistNanosleepPolicy) { + BlacklistNanosleepPolicy::AssertNanosleepFails(); +} + +BPF_TEST_C(SandboxBPF, UseVsyscall, BlacklistNanosleepPolicy) { + time_t current_time; + // time() is implemented as a vsyscall. With an older glibc, with + // vsyscall=emulate and some versions of the seccomp BPF patch + // we may get SIGKILL-ed. Detect this! + BPF_ASSERT_NE(static_cast<time_t>(-1), time(¤t_time)); +} + +// Now do a simple whitelist test + +class WhitelistGetpidPolicy : public Policy { + public: + WhitelistGetpidPolicy() {} + ~WhitelistGetpidPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + switch (sysno) { + case __NR_getpid: + case __NR_exit_group: + return Allow(); + default: + return Error(ENOMEM); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(WhitelistGetpidPolicy); +}; + +BPF_TEST_C(SandboxBPF, ApplyBasicWhitelistPolicy, WhitelistGetpidPolicy) { + // getpid() should be allowed + errno = 0; + BPF_ASSERT(sys_getpid() > 0); + BPF_ASSERT(errno == 0); + + // getpgid() should be denied + BPF_ASSERT(getpgid(0) == -1); + BPF_ASSERT(errno == ENOMEM); +} + +// 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); + *(static_cast<int*>(aux)) = kExpectedReturnValue; + return -ENOMEM; +} + +class BlacklistNanosleepTrapPolicy : public Policy { + public: + explicit BlacklistNanosleepTrapPolicy(int* aux) : aux_(aux) {} + ~BlacklistNanosleepTrapPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + switch (sysno) { + case __NR_nanosleep: + return Trap(EnomemHandler, aux_); + default: + return Allow(); + } + } + + private: + int* aux_; + + DISALLOW_COPY_AND_ASSIGN(BlacklistNanosleepTrapPolicy); +}; + +BPF_TEST(SandboxBPF, + BasicBlacklistWithSigsys, + BlacklistNanosleepTrapPolicy, + int /* (*BPF_AUX) */) { + // getpid() should work properly + errno = 0; + BPF_ASSERT(sys_getpid() > 0); + BPF_ASSERT(errno == 0); + + // Our Auxiliary Data, should be reset by the signal handler + *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); +} + +// A simple test that verifies we can return arbitrary errno values. + +class ErrnoTestPolicy : public Policy { + public: + ErrnoTestPolicy() {} + ~ErrnoTestPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override; + + private: + DISALLOW_COPY_AND_ASSIGN(ErrnoTestPolicy); +}; + +ResultExpr ErrnoTestPolicy::EvaluateSyscall(int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + switch (sysno) { + case __NR_dup3: // dup2 is a wrapper of dup3 in android +#if defined(__NR_dup2) + case __NR_dup2: +#endif + // Pretend that dup2() worked, but don't actually do anything. + return Error(0); + case __NR_setuid: +#if defined(__NR_setuid32) + case __NR_setuid32: +#endif + // Return errno = 1. + return Error(1); + case __NR_setgid: +#if defined(__NR_setgid32) + case __NR_setgid32: +#endif + // Return maximum errno value (typically 4095). + return Error(ErrorCode::ERR_MAX_ERRNO); + case __NR_uname: + // Return errno = 42; + return Error(42); + default: + return Allow(); + } +} + +BPF_TEST_C(SandboxBPF, ErrnoTest, ErrnoTestPolicy) { + // Verify that dup2() returns success, but doesn't actually run. + int fds[4]; + BPF_ASSERT(pipe(fds) == 0); + BPF_ASSERT(pipe(fds + 2) == 0); + BPF_ASSERT(dup2(fds[2], fds[0]) == 0); + char buf[1] = {}; + BPF_ASSERT(write(fds[1], "\x55", 1) == 1); + BPF_ASSERT(write(fds[3], "\xAA", 1) == 1); + BPF_ASSERT(read(fds[0], buf, 1) == 1); + + // If dup2() executed, we will read \xAA, but it dup2() has been turned + // into a no-op by our policy, then we will read \x55. + BPF_ASSERT(buf[0] == '\x55'); + + // Verify that we can return the minimum and maximum errno values. + errno = 0; + BPF_ASSERT(setuid(0) == -1); + BPF_ASSERT(errno == 1); + + // On Android, errno is only supported up to 255, otherwise errno + // processing is skipped. + // We work around this (crbug.com/181647). + if (sandbox::IsAndroid() && setgid(0) != -1) { + errno = 0; + BPF_ASSERT(setgid(0) == -ErrorCode::ERR_MAX_ERRNO); + BPF_ASSERT(errno == 0); + } else { + errno = 0; + BPF_ASSERT(setgid(0) == -1); + BPF_ASSERT(errno == ErrorCode::ERR_MAX_ERRNO); + } + + // Finally, test an errno in between the minimum and maximum. + errno = 0; + struct utsname uts_buf; + BPF_ASSERT(uname(&uts_buf) == -1); + BPF_ASSERT(errno == 42); +} + +// Testing the stacking of two sandboxes + +class StackingPolicyPartOne : public Policy { + public: + StackingPolicyPartOne() {} + ~StackingPolicyPartOne() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + switch (sysno) { + case __NR_getppid: { + const Arg<int> arg(0); + return If(arg == 0, Allow()).Else(Error(EPERM)); + } + default: + return Allow(); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(StackingPolicyPartOne); +}; + +class StackingPolicyPartTwo : public Policy { + public: + StackingPolicyPartTwo() {} + ~StackingPolicyPartTwo() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + switch (sysno) { + case __NR_getppid: { + const Arg<int> arg(0); + return If(arg == 0, Error(EINVAL)).Else(Allow()); + } + default: + return Allow(); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(StackingPolicyPartTwo); +}; + +BPF_TEST_C(SandboxBPF, StackingPolicy, StackingPolicyPartOne) { + errno = 0; + BPF_ASSERT(syscall(__NR_getppid, 0) > 0); + BPF_ASSERT(errno == 0); + + BPF_ASSERT(syscall(__NR_getppid, 1) == -1); + BPF_ASSERT(errno == EPERM); + + // Stack a second sandbox with its own policy. Verify that we can further + // restrict filters, but we cannot relax existing filters. + SandboxBPF sandbox(new StackingPolicyPartTwo()); + BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::SeccompLevel::SINGLE_THREADED)); + + errno = 0; + BPF_ASSERT(syscall(__NR_getppid, 0) == -1); + BPF_ASSERT(errno == EINVAL); + + BPF_ASSERT(syscall(__NR_getppid, 1) == -1); + BPF_ASSERT(errno == EPERM); +} + +// A more complex, but synthetic policy. This tests the correctness of the BPF +// program by iterating through all syscalls and checking for an errno that +// depends on the syscall number. Unlike the Verifier, this exercises the BPF +// interpreter in the kernel. + +// We try to make sure we exercise optimizations in the BPF compiler. We make +// sure that the compiler can have an opportunity to coalesce syscalls with +// contiguous numbers and we also make sure that disjoint sets can return the +// same errno. +int SysnoToRandomErrno(int sysno) { + // Small contiguous sets of 3 system calls return an errno equal to the + // index of that set + 1 (so that we never return a NUL errno). + return ((sysno & ~3) >> 2) % 29 + 1; +} + +class SyntheticPolicy : public Policy { + public: + SyntheticPolicy() {} + ~SyntheticPolicy() override {} + + ResultExpr EvaluateSyscall(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 Allow(); + } + return Error(SysnoToRandomErrno(sysno)); + } + + 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 >= + static_cast<int>(MAX_PUBLIC_SYSCALL)); + + for (int syscall_number = static_cast<int>(MIN_SYSCALL); + syscall_number <= static_cast<int>(MAX_PUBLIC_SYSCALL); + ++syscall_number) { + if (syscall_number == __NR_exit_group || syscall_number == __NR_write) { + // exit_group() is special + continue; + } + errno = 0; + BPF_ASSERT(syscall(syscall_number) == -1); + BPF_ASSERT(errno == SysnoToRandomErrno(syscall_number)); + } +} + +#if defined(__arm__) +// A simple policy that tests whether ARM private system calls are supported +// by our BPF compiler and by the BPF interpreter in the kernel. + +// For ARM private system calls, return an errno equal to their offset from +// MIN_PRIVATE_SYSCALL plus 1 (to avoid NUL errno). +int ArmPrivateSysnoToErrno(int sysno) { + if (sysno >= static_cast<int>(MIN_PRIVATE_SYSCALL) && + sysno <= static_cast<int>(MAX_PRIVATE_SYSCALL)) { + return (sysno - MIN_PRIVATE_SYSCALL) + 1; + } else { + return ENOSYS; + } +} + +class ArmPrivatePolicy : public Policy { + public: + ArmPrivatePolicy() {} + ~ArmPrivatePolicy() override {} + + ResultExpr EvaluateSyscall(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 Error(ArmPrivateSysnoToErrno(sysno)); + } + return Allow(); + } + + 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) { + errno = 0; + BPF_ASSERT(syscall(syscall_number) == -1); + BPF_ASSERT(errno == ArmPrivateSysnoToErrno(syscall_number)); + } +} +#endif // defined(__arm__) + +intptr_t CountSyscalls(const struct arch_seccomp_data& args, void* aux) { + // Count all invocations of our callback function. + ++*reinterpret_cast<int*>(aux); + + // Verify that within the callback function all filtering is temporarily + // disabled. + BPF_ASSERT(sys_getpid() > 1); + + // Verify that we can now call the underlying system call without causing + // infinite recursion. + return SandboxBPF::ForwardSyscall(args); +} + +class GreyListedPolicy : public Policy { + public: + explicit GreyListedPolicy(int* aux) : aux_(aux) { + // Set the global environment for unsafe traps once. + EnableUnsafeTraps(); + } + ~GreyListedPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + // Some system calls must always be allowed, if our policy wants to make + // use of UnsafeTrap() + if (SandboxBPF::IsRequiredForUnsafeTrap(sysno)) { + return Allow(); + } else if (sysno == __NR_getpid) { + // Disallow getpid() + return Error(EPERM); + } else { + // Allow (and count) all other system calls. + return UnsafeTrap(CountSyscalls, aux_); + } + } + + private: + int* aux_; + + DISALLOW_COPY_AND_ASSIGN(GreyListedPolicy); +}; + +BPF_TEST(SandboxBPF, GreyListedPolicy, GreyListedPolicy, int /* (*BPF_AUX) */) { + BPF_ASSERT(sys_getpid() == -1); + BPF_ASSERT(errno == EPERM); + BPF_ASSERT(*BPF_AUX == 0); + BPF_ASSERT(syscall(__NR_geteuid) == syscall(__NR_getuid)); + BPF_ASSERT(*BPF_AUX == 2); + char name[17] = {}; + BPF_ASSERT(!syscall(__NR_prctl, + PR_GET_NAME, + name, + (void*)NULL, + (void*)NULL, + (void*)NULL)); + BPF_ASSERT(*BPF_AUX == 3); + BPF_ASSERT(*name); +} + +SANDBOX_TEST(SandboxBPF, EnableUnsafeTrapsInSigSysHandler) { + // Disabling warning messages that could confuse our test framework. + setenv(kSandboxDebuggingEnv, "t", 0); + Die::SuppressInfoMessages(true); + + unsetenv(kSandboxDebuggingEnv); + SANDBOX_ASSERT(Trap::Registry()->EnableUnsafeTraps() == false); + setenv(kSandboxDebuggingEnv, "", 1); + SANDBOX_ASSERT(Trap::Registry()->EnableUnsafeTraps() == false); + setenv(kSandboxDebuggingEnv, "t", 1); + SANDBOX_ASSERT(Trap::Registry()->EnableUnsafeTraps() == true); +} + +intptr_t PrctlHandler(const struct arch_seccomp_data& args, void*) { + if (args.args[0] == PR_CAPBSET_DROP && static_cast<int>(args.args[1]) == -1) { + // prctl(PR_CAPBSET_DROP, -1) is never valid. The kernel will always + // return an error. But our handler allows this call. + return 0; + } else { + return SandboxBPF::ForwardSyscall(args); + } +} + +class PrctlPolicy : public Policy { + public: + PrctlPolicy() {} + ~PrctlPolicy() override {} + + ResultExpr EvaluateSyscall(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 UnsafeTrap(PrctlHandler, NULL); + } + + // Allow all other system calls. + return Allow(); + } + + 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( + !prctl(PR_CAPBSET_DROP, -1, (void*)NULL, (void*)NULL, (void*)NULL)); + + // Verify that the call will fail, if it makes it all the way to the kernel. + BPF_ASSERT( + prctl(PR_CAPBSET_DROP, -2, (void*)NULL, (void*)NULL, (void*)NULL) == -1); + + // And verify that other uses of prctl() work just fine. + char name[17] = {}; + BPF_ASSERT(!syscall(__NR_prctl, + PR_GET_NAME, + name, + (void*)NULL, + (void*)NULL, + (void*)NULL)); + BPF_ASSERT(*name); + + // Finally, verify that system calls other than prctl() are completely + // unaffected by our policy. + struct utsname uts = {}; + BPF_ASSERT(!uname(&uts)); + BPF_ASSERT(!strcmp(uts.sysname, "Linux")); +} + +intptr_t AllowRedirectedSyscall(const struct arch_seccomp_data& args, void*) { + return SandboxBPF::ForwardSyscall(args); +} + +class RedirectAllSyscallsPolicy : public Policy { + public: + RedirectAllSyscallsPolicy() {} + ~RedirectAllSyscallsPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override; + + private: + DISALLOW_COPY_AND_ASSIGN(RedirectAllSyscallsPolicy); +}; + +ResultExpr RedirectAllSyscallsPolicy::EvaluateSyscall(int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + setenv(kSandboxDebuggingEnv, "t", 0); + Die::SuppressInfoMessages(true); + + // Some system calls must always be allowed, if our policy wants to make + // use of UnsafeTrap() + if (SandboxBPF::IsRequiredForUnsafeTrap(sysno)) + return Allow(); + return UnsafeTrap(AllowRedirectedSyscall, NULL); +} + +#if !defined(ADDRESS_SANITIZER) +// ASan does not allow changing the signal handler for SIGBUS, and treats it as +// a fatal signal. + +int bus_handler_fd_ = -1; + +void SigBusHandler(int, siginfo_t* info, void* void_context) { + BPF_ASSERT(write(bus_handler_fd_, "\x55", 1) == 1); +} + +BPF_TEST_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 + // in more traditional ways. This test verifies that basic functionality + // of SIGBUS is not impacted, but it is certainly possibly to construe + // more complex uses of signals where our use of the SIGBUS mask is not + // 100% transparent. This is expected behavior. + int fds[2]; + BPF_ASSERT(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); + bus_handler_fd_ = fds[1]; + struct sigaction sa = {}; + sa.sa_sigaction = SigBusHandler; + sa.sa_flags = SA_SIGINFO; + BPF_ASSERT(sigaction(SIGBUS, &sa, NULL) == 0); + raise(SIGBUS); + char c = '\000'; + BPF_ASSERT(read(fds[0], &c, 1) == 1); + BPF_ASSERT(close(fds[0]) == 0); + BPF_ASSERT(close(fds[1]) == 0); + BPF_ASSERT(c == 0x55); +} +#endif // !defined(ADDRESS_SANITIZER) + +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 + // all of our efforts. So, it makes sense to test that sigprocmask() + // works, even if we have a policy in place that makes use of UnsafeTrap(). + // In practice, this works because we force sigprocmask() to be handled + // entirely in the kernel. + sigset_t mask0, mask1, mask2; + + // Call sigprocmask() to verify that SIGUSR2 wasn't blocked, if we didn't + // change the mask (it shouldn't have been, as it isn't blocked by default + // in POSIX). + // + // Use SIGUSR2 because Android seems to use SIGUSR1 for some purpose. + sigemptyset(&mask0); + BPF_ASSERT(!sigprocmask(SIG_BLOCK, &mask0, &mask1)); + BPF_ASSERT(!sigismember(&mask1, SIGUSR2)); + + // Try again, and this time we verify that we can block it. This + // requires a second call to sigprocmask(). + sigaddset(&mask0, SIGUSR2); + BPF_ASSERT(!sigprocmask(SIG_BLOCK, &mask0, NULL)); + BPF_ASSERT(!sigprocmask(SIG_BLOCK, NULL, &mask2)); + BPF_ASSERT(sigismember(&mask2, SIGUSR2)); +} + +BPF_TEST_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() + // uses some other method to make system calls, then it is responsible + // for computing the correct return code. + // This test verifies that ForwardSyscall() does the correct thing. + + // The glibc system wrapper will ultimately set errno for us. So, from normal + // userspace, all of this should be completely transparent. + errno = 0; + BPF_ASSERT(close(-1) == -1); + BPF_ASSERT(errno == EBADF); + + // Explicitly avoid the glibc wrapper. This is not normally the way anybody + // would make system calls, but it allows us to verify that we don't + // accidentally mess with errno, when we shouldn't. + errno = 0; + struct arch_seccomp_data args = {}; + args.nr = __NR_close; + args.args[0] = -1; + BPF_ASSERT(SandboxBPF::ForwardSyscall(args) == -EBADF); + BPF_ASSERT(errno == 0); +} + +// Simple test demonstrating how to use SandboxBPF::Cond() + +class SimpleCondTestPolicy : public Policy { + public: + SimpleCondTestPolicy() {} + ~SimpleCondTestPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override; + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleCondTestPolicy); +}; + +ResultExpr SimpleCondTestPolicy::EvaluateSyscall(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. + int flags_argument_position = -1; + switch (sysno) { +#if defined(__NR_open) + case __NR_open: + flags_argument_position = 1; +#endif + case __NR_openat: { // open can be a wrapper for openat(2). + if (sysno == __NR_openat) + flags_argument_position = 2; + + // Allow opening files for reading, but don't allow writing. + static_assert(O_RDONLY == 0, "O_RDONLY must be all zero bits"); + const Arg<int> flags(flags_argument_position); + return If((flags & O_ACCMODE) != 0, Error(EROFS)).Else(Allow()); + } + case __NR_prctl: { + // Allow prctl(PR_SET_DUMPABLE) and prctl(PR_GET_DUMPABLE), but + // disallow everything else. + const Arg<int> option(0); + return If(option == PR_SET_DUMPABLE || option == PR_GET_DUMPABLE, Allow()) + .Else(Error(ENOMEM)); + } + default: + return Allow(); + } +} + +BPF_TEST_C(SandboxBPF, SimpleCondTest, SimpleCondTestPolicy) { + int fd; + BPF_ASSERT((fd = open("/proc/self/comm", O_RDWR)) == -1); + BPF_ASSERT(errno == EROFS); + BPF_ASSERT((fd = open("/proc/self/comm", O_RDONLY)) >= 0); + close(fd); + + int ret; + BPF_ASSERT((ret = prctl(PR_GET_DUMPABLE)) >= 0); + BPF_ASSERT(prctl(PR_SET_DUMPABLE, 1 - ret) == 0); + BPF_ASSERT(prctl(PR_GET_ENDIAN, &ret) == -1); + BPF_ASSERT(errno == ENOMEM); +} + +// This test exercises the SandboxBPF::Cond() method by building a complex +// tree of conditional equality operations. It then makes system calls and +// verifies that they return the values that we expected from our BPF +// program. +class EqualityStressTest { + public: + EqualityStressTest() { + // We want a deterministic test + srand(0); + + // Iterates over system call numbers and builds a random tree of + // equality tests. + // We are actually constructing a graph of ArgValue objects. This + // graph will later be used to a) compute our sandbox policy, and + // b) drive the code that verifies the output from the BPF program. + static_assert( + kNumTestCases < (int)(MAX_PUBLIC_SYSCALL - MIN_SYSCALL - 10), + "kNumTestCases must be significantly smaller than the number " + "of system calls"); + for (int sysno = MIN_SYSCALL, end = kNumTestCases; sysno < end; ++sysno) { + if (IsReservedSyscall(sysno)) { + // Skip reserved system calls. This ensures that our test frame + // work isn't impacted by the fact that we are overriding + // a lot of different system calls. + ++end; + arg_values_.push_back(NULL); + } else { + arg_values_.push_back( + RandomArgValue(rand() % kMaxArgs, 0, rand() % kMaxArgs)); + } + } + } + + ~EqualityStressTest() { + for (std::vector<ArgValue*>::iterator iter = arg_values_.begin(); + iter != arg_values_.end(); + ++iter) { + DeleteArgValue(*iter); + } + } + + ResultExpr Policy(int sysno) { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno < 0 || sysno >= (int)arg_values_.size() || + IsReservedSyscall(sysno)) { + // We only return ErrorCode values for the system calls that + // are part of our test data. Every other system call remains + // allowed. + return Allow(); + } else { + // ToErrorCode() turns an ArgValue object into an ErrorCode that is + // suitable for use by a sandbox policy. + return ToErrorCode(arg_values_[sysno]); + } + } + + void VerifyFilter() { + // Iterate over all system calls. Skip the system calls that have + // previously been determined as being reserved. + for (int sysno = 0; sysno < (int)arg_values_.size(); ++sysno) { + if (!arg_values_[sysno]) { + // Skip reserved system calls. + continue; + } + // Verify that system calls return the values that we expect them to + // return. This involves passing different combinations of system call + // parameters in order to exercise all possible code paths through the + // BPF filter program. + // We arbitrarily start by setting all six system call arguments to + // zero. And we then recursive traverse our tree of ArgValues to + // determine the necessary combinations of parameters. + intptr_t args[6] = {}; + Verify(sysno, args, *arg_values_[sysno]); + } + } + + private: + struct ArgValue { + int argno; // Argument number to inspect. + int size; // Number of test cases (must be > 0). + struct Tests { + uint32_t k_value; // Value to compare syscall arg against. + int err; // If non-zero, errno value to return. + struct ArgValue* arg_value; // Otherwise, more args needs inspecting. + }* tests; + int err; // If none of the tests passed, this is what + struct ArgValue* arg_value; // we'll return (this is the "else" branch). + }; + + bool IsReservedSyscall(int sysno) { + // There are a handful of system calls that we should never use in our + // test cases. These system calls are needed to allow the test framework + // to run properly. + // If we wanted to write fully generic code, there are more system calls + // that could be listed here, and it is quite difficult to come up with a + // truly comprehensive list. After all, we are deliberately making system + // calls unavailable. In practice, we have a pretty good idea of the system + // calls that will be made by this particular test. So, this small list is + // sufficient. But if anybody copy'n'pasted this code for other uses, they + // would have to review that the list. + return sysno == __NR_read || sysno == __NR_write || sysno == __NR_exit || + sysno == __NR_exit_group || sysno == __NR_restart_syscall; + } + + ArgValue* RandomArgValue(int argno, int args_mask, int remaining_args) { + // Create a new ArgValue and fill it with random data. We use as bit mask + // to keep track of the system call parameters that have previously been + // set; this ensures that we won't accidentally define a contradictory + // set of equality tests. + struct ArgValue* arg_value = new ArgValue(); + args_mask |= 1 << argno; + arg_value->argno = argno; + + // Apply some restrictions on just how complex our tests can be. + // Otherwise, we end up with a BPF program that is too complicated for + // the kernel to load. + int fan_out = kMaxFanOut; + if (remaining_args > 3) { + fan_out = 1; + } else if (remaining_args > 2) { + fan_out = 2; + } + + // Create a couple of different test cases with randomized values that + // we want to use when comparing system call parameter number "argno". + arg_value->size = rand() % fan_out + 1; + arg_value->tests = new ArgValue::Tests[arg_value->size]; + + uint32_t k_value = rand(); + for (int n = 0; n < arg_value->size; ++n) { + // Ensure that we have unique values + k_value += rand() % (RAND_MAX / (kMaxFanOut + 1)) + 1; + + // There are two possible types of nodes. Either this is a leaf node; + // in that case, we have completed all the equality tests that we + // wanted to perform, and we can now compute a random "errno" value that + // we should return. Or this is part of a more complex boolean + // expression; in that case, we have to recursively add tests for some + // of system call parameters that we have not yet included in our + // tests. + arg_value->tests[n].k_value = k_value; + if (!remaining_args || (rand() & 1)) { + arg_value->tests[n].err = (rand() % 1000) + 1; + arg_value->tests[n].arg_value = NULL; + } else { + arg_value->tests[n].err = 0; + arg_value->tests[n].arg_value = + RandomArgValue(RandomArg(args_mask), args_mask, remaining_args - 1); + } + } + // Finally, we have to define what we should return if none of the + // previous equality tests pass. Again, we can either deal with a leaf + // node, or we can randomly add another couple of tests. + if (!remaining_args || (rand() & 1)) { + arg_value->err = (rand() % 1000) + 1; + arg_value->arg_value = NULL; + } else { + arg_value->err = 0; + arg_value->arg_value = + RandomArgValue(RandomArg(args_mask), args_mask, remaining_args - 1); + } + // We have now built a new (sub-)tree of ArgValues defining a set of + // boolean expressions for testing random system call arguments against + // random values. Return this tree to our caller. + return arg_value; + } + + int RandomArg(int args_mask) { + // Compute a random system call parameter number. + int argno = rand() % kMaxArgs; + + // Make sure that this same parameter number has not previously been + // used. Otherwise, we could end up with a test that is impossible to + // satisfy (e.g. args[0] == 1 && args[0] == 2). + while (args_mask & (1 << argno)) { + argno = (argno + 1) % kMaxArgs; + } + return argno; + } + + void DeleteArgValue(ArgValue* arg_value) { + // Delete an ArgValue and all of its child nodes. This requires + // recursively descending into the tree. + if (arg_value) { + if (arg_value->size) { + for (int n = 0; n < arg_value->size; ++n) { + if (!arg_value->tests[n].err) { + DeleteArgValue(arg_value->tests[n].arg_value); + } + } + delete[] arg_value->tests; + } + if (!arg_value->err) { + DeleteArgValue(arg_value->arg_value); + } + delete arg_value; + } + } + + ResultExpr ToErrorCode(ArgValue* arg_value) { + // Compute the ResultExpr that should be returned, if none of our + // tests succeed (i.e. the system call parameter doesn't match any + // of the values in arg_value->tests[].k_value). + ResultExpr err; + if (arg_value->err) { + // If this was a leaf node, return the errno value that we expect to + // return from the BPF filter program. + err = Error(arg_value->err); + } else { + // If this wasn't a leaf node yet, recursively descend into the rest + // of the tree. This will end up adding a few more SandboxBPF::Cond() + // tests to our ErrorCode. + err = ToErrorCode(arg_value->arg_value); + } + + // Now, iterate over all the test cases that we want to compare against. + // This builds a chain of SandboxBPF::Cond() tests + // (aka "if ... elif ... elif ... elif ... fi") + for (int n = arg_value->size; n-- > 0;) { + ResultExpr matched; + // Again, we distinguish between leaf nodes and subtrees. + if (arg_value->tests[n].err) { + matched = Error(arg_value->tests[n].err); + } else { + matched = ToErrorCode(arg_value->tests[n].arg_value); + } + // For now, all of our tests are limited to 32bit. + // We have separate tests that check the behavior of 32bit vs. 64bit + // conditional expressions. + const Arg<uint32_t> arg(arg_value->argno); + err = If(arg == arg_value->tests[n].k_value, matched).Else(err); + } + return err; + } + + void Verify(int sysno, intptr_t* args, const ArgValue& arg_value) { + uint32_t mismatched = 0; + // Iterate over all the k_values in arg_value.tests[] and verify that + // we see the expected return values from system calls, when we pass + // the k_value as a parameter in a system call. + for (int n = arg_value.size; n-- > 0;) { + mismatched += arg_value.tests[n].k_value; + args[arg_value.argno] = arg_value.tests[n].k_value; + if (arg_value.tests[n].err) { + VerifyErrno(sysno, args, arg_value.tests[n].err); + } else { + Verify(sysno, args, *arg_value.tests[n].arg_value); + } + } + // Find a k_value that doesn't match any of the k_values in + // arg_value.tests[]. In most cases, the current value of "mismatched" + // would fit this requirement. But on the off-chance that it happens + // to collide, we double-check. + try_again: + for (int n = arg_value.size; n-- > 0;) { + if (mismatched == arg_value.tests[n].k_value) { + ++mismatched; + goto try_again; + } + } + // Now verify that we see the expected return value from system calls, + // if we pass a value that doesn't match any of the conditions (i.e. this + // is testing the "else" clause of the conditions). + args[arg_value.argno] = mismatched; + if (arg_value.err) { + VerifyErrno(sysno, args, arg_value.err); + } else { + Verify(sysno, args, *arg_value.arg_value); + } + // Reset args[arg_value.argno]. This is not technically needed, but it + // makes it easier to reason about the correctness of our tests. + args[arg_value.argno] = 0; + } + + void VerifyErrno(int sysno, intptr_t* args, int err) { + // We installed BPF filters that return different errno values + // based on the system call number and the parameters that we decided + // to pass in. Verify that this condition holds true. + BPF_ASSERT( + Syscall::Call( + sysno, args[0], args[1], args[2], args[3], args[4], args[5]) == + -err); + } + + // Vector of ArgValue trees. These trees define all the possible boolean + // expressions that we want to turn into a BPF filter program. + std::vector<ArgValue*> arg_values_; + + // Don't increase these values. We are pushing the limits of the maximum + // BPF program that the kernel will allow us to load. If the values are + // increased too much, the test will start failing. +#if defined(__aarch64__) + static const int kNumTestCases = 30; +#else + static const int kNumTestCases = 40; +#endif + static const int kMaxFanOut = 3; + static const int kMaxArgs = 6; +}; + +class EqualityStressTestPolicy : public Policy { + public: + explicit EqualityStressTestPolicy(EqualityStressTest* aux) : aux_(aux) {} + ~EqualityStressTestPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + return aux_->Policy(sysno); + } + + private: + EqualityStressTest* aux_; + + DISALLOW_COPY_AND_ASSIGN(EqualityStressTestPolicy); +}; + +BPF_TEST(SandboxBPF, + EqualityTests, + EqualityStressTestPolicy, + EqualityStressTest /* (*BPF_AUX) */) { + BPF_AUX->VerifyFilter(); +} + +class EqualityArgumentWidthPolicy : public Policy { + public: + EqualityArgumentWidthPolicy() {} + ~EqualityArgumentWidthPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override; + + private: + DISALLOW_COPY_AND_ASSIGN(EqualityArgumentWidthPolicy); +}; + +ResultExpr EqualityArgumentWidthPolicy::EvaluateSyscall(int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno == __NR_uname) { + const Arg<int> option(0); + const Arg<uint32_t> arg32(1); + const Arg<uint64_t> arg64(1); + return Switch(option) + .Case(0, If(arg32 == 0x55555555, Error(1)).Else(Error(2))) +#if __SIZEOF_POINTER__ > 4 + .Case(1, If(arg64 == 0x55555555AAAAAAAAULL, Error(1)).Else(Error(2))) +#endif + .Default(Error(3)); + } + return Allow(); +} + +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(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); +#endif +} + +#if __SIZEOF_POINTER__ > 4 +// On 32bit machines, there is no way to pass a 64bit argument through the +// syscall interface. So, we have to skip the part of the test that requires +// 64bit arguments. +BPF_DEATH_TEST_C(SandboxBPF, + EqualityArgumentUnallowed64bit, + DEATH_MESSAGE("Unexpected 64bit argument detected"), + EqualityArgumentWidthPolicy) { + Syscall::Call(__NR_uname, 0, 0x5555555555555555ULL); +} +#endif + +class EqualityWithNegativeArgumentsPolicy : public Policy { + public: + EqualityWithNegativeArgumentsPolicy() {} + ~EqualityWithNegativeArgumentsPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno == __NR_uname) { + // TODO(mdempsky): This currently can't be Arg<int> because then + // 0xFFFFFFFF will be treated as a (signed) int, and then when + // Arg::EqualTo casts it to uint64_t, it will be sign extended. + const Arg<unsigned> arg(0); + return If(arg == 0xFFFFFFFF, Error(1)).Else(Error(2)); + } + return Allow(); + } + + 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_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(Syscall::Call(__NR_uname, 0xFFFFFFFF00000000LL) == -1); +} +#endif + +class AllBitTestPolicy : public Policy { + public: + AllBitTestPolicy() {} + ~AllBitTestPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override; + + private: + static ResultExpr HasAllBits32(uint32_t bits); + static ResultExpr HasAllBits64(uint64_t bits); + + DISALLOW_COPY_AND_ASSIGN(AllBitTestPolicy); +}; + +ResultExpr AllBitTestPolicy::HasAllBits32(uint32_t bits) { + if (bits == 0) { + return Error(1); + } + const Arg<uint32_t> arg(1); + return If((arg & bits) == bits, Error(1)).Else(Error(0)); +} + +ResultExpr AllBitTestPolicy::HasAllBits64(uint64_t bits) { + if (bits == 0) { + return Error(1); + } + const Arg<uint64_t> arg(1); + return If((arg & bits) == bits, Error(1)).Else(Error(0)); +} + +ResultExpr AllBitTestPolicy::EvaluateSyscall(int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + // Test masked-equality cases that should trigger the "has all bits" + // peephole optimizations. 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 (sysno == __NR_uname) { + const Arg<int> option(0); + return Switch(option) + .Case(0, HasAllBits32(0x0)) + .Case(1, HasAllBits32(0x1)) + .Case(2, HasAllBits32(0x3)) + .Case(3, HasAllBits32(0x80000000)) +#if __SIZEOF_POINTER__ > 4 + .Case(4, HasAllBits64(0x0)) + .Case(5, HasAllBits64(0x1)) + .Case(6, HasAllBits64(0x3)) + .Case(7, HasAllBits64(0x80000000)) + .Case(8, HasAllBits64(0x100000000ULL)) + .Case(9, HasAllBits64(0x300000000ULL)) + .Case(10, HasAllBits64(0x100000001ULL)) +#endif + .Default(Kill("Invalid test case number")); + } + return Allow(); +} + +// Define a macro that performs tests using our test policy. +// NOTE: Not all of the arguments in this macro are actually used! +// They are here just to serve as documentation of the conditions +// implemented in the test policy. +// Most notably, "op" and "mask" are unused by the macro. If you want +// to make changes to these values, you will have to edit the +// test policy instead. +#define BITMASK_TEST(testcase, arg, op, mask, expected_value) \ + BPF_ASSERT(Syscall::Call(__NR_uname, (testcase), (arg)) == (expected_value)) + +// Our uname() system call returns ErrorCode(1) for success and +// ErrorCode(0) for failure. Syscall::Call() turns this into an +// exit code of -1 or 0. +#define EXPECT_FAILURE 0 +#define EXPECT_SUCCESS -1 + +// A couple of our tests behave differently on 32bit and 64bit systems, as +// there is no way for a 32bit system call to pass in a 64bit system call +// argument "arg". +// We expect these tests to succeed on 64bit systems, but to tail on 32bit +// systems. +#define EXPT64_SUCCESS (sizeof(void*) > 4 ? EXPECT_SUCCESS : EXPECT_FAILURE) +BPF_TEST_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); + BITMASK_TEST( 0, 3, ALLBITS32, 0, EXPECT_SUCCESS); + BITMASK_TEST( 0, 0xFFFFFFFFU, ALLBITS32, 0, EXPECT_SUCCESS); + BITMASK_TEST( 0, -1LL, ALLBITS32, 0, EXPECT_SUCCESS); + + // 32bit test: all of 0x1 + BITMASK_TEST( 1, 0, ALLBITS32, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 1, 1, ALLBITS32, 0x1, EXPECT_SUCCESS); + BITMASK_TEST( 1, 2, ALLBITS32, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 1, 3, ALLBITS32, 0x1, EXPECT_SUCCESS); + + // 32bit test: all of 0x3 + BITMASK_TEST( 2, 0, ALLBITS32, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 2, 1, ALLBITS32, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 2, 2, ALLBITS32, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 2, 3, ALLBITS32, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 2, 7, ALLBITS32, 0x3, EXPECT_SUCCESS); + + // 32bit test: all of 0x80000000 + BITMASK_TEST( 3, 0, ALLBITS32, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 3, 0x40000000U, ALLBITS32, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 3, 0x80000000U, ALLBITS32, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 3, 0xC0000000U, ALLBITS32, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 3, -0x80000000LL, ALLBITS32, 0x80000000, EXPECT_SUCCESS); + +#if __SIZEOF_POINTER__ > 4 + // 64bit test: all of 0x0 (should always be true) + BITMASK_TEST( 4, 0, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, 1, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, 3, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, 0xFFFFFFFFU, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, 0x100000000LL, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, 0x300000000LL, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4,0x8000000000000000LL, ALLBITS64, 0, EXPECT_SUCCESS); + BITMASK_TEST( 4, -1LL, ALLBITS64, 0, EXPECT_SUCCESS); + + // 64bit test: all of 0x1 + BITMASK_TEST( 5, 0, ALLBITS64, 1, EXPECT_FAILURE); + BITMASK_TEST( 5, 1, ALLBITS64, 1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 2, ALLBITS64, 1, EXPECT_FAILURE); + BITMASK_TEST( 5, 3, ALLBITS64, 1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 0x100000000LL, ALLBITS64, 1, EXPECT_FAILURE); + BITMASK_TEST( 5, 0x100000001LL, ALLBITS64, 1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 0x100000002LL, ALLBITS64, 1, EXPECT_FAILURE); + BITMASK_TEST( 5, 0x100000003LL, ALLBITS64, 1, EXPECT_SUCCESS); + + // 64bit test: all of 0x3 + BITMASK_TEST( 6, 0, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 1, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 2, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 3, ALLBITS64, 3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 7, ALLBITS64, 3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000000LL, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 0x100000001LL, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 0x100000002LL, ALLBITS64, 3, EXPECT_FAILURE); + BITMASK_TEST( 6, 0x100000003LL, ALLBITS64, 3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000007LL, ALLBITS64, 3, EXPECT_SUCCESS); + + // 64bit test: all of 0x80000000 + BITMASK_TEST( 7, 0, ALLBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x40000000U, ALLBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x80000000U, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0xC0000000U, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, -0x80000000LL, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0x100000000LL, ALLBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x140000000LL, ALLBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x180000000LL, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0x1C0000000LL, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, -0x180000000LL, ALLBITS64, 0x80000000, EXPECT_SUCCESS); + + // 64bit test: all of 0x100000000 + BITMASK_TEST( 8, 0x000000000LL, ALLBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x100000000LL, ALLBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x200000000LL, ALLBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x300000000LL, ALLBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x000000001LL, ALLBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x100000001LL, ALLBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x200000001LL, ALLBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x300000001LL, ALLBITS64,0x100000000, EXPT64_SUCCESS); + + // 64bit test: all of 0x300000000 + BITMASK_TEST( 9, 0x000000000LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x100000000LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x200000000LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x300000000LL, ALLBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x700000000LL, ALLBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x000000001LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x100000001LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x200000001LL, ALLBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x300000001LL, ALLBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x700000001LL, ALLBITS64,0x300000000, EXPT64_SUCCESS); + + // 64bit test: all of 0x100000001 + BITMASK_TEST(10, 0x000000000LL, ALLBITS64,0x100000001, EXPECT_FAILURE); + BITMASK_TEST(10, 0x000000001LL, ALLBITS64,0x100000001, EXPECT_FAILURE); + BITMASK_TEST(10, 0x100000000LL, ALLBITS64,0x100000001, EXPECT_FAILURE); + BITMASK_TEST(10, 0x100000001LL, ALLBITS64,0x100000001, EXPT64_SUCCESS); + BITMASK_TEST(10, 0xFFFFFFFFU, ALLBITS64,0x100000001, EXPECT_FAILURE); + BITMASK_TEST(10, -1L, ALLBITS64,0x100000001, EXPT64_SUCCESS); +#endif +} + +class AnyBitTestPolicy : public Policy { + public: + AnyBitTestPolicy() {} + ~AnyBitTestPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override; + + private: + static ResultExpr HasAnyBits32(uint32_t); + static ResultExpr HasAnyBits64(uint64_t); + + DISALLOW_COPY_AND_ASSIGN(AnyBitTestPolicy); +}; + +ResultExpr AnyBitTestPolicy::HasAnyBits32(uint32_t bits) { + if (bits == 0) { + return Error(0); + } + const Arg<uint32_t> arg(1); + return If((arg & bits) != 0, Error(1)).Else(Error(0)); +} + +ResultExpr AnyBitTestPolicy::HasAnyBits64(uint64_t bits) { + if (bits == 0) { + return Error(0); + } + const Arg<uint64_t> arg(1); + return If((arg & bits) != 0, Error(1)).Else(Error(0)); +} + +ResultExpr AnyBitTestPolicy::EvaluateSyscall(int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + // Test masked-equality cases that should trigger the "has any bits" + // peephole optimizations. 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 (sysno == __NR_uname) { + const Arg<int> option(0); + return Switch(option) + .Case(0, HasAnyBits32(0x0)) + .Case(1, HasAnyBits32(0x1)) + .Case(2, HasAnyBits32(0x3)) + .Case(3, HasAnyBits32(0x80000000)) +#if __SIZEOF_POINTER__ > 4 + .Case(4, HasAnyBits64(0x0)) + .Case(5, HasAnyBits64(0x1)) + .Case(6, HasAnyBits64(0x3)) + .Case(7, HasAnyBits64(0x80000000)) + .Case(8, HasAnyBits64(0x100000000ULL)) + .Case(9, HasAnyBits64(0x300000000ULL)) + .Case(10, HasAnyBits64(0x100000001ULL)) +#endif + .Default(Kill("Invalid test case number")); + } + return Allow(); +} + +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); + BITMASK_TEST( 0, 3, ANYBITS32, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 0, 0xFFFFFFFFU, ANYBITS32, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 0, -1LL, ANYBITS32, 0x0, EXPECT_FAILURE); + + // 32bit test: any of 0x1 + BITMASK_TEST( 1, 0, ANYBITS32, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 1, 1, ANYBITS32, 0x1, EXPECT_SUCCESS); + BITMASK_TEST( 1, 2, ANYBITS32, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 1, 3, ANYBITS32, 0x1, EXPECT_SUCCESS); + + // 32bit test: any of 0x3 + BITMASK_TEST( 2, 0, ANYBITS32, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 2, 1, ANYBITS32, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 2, 2, ANYBITS32, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 2, 3, ANYBITS32, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 2, 7, ANYBITS32, 0x3, EXPECT_SUCCESS); + + // 32bit test: any of 0x80000000 + BITMASK_TEST( 3, 0, ANYBITS32, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 3, 0x40000000U, ANYBITS32, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 3, 0x80000000U, ANYBITS32, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 3, 0xC0000000U, ANYBITS32, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 3, -0x80000000LL, ANYBITS32, 0x80000000, EXPECT_SUCCESS); + +#if __SIZEOF_POINTER__ > 4 + // 64bit test: any of 0x0 (should always be false) + BITMASK_TEST( 4, 0, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, 1, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, 3, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, 0xFFFFFFFFU, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, 0x100000000LL, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, 0x300000000LL, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4,0x8000000000000000LL, ANYBITS64, 0x0, EXPECT_FAILURE); + BITMASK_TEST( 4, -1LL, ANYBITS64, 0x0, EXPECT_FAILURE); + + // 64bit test: any of 0x1 + BITMASK_TEST( 5, 0, ANYBITS64, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 5, 1, ANYBITS64, 0x1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 2, ANYBITS64, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 5, 3, ANYBITS64, 0x1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 0x100000001LL, ANYBITS64, 0x1, EXPECT_SUCCESS); + BITMASK_TEST( 5, 0x100000000LL, ANYBITS64, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 5, 0x100000002LL, ANYBITS64, 0x1, EXPECT_FAILURE); + BITMASK_TEST( 5, 0x100000003LL, ANYBITS64, 0x1, EXPECT_SUCCESS); + + // 64bit test: any of 0x3 + BITMASK_TEST( 6, 0, ANYBITS64, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 6, 1, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 2, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 3, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 7, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000000LL, ANYBITS64, 0x3, EXPECT_FAILURE); + BITMASK_TEST( 6, 0x100000001LL, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000002LL, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000003LL, ANYBITS64, 0x3, EXPECT_SUCCESS); + BITMASK_TEST( 6, 0x100000007LL, ANYBITS64, 0x3, EXPECT_SUCCESS); + + // 64bit test: any of 0x80000000 + BITMASK_TEST( 7, 0, ANYBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x40000000U, ANYBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x80000000U, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0xC0000000U, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, -0x80000000LL, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0x100000000LL, ANYBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x140000000LL, ANYBITS64, 0x80000000, EXPECT_FAILURE); + BITMASK_TEST( 7, 0x180000000LL, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, 0x1C0000000LL, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + BITMASK_TEST( 7, -0x180000000LL, ANYBITS64, 0x80000000, EXPECT_SUCCESS); + + // 64bit test: any of 0x100000000 + BITMASK_TEST( 8, 0x000000000LL, ANYBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x100000000LL, ANYBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x200000000LL, ANYBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x300000000LL, ANYBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x000000001LL, ANYBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x100000001LL, ANYBITS64,0x100000000, EXPT64_SUCCESS); + BITMASK_TEST( 8, 0x200000001LL, ANYBITS64,0x100000000, EXPECT_FAILURE); + BITMASK_TEST( 8, 0x300000001LL, ANYBITS64,0x100000000, EXPT64_SUCCESS); + + // 64bit test: any of 0x300000000 + BITMASK_TEST( 9, 0x000000000LL, ANYBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x100000000LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x200000000LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x300000000LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x700000000LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x000000001LL, ANYBITS64,0x300000000, EXPECT_FAILURE); + BITMASK_TEST( 9, 0x100000001LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x200000001LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x300000001LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + BITMASK_TEST( 9, 0x700000001LL, ANYBITS64,0x300000000, EXPT64_SUCCESS); + + // 64bit test: any of 0x100000001 + BITMASK_TEST( 10, 0x000000000LL, ANYBITS64,0x100000001, EXPECT_FAILURE); + BITMASK_TEST( 10, 0x000000001LL, ANYBITS64,0x100000001, EXPECT_SUCCESS); + BITMASK_TEST( 10, 0x100000000LL, ANYBITS64,0x100000001, EXPT64_SUCCESS); + BITMASK_TEST( 10, 0x100000001LL, ANYBITS64,0x100000001, EXPECT_SUCCESS); + BITMASK_TEST( 10, 0xFFFFFFFFU, ANYBITS64,0x100000001, EXPECT_SUCCESS); + BITMASK_TEST( 10, -1L, ANYBITS64,0x100000001, EXPECT_SUCCESS); +#endif +} + +class MaskedEqualTestPolicy : public Policy { + public: + MaskedEqualTestPolicy() {} + ~MaskedEqualTestPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override; + + private: + static ResultExpr MaskedEqual32(uint32_t mask, uint32_t value); + static ResultExpr MaskedEqual64(uint64_t mask, uint64_t value); + + DISALLOW_COPY_AND_ASSIGN(MaskedEqualTestPolicy); +}; + +ResultExpr MaskedEqualTestPolicy::MaskedEqual32(uint32_t mask, uint32_t value) { + const Arg<uint32_t> arg(1); + return If((arg & mask) == value, Error(1)).Else(Error(0)); +} + +ResultExpr MaskedEqualTestPolicy::MaskedEqual64(uint64_t mask, uint64_t value) { + const Arg<uint64_t> arg(1); + return If((arg & mask) == value, Error(1)).Else(Error(0)); +} + +ResultExpr MaskedEqualTestPolicy::EvaluateSyscall(int sysno) const { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + + if (sysno == __NR_uname) { + const Arg<int> option(0); + return Switch(option) + .Case(0, MaskedEqual32(0x00ff00ff, 0x005500aa)) +#if __SIZEOF_POINTER__ > 4 + .Case(1, MaskedEqual64(0x00ff00ff00000000, 0x005500aa00000000)) + .Case(2, MaskedEqual64(0x00ff00ff00ff00ff, 0x005500aa005500aa)) +#endif + .Default(Kill("Invalid test case number")); + } + + return Allow(); +} + +#define MASKEQ_TEST(rulenum, arg, expected_result) \ + BPF_ASSERT(Syscall::Call(__NR_uname, (rulenum), (arg)) == (expected_result)) + +BPF_TEST_C(SandboxBPF, MaskedEqualTests, MaskedEqualTestPolicy) { + // Allowed: 0x__55__aa + MASKEQ_TEST(0, 0x00000000, EXPECT_FAILURE); + MASKEQ_TEST(0, 0x00000001, EXPECT_FAILURE); + MASKEQ_TEST(0, 0x00000003, EXPECT_FAILURE); + MASKEQ_TEST(0, 0x00000100, EXPECT_FAILURE); + MASKEQ_TEST(0, 0x00000300, EXPECT_FAILURE); + MASKEQ_TEST(0, 0x005500aa, EXPECT_SUCCESS); + MASKEQ_TEST(0, 0x005500ab, EXPECT_FAILURE); + MASKEQ_TEST(0, 0x005600aa, EXPECT_FAILURE); + MASKEQ_TEST(0, 0x005501aa, EXPECT_SUCCESS); + MASKEQ_TEST(0, 0x005503aa, EXPECT_SUCCESS); + MASKEQ_TEST(0, 0x555500aa, EXPECT_SUCCESS); + MASKEQ_TEST(0, 0xaa5500aa, EXPECT_SUCCESS); + +#if __SIZEOF_POINTER__ > 4 + // Allowed: 0x__55__aa________ + MASKEQ_TEST(1, 0x0000000000000000, EXPECT_FAILURE); + MASKEQ_TEST(1, 0x0000000000000010, EXPECT_FAILURE); + MASKEQ_TEST(1, 0x0000000000000050, EXPECT_FAILURE); + MASKEQ_TEST(1, 0x0000000100000000, EXPECT_FAILURE); + MASKEQ_TEST(1, 0x0000000300000000, EXPECT_FAILURE); + MASKEQ_TEST(1, 0x0000010000000000, EXPECT_FAILURE); + MASKEQ_TEST(1, 0x0000030000000000, EXPECT_FAILURE); + MASKEQ_TEST(1, 0x005500aa00000000, EXPECT_SUCCESS); + MASKEQ_TEST(1, 0x005500ab00000000, EXPECT_FAILURE); + MASKEQ_TEST(1, 0x005600aa00000000, EXPECT_FAILURE); + MASKEQ_TEST(1, 0x005501aa00000000, EXPECT_SUCCESS); + MASKEQ_TEST(1, 0x005503aa00000000, EXPECT_SUCCESS); + MASKEQ_TEST(1, 0x555500aa00000000, EXPECT_SUCCESS); + MASKEQ_TEST(1, 0xaa5500aa00000000, EXPECT_SUCCESS); + MASKEQ_TEST(1, 0xaa5500aa00000000, EXPECT_SUCCESS); + MASKEQ_TEST(1, 0xaa5500aa0000cafe, EXPECT_SUCCESS); + + // Allowed: 0x__55__aa__55__aa + MASKEQ_TEST(2, 0x0000000000000000, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x0000000000000010, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x0000000000000050, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x0000000100000000, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x0000000300000000, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x0000010000000000, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x0000030000000000, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x00000000005500aa, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x005500aa00000000, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x005500aa005500aa, EXPECT_SUCCESS); + MASKEQ_TEST(2, 0x005500aa005700aa, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x005700aa005500aa, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x005500aa004500aa, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x004500aa005500aa, EXPECT_FAILURE); + MASKEQ_TEST(2, 0x005512aa005500aa, EXPECT_SUCCESS); + MASKEQ_TEST(2, 0x005500aa005534aa, EXPECT_SUCCESS); + MASKEQ_TEST(2, 0xff5500aa0055ffaa, EXPECT_SUCCESS); +#endif +} + +intptr_t PthreadTrapHandler(const struct arch_seccomp_data& args, void* aux) { + if (args.args[0] != (CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | SIGCHLD)) { + // We expect to get called for an attempt to fork(). No need to log that + // call. But if we ever get called for anything else, we want to verbosely + // print as much information as possible. + const char* msg = (const char*)aux; + printf( + "Clone() was called with unexpected arguments\n" + " nr: %d\n" + " 1: 0x%llX\n" + " 2: 0x%llX\n" + " 3: 0x%llX\n" + " 4: 0x%llX\n" + " 5: 0x%llX\n" + " 6: 0x%llX\n" + "%s\n", + args.nr, + (long long)args.args[0], + (long long)args.args[1], + (long long)args.args[2], + (long long)args.args[3], + (long long)args.args[4], + (long long)args.args[5], + msg); + } + return -EPERM; +} + +class PthreadPolicyEquality : public Policy { + public: + PthreadPolicyEquality() {} + ~PthreadPolicyEquality() override {} + + ResultExpr EvaluateSyscall(int sysno) const override; + + private: + DISALLOW_COPY_AND_ASSIGN(PthreadPolicyEquality); +}; + +ResultExpr PthreadPolicyEquality::EvaluateSyscall(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 (sysno == __NR_clone) { + // We have seen two different valid combinations of flags. Glibc + // uses the more modern flags, sets the TLS from the call to clone(), and + // uses futexes to monitor threads. Android's C run-time library, doesn't + // do any of this, but it sets the obsolete (and no-op) CLONE_DETACHED. + // More recent versions of Android don't set CLONE_DETACHED anymore, so + // the last case accounts for that. + // The following policy is very strict. It only allows the exact masks + // that we have seen in known implementations. It is probably somewhat + // stricter than what we would want to do. + const uint64_t kGlibcCloneMask = CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | + CLONE_SYSVSEM | CLONE_SETTLS | + CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID; + const uint64_t kBaseAndroidCloneMask = CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | + CLONE_SYSVSEM; + const Arg<unsigned long> flags(0); + return If(flags == kGlibcCloneMask || + flags == (kBaseAndroidCloneMask | CLONE_DETACHED) || + flags == kBaseAndroidCloneMask, + Allow()).Else(Trap(PthreadTrapHandler, "Unknown mask")); + } + + return Allow(); +} + +class PthreadPolicyBitMask : public Policy { + public: + PthreadPolicyBitMask() {} + ~PthreadPolicyBitMask() override {} + + ResultExpr EvaluateSyscall(int sysno) const override; + + private: + static BoolExpr HasAnyBits(const Arg<unsigned long>& arg, unsigned long bits); + static BoolExpr HasAllBits(const Arg<unsigned long>& arg, unsigned long bits); + + DISALLOW_COPY_AND_ASSIGN(PthreadPolicyBitMask); +}; + +BoolExpr PthreadPolicyBitMask::HasAnyBits(const Arg<unsigned long>& arg, + unsigned long bits) { + return (arg & bits) != 0; +} + +BoolExpr PthreadPolicyBitMask::HasAllBits(const Arg<unsigned long>& arg, + unsigned long bits) { + return (arg & bits) == bits; +} + +ResultExpr PthreadPolicyBitMask::EvaluateSyscall(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 (sysno == __NR_clone) { + // We have seen two different valid combinations of flags. Glibc + // uses the more modern flags, sets the TLS from the call to clone(), and + // uses futexes to monitor threads. Android's C run-time library, doesn't + // do any of this, but it sets the obsolete (and no-op) CLONE_DETACHED. + // The following policy allows for either combination of flags, but it + // is generally a little more conservative than strictly necessary. We + // err on the side of rather safe than sorry. + // Very noticeably though, we disallow fork() (which is often just a + // wrapper around clone()). + const unsigned long kMandatoryFlags = CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | + CLONE_SYSVSEM; + const unsigned long kFutexFlags = + CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID; + const unsigned long kNoopFlags = CLONE_DETACHED; + const unsigned long kKnownFlags = + kMandatoryFlags | kFutexFlags | kNoopFlags; + + const Arg<unsigned long> flags(0); + return If(HasAnyBits(flags, ~kKnownFlags), + Trap(PthreadTrapHandler, "Unexpected CLONE_XXX flag found")) + .ElseIf(!HasAllBits(flags, kMandatoryFlags), + Trap(PthreadTrapHandler, + "Missing mandatory CLONE_XXX flags " + "when creating new thread")) + .ElseIf( + !HasAllBits(flags, kFutexFlags) && HasAnyBits(flags, kFutexFlags), + Trap(PthreadTrapHandler, + "Must set either all or none of the TLS and futex bits in " + "call to clone()")) + .Else(Allow()); + } + + return Allow(); +} + +static void* ThreadFnc(void* arg) { + ++*reinterpret_cast<int*>(arg); + Syscall::Call(__NR_futex, arg, FUTEX_WAKE, 1, 0, 0, 0); + return NULL; +} + +static void PthreadTest() { + // Attempt to start a joinable thread. This should succeed. + pthread_t thread; + int thread_ran = 0; + BPF_ASSERT(!pthread_create(&thread, NULL, ThreadFnc, &thread_ran)); + BPF_ASSERT(!pthread_join(thread, NULL)); + BPF_ASSERT(thread_ran); + + // Attempt to start a detached thread. This should succeed. + thread_ran = 0; + pthread_attr_t attr; + BPF_ASSERT(!pthread_attr_init(&attr)); + BPF_ASSERT(!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); + BPF_ASSERT(!pthread_create(&thread, &attr, ThreadFnc, &thread_ran)); + BPF_ASSERT(!pthread_attr_destroy(&attr)); + while (Syscall::Call(__NR_futex, &thread_ran, FUTEX_WAIT, 0, 0, 0, 0) == + -EINTR) { + } + BPF_ASSERT(thread_ran); + + // Attempt to fork() a process using clone(). This should fail. We use the + // same flags that glibc uses when calling fork(). But we don't actually + // try calling the fork() implementation in the C run-time library, as + // run-time libraries other than glibc might call __NR_fork instead of + // __NR_clone, and that would introduce a bogus test failure. + int pid; + BPF_ASSERT(Syscall::Call(__NR_clone, + CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | SIGCHLD, + 0, + 0, + &pid) == -EPERM); +} + +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 + +#if defined(__arm__) +#ifndef PTRACE_SET_SYSCALL +#define PTRACE_SET_SYSCALL 23 +#endif +#endif + +#if defined(__aarch64__) +#ifndef PTRACE_GETREGS +#define PTRACE_GETREGS 12 +#endif +#endif + +#if defined(__aarch64__) +#ifndef PTRACE_SETREGS +#define PTRACE_SETREGS 13 +#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 Policy { + public: + TraceAllPolicy() {} + ~TraceAllPolicy() override {} + + ResultExpr EvaluateSyscall(int system_call_number) const override { + return Trace(kTraceData); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TraceAllPolicy); +}; + +SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(SeccompRetTrace)) { + if (!SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::SINGLE_THREADED)) { + return; + } + +// This test is disabled on arm due to a kernel bug. +// See https://code.google.com/p/chromium/issues/detail?id=383977 +#if defined(__arm__) || defined(__aarch64__) + printf("This test is currently disabled on ARM32/64 due to a kernel bug."); + return; +#endif + +#if defined(__mips__) + // TODO: Figure out how to support specificity of handling indirect syscalls + // in this test and enable it. + printf("This test is currently disabled on MIPS."); + 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(new TraceAllPolicy); + BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::SeccompLevel::SINGLE_THREADED)); + + // getpid is allowed. + BPF_ASSERT_EQ(my_pid, sys_getpid()); + + // write to stdout is skipped and returns a fake value. + BPF_ASSERT_EQ(kExpectedReturnValue, + syscall(__NR_write, STDOUT_FILENO, "A", 1)); + + // kill is rewritten to exit(kExpectedReturnValue). + syscall(__NR_kill, my_pid, SIGKILL); + + // Should not be reached. + BPF_ASSERT(false); + } + + int status; + BPF_ASSERT(HANDLE_EINTR(waitpid(pid, &status, WUNTRACED)) != -1); + BPF_ASSERT(WIFSTOPPED(status)); + + BPF_ASSERT_NE(-1, + ptrace(PTRACE_SETOPTIONS, + pid, + NULL, + reinterpret_cast<void*>(PTRACE_O_TRACESECCOMP))); + BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL)); + while (true) { + BPF_ASSERT(HANDLE_EINTR(waitpid(pid, &status, 0)) != -1); + if (WIFEXITED(status) || WIFSIGNALED(status)) { + BPF_ASSERT(WIFEXITED(status)); + BPF_ASSERT_EQ(kExpectedReturnValue, WEXITSTATUS(status)); + break; + } + + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP || + !IS_SECCOMP_EVENT(status)) { + BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL)); + continue; + } + + unsigned long data; + BPF_ASSERT_NE(-1, ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data)); + BPF_ASSERT_EQ(kTraceData, data); + + regs_struct regs; + BPF_ASSERT_NE(-1, ptrace(PTRACE_GETREGS, pid, NULL, ®s)); + switch (SECCOMP_PT_SYSCALL(regs)) { + case __NR_write: + // Skip writes to stdout, make it return kExpectedReturnValue. Allow + // writes to stderr so that BPF_ASSERT messages show up. + if (SECCOMP_PT_PARM1(regs) == STDOUT_FILENO) { + BPF_ASSERT_NE(-1, SetSyscall(pid, ®s, -1)); + SECCOMP_PT_RESULT(regs) = kExpectedReturnValue; + BPF_ASSERT_NE(-1, ptrace(PTRACE_SETREGS, pid, NULL, ®s)); + } + break; + + case __NR_kill: + // Rewrite to exit(kExpectedReturnValue). + BPF_ASSERT_NE(-1, SetSyscall(pid, ®s, __NR_exit)); + SECCOMP_PT_PARM1(regs) = kExpectedReturnValue; + BPF_ASSERT_NE(-1, ptrace(PTRACE_SETREGS, pid, NULL, ®s)); + break; + + default: + // Allow all other syscalls. + break; + } + + BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL)); + } +} + +// Android does not expose pread64 nor pwrite64. +#if !defined(OS_ANDROID) + +bool FullPwrite64(int fd, const char* buffer, size_t count, off64_t offset) { + while (count > 0) { + const ssize_t transfered = + HANDLE_EINTR(pwrite64(fd, buffer, count, offset)); + if (transfered <= 0 || static_cast<size_t>(transfered) > count) { + return false; + } + count -= transfered; + buffer += transfered; + offset += transfered; + } + return true; +} + +bool FullPread64(int fd, char* buffer, size_t count, off64_t offset) { + while (count > 0) { + const ssize_t transfered = HANDLE_EINTR(pread64(fd, buffer, count, offset)); + if (transfered <= 0 || static_cast<size_t>(transfered) > count) { + return false; + } + count -= transfered; + buffer += transfered; + offset += transfered; + } + return true; +} + +bool pread_64_was_forwarded = false; + +class TrapPread64Policy : public Policy { + public: + TrapPread64Policy() {} + ~TrapPread64Policy() override {} + + ResultExpr EvaluateSyscall(int system_call_number) const override { + // Set the global environment for unsafe traps once. + if (system_call_number == MIN_SYSCALL) { + EnableUnsafeTraps(); + } + + if (system_call_number == __NR_pread64) { + return UnsafeTrap(ForwardPreadHandler, NULL); + } + return Allow(); + } + + private: + static intptr_t ForwardPreadHandler(const struct arch_seccomp_data& args, + void* aux) { + BPF_ASSERT(args.nr == __NR_pread64); + pread_64_was_forwarded = true; + + return SandboxBPF::ForwardSyscall(args); + } + + DISALLOW_COPY_AND_ASSIGN(TrapPread64Policy); +}; + +// pread(2) takes a 64 bits offset. On 32 bits systems, it will be split +// between two arguments. In this test, we make sure that ForwardSyscall() can +// forward it properly. +BPF_TEST_C(SandboxBPF, Pread64, TrapPread64Policy) { + ScopedTemporaryFile temp_file; + const uint64_t kLargeOffset = (static_cast<uint64_t>(1) << 32) | 0xBEEF; + const char kTestString[] = "This is a test!"; + BPF_ASSERT(FullPwrite64( + temp_file.fd(), kTestString, sizeof(kTestString), kLargeOffset)); + + char read_test_string[sizeof(kTestString)] = {0}; + BPF_ASSERT(FullPread64(temp_file.fd(), + read_test_string, + sizeof(read_test_string), + kLargeOffset)); + BPF_ASSERT_EQ(0, memcmp(kTestString, read_test_string, sizeof(kTestString))); + BPF_ASSERT(pread_64_was_forwarded); +} + +#endif // !defined(OS_ANDROID) + +void* TsyncApplyToTwoThreadsFunc(void* cond_ptr) { + base::WaitableEvent* event = static_cast<base::WaitableEvent*>(cond_ptr); + + // Wait for the main thread to signal that the filter has been applied. + if (!event->IsSignaled()) { + event->Wait(); + } + + BPF_ASSERT(event->IsSignaled()); + + BlacklistNanosleepPolicy::AssertNanosleepFails(); + + return NULL; +} + +SANDBOX_TEST(SandboxBPF, Tsync) { + const bool supports_multi_threaded = SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::MULTI_THREADED); +// On Chrome OS tsync is mandatory. +#if defined(OS_CHROMEOS) + if (base::SysInfo::IsRunningOnChromeOS()) { + BPF_ASSERT_EQ(true, supports_multi_threaded); + } +// else a Chrome OS build not running on a Chrome OS device e.g. Chrome bots. +// In this case fall through. +#endif + if (!supports_multi_threaded) { + return; + } + + base::WaitableEvent event(true, false); + + // Create a thread on which to invoke the blocked syscall. + pthread_t thread; + BPF_ASSERT_EQ( + 0, pthread_create(&thread, NULL, &TsyncApplyToTwoThreadsFunc, &event)); + + // Test that nanoseelp success. + const struct timespec ts = {0, 0}; + BPF_ASSERT_EQ(0, HANDLE_EINTR(syscall(__NR_nanosleep, &ts, NULL))); + + // Engage the sandbox. + SandboxBPF sandbox(new BlacklistNanosleepPolicy()); + BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::SeccompLevel::MULTI_THREADED)); + + // This thread should have the filter applied as well. + BlacklistNanosleepPolicy::AssertNanosleepFails(); + + // Signal the condition to invoke the system call. + event.Signal(); + + // Wait for the thread to finish. + BPF_ASSERT_EQ(0, pthread_join(thread, NULL)); +} + +class AllowAllPolicy : public Policy { + public: + AllowAllPolicy() {} + ~AllowAllPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { return Allow(); } + + private: + DISALLOW_COPY_AND_ASSIGN(AllowAllPolicy); +}; + +SANDBOX_DEATH_TEST( + SandboxBPF, + StartMultiThreadedAsSingleThreaded, + DEATH_MESSAGE( + ThreadHelpers::GetAssertSingleThreadedErrorMessageForTests())) { + base::Thread thread("sandbox.linux.StartMultiThreadedAsSingleThreaded"); + BPF_ASSERT(thread.Start()); + + SandboxBPF sandbox(new AllowAllPolicy()); + BPF_ASSERT(!sandbox.StartSandbox(SandboxBPF::SeccompLevel::SINGLE_THREADED)); +} + +// http://crbug.com/407357 +#if !defined(THREAD_SANITIZER) +SANDBOX_DEATH_TEST( + SandboxBPF, + StartSingleThreadedAsMultiThreaded, + DEATH_MESSAGE( + "Cannot start sandbox; process may be single-threaded when " + "reported as not")) { + SandboxBPF sandbox(new AllowAllPolicy()); + BPF_ASSERT(!sandbox.StartSandbox(SandboxBPF::SeccompLevel::MULTI_THREADED)); +} +#endif // !defined(THREAD_SANITIZER) + +// A stub handler for the UnsafeTrap. Never called. +intptr_t NoOpHandler(const struct arch_seccomp_data& args, void*) { + return -1; +} + +class UnsafeTrapWithCondPolicy : public Policy { + public: + UnsafeTrapWithCondPolicy() {} + ~UnsafeTrapWithCondPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + setenv(kSandboxDebuggingEnv, "t", 0); + Die::SuppressInfoMessages(true); + + if (SandboxBPF::IsRequiredForUnsafeTrap(sysno)) + return Allow(); + + switch (sysno) { + case __NR_uname: { + const Arg<uint32_t> arg(0); + return If(arg == 0, Allow()).Else(Error(EPERM)); + } + case __NR_setgid: { + const Arg<uint32_t> arg(0); + return Switch(arg) + .Case(100, Error(ENOMEM)) + .Case(200, Error(ENOSYS)) + .Default(Error(EPERM)); + } + case __NR_close: + case __NR_exit_group: + case __NR_write: + return Allow(); + case __NR_getppid: + return UnsafeTrap(NoOpHandler, NULL); + default: + return Error(EPERM); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(UnsafeTrapWithCondPolicy); +}; + +BPF_TEST_C(SandboxBPF, UnsafeTrapWithCond, UnsafeTrapWithCondPolicy) { + BPF_ASSERT_EQ(-1, syscall(__NR_uname, 0)); + BPF_ASSERT_EQ(EFAULT, errno); + + BPF_ASSERT_EQ(-1, syscall(__NR_uname, 1)); + BPF_ASSERT_EQ(EPERM, errno); + + BPF_ASSERT_EQ(-1, syscall(__NR_setgid, 100)); + BPF_ASSERT_EQ(ENOMEM, errno); + + BPF_ASSERT_EQ(-1, syscall(__NR_setgid, 200)); + BPF_ASSERT_EQ(ENOSYS, errno); + + BPF_ASSERT_EQ(-1, syscall(__NR_setgid, 300)); + BPF_ASSERT_EQ(EPERM, errno); +} + +} // namespace + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/integration_tests/namespace_unix_domain_socket_unittest.cc b/sandbox/linux/integration_tests/namespace_unix_domain_socket_unittest.cc new file mode 100644 index 0000000000..9d79bff1c6 --- /dev/null +++ b/sandbox/linux/integration_tests/namespace_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.h" +#include "sandbox/linux/services/syscall_wrappers.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::Process process = base::Process::Open(pid); + CHECK(process.IsValid()); + base::ProcessId ret = base::GetParentProcessId(process.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(base::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 = base::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(base::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(base::UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = sys_clone(CLONE_NEWPID | SIGCHLD, 0, 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, sys_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(base::UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = sys_clone(CLONE_NEWPID | SIGCHLD, 0, 0, 0, 0); + CHECK_NE(-1, pid); + if (pid == 0) { + // Child process. + recv_sock.reset(); + + const pid_t pid2 = sys_clone(CLONE_NEWPID | SIGCHLD, 0, 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, sys_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(base::UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = sys_clone(CLONE_NEWPID | SIGCHLD, 0, 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/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc b/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc new file mode 100644 index 0000000000..9aa320997b --- /dev/null +++ b/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc @@ -0,0 +1,180 @@ +// Copyright 2015 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 <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include <vector> + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/syscall_broker/broker_file_permission.h" +#include "sandbox/linux/syscall_broker/broker_process.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +using bpf_dsl::Allow; +using bpf_dsl::ResultExpr; +using bpf_dsl::Trap; + +bool NoOpCallback() { + return true; +} + +// Test a trap handler that makes use of a broker process to open(). + +class InitializedOpenBroker { + public: + InitializedOpenBroker() : initialized_(false) { + std::vector<syscall_broker::BrokerFilePermission> permissions; + permissions.push_back( + syscall_broker::BrokerFilePermission::ReadOnly("/proc/allowed")); + permissions.push_back( + syscall_broker::BrokerFilePermission::ReadOnly("/proc/cpuinfo")); + + broker_process_.reset( + new syscall_broker::BrokerProcess(EPERM, permissions)); + BPF_ASSERT(broker_process() != NULL); + BPF_ASSERT(broker_process_->Init(base::Bind(&NoOpCallback))); + + initialized_ = true; + } + bool initialized() { return initialized_; } + class syscall_broker::BrokerProcess* broker_process() { + return broker_process_.get(); + } + + private: + bool initialized_; + scoped_ptr<class syscall_broker::BrokerProcess> broker_process_; + DISALLOW_COPY_AND_ASSIGN(InitializedOpenBroker); +}; + +intptr_t BrokerOpenTrapHandler(const struct arch_seccomp_data& args, + void* aux) { + BPF_ASSERT(aux); + syscall_broker::BrokerProcess* broker_process = + static_cast<syscall_broker::BrokerProcess*>(aux); + switch (args.nr) { + case __NR_faccessat: // access is a wrapper of faccessat in android + BPF_ASSERT(static_cast<int>(args.args[0]) == AT_FDCWD); + return broker_process->Access(reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); +#if defined(__NR_access) + case __NR_access: + return broker_process->Access(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); +#endif +#if defined(__NR_open) + case __NR_open: + return broker_process->Open(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); +#endif + case __NR_openat: + // We only call open() so if we arrive here, it's because glibc uses + // the openat() system call. + BPF_ASSERT(static_cast<int>(args.args[0]) == AT_FDCWD); + return broker_process->Open(reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); + default: + BPF_ASSERT(false); + return -ENOSYS; + } +} + +class DenyOpenPolicy : public bpf_dsl::Policy { + public: + explicit DenyOpenPolicy(InitializedOpenBroker* iob) : iob_(iob) {} + ~DenyOpenPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + + switch (sysno) { + case __NR_faccessat: +#if defined(__NR_access) + case __NR_access: +#endif +#if defined(__NR_open) + case __NR_open: +#endif + case __NR_openat: + // We get a InitializedOpenBroker class, but our trap handler wants + // the syscall_broker::BrokerProcess object. + return Trap(BrokerOpenTrapHandler, iob_->broker_process()); + default: + return Allow(); + } + } + + private: + InitializedOpenBroker* iob_; + + DISALLOW_COPY_AND_ASSIGN(DenyOpenPolicy); +}; + +// We use a InitializedOpenBroker class, so that we can run unsandboxed +// code in its constructor, which is the only way to do so in a BPF_TEST. +BPF_TEST(SandboxBPF, + UseOpenBroker, + DenyOpenPolicy, + InitializedOpenBroker /* (*BPF_AUX) */) { + BPF_ASSERT(BPF_AUX->initialized()); + syscall_broker::BrokerProcess* broker_process = BPF_AUX->broker_process(); + BPF_ASSERT(broker_process != NULL); + + // First, use the broker "manually" + BPF_ASSERT(broker_process->Open("/proc/denied", O_RDONLY) == -EPERM); + BPF_ASSERT(broker_process->Access("/proc/denied", R_OK) == -EPERM); + BPF_ASSERT(broker_process->Open("/proc/allowed", O_RDONLY) == -ENOENT); + BPF_ASSERT(broker_process->Access("/proc/allowed", R_OK) == -ENOENT); + + // Now use glibc's open() as an external library would. + BPF_ASSERT(open("/proc/denied", O_RDONLY) == -1); + BPF_ASSERT(errno == EPERM); + + BPF_ASSERT(open("/proc/allowed", O_RDONLY) == -1); + BPF_ASSERT(errno == ENOENT); + + // Also test glibc's openat(), some versions of libc use it transparently + // instead of open(). + BPF_ASSERT(openat(AT_FDCWD, "/proc/denied", O_RDONLY) == -1); + BPF_ASSERT(errno == EPERM); + + BPF_ASSERT(openat(AT_FDCWD, "/proc/allowed", O_RDONLY) == -1); + BPF_ASSERT(errno == ENOENT); + + // And test glibc's access(). + BPF_ASSERT(access("/proc/denied", R_OK) == -1); + BPF_ASSERT(errno == EPERM); + + BPF_ASSERT(access("/proc/allowed", R_OK) == -1); + BPF_ASSERT(errno == ENOENT); + + // This is also white listed and does exist. + int cpu_info_access = access("/proc/cpuinfo", R_OK); + BPF_ASSERT(cpu_info_access == 0); + int cpu_info_fd = open("/proc/cpuinfo", O_RDONLY); + BPF_ASSERT(cpu_info_fd >= 0); + char buf[1024]; + BPF_ASSERT(read(cpu_info_fd, buf, sizeof(buf)) > 0); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/sandbox_linux.gypi b/sandbox/linux/sandbox_linux.gypi new file mode 100644 index 0000000000..a7bd259d8a --- /dev/null +++ b/sandbox/linux/sandbox_linux.gypi @@ -0,0 +1,416 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'variables': { + 'conditions': [ + ['OS=="linux"', { + 'compile_suid_client': 1, + 'compile_credentials': 1, + 'use_base_test_suite': 1, + }, { + 'compile_suid_client': 0, + 'compile_credentials': 0, + 'use_base_test_suite': 0, + }], + ['OS=="linux" and (target_arch=="ia32" or target_arch=="x64" or ' + 'target_arch=="mipsel")', { + 'compile_seccomp_bpf_demo': 1, + }, { + 'compile_seccomp_bpf_demo': 0, + }], + ], + }, + 'target_defaults': { + 'target_conditions': [ + # All linux/ files will automatically be excluded on Android + # so make sure we re-include them explicitly. + ['OS == "android"', { + 'sources/': [ + ['include', '^linux/'], + ], + }], + ], + }, + 'targets': [ + # We have two principal targets: sandbox and sandbox_linux_unittests + # All other targets are listed as dependencies. + # There is one notable exception: for historical reasons, chrome_sandbox is + # the setuid sandbox and is its own target. + { + 'target_name': 'sandbox', + 'type': 'none', + 'dependencies': [ + 'sandbox_services', + ], + 'conditions': [ + [ 'compile_suid_client==1', { + 'dependencies': [ + 'suid_sandbox_client', + ], + }], + # Compile seccomp BPF when we support it. + [ 'use_seccomp_bpf==1', { + 'dependencies': [ + 'seccomp_bpf', + 'seccomp_bpf_helpers', + ], + }], + ], + }, + { + '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', + ] + }], + [ 'use_base_test_suite==1', { + 'dependencies': [ + '../base/base.gyp:test_support_base', + ], + 'defines': [ + 'SANDBOX_USES_BASE_TEST_SUITE', + ], + }], + ], + }, + { + # The main sandboxing test target. + 'target_name': 'sandbox_linux_unittests', + 'includes': [ + 'sandbox_linux_test_sources.gypi', + ], + 'type': 'executable', + }, + { + # This target is the shared library used by Android APK (i.e. + # JNI-friendly) tests. + 'target_name': 'sandbox_linux_jni_unittests', + 'includes': [ + 'sandbox_linux_test_sources.gypi', + ], + 'type': 'shared_library', + 'conditions': [ + [ 'OS == "android"', { + 'dependencies': [ + '../testing/android/native_test.gyp:native_test_native_code', + ], + }], + ], + }, + { + 'target_name': 'seccomp_bpf', + 'type': '<(component)', + 'sources': [ + 'bpf_dsl/bpf_dsl.cc', + 'bpf_dsl/bpf_dsl.h', + 'bpf_dsl/bpf_dsl_forward.h', + 'bpf_dsl/bpf_dsl_impl.h', + 'bpf_dsl/codegen.cc', + 'bpf_dsl/codegen.h', + 'bpf_dsl/cons.h', + 'bpf_dsl/dump_bpf.cc', + 'bpf_dsl/dump_bpf.h', + 'bpf_dsl/linux_syscall_ranges.h', + 'bpf_dsl/policy.cc', + 'bpf_dsl/policy.h', + 'bpf_dsl/policy_compiler.cc', + 'bpf_dsl/policy_compiler.h', + 'bpf_dsl/seccomp_macros.h', + 'bpf_dsl/seccomp_macros.h', + 'bpf_dsl/syscall_set.cc', + 'bpf_dsl/syscall_set.h', + 'bpf_dsl/trap_registry.h', + 'bpf_dsl/verifier.cc', + 'bpf_dsl/verifier.h', + 'seccomp-bpf/die.cc', + 'seccomp-bpf/die.h', + 'seccomp-bpf/errorcode.cc', + 'seccomp-bpf/errorcode.h', + 'seccomp-bpf/sandbox_bpf.cc', + 'seccomp-bpf/sandbox_bpf.h', + 'seccomp-bpf/syscall.cc', + 'seccomp-bpf/syscall.h', + 'seccomp-bpf/trap.cc', + 'seccomp-bpf/trap.h', + ], + 'dependencies': [ + '../base/base.gyp:base', + 'sandbox_services', + 'sandbox_services_headers', + ], + 'defines': [ + 'SANDBOX_IMPLEMENTATION', + ], + 'includes': [ + # Disable LTO due to compiler bug + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57703 + '../../build/android/disable_lto.gypi', + ], + 'include_dirs': [ + '../..', + ], + }, + { + 'target_name': 'seccomp_bpf_helpers', + 'type': '<(component)', + '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', + ], + 'dependencies': [ + '../base/base.gyp:base', + 'sandbox_services', + 'seccomp_bpf', + ], + 'defines': [ + 'SANDBOX_IMPLEMENTATION', + ], + 'include_dirs': [ + '../..', + ], + }, + { + # The setuid sandbox, for Linux + 'target_name': 'chrome_sandbox', + 'type': 'executable', + 'sources': [ + 'suid/common/sandbox.h', + 'suid/common/suid_unsafe_environment_variables.h', + 'suid/process_util.h', + 'suid/process_util_linux.c', + 'suid/sandbox.c', + ], + 'cflags': [ + # For ULLONG_MAX + '-std=gnu99', + ], + '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': '<(component)', + 'sources': [ + 'services/init_process_reaper.cc', + 'services/init_process_reaper.h', + 'services/proc_util.cc', + 'services/proc_util.h', + 'services/resource_limits.cc', + 'services/resource_limits.h', + 'services/scoped_process.cc', + 'services/scoped_process.h', + 'services/syscall_wrappers.cc', + 'services/syscall_wrappers.h', + 'services/thread_helpers.cc', + 'services/thread_helpers.h', + 'services/yama.cc', + 'services/yama.h', + 'syscall_broker/broker_channel.cc', + 'syscall_broker/broker_channel.h', + 'syscall_broker/broker_client.cc', + 'syscall_broker/broker_client.h', + 'syscall_broker/broker_common.h', + 'syscall_broker/broker_file_permission.cc', + 'syscall_broker/broker_file_permission.h', + 'syscall_broker/broker_host.cc', + 'syscall_broker/broker_host.h', + 'syscall_broker/broker_policy.cc', + 'syscall_broker/broker_policy.h', + 'syscall_broker/broker_process.cc', + 'syscall_broker/broker_process.h', + ], + 'dependencies': [ + '../base/base.gyp:base', + ], + 'defines': [ + 'SANDBOX_IMPLEMENTATION', + ], + 'conditions': [ + ['compile_credentials==1', { + 'sources': [ + 'services/credentials.cc', + 'services/credentials.h', + 'services/namespace_sandbox.cc', + 'services/namespace_sandbox.h', + 'services/namespace_utils.cc', + 'services/namespace_utils.h', + ], + 'dependencies': [ + # for capability.h. + 'sandbox_services_headers', + ], + }], + ], + 'include_dirs': [ + '..', + ], + }, + { 'target_name': 'sandbox_services_headers', + 'type': 'none', + 'sources': [ + 'system_headers/arm64_linux_syscalls.h', + 'system_headers/arm64_linux_ucontext.h', + 'system_headers/arm_linux_syscalls.h', + 'system_headers/arm_linux_ucontext.h', + 'system_headers/capability.h', + 'system_headers/i386_linux_ucontext.h', + 'system_headers/linux_futex.h', + 'system_headers/linux_seccomp.h', + 'system_headers/linux_syscalls.h', + 'system_headers/linux_time.h', + 'system_headers/linux_ucontext.h', + 'system_headers/mips_linux_syscalls.h', + 'system_headers/mips_linux_ucontext.h', + 'system_headers/x86_32_linux_syscalls.h', + 'system_headers/x86_64_linux_syscalls.h', + ], + 'include_dirs': [ + '..', + ], + }, + { + # We make this its own target so that it does not interfere + # with our tests. + 'target_name': 'libc_urandom_override', + 'type': 'static_library', + 'sources': [ + 'services/libc_urandom_override.cc', + 'services/libc_urandom_override.h', + ], + 'dependencies': [ + '../base/base.gyp:base', + ], + 'include_dirs': [ + '..', + ], + }, + { + 'target_name': 'suid_sandbox_client', + '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', + 'suid/client/setuid_sandbox_host.cc', + 'suid/client/setuid_sandbox_host.h', + ], + 'defines': [ + 'SANDBOX_IMPLEMENTATION', + ], + 'dependencies': [ + '../base/base.gyp:base', + 'sandbox_services', + ], + 'include_dirs': [ + '..', + ], + }, + ], + 'conditions': [ + [ '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)' ], + }], + }, + { + 'target_name': 'sandbox_linux_unittests_deps', + 'type': 'none', + 'dependencies': [ + 'sandbox_linux_unittests_stripped', + ], + # For the component build, ensure dependent shared libraries are + # stripped and put alongside sandbox_linux_unittests to simplify pushing + # to the device. + 'variables': { + 'output_dir': '<(PRODUCT_DIR)/sandbox_linux_unittests_deps/', + 'native_binary': '<(PRODUCT_DIR)/sandbox_linux_unittests_stripped', + 'include_main_binary': 0, + }, + 'includes': [ + '../../build/android/native_app_dependencies.gypi' + ], + }], + }], + [ 'OS=="android"', { + 'targets': [ + { + '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' ], + } + ], + }], + ['test_isolation_mode != "noop"', { + 'targets': [ + { + 'target_name': 'sandbox_linux_unittests_run', + 'type': 'none', + 'dependencies': [ + 'sandbox_linux_unittests', + ], + 'includes': [ + '../../build/isolate.gypi', + ], + 'sources': [ + '../sandbox_linux_unittests.isolate', + ], + }, + ], + }], + ], +} diff --git a/sandbox/linux/sandbox_linux_nacl_nonsfi.gyp b/sandbox/linux/sandbox_linux_nacl_nonsfi.gyp new file mode 100644 index 0000000000..87ad06ccdc --- /dev/null +++ b/sandbox/linux/sandbox_linux_nacl_nonsfi.gyp @@ -0,0 +1,88 @@ +# Copyright 2015 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'includes': [ + '../../build/common_untrusted.gypi', + ], + 'conditions': [ + ['disable_nacl==0 and disable_nacl_untrusted==0', { + 'targets': [ + { + 'target_name': 'sandbox_linux_nacl_nonsfi', + 'type': 'none', + 'variables': { + 'nacl_untrusted_build': 1, + 'nlib_target': 'libsandbox_linux_nacl_nonsfi.a', + 'build_glibc': 0, + 'build_newlib': 0, + 'build_irt': 0, + 'build_pnacl_newlib': 0, + 'build_nonsfi_helper': 1, + + 'sources': [ + # This is the subset of linux build target, needed for + # nacl_helper_nonsfi's sandbox implementation. + 'bpf_dsl/bpf_dsl.cc', + 'bpf_dsl/codegen.cc', + 'bpf_dsl/dump_bpf.cc', + 'bpf_dsl/policy.cc', + 'bpf_dsl/policy_compiler.cc', + 'bpf_dsl/syscall_set.cc', + 'bpf_dsl/verifier.cc', + 'seccomp-bpf-helpers/sigsys_handlers.cc', + 'seccomp-bpf-helpers/syscall_parameters_restrictions.cc', + 'seccomp-bpf/die.cc', + 'seccomp-bpf/errorcode.cc', + 'seccomp-bpf/sandbox_bpf.cc', + 'seccomp-bpf/syscall.cc', + 'seccomp-bpf/trap.cc', + 'services/credentials.cc', + 'services/namespace_sandbox.cc', + 'services/namespace_utils.cc', + 'services/proc_util.cc', + 'services/resource_limits.cc', + 'services/syscall_wrappers.cc', + 'services/thread_helpers.cc', + 'suid/client/setuid_sandbox_client.cc', + ], + }, + 'dependencies': [ + '../../base/base_nacl.gyp:base_nacl_nonsfi', + ], + }, + ], + }], + + ['disable_nacl==0 and disable_nacl_untrusted==0 and enable_nacl_nonsfi_test==1', { + 'targets': [ + { + 'target_name': 'sandbox_linux_test_utils_nacl_nonsfi', + 'type': 'none', + 'variables': { + 'nacl_untrusted_build': 1, + 'nlib_target': 'libsandbox_linux_test_utils_nacl_nonsfi.a', + 'build_glibc': 0, + 'build_newlib': 0, + 'build_irt': 0, + 'build_pnacl_newlib': 0, + 'build_nonsfi_helper': 1, + + 'sources': [ + 'seccomp-bpf/sandbox_bpf_test_runner.cc', + 'tests/sandbox_test_runner.cc', + 'tests/unit_tests.cc', + ], + }, + 'dependencies': [ + '../../testing/gtest_nacl.gyp:gtest_nacl', + ], + }, + ], + }], + ], +} diff --git a/sandbox/linux/sandbox_linux_test_sources.gypi b/sandbox/linux/sandbox_linux_test_sources.gypi new file mode 100644 index 0000000000..82d7532056 --- /dev/null +++ b/sandbox/linux/sandbox_linux_test_sources.gypi @@ -0,0 +1,84 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Tests need to be compiled in the same link unit, so we have to list them +# in a separate .gypi file. +{ + 'dependencies': [ + 'sandbox', + 'sandbox_linux_test_utils', + 'sandbox_services', + '../base/base.gyp:base', + '../testing/gtest.gyp:gtest', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'services/proc_util_unittest.cc', + 'services/scoped_process_unittest.cc', + 'services/resource_limits_unittests.cc', + 'services/syscall_wrappers_unittest.cc', + 'services/thread_helpers_unittests.cc', + 'services/yama_unittests.cc', + 'syscall_broker/broker_file_permission_unittest.cc', + 'syscall_broker/broker_process_unittest.cc', + 'tests/main.cc', + 'tests/scoped_temporary_file.cc', + 'tests/scoped_temporary_file.h', + 'tests/scoped_temporary_file_unittest.cc', + 'tests/test_utils_unittest.cc', + 'tests/unit_tests_unittest.cc', + ], + 'conditions': [ + [ 'compile_suid_client==1', { + 'sources': [ + 'suid/client/setuid_sandbox_client_unittest.cc', + 'suid/client/setuid_sandbox_host_unittest.cc', + ], + }], + [ 'use_seccomp_bpf==1', { + 'sources': [ + 'bpf_dsl/bpf_dsl_unittest.cc', + 'bpf_dsl/codegen_unittest.cc', + 'bpf_dsl/cons_unittest.cc', + 'bpf_dsl/syscall_set_unittest.cc', + 'integration_tests/bpf_dsl_seccomp_unittest.cc', + 'integration_tests/seccomp_broker_process_unittest.cc', + 'seccomp-bpf-helpers/baseline_policy_unittest.cc', + 'seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc', + 'seccomp-bpf/bpf_tests_unittest.cc', + 'seccomp-bpf/errorcode_unittest.cc', + 'seccomp-bpf/sandbox_bpf_unittest.cc', + 'seccomp-bpf/syscall_unittest.cc', + 'seccomp-bpf/trap_unittest.cc', + ], + }], + [ 'compile_credentials==1', { + 'sources': [ + 'integration_tests/namespace_unix_domain_socket_unittest.cc', + 'services/credentials_unittest.cc', + 'services/namespace_utils_unittest.cc', + ], + 'dependencies': [ + '../build/linux/system.gyp:libcap' + ], + 'conditions': [ + [ 'use_base_test_suite==1', { + 'sources': [ + 'services/namespace_sandbox_unittest.cc', + ] + }] + ], + }], + [ 'use_base_test_suite==1', { + 'dependencies': [ + '../base/base.gyp:test_support_base', + ], + 'defines': [ + 'SANDBOX_USES_BASE_TEST_SUITE', + ], + }], + ], +} diff --git a/sandbox/linux/seccomp-bpf-helpers/DEPS b/sandbox/linux/seccomp-bpf-helpers/DEPS new file mode 100644 index 0000000000..4419fd1da3 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+sandbox/linux/bpf_dsl", + "+sandbox/linux/seccomp-bpf", + "+sandbox/linux/services", + "+sandbox/linux/system_headers", + "+third_party/lss/linux_syscall_support.h", +] diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc new file mode 100644 index 0000000000..8c679a3d41 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc @@ -0,0 +1,270 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf-helpers/baseline_policy.h" + +#include <errno.h> +#include <sys/mman.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" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" +#include "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h" +#include "sandbox/linux/seccomp-bpf-helpers/syscall_sets.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +// Changing this implementation will have an effect on *all* policies. +// Currently this means: Renderer/Worker, GPU, Flash and NaCl. + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::Arg; +using sandbox::bpf_dsl::Error; +using sandbox::bpf_dsl::If; +using sandbox::bpf_dsl::ResultExpr; + +namespace sandbox { + +namespace { + +bool IsBaselinePolicyAllowed(int sysno) { + return SyscallSets::IsAllowedAddressSpaceAccess(sysno) || + SyscallSets::IsAllowedBasicScheduler(sysno) || + SyscallSets::IsAllowedEpoll(sysno) || + SyscallSets::IsAllowedFileSystemAccessViaFd(sysno) || + SyscallSets::IsAllowedFutex(sysno) || + SyscallSets::IsAllowedGeneralIo(sysno) || + SyscallSets::IsAllowedGetOrModifySocket(sysno) || + SyscallSets::IsAllowedGettime(sysno) || + SyscallSets::IsAllowedProcessStartOrDeath(sysno) || + SyscallSets::IsAllowedSignalHandling(sysno) || + SyscallSets::IsGetSimpleId(sysno) || + SyscallSets::IsKernelInternalApi(sysno) || +#if defined(__arm__) + SyscallSets::IsArmPrivate(sysno) || +#endif +#if defined(__mips__) + SyscallSets::IsMipsPrivate(sysno) || +#endif + SyscallSets::IsAllowedOperationOnFd(sysno); +} + +// System calls that will trigger the crashing SIGSYS handler. +bool IsBaselinePolicyWatched(int sysno) { + return SyscallSets::IsAdminOperation(sysno) || + SyscallSets::IsAdvancedScheduler(sysno) || + SyscallSets::IsAdvancedTimer(sysno) || + SyscallSets::IsAsyncIo(sysno) || + SyscallSets::IsDebug(sysno) || + SyscallSets::IsEventFd(sysno) || + SyscallSets::IsExtendedAttributes(sysno) || + SyscallSets::IsFaNotify(sysno) || + SyscallSets::IsFsControl(sysno) || + SyscallSets::IsGlobalFSViewChange(sysno) || + SyscallSets::IsGlobalProcessEnvironment(sysno) || + SyscallSets::IsGlobalSystemStatus(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) || +#if defined(__i386__) || defined(__mips__) + SyscallSets::IsSocketCall(sysno) || +#endif +#if defined(__arm__) + SyscallSets::IsArmPciConfig(sysno) || +#endif +#if defined(__mips__) + SyscallSets::IsMipsMisc(sysno) || +#endif + SyscallSets::IsTimer(sysno); +} + +// |fs_denied_errno| is the errno return for denied filesystem access. +ResultExpr EvaluateSyscallImpl(int fs_denied_errno, + pid_t current_pid, + 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(); + } + + if (sysno == __NR_sched_getaffinity) { + return Allow(); + } + + // Used when RSS limiting is enabled in sanitizers. + if (sysno == __NR_getrusage) { + return RestrictGetrusage(); + } + + if (sysno == __NR_sigaltstack) { + // Required for better stack overflow detection in ASan. Disallowed in + // non-ASan builds. + return Allow(); + } +#endif // defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || + // defined(MEMORY_SANITIZER) + + if (IsBaselinePolicyAllowed(sysno)) { + return Allow(); + } + +#if defined(OS_ANDROID) + // Needed for thread creation. + if (sysno == __NR_sigaltstack) + return Allow(); +#endif + + if (sysno == __NR_clock_gettime) { + return RestrictClockID(); + } + + if (sysno == __NR_clone) { + return RestrictCloneToThreadsAndEPERMFork(); + } + + if (sysno == __NR_fcntl) + return RestrictFcntlCommands(); + +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + if (sysno == __NR_fcntl64) + return RestrictFcntlCommands(); +#endif + +#if !defined(__aarch64__) + // fork() is never used as a system call (clone() is used instead), but we + // have seen it in fallback code on Android. + if (sysno == __NR_fork) { + return Error(EPERM); + } +#endif + + if (sysno == __NR_futex) + return RestrictFutex(); + + if (sysno == __NR_set_robust_list) + return Error(EPERM); + + if (sysno == __NR_getpriority || sysno ==__NR_setpriority) + return RestrictGetSetpriority(current_pid); + + if (sysno == __NR_madvise) { + // Only allow MADV_DONTNEED (aka MADV_FREE). + const Arg<int> advice(2); + return If(advice == MADV_DONTNEED, Allow()).Else(Error(EPERM)); + } + +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) || \ + defined(__aarch64__) + if (sysno == __NR_mmap) + return RestrictMmapFlags(); +#endif + +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + if (sysno == __NR_mmap2) + return RestrictMmapFlags(); +#endif + + if (sysno == __NR_mprotect) + return RestrictMprotectFlags(); + + if (sysno == __NR_prctl) + return RestrictPrctl(); + +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + if (sysno == __NR_socketpair) { + // Only allow AF_UNIX, PF_UNIX. Crash if anything else is seen. + static_assert(AF_UNIX == PF_UNIX, + "af_unix and pf_unix should not be different"); + const Arg<int> domain(0); + return If(domain == AF_UNIX, Allow()).Else(CrashSIGSYS()); + } +#endif + + if (SyscallSets::IsKill(sysno)) { + return RestrictKillTarget(current_pid, sysno); + } + + if (SyscallSets::IsFileSystem(sysno) || + SyscallSets::IsCurrentDirectory(sysno)) { + return Error(fs_denied_errno); + } + + if (SyscallSets::IsSeccomp(sysno)) + return Error(EPERM); + + if (SyscallSets::IsAnySystemV(sysno)) { + return Error(EPERM); + } + + if (SyscallSets::IsUmask(sysno) || + SyscallSets::IsDeniedFileSystemAccessViaFd(sysno) || + SyscallSets::IsDeniedGetOrModifySocket(sysno) || + SyscallSets::IsProcessPrivilegeChange(sysno)) { + return Error(EPERM); + } + +#if defined(__i386__) || defined(__mips__) + if (SyscallSets::IsSocketCall(sysno)) + return RestrictSocketcallCommand(); +#endif + + if (IsBaselinePolicyWatched(sysno)) { + // Previously unseen syscalls. TODO(jln): some of these should + // be denied gracefully right away. + return CrashSIGSYS(); + } + + // In any other case crash the program with our SIGSYS handler. + return CrashSIGSYS(); +} + +} // namespace. + +// Unfortunately C++03 doesn't allow delegated constructors. +// Call other constructor when C++11 lands. +BaselinePolicy::BaselinePolicy() : BaselinePolicy(EPERM) {} + +BaselinePolicy::BaselinePolicy(int fs_denied_errno) + : fs_denied_errno_(fs_denied_errno), policy_pid_(sys_getpid()) { +} + +BaselinePolicy::~BaselinePolicy() { + // Make sure that this policy is created, used and destroyed by a single + // process. + DCHECK_EQ(sys_getpid(), policy_pid_); +} + +ResultExpr BaselinePolicy::EvaluateSyscall(int sysno) const { + // Sanity check that we're only called with valid syscall numbers. + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + // Make sure that this policy is used in the creating process. + if (1 == sysno) { + DCHECK_EQ(sys_getpid(), policy_pid_); + } + return EvaluateSyscallImpl(fs_denied_errno_, policy_pid_, sysno); +} + +ResultExpr BaselinePolicy::InvalidSyscall() const { + return CrashSIGSYS(); +} + +} // namespace sandbox. diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h new file mode 100644 index 0000000000..4169d9c3e2 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h @@ -0,0 +1,48 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_HELPERS_BASELINE_POLICY_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_HELPERS_BASELINE_POLICY_H_ + +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This is a helper to build seccomp-bpf policies, i.e. policies for a sandbox +// that reduces the Linux kernel's attack surface. Given its nature, it doesn't +// have a clear semantics and is mostly "implementation-defined". +// +// This class implements the Policy interface with a "baseline" +// policy for use 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. +// 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 bpf_dsl::Policy { + public: + BaselinePolicy(); + // |fs_denied_errno| is the errno returned when a filesystem access system + // call is denied. + explicit BaselinePolicy(int fs_denied_errno); + ~BaselinePolicy() override; + + bpf_dsl::ResultExpr EvaluateSyscall(int system_call_number) const override; + bpf_dsl::ResultExpr InvalidSyscall() const override; + pid_t policy_pid() const { return policy_pid_; } + + private: + int fs_denied_errno_; + + // The PID that the policy applies to (should be equal to the current pid). + pid_t policy_pid_; + + DISALLOW_COPY_AND_ASSIGN(BaselinePolicy); +}; + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SECCOMP_BPF_HELPERS_BASELINE_POLICY_H_ diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc new file mode 100644 index 0000000000..614849f61c --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc @@ -0,0 +1,334 @@ +// 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 <fcntl.h> +#include <sched.h> +#include <signal.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/resource.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 <time.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/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/services/thread_helpers.h" +#include "sandbox/linux/system_headers/linux_futex.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +namespace { + +// 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; + TestUtils::HandlePostForkReturn(pid); + + BPF_ASSERT_EQ(-1, pid); + BPF_ASSERT_EQ(EPERM, fork_errno); +} + +pid_t ForkX86Glibc() { + static pid_t ptid; + return sys_clone(CLONE_PARENT_SETTID | SIGCHLD, nullptr, &ptid, nullptr, + nullptr); +} + +BPF_TEST_C(BaselinePolicy, ForkX86Eperm, BaselinePolicy) { + errno = 0; + pid_t pid = ForkX86Glibc(); + const int fork_errno = errno; + TestUtils::HandlePostForkReturn(pid); + + BPF_ASSERT_EQ(-1, pid); + BPF_ASSERT_EQ(EPERM, fork_errno); +} + +pid_t ForkARMGlibc() { + static pid_t ctid; + return sys_clone(CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, nullptr, + nullptr, &ctid, nullptr); +} + +BPF_TEST_C(BaselinePolicy, ForkArmEperm, BaselinePolicy) { + errno = 0; + pid_t pid = ForkARMGlibc(); + const int fork_errno = errno; + TestUtils::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_SEGV_MESSAGE(GetCloneErrorMessageContentForTests()), + BaselinePolicy) { + pid_t pid = sys_clone(CLONE_THREAD | SIGCHLD); + TestUtils::HandlePostForkReturn(pid); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + DisallowedKillCrashes, + DEATH_SEGV_MESSAGE(GetKillErrorMessageContentForTests()), + BaselinePolicy) { + BPF_ASSERT_NE(1, getpid()); + kill(1, 0); + _exit(0); +} + +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__) || defined(__aarch64__) +BPF_DEATH_TEST_C(BaselinePolicy, + SocketpairWrongDomain, + DEATH_SEGV_MESSAGE(GetErrorMessageContentForTests()), + BaselinePolicy) { + int sv[2]; + ignore_result(socketpair(AF_INET, SOCK_STREAM, 0, sv)); + _exit(1); +} +#endif // defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + +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); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + SIGSYS_InvalidSyscall, + DEATH_SEGV_MESSAGE(GetErrorMessageContentForTests()), + BaselinePolicy) { + Syscall::InvalidCall(); +} + +// 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_SEGV_MESSAGE(GetErrorMessageContentForTests()), \ + BaselinePolicy) { \ + syscall(sysno, 0, 0, 0, 0, 0, 0); \ + _exit(1); \ + } + +TEST_BASELINE_SIGSYS(__NR_acct); +TEST_BASELINE_SIGSYS(__NR_chroot); +TEST_BASELINE_SIGSYS(__NR_fanotify_init); +TEST_BASELINE_SIGSYS(__NR_fgetxattr); +TEST_BASELINE_SIGSYS(__NR_getcpu); +TEST_BASELINE_SIGSYS(__NR_getitimer); +TEST_BASELINE_SIGSYS(__NR_init_module); +TEST_BASELINE_SIGSYS(__NR_io_cancel); +TEST_BASELINE_SIGSYS(__NR_keyctl); +TEST_BASELINE_SIGSYS(__NR_mq_open); +TEST_BASELINE_SIGSYS(__NR_ptrace); +TEST_BASELINE_SIGSYS(__NR_sched_setaffinity); +TEST_BASELINE_SIGSYS(__NR_setpgid); +TEST_BASELINE_SIGSYS(__NR_swapon); +TEST_BASELINE_SIGSYS(__NR_sysinfo); +TEST_BASELINE_SIGSYS(__NR_syslog); +TEST_BASELINE_SIGSYS(__NR_timer_create); + +#if !defined(__aarch64__) +TEST_BASELINE_SIGSYS(__NR_eventfd); +TEST_BASELINE_SIGSYS(__NR_inotify_init); +TEST_BASELINE_SIGSYS(__NR_vserver); +#endif + +BPF_DEATH_TEST_C(BaselinePolicy, + FutexWithRequeuePriorityInheritence, + DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()), + BaselinePolicy) { + syscall(__NR_futex, NULL, FUTEX_CMP_REQUEUE_PI, 0, NULL, NULL, 0); + _exit(1); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + FutexWithRequeuePriorityInheritencePrivate, + DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()), + BaselinePolicy) { + syscall(__NR_futex, NULL, FUTEX_CMP_REQUEUE_PI_PRIVATE, 0, NULL, NULL, 0); + _exit(1); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + FutexWithUnlockPIPrivate, + DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()), + BaselinePolicy) { + syscall(__NR_futex, NULL, FUTEX_UNLOCK_PI_PRIVATE, 0, NULL, NULL, 0); + _exit(1); +} + +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_SEGV_MESSAGE(GetPrctlErrorMessageContentForTests()), + BaselinePolicy) { + prctl(PR_CAPBSET_READ, 0, 0, 0, 0); + _exit(1); +} + +BPF_TEST_C(BaselinePolicy, GetOrSetPriority, BaselinePolicy) { + errno = 0; + const int original_prio = getpriority(PRIO_PROCESS, 0); + // Check errno instead of the return value since this system call can return + // -1 as a valid value. + BPF_ASSERT_EQ(0, errno); + + errno = 0; + int rc = getpriority(PRIO_PROCESS, getpid()); + BPF_ASSERT_EQ(0, errno); + + rc = getpriority(PRIO_PROCESS, getpid() + 1); + BPF_ASSERT_EQ(-1, rc); + BPF_ASSERT_EQ(EPERM, errno); + + rc = setpriority(PRIO_PROCESS, 0, original_prio); + BPF_ASSERT_EQ(0, rc); + + rc = setpriority(PRIO_PROCESS, getpid(), original_prio); + BPF_ASSERT_EQ(0, rc); + + errno = 0; + rc = setpriority(PRIO_PROCESS, getpid() + 1, original_prio); + BPF_ASSERT_EQ(-1, rc); + BPF_ASSERT_EQ(EPERM, errno); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + GetPrioritySigsys, + DEATH_SEGV_MESSAGE(GetErrorMessageContentForTests()), + BaselinePolicy) { + getpriority(PRIO_USER, 0); + _exit(1); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + ClockGettimeWithDisallowedClockCrashes, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + BaselinePolicy) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc b/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc new file mode 100644 index 0000000000..05250d147f --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc @@ -0,0 +1,297 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Note: any code in this file MUST be async-signal safe. + +#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" + +#include <sys/syscall.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +#if defined(__mips__) +// __NR_Linux, is defined in <asm/unistd.h>. +#include <asm/unistd.h> +#endif + +#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() { +#if defined(__x86_64__) + return true; +#else + return false; +#endif +} + +// Write |error_message| to stderr. Similar to RawLog(), but a bit more careful +// about async-signal safety. |size| is the size to write and should typically +// not include a terminating \0. +void WriteToStdErr(const char* error_message, size_t size) { + while (size > 0) { + // TODO(jln): query the current policy to check if send() is available and + // use it to perform a non-blocking write. + const int ret = HANDLE_EINTR(write(STDERR_FILENO, error_message, size)); + // We can't handle any type of error here. + if (ret <= 0 || static_cast<size_t>(ret) > size) break; + size -= ret; + error_message += ret; + } +} + +// Invalid syscall values are truncated to zero. +// On architectures where base value is zero (Intel and Arm), +// syscall number is the same as offset from base. +// This function returns values between 0 and 1023 on all architectures. +// On architectures where base value is different than zero (currently only +// Mips), we are truncating valid syscall values to offset from base. +uint32_t SyscallNumberToOffsetFromBase(uint32_t sysno) { +#if defined(__mips__) + // On MIPS syscall numbers are in different range than on x86 and ARM. + // Valid MIPS O32 ABI syscall __NR_syscall will be truncated to zero for + // simplicity. + sysno = sysno - __NR_Linux; +#endif + + if (sysno >= 1024) + sysno = 0; + + return sysno; +} + +// Print a seccomp-bpf failure to handle |sysno| to stderr in an +// async-signal safe way. +void PrintSyscallError(uint32_t sysno) { + if (sysno >= 1024) + sysno = 0; + // TODO(markus): replace with async-signal safe snprintf when available. + const size_t kNumDigits = 4; + char sysno_base10[kNumDigits]; + uint32_t rem = sysno; + uint32_t mod = 0; + for (int i = kNumDigits - 1; i >= 0; i--) { + mod = rem % 10; + rem /= 10; + sysno_base10[i] = '0' + mod; + } +#if defined(__mips__) && (_MIPS_SIM == _MIPS_SIM_ABI32) + static const char kSeccompErrorPrefix[] = __FILE__ + ":**CRASHING**:" SECCOMP_MESSAGE_COMMON_CONTENT " in syscall 4000 + "; +#else + static const char kSeccompErrorPrefix[] = + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_COMMON_CONTENT " in syscall "; +#endif + static const char kSeccompErrorPostfix[] = "\n"; + WriteToStdErr(kSeccompErrorPrefix, sizeof(kSeccompErrorPrefix) - 1); + WriteToStdErr(sysno_base10, sizeof(sysno_base10)); + WriteToStdErr(kSeccompErrorPostfix, sizeof(kSeccompErrorPostfix) - 1); +} + +} // namespace. + +namespace sandbox { + +intptr_t CrashSIGSYS_Handler(const struct arch_seccomp_data& args, void* aux) { + uint32_t syscall = SyscallNumberToOffsetFromBase(args.nr); + + PrintSyscallError(syscall); + + // Encode 8-bits of the 1st two arguments too, so we can discern which socket + // type, which fcntl, ... etc., without being likely to hit a mapped + // address. + // Do not encode more bits here without thinking about increasing the + // likelihood of collision with mapped pages. + syscall |= ((args.args[0] & 0xffUL) << 12); + syscall |= ((args.args[1] & 0xffUL) << 20); + // Purposefully dereference the syscall as an address so it'll show up very + // clearly and easily in crash dumps. + volatile char* addr = reinterpret_cast<volatile char*>(syscall); + *addr = '\0'; + // In case we hit a mapped address, hit the null page with just the syscall, + // for paranoia. + syscall &= 0xfffUL; + addr = reinterpret_cast<volatile char*>(syscall); + *addr = '\0'; + for (;;) + _exit(1); +} + +// 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. + volatile uint64_t clone_flags = args.args[0]; + volatile char* addr; + if (IsArchitectureX86_64()) { + addr = reinterpret_cast<volatile char*>(clone_flags & 0xFFFFFF); + *addr = '\0'; + } + // Hit the NULL page if this fails to fault. + addr = reinterpret_cast<volatile char*>(clone_flags & 0xFFF); + *addr = '\0'; + for (;;) + _exit(1); +} + +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. + volatile uint64_t option = args.args[0]; + volatile char* addr = + reinterpret_cast<volatile char*>(option & 0xFFF); + *addr = '\0'; + for (;;) + _exit(1); +} + +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. + volatile uint64_t request = args.args[1]; + volatile char* addr = reinterpret_cast<volatile char*>(request & 0xFFFF); + *addr = '\0'; + // Hit the NULL page if this fails. + addr = reinterpret_cast<volatile char*>(request & 0xFFF); + *addr = '\0'; + for (;;) + _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 "pid" volatile so that we can see it on the stack in a minidump. + volatile uint64_t my_pid = sys_getpid(); + volatile char* addr = reinterpret_cast<volatile char*>(my_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); +} + +intptr_t SIGSYSSchedHandler(const struct arch_seccomp_data& args, + void* aux) { + switch (args.nr) { + case __NR_sched_getaffinity: + case __NR_sched_getattr: + case __NR_sched_getparam: + case __NR_sched_getscheduler: + case __NR_sched_rr_get_interval: + case __NR_sched_setaffinity: + case __NR_sched_setattr: + case __NR_sched_setparam: + case __NR_sched_setscheduler: + const pid_t tid = sys_gettid(); + // The first argument is the pid. If is our thread id, then replace it + // with 0, which is equivalent and allowed by the policy. + if (args.args[0] == static_cast<uint64_t>(tid)) { + return Syscall::Call(args.nr, + 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])); + } + break; + } + + CrashSIGSYS_Handler(args, aux); + + // Should never be reached. + RAW_CHECK(false); + return -ENOSYS; +} + +bpf_dsl::ResultExpr CrashSIGSYS() { + return bpf_dsl::Trap(CrashSIGSYS_Handler, NULL); +} + +bpf_dsl::ResultExpr CrashSIGSYSClone() { + return bpf_dsl::Trap(SIGSYSCloneFailure, NULL); +} + +bpf_dsl::ResultExpr CrashSIGSYSPrctl() { + return bpf_dsl::Trap(SIGSYSPrctlFailure, NULL); +} + +bpf_dsl::ResultExpr CrashSIGSYSIoctl() { + return bpf_dsl::Trap(SIGSYSIoctlFailure, NULL); +} + +bpf_dsl::ResultExpr CrashSIGSYSKill() { + return bpf_dsl::Trap(SIGSYSKillFailure, NULL); +} + +bpf_dsl::ResultExpr CrashSIGSYSFutex() { + return bpf_dsl::Trap(SIGSYSFutexFailure, NULL); +} + +bpf_dsl::ResultExpr RewriteSchedSIGSYS() { + return bpf_dsl::Trap(SIGSYSSchedHandler, NULL); +} + +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/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h b/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h new file mode 100644 index 0000000000..c64e994172 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h @@ -0,0 +1,82 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SIGSYS_HANDLERS_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SIGSYS_HANDLERS_H_ + +#include <stdint.h> + +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/sandbox_export.h" + +// The handlers are suitable for use in Trap() error codes. They are +// guaranteed to be async-signal safe. +// See sandbox/linux/seccomp-bpf/trap.h to see how they work. + +namespace sandbox { + +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. +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|. +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. +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. +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); +// If the syscall is not being called on the current tid, crashes in the same +// way as CrashSIGSYS_Handler. Otherwise, returns the result of calling the +// syscall with the pid argument set to 0 (which for these calls means the +// current thread). The following syscalls are supported: +// +// sched_getaffinity(), sched_getattr(), sched_getparam(), sched_getscheduler(), +// sched_rr_get_interval(), sched_setaffinity(), sched_setattr(), +// sched_setparam(), sched_setscheduler() +SANDBOX_EXPORT intptr_t + SIGSYSSchedHandler(const struct arch_seccomp_data& args, void* aux); + +// Variants of the above functions for use with bpf_dsl. +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYS(); +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSClone(); +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSPrctl(); +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSIoctl(); +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSKill(); +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSFutex(); +SANDBOX_EXPORT bpf_dsl::ResultExpr RewriteSchedSIGSYS(); + +// 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. + +#endif // SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SIGSYS_HANDLERS_H_ diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc new file mode 100644 index 0000000000..58ffb843a8 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc @@ -0,0 +1,319 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h" + +#include <errno.h> +#include <fcntl.h> +#include <fcntl.h> +#include <linux/net.h> +#include <sched.h> +#include <signal.h> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/system_headers/linux_futex.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/system_headers/linux_time.h" + +// PNaCl toolchain does not provide sys/ioctl.h header. +#if !defined(OS_NACL_NONSFI) +#include <sys/ioctl.h> +#endif + +#if defined(OS_ANDROID) + +#if !defined(F_DUPFD_CLOEXEC) +#define F_DUPFD_CLOEXEC (F_LINUX_SPECIFIC_BASE + 6) +#endif + +// https://android.googlesource.com/platform/bionic/+/lollipop-release/libc/private/bionic_prctl.h +#if !defined(PR_SET_VMA) +#define PR_SET_VMA 0x53564d41 +#endif + +// https://android.googlesource.com/platform/system/core/+/lollipop-release/libcutils/sched_policy.c +#if !defined(PR_SET_TIMERSLACK_PID) +#define PR_SET_TIMERSLACK_PID 41 +#endif + +#endif // defined(OS_ANDROID) + +#if defined(__arm__) && !defined(MAP_STACK) +#define MAP_STACK 0x20000 // Daisy build environment has old headers. +#endif + +#if defined(__mips__) && !defined(MAP_STACK) +#define MAP_STACK 0x40000 +#endif +namespace { + +inline bool IsArchitectureX86_64() { +#if defined(__x86_64__) + return true; +#else + return false; +#endif +} + +inline bool IsArchitectureI386() { +#if defined(__i386__) + return true; +#else + return false; +#endif +} + +inline bool IsAndroid() { +#if defined(OS_ANDROID) + return true; +#else + return false; +#endif +} + +inline bool IsArchitectureMips() { +#if defined(__mips__) + return true; +#else + return false; +#endif +} + +} // namespace. + +#define CASES SANDBOX_BPF_DSL_CASES + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::Arg; +using sandbox::bpf_dsl::BoolExpr; +using sandbox::bpf_dsl::Error; +using sandbox::bpf_dsl::If; +using sandbox::bpf_dsl::ResultExpr; + +namespace sandbox { + +#if !defined(OS_NACL_NONSFI) +// 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. +ResultExpr RestrictCloneToThreadsAndEPERMFork() { + const Arg<unsigned long> flags(0); + + // TODO(mdempsky): Extend DSL to support (flags & ~mask1) == mask2. + const uint64_t kAndroidCloneMask = CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | + CLONE_SYSVSEM; + const uint64_t kObsoleteAndroidCloneMask = kAndroidCloneMask | CLONE_DETACHED; + + const uint64_t kGlibcPthreadFlags = + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | + CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID; + const BoolExpr glibc_test = flags == kGlibcPthreadFlags; + + const BoolExpr android_test = flags == kAndroidCloneMask || + flags == kObsoleteAndroidCloneMask || + flags == kGlibcPthreadFlags; + + return If(IsAndroid() ? android_test : glibc_test, Allow()) + .ElseIf((flags & (CLONE_VM | CLONE_THREAD)) == 0, Error(EPERM)) + .Else(CrashSIGSYSClone()); +} + +ResultExpr RestrictPrctl() { + // Will need to add seccomp compositing in the future. PR_SET_PTRACER is + // used by breakpad but not needed anymore. + const Arg<int> option(0); + return Switch(option) + .CASES((PR_GET_NAME, PR_SET_NAME, PR_GET_DUMPABLE, PR_SET_DUMPABLE), + Allow()) +#if defined(OS_ANDROID) + .CASES((PR_SET_VMA, PR_SET_TIMERSLACK_PID), Allow()) +#endif + .Default(CrashSIGSYSPrctl()); +} + +ResultExpr RestrictIoctl() { + const Arg<int> request(1); + return Switch(request).CASES((TCGETS, FIONREAD), Allow()).Default( + CrashSIGSYSIoctl()); +} + +ResultExpr RestrictMmapFlags() { + // The flags you see are actually the allowed ones, and the variable is a + // "denied" mask because of the negation operator. + // Significantly, we don't permit MAP_HUGETLB, or the newer flags such as + // MAP_POPULATE. + // TODO(davidung), remove MAP_DENYWRITE with updated Tegra libraries. + const uint64_t kAllowedMask = MAP_SHARED | MAP_PRIVATE | MAP_ANONYMOUS | + MAP_STACK | MAP_NORESERVE | MAP_FIXED | + MAP_DENYWRITE; + const Arg<int> flags(3); + return If((flags & ~kAllowedMask) == 0, Allow()).Else(CrashSIGSYS()); +} + +ResultExpr RestrictMprotectFlags() { + // The flags you see are actually the allowed ones, and the variable is a + // "denied" mask because of the negation operator. + // Significantly, we don't permit weird undocumented flags such as + // PROT_GROWSDOWN. + const uint64_t kAllowedMask = PROT_READ | PROT_WRITE | PROT_EXEC; + const Arg<int> prot(2); + return If((prot & ~kAllowedMask) == 0, Allow()).Else(CrashSIGSYS()); +} + +ResultExpr RestrictFcntlCommands() { + // We also restrict the flags in F_SETFL. We don't want to permit flags with + // a history of trouble such as O_DIRECT. The flags you see are actually the + // allowed ones, and the variable is a "denied" mask because of the negation + // operator. + // Glibc overrides the kernel's O_LARGEFILE value. Account for this. + uint64_t kOLargeFileFlag = O_LARGEFILE; + if (IsArchitectureX86_64() || IsArchitectureI386() || IsArchitectureMips()) + kOLargeFileFlag = 0100000; + + const Arg<int> cmd(1); + const Arg<long> long_arg(2); + + const uint64_t kAllowedMask = O_ACCMODE | O_APPEND | O_NONBLOCK | O_SYNC | + kOLargeFileFlag | O_CLOEXEC | O_NOATIME; + return Switch(cmd) + .CASES((F_GETFL, + F_GETFD, + F_SETFD, + F_SETLK, + F_SETLKW, + F_GETLK, + F_DUPFD, + F_DUPFD_CLOEXEC), + Allow()) + .Case(F_SETFL, + If((long_arg & ~kAllowedMask) == 0, Allow()).Else(CrashSIGSYS())) + .Default(CrashSIGSYS()); +} + +#if defined(__i386__) || defined(__mips__) +ResultExpr RestrictSocketcallCommand() { + // Unfortunately, we are unable to restrict the first parameter to + // socketpair(2). Whilst initially sounding bad, it's noteworthy that very + // few protocols actually support socketpair(2). The scary call that we're + // worried about, socket(2), remains blocked. + const Arg<int> call(0); + return Switch(call) + .CASES((SYS_SOCKETPAIR, + SYS_SHUTDOWN, + SYS_RECV, + SYS_SEND, + SYS_RECVFROM, + SYS_SENDTO, + SYS_RECVMSG, + SYS_SENDMSG), + Allow()) + .Default(Error(EPERM)); +} +#endif + +ResultExpr RestrictKillTarget(pid_t target_pid, int sysno) { + switch (sysno) { + case __NR_kill: + case __NR_tgkill: { + const Arg<pid_t> pid(0); + return If(pid == target_pid, Allow()).Else(CrashSIGSYSKill()); + } + case __NR_tkill: + return CrashSIGSYSKill(); + default: + NOTREACHED(); + return CrashSIGSYS(); + } +} + +ResultExpr RestrictFutex() { + const uint64_t kAllowedFutexFlags = FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME; + const Arg<int> op(1); + return Switch(op & ~kAllowedFutexFlags) + .CASES((FUTEX_WAIT, + FUTEX_WAKE, + FUTEX_REQUEUE, + FUTEX_CMP_REQUEUE, + FUTEX_WAKE_OP, + FUTEX_WAIT_BITSET, + FUTEX_WAKE_BITSET), + Allow()) + .Default(CrashSIGSYSFutex()); +} + +ResultExpr RestrictGetSetpriority(pid_t target_pid) { + const Arg<int> which(0); + const Arg<int> who(1); + return If(which == PRIO_PROCESS, + If(who == 0 || who == target_pid, Allow()).Else(Error(EPERM))) + .Else(CrashSIGSYS()); +} + +ResultExpr RestrictSchedTarget(pid_t target_pid, int sysno) { + switch (sysno) { + case __NR_sched_getaffinity: + case __NR_sched_getattr: + case __NR_sched_getparam: + case __NR_sched_getscheduler: + case __NR_sched_rr_get_interval: + case __NR_sched_setaffinity: + case __NR_sched_setattr: + case __NR_sched_setparam: + case __NR_sched_setscheduler: { + const Arg<pid_t> pid(0); + return If(pid == 0 || pid == target_pid, Allow()) + .Else(RewriteSchedSIGSYS()); + } + default: + NOTREACHED(); + return CrashSIGSYS(); + } +} + +ResultExpr RestrictPrlimit64(pid_t target_pid) { + const Arg<pid_t> pid(0); + return If(pid == 0 || pid == target_pid, Allow()).Else(CrashSIGSYS()); +} + +ResultExpr RestrictGetrusage() { + const Arg<int> who(0); + return If(who == RUSAGE_SELF, Allow()).Else(CrashSIGSYS()); +} +#endif // !defined(OS_NACL_NONSFI) + +ResultExpr RestrictClockID() { + static_assert(4 == sizeof(clockid_t), "clockid_t is not 32bit"); + const Arg<clockid_t> clockid(0); + return If( +#if defined(OS_CHROMEOS) + // Allow the special clock for Chrome OS used by Chrome tracing. + clockid == base::TraceTicks::kClockSystemTrace || +#endif + clockid == CLOCK_MONOTONIC || + clockid == CLOCK_MONOTONIC_COARSE || + clockid == CLOCK_PROCESS_CPUTIME_ID || + clockid == CLOCK_REALTIME || + clockid == CLOCK_REALTIME_COARSE || + clockid == CLOCK_THREAD_CPUTIME_ID, + Allow()).Else(CrashSIGSYS()); +} + +} // namespace sandbox. diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h new file mode 100644 index 0000000000..9eb35d10e0 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h @@ -0,0 +1,100 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_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/linux/bpf_dsl/bpf_dsl_forward.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 a +// bpf_dsl::ResultExpr suitable to restrict certain system call parameters. + +namespace sandbox { + +// Allow clone(2) for threads. +// Reject fork(2) attempts with EPERM. +// Don't restrict on ASAN. +// Crash if anything else is attempted. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictCloneToThreadsAndEPERMFork(); + +// Allow PR_SET_NAME, PR_SET_DUMPABLE, PR_GET_DUMPABLE. +// Crash if anything else is attempted. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictPrctl(); + +// Allow TCGETS and FIONREAD. +// Crash if anything else is attempted. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictIoctl(); + +// 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. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictMmapFlags(); + +// Restrict the prot argument in mprotect(2). +// Only allow: PROT_READ | PROT_WRITE | PROT_EXEC. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictMprotectFlags(); + +// 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. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictFcntlCommands(); + +#if defined(__i386__) || defined(__mips__) +// Restrict socketcall(2) to only allow socketpair(2), send(2), recv(2), +// sendto(2), recvfrom(2), shutdown(2), sendmsg(2) and recvmsg(2). +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictSocketcallCommand(); +#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. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictKillTarget(pid_t target_pid, + int sysno); + +// Crash if FUTEX_CMP_REQUEUE_PI is used in the second argument of futex(2). +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictFutex(); + +// Crash if |which| is not PRIO_PROCESS. EPERM if |who| is not 0, neither +// |target_pid| while calling setpriority(2) / getpriority(2). +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictGetSetpriority(pid_t target_pid); + +// Restricts |pid| for sched_* syscalls which take a pid as the first argument. +// We only allow calling these syscalls if the pid argument is equal to the pid +// of the sandboxed process or 0 (indicating the current thread). The following +// syscalls are supported: +// +// sched_getaffinity(), sched_getattr(), sched_getparam(), sched_getscheduler(), +// sched_rr_get_interval(), sched_setaffinity(), sched_setattr(), +// sched_setparam(), sched_setscheduler() +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictSchedTarget(pid_t target_pid, + int sysno); + +// Restricts the |pid| argument of prlimit64 to 0 (meaning the calling process) +// or target_pid. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictPrlimit64(pid_t target_pid); + +// Restricts the |who| argument of getrusage to RUSAGE_SELF (meaning the calling +// process). +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictGetrusage(); + +// Restrict |clk_id| for clock_getres(), clock_gettime() and clock_settime(). +// We allow accessing only CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID, +// CLOCK_REALTIME, and CLOCK_THREAD_CPUTIME_ID. In particular, this disallows +// access to arbitrary per-{process,thread} CPU-time clock IDs (such as those +// returned by {clock,pthread}_getcpuclockid), which can leak information +// about the state of the host OS. +// On Chrome OS, base::TraceTicks::kClockSystemTrace is also allowed. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictClockID(); + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_PARAMETERS_RESTRICTIONS_H_ diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc new file mode 100644 index 0000000000..aaed480d69 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc @@ -0,0 +1,282 @@ +// 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/syscall_parameters_restrictions.h" + +#include <errno.h> +#include <sched.h> +#include <sys/resource.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/synchronization/waitable_event.h" +#include "base/sys_info.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy.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/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/system_headers/linux_time.h" +#include "sandbox/linux/tests/unit_tests.h" + +#if !defined(OS_ANDROID) +#include "third_party/lss/linux_syscall_support.h" // for MAKE_PROCESS_CPUCLOCK +#endif + +namespace sandbox { + +namespace { + +// NOTE: most of the parameter restrictions are tested in +// baseline_policy_unittest.cc as a more end-to-end test. + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::ResultExpr; + +class RestrictClockIdPolicy : public bpf_dsl::Policy { + public: + RestrictClockIdPolicy() {} + ~RestrictClockIdPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + switch (sysno) { + case __NR_clock_gettime: + case __NR_clock_getres: + return RestrictClockID(); + default: + return Allow(); + } + } +}; + +void CheckClock(clockid_t clockid) { + struct timespec ts; + ts.tv_sec = -1; + ts.tv_nsec = -1; + BPF_ASSERT_EQ(0, clock_getres(clockid, &ts)); + BPF_ASSERT_EQ(0, ts.tv_sec); + BPF_ASSERT_LE(0, ts.tv_nsec); + ts.tv_sec = -1; + ts.tv_nsec = -1; + BPF_ASSERT_EQ(0, clock_gettime(clockid, &ts)); + BPF_ASSERT_LE(0, ts.tv_sec); + BPF_ASSERT_LE(0, ts.tv_nsec); +} + +BPF_TEST_C(ParameterRestrictions, + clock_gettime_allowed, + RestrictClockIdPolicy) { + CheckClock(CLOCK_MONOTONIC); + CheckClock(CLOCK_MONOTONIC_COARSE); + CheckClock(CLOCK_PROCESS_CPUTIME_ID); + CheckClock(CLOCK_REALTIME); + CheckClock(CLOCK_REALTIME_COARSE); + CheckClock(CLOCK_THREAD_CPUTIME_ID); +} + +BPF_DEATH_TEST_C(ParameterRestrictions, + clock_gettime_crash_monotonic_raw, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictClockIdPolicy) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); +} + +#if defined(OS_CHROMEOS) + +// A custom BPF tester delegate to run IsRunningOnChromeOS() before +// the sandbox is enabled because we cannot run it with non-SFI BPF +// sandbox enabled. +class ClockSystemTesterDelegate : public sandbox::BPFTesterDelegate { + public: + ClockSystemTesterDelegate() + : is_running_on_chromeos_(base::SysInfo::IsRunningOnChromeOS()) {} + ~ClockSystemTesterDelegate() override {} + + scoped_ptr<sandbox::bpf_dsl::Policy> GetSandboxBPFPolicy() override { + return scoped_ptr<sandbox::bpf_dsl::Policy>(new RestrictClockIdPolicy()); + } + void RunTestFunction() override { + if (is_running_on_chromeos_) { + CheckClock(base::TraceTicks::kClockSystemTrace); + } else { + struct timespec ts; + // kClockSystemTrace is 11, which is CLOCK_THREAD_CPUTIME_ID of + // the init process (pid=1). If kernel supports this feature, + // this may succeed even if this is not running on Chrome OS. We + // just check this clock_gettime call does not crash. + clock_gettime(base::TraceTicks::kClockSystemTrace, &ts); + } + } + + private: + const bool is_running_on_chromeos_; + DISALLOW_COPY_AND_ASSIGN(ClockSystemTesterDelegate); +}; + +BPF_TEST_D(BPFTest, BPFTestWithDelegateClass, ClockSystemTesterDelegate); + +#elif defined(OS_LINUX) + +BPF_DEATH_TEST_C(ParameterRestrictions, + clock_gettime_crash_system_trace, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictClockIdPolicy) { + struct timespec ts; + clock_gettime(base::TraceTicks::kClockSystemTrace, &ts); +} + +#endif // defined(OS_CHROMEOS) + +#if !defined(OS_ANDROID) +BPF_DEATH_TEST_C(ParameterRestrictions, + clock_gettime_crash_cpu_clock, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictClockIdPolicy) { + // We can't use clock_getcpuclockid() because it's not implemented in newlib, + // and it might not work inside the sandbox anyway. + const pid_t kInitPID = 1; + const clockid_t kInitCPUClockID = + MAKE_PROCESS_CPUCLOCK(kInitPID, CPUCLOCK_SCHED); + + struct timespec ts; + clock_gettime(kInitCPUClockID, &ts); +} +#endif // !defined(OS_ANDROID) + +class RestrictSchedPolicy : public bpf_dsl::Policy { + public: + RestrictSchedPolicy() {} + ~RestrictSchedPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + switch (sysno) { + case __NR_sched_getparam: + return RestrictSchedTarget(getpid(), sysno); + default: + return Allow(); + } + } +}; + +void CheckSchedGetParam(pid_t pid, struct sched_param* param) { + BPF_ASSERT_EQ(0, sched_getparam(pid, param)); +} + +void SchedGetParamThread(base::WaitableEvent* thread_run) { + const pid_t pid = getpid(); + const pid_t tid = sys_gettid(); + BPF_ASSERT_NE(pid, tid); + + struct sched_param current_pid_param; + CheckSchedGetParam(pid, ¤t_pid_param); + + struct sched_param zero_param; + CheckSchedGetParam(0, &zero_param); + + struct sched_param tid_param; + CheckSchedGetParam(tid, &tid_param); + + BPF_ASSERT_EQ(zero_param.sched_priority, tid_param.sched_priority); + + // Verify that the SIGSYS handler sets errno properly. + errno = 0; + BPF_ASSERT_EQ(-1, sched_getparam(tid, NULL)); + BPF_ASSERT_EQ(EINVAL, errno); + + thread_run->Signal(); +} + +BPF_TEST_C(ParameterRestrictions, + sched_getparam_allowed, + RestrictSchedPolicy) { + base::WaitableEvent thread_run(true, false); + // Run the actual test in a new thread so that the current pid and tid are + // different. + base::Thread getparam_thread("sched_getparam_thread"); + BPF_ASSERT(getparam_thread.Start()); + getparam_thread.message_loop()->PostTask( + FROM_HERE, base::Bind(&SchedGetParamThread, &thread_run)); + BPF_ASSERT(thread_run.TimedWait(base::TimeDelta::FromMilliseconds(5000))); + getparam_thread.Stop(); +} + +BPF_DEATH_TEST_C(ParameterRestrictions, + sched_getparam_crash_non_zero, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictSchedPolicy) { + const pid_t kInitPID = 1; + struct sched_param param; + sched_getparam(kInitPID, ¶m); +} + +class RestrictPrlimit64Policy : public bpf_dsl::Policy { + public: + RestrictPrlimit64Policy() {} + ~RestrictPrlimit64Policy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + switch (sysno) { + case __NR_prlimit64: + return RestrictPrlimit64(getpid()); + default: + return Allow(); + } + } +}; + +BPF_TEST_C(ParameterRestrictions, prlimit64_allowed, RestrictPrlimit64Policy) { + BPF_ASSERT_EQ(0, sys_prlimit64(0, RLIMIT_AS, NULL, NULL)); + BPF_ASSERT_EQ(0, sys_prlimit64(getpid(), RLIMIT_AS, NULL, NULL)); +} + +BPF_DEATH_TEST_C(ParameterRestrictions, + prlimit64_crash_not_self, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictPrlimit64Policy) { + const pid_t kInitPID = 1; + BPF_ASSERT_NE(kInitPID, getpid()); + sys_prlimit64(kInitPID, RLIMIT_AS, NULL, NULL); +} + +class RestrictGetrusagePolicy : public bpf_dsl::Policy { + public: + RestrictGetrusagePolicy() {} + ~RestrictGetrusagePolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + switch (sysno) { + case __NR_getrusage: + return RestrictGetrusage(); + default: + return Allow(); + } + } +}; + +BPF_TEST_C(ParameterRestrictions, getrusage_allowed, RestrictGetrusagePolicy) { + struct rusage usage; + BPF_ASSERT_EQ(0, getrusage(RUSAGE_SELF, &usage)); +} + +BPF_DEATH_TEST_C(ParameterRestrictions, + getrusage_crash_not_self, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictGetrusagePolicy) { + struct rusage usage; + getrusage(RUSAGE_CHILDREN, &usage); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc new file mode 100644 index 0000000000..c217d47e2d --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc @@ -0,0 +1,1060 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf-helpers/syscall_sets.h" + +#include "build/build_config.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { + +// The functions below cover all existing i386, x86_64, and ARM system calls; +// excluding syscalls made obsolete in ARM EABI. +// The implicitly defined sets form a partition of the sets of +// system calls. + +bool SyscallSets::IsKill(int sysno) { + switch (sysno) { + case __NR_kill: + case __NR_tgkill: + case __NR_tkill: // Deprecated. + return true; + default: + return false; + } +} + +bool SyscallSets::IsAllowedGettime(int sysno) { + switch (sysno) { + case __NR_gettimeofday: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_time: +#endif + return true; + case __NR_adjtimex: // Privileged. + case __NR_clock_adjtime: // Privileged. + case __NR_clock_getres: // Could be allowed. + case __NR_clock_gettime: + case __NR_clock_nanosleep: // Could be allowed. + case __NR_clock_settime: // Privileged. +#if defined(__i386__) || defined(__mips__) + case __NR_ftime: // Obsolete. +#endif + case __NR_settimeofday: // Privileged. +#if defined(__i386__) || defined(__mips__) + case __NR_stime: +#endif + default: + return false; + } +} + +bool SyscallSets::IsCurrentDirectory(int sysno) { + switch (sysno) { + case __NR_getcwd: + case __NR_chdir: + case __NR_fchdir: + return true; + default: + return false; + } +} + +bool SyscallSets::IsUmask(int sysno) { + switch (sysno) { + case __NR_umask: + return true; + default: + return false; + } +} + +// System calls that directly access the file system. They might acquire +// a new file descriptor or otherwise perform an operation directly +// via a path. +// Both EPERM and ENOENT are valid errno unless otherwise noted in comment. +bool SyscallSets::IsFileSystem(int sysno) { + switch (sysno) { +#if !defined(__aarch64__) + case __NR_access: // EPERM not a valid errno. + case __NR_chmod: + case __NR_chown: +#if defined(__i386__) || defined(__arm__) + case __NR_chown32: +#endif + case __NR_creat: + case __NR_futimesat: // Should be called utimesat ? + case __NR_lchown: + case __NR_link: + case __NR_lstat: // EPERM not a valid errno. + case __NR_mkdir: + case __NR_mknod: + case __NR_open: + case __NR_readlink: // EPERM not a valid errno. + case __NR_rename: + case __NR_rmdir: + case __NR_stat: // EPERM not a valid errno. + case __NR_symlink: + case __NR_unlink: + case __NR_uselib: // Neither EPERM, nor ENOENT are valid errno. + case __NR_ustat: // Same as above. Deprecated. + case __NR_utimes: +#endif // !defined(__aarch64__) + + case __NR_execve: + case __NR_faccessat: // EPERM not a valid errno. + case __NR_fchmodat: + case __NR_fchownat: // Should be called chownat ? +#if defined(__x86_64__) || defined(__aarch64__) + case __NR_newfstatat: // fstatat(). EPERM not a valid errno. +#elif defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_fstatat64: +#endif +#if defined(__i386__) || defined(__arm__) + case __NR_lchown32: +#endif + case __NR_linkat: + case __NR_lookup_dcookie: // ENOENT not a valid errno. + +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_lstat64: +#endif +#if defined(__i386__) || defined(__arm__) || defined(__x86_64__) + case __NR_memfd_create: +#endif + case __NR_mkdirat: + case __NR_mknodat: +#if defined(__i386__) + case __NR_oldlstat: + case __NR_oldstat: +#endif + case __NR_openat: + case __NR_readlinkat: + case __NR_renameat: + case __NR_renameat2: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_stat64: +#endif + case __NR_statfs: // EPERM not a valid errno. +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_statfs64: +#endif + case __NR_symlinkat: + case __NR_truncate: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_truncate64: +#endif + case __NR_unlinkat: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_utime: +#endif + case __NR_utimensat: // New. + return true; + default: + return false; + } +} + +bool SyscallSets::IsAllowedFileSystemAccessViaFd(int sysno) { + switch (sysno) { + case __NR_fstat: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_fstat64: +#endif + return true; +// TODO(jln): these should be denied gracefully as well (moved below). +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_fadvise64: // EPERM not a valid errno. +#endif +#if defined(__i386__) + case __NR_fadvise64_64: +#endif +#if defined(__arm__) + case __NR_arm_fadvise64_64: +#endif + case __NR_fdatasync: // EPERM not a valid errno. + case __NR_flock: // EPERM not a valid errno. + case __NR_fstatfs: // Give information about the whole filesystem. +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_fstatfs64: +#endif + case __NR_fsync: // EPERM not a valid errno. +#if defined(__i386__) + case __NR_oldfstat: +#endif +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_sync_file_range: // EPERM not a valid errno. +#elif defined(__arm__) + case __NR_arm_sync_file_range: // EPERM not a valid errno. +#endif + default: + return false; + } +} + +// EPERM is a good errno for any of these. +bool SyscallSets::IsDeniedFileSystemAccessViaFd(int sysno) { + switch (sysno) { + case __NR_fallocate: + case __NR_fchmod: + case __NR_fchown: + case __NR_ftruncate: +#if defined(__i386__) || defined(__arm__) + case __NR_fchown32: +#endif +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_ftruncate64: +#endif +#if !defined(__aarch64__) + case __NR_getdents: // EPERM not a valid errno. +#endif + case __NR_getdents64: // EPERM not a valid errno. +#if defined(__i386__) || defined(__mips__) + case __NR_readdir: +#endif + return true; + default: + return false; + } +} + +bool SyscallSets::IsGetSimpleId(int sysno) { + switch (sysno) { + case __NR_capget: + case __NR_getegid: + case __NR_geteuid: + case __NR_getgid: + case __NR_getgroups: + case __NR_getpid: + case __NR_getppid: + case __NR_getresgid: + case __NR_getsid: + case __NR_gettid: + case __NR_getuid: + case __NR_getresuid: +#if defined(__i386__) || defined(__arm__) + case __NR_getegid32: + case __NR_geteuid32: + case __NR_getgid32: + case __NR_getgroups32: + case __NR_getresgid32: + case __NR_getresuid32: + case __NR_getuid32: +#endif + return true; + default: + return false; + } +} + +bool SyscallSets::IsProcessPrivilegeChange(int sysno) { + switch (sysno) { + case __NR_capset: +#if defined(__i386__) || defined(__x86_64__) + case __NR_ioperm: // Intel privilege. + case __NR_iopl: // Intel privilege. +#endif + case __NR_setfsgid: + case __NR_setfsuid: + case __NR_setgid: + case __NR_setgroups: + case __NR_setregid: + case __NR_setresgid: + case __NR_setresuid: + case __NR_setreuid: + case __NR_setuid: +#if defined(__i386__) || defined(__arm__) + case __NR_setfsgid32: + case __NR_setfsuid32: + case __NR_setgid32: + case __NR_setgroups32: + case __NR_setregid32: + case __NR_setresgid32: + case __NR_setresuid32: + case __NR_setreuid32: + case __NR_setuid32: +#endif + return true; + default: + return false; + } +} + +bool SyscallSets::IsProcessGroupOrSession(int sysno) { + switch (sysno) { + case __NR_setpgid: +#if !defined(__aarch64__) + case __NR_getpgrp: +#endif + case __NR_setsid: + case __NR_getpgid: + return true; + default: + return false; + } +} + +bool SyscallSets::IsAllowedSignalHandling(int sysno) { + switch (sysno) { + case __NR_rt_sigaction: + case __NR_rt_sigprocmask: + case __NR_rt_sigreturn: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_sigaction: + case __NR_sigprocmask: + case __NR_sigreturn: +#endif + return true; + case __NR_rt_sigpending: + case __NR_rt_sigqueueinfo: + case __NR_rt_sigsuspend: + case __NR_rt_sigtimedwait: + case __NR_rt_tgsigqueueinfo: + case __NR_sigaltstack: +#if !defined(__aarch64__) + case __NR_signalfd: +#endif + case __NR_signalfd4: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_sigpending: + case __NR_sigsuspend: +#endif +#if defined(__i386__) || defined(__mips__) + case __NR_signal: + case __NR_sgetmask: // Obsolete. + case __NR_ssetmask: +#endif + default: + return false; + } +} + +bool SyscallSets::IsAllowedOperationOnFd(int sysno) { + switch (sysno) { + case __NR_close: + case __NR_dup: +#if !defined(__aarch64__) + case __NR_dup2: +#endif + case __NR_dup3: +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_shutdown: +#endif + return true; + case __NR_fcntl: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_fcntl64: +#endif + default: + return false; + } +} + +bool SyscallSets::IsKernelInternalApi(int sysno) { + switch (sysno) { + case __NR_restart_syscall: +#if defined(__arm__) + case __ARM_NR_cmpxchg: +#endif + return true; + default: + return false; + } +} + +// This should be thought through in conjunction with IsFutex(). +bool SyscallSets::IsAllowedProcessStartOrDeath(int sysno) { + switch (sysno) { + case __NR_exit: + case __NR_exit_group: + case __NR_wait4: + case __NR_waitid: +#if defined(__i386__) + case __NR_waitpid: +#endif + return true; + case __NR_clone: // Should be parameter-restricted. + case __NR_setns: // Privileged. +#if !defined(__aarch64__) + case __NR_fork: +#endif +#if defined(__i386__) || defined(__x86_64__) + case __NR_get_thread_area: +#endif +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_set_thread_area: +#endif + case __NR_set_tid_address: + case __NR_unshare: +#if !defined(__mips__) && !defined(__aarch64__) + case __NR_vfork: +#endif + default: + return false; + } +} + +// It's difficult to restrict those, but there is attack surface here. +bool SyscallSets::IsAllowedFutex(int sysno) { + switch (sysno) { + case __NR_get_robust_list: + case __NR_set_robust_list: + case __NR_futex: + default: + return false; + } +} + +bool SyscallSets::IsAllowedEpoll(int sysno) { + switch (sysno) { +#if !defined(__aarch64__) + case __NR_epoll_create: + case __NR_epoll_wait: +#endif + case __NR_epoll_create1: + case __NR_epoll_ctl: + return true; + default: +#if defined(__x86_64__) + case __NR_epoll_ctl_old: +#endif + case __NR_epoll_pwait: +#if defined(__x86_64__) + case __NR_epoll_wait_old: +#endif + return false; + } +} + +bool SyscallSets::IsAllowedGetOrModifySocket(int sysno) { + switch (sysno) { +#if !defined(__aarch64__) + case __NR_pipe: +#endif + case __NR_pipe2: + return true; + default: +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_socketpair: // We will want to inspect its argument. +#endif + return false; + } +} + +bool SyscallSets::IsDeniedGetOrModifySocket(int sysno) { + switch (sysno) { +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_accept: + case __NR_accept4: + case __NR_bind: + case __NR_connect: + case __NR_socket: + case __NR_listen: + return true; +#endif + default: + return false; + } +} + +#if defined(__i386__) || defined(__mips__) +// Big multiplexing system call for sockets. +bool SyscallSets::IsSocketCall(int sysno) { + switch (sysno) { + case __NR_socketcall: + return true; + default: + return false; + } +} +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) +bool SyscallSets::IsNetworkSocketInformation(int sysno) { + switch (sysno) { + case __NR_getpeername: + case __NR_getsockname: + case __NR_getsockopt: + case __NR_setsockopt: + return true; + default: + return false; + } +} +#endif + +bool SyscallSets::IsAllowedAddressSpaceAccess(int sysno) { + switch (sysno) { + case __NR_brk: + case __NR_mlock: + case __NR_munlock: + case __NR_munmap: + return true; + case __NR_madvise: + case __NR_mincore: + case __NR_mlockall: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_mmap: +#endif +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_mmap2: +#endif +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_modify_ldt: +#endif + case __NR_mprotect: + case __NR_mremap: + case __NR_msync: + case __NR_munlockall: + case __NR_readahead: + case __NR_remap_file_pages: +#if defined(__i386__) + case __NR_vm86: + case __NR_vm86old: +#endif + default: + return false; + } +} + +bool SyscallSets::IsAllowedGeneralIo(int sysno) { + switch (sysno) { + case __NR_lseek: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR__llseek: +#endif +#if !defined(__aarch64__) + case __NR_poll: +#endif + case __NR_ppoll: + case __NR_pselect6: + case __NR_read: + case __NR_readv: +#if defined(__arm__) || defined(__mips__) + case __NR_recv: +#endif +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_recvfrom: // Could specify source. + case __NR_recvmsg: // Could specify source. +#endif +#if defined(__i386__) || defined(__x86_64__) + case __NR_select: +#endif +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR__newselect: +#endif +#if defined(__arm__) + case __NR_send: +#endif +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_sendmsg: // Could specify destination. + case __NR_sendto: // Could specify destination. +#endif + case __NR_write: + case __NR_writev: + return true; + case __NR_ioctl: // Can be very powerful. + case __NR_pread64: + case __NR_preadv: + case __NR_pwrite64: + case __NR_pwritev: + case __NR_recvmmsg: // Could specify source. + case __NR_sendfile: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_sendfile64: +#endif + case __NR_sendmmsg: // Could specify destination. + case __NR_splice: + case __NR_tee: + case __NR_vmsplice: + default: + return false; + } +} + +bool SyscallSets::IsPrctl(int sysno) { + switch (sysno) { +#if defined(__x86_64__) + case __NR_arch_prctl: +#endif + case __NR_prctl: + return true; + default: + return false; + } +} + +bool SyscallSets::IsSeccomp(int sysno) { + switch (sysno) { + case __NR_seccomp: + return true; + default: + return false; + } +} + +bool SyscallSets::IsAllowedBasicScheduler(int sysno) { + switch (sysno) { + case __NR_sched_yield: +#if !defined(__aarch64__) + case __NR_pause: +#endif + case __NR_nanosleep: + return true; + case __NR_getpriority: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_nice: +#endif + case __NR_setpriority: + default: + return false; + } +} + +bool SyscallSets::IsAdminOperation(int sysno) { + switch (sysno) { +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_bdflush: +#endif + case __NR_kexec_load: + case __NR_reboot: + case __NR_setdomainname: + case __NR_sethostname: + case __NR_syslog: + return true; + default: + return false; + } +} + +bool SyscallSets::IsKernelModule(int sysno) { + switch (sysno) { +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_create_module: + case __NR_get_kernel_syms: // Should ENOSYS. + case __NR_query_module: +#endif + case __NR_delete_module: + case __NR_init_module: + case __NR_finit_module: + return true; + default: + return false; + } +} + +bool SyscallSets::IsGlobalFSViewChange(int sysno) { + switch (sysno) { + case __NR_pivot_root: + case __NR_chroot: + case __NR_sync: + return true; + default: + return false; + } +} + +bool SyscallSets::IsFsControl(int sysno) { + switch (sysno) { + case __NR_mount: + case __NR_nfsservctl: + case __NR_quotactl: + case __NR_swapoff: + case __NR_swapon: +#if defined(__i386__) || defined(__mips__) + case __NR_umount: +#endif + case __NR_umount2: + return true; + default: + return false; + } +} + +bool SyscallSets::IsNuma(int sysno) { + switch (sysno) { + case __NR_get_mempolicy: + case __NR_getcpu: + case __NR_mbind: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_migrate_pages: +#endif + case __NR_move_pages: + case __NR_set_mempolicy: + return true; + default: + return false; + } +} + +bool SyscallSets::IsMessageQueue(int sysno) { + switch (sysno) { + case __NR_mq_getsetattr: + case __NR_mq_notify: + case __NR_mq_open: + case __NR_mq_timedreceive: + case __NR_mq_timedsend: + case __NR_mq_unlink: + return true; + default: + return false; + } +} + +bool SyscallSets::IsGlobalProcessEnvironment(int sysno) { + switch (sysno) { + case __NR_acct: // Privileged. +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_getrlimit: +#endif +#if defined(__i386__) || defined(__arm__) + case __NR_ugetrlimit: +#endif +#if defined(__i386__) || defined(__mips__) + case __NR_ulimit: +#endif + case __NR_getrusage: + case __NR_personality: // Can change its personality as well. + case __NR_prlimit64: // Like setrlimit / getrlimit. + case __NR_setrlimit: + case __NR_times: + return true; + default: + return false; + } +} + +bool SyscallSets::IsDebug(int sysno) { + switch (sysno) { + case __NR_ptrace: + case __NR_process_vm_readv: + case __NR_process_vm_writev: + case __NR_kcmp: + return true; + default: + return false; + } +} + +bool SyscallSets::IsGlobalSystemStatus(int sysno) { + switch (sysno) { +#if !defined(__aarch64__) + case __NR__sysctl: + case __NR_sysfs: +#endif + case __NR_sysinfo: + case __NR_uname: +#if defined(__i386__) + case __NR_olduname: + case __NR_oldolduname: +#endif + return true; + default: + return false; + } +} + +bool SyscallSets::IsEventFd(int sysno) { + switch (sysno) { +#if !defined(__aarch64__) + case __NR_eventfd: +#endif + case __NR_eventfd2: + return true; + default: + return false; + } +} + +// Asynchronous I/O API. +bool SyscallSets::IsAsyncIo(int sysno) { + switch (sysno) { + case __NR_io_cancel: + case __NR_io_destroy: + case __NR_io_getevents: + case __NR_io_setup: + case __NR_io_submit: + return true; + default: + return false; + } +} + +bool SyscallSets::IsKeyManagement(int sysno) { + switch (sysno) { + case __NR_add_key: + case __NR_keyctl: + case __NR_request_key: + return true; + default: + return false; + } +} + +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) +bool SyscallSets::IsSystemVSemaphores(int sysno) { + switch (sysno) { + case __NR_semctl: + case __NR_semget: + case __NR_semop: + case __NR_semtimedop: + return true; + default: + return false; + } +} +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) +// These give a lot of ambient authority and bypass the setuid sandbox. +bool SyscallSets::IsSystemVSharedMemory(int sysno) { + switch (sysno) { + case __NR_shmat: + case __NR_shmctl: + case __NR_shmdt: + case __NR_shmget: + return true; + default: + return false; + } +} +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) +bool SyscallSets::IsSystemVMessageQueue(int sysno) { + switch (sysno) { + case __NR_msgctl: + case __NR_msgget: + case __NR_msgrcv: + case __NR_msgsnd: + return true; + default: + return false; + } +} +#endif + +#if defined(__i386__) || defined(__mips__) +// Big system V multiplexing system call. +bool SyscallSets::IsSystemVIpc(int sysno) { + switch (sysno) { + case __NR_ipc: + return true; + default: + return false; + } +} +#endif + +bool SyscallSets::IsAnySystemV(int sysno) { +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + return IsSystemVMessageQueue(sysno) || IsSystemVSemaphores(sysno) || + IsSystemVSharedMemory(sysno); +#elif defined(__i386__) || defined(__mips__) + return IsSystemVIpc(sysno); +#endif +} + +bool SyscallSets::IsAdvancedScheduler(int sysno) { + switch (sysno) { + case __NR_ioprio_get: // IO scheduler. + case __NR_ioprio_set: + case __NR_sched_get_priority_max: + case __NR_sched_get_priority_min: + case __NR_sched_getaffinity: + case __NR_sched_getattr: + case __NR_sched_getparam: + case __NR_sched_getscheduler: + case __NR_sched_rr_get_interval: + case __NR_sched_setaffinity: + case __NR_sched_setattr: + case __NR_sched_setparam: + case __NR_sched_setscheduler: + return true; + default: + return false; + } +} + +bool SyscallSets::IsInotify(int sysno) { + switch (sysno) { + case __NR_inotify_add_watch: +#if !defined(__aarch64__) + case __NR_inotify_init: +#endif + case __NR_inotify_init1: + case __NR_inotify_rm_watch: + return true; + default: + return false; + } +} + +bool SyscallSets::IsFaNotify(int sysno) { + switch (sysno) { + case __NR_fanotify_init: + case __NR_fanotify_mark: + return true; + default: + return false; + } +} + +bool SyscallSets::IsTimer(int sysno) { + switch (sysno) { + case __NR_getitimer: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_alarm: +#endif + case __NR_setitimer: + return true; + default: + return false; + } +} + +bool SyscallSets::IsAdvancedTimer(int sysno) { + switch (sysno) { + case __NR_timer_create: + case __NR_timer_delete: + case __NR_timer_getoverrun: + case __NR_timer_gettime: + case __NR_timer_settime: + case __NR_timerfd_create: + case __NR_timerfd_gettime: + case __NR_timerfd_settime: + return true; + default: + return false; + } +} + +bool SyscallSets::IsExtendedAttributes(int sysno) { + switch (sysno) { + case __NR_fgetxattr: + case __NR_flistxattr: + case __NR_fremovexattr: + case __NR_fsetxattr: + case __NR_getxattr: + case __NR_lgetxattr: + case __NR_listxattr: + case __NR_llistxattr: + case __NR_lremovexattr: + case __NR_lsetxattr: + case __NR_removexattr: + case __NR_setxattr: + return true; + default: + return false; + } +} + +// Various system calls that need to be researched. +// TODO(jln): classify this better. +bool SyscallSets::IsMisc(int sysno) { + switch (sysno) { +#if !defined(__mips__) + case __NR_getrandom: +#endif + case __NR_name_to_handle_at: + case __NR_open_by_handle_at: + case __NR_perf_event_open: + case __NR_syncfs: + case __NR_vhangup: +// The system calls below are not implemented. +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_afs_syscall: +#endif +#if defined(__i386__) || defined(__mips__) + case __NR_break: +#endif +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_getpmsg: +#endif +#if defined(__i386__) || defined(__mips__) + case __NR_gtty: + case __NR_idle: + case __NR_lock: + case __NR_mpx: + case __NR_prof: + case __NR_profil: +#endif +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_putpmsg: +#endif +#if defined(__x86_64__) + case __NR_security: +#endif +#if defined(__i386__) || defined(__mips__) + case __NR_stty: +#endif +#if defined(__x86_64__) + case __NR_tuxcall: +#endif +#if !defined(__aarch64__) + case __NR_vserver: +#endif + return true; + default: + return false; + } +} + +#if defined(__arm__) +bool SyscallSets::IsArmPciConfig(int sysno) { + switch (sysno) { + case __NR_pciconfig_iobase: + case __NR_pciconfig_read: + case __NR_pciconfig_write: + return true; + default: + return false; + } +} + +bool SyscallSets::IsArmPrivate(int sysno) { + switch (sysno) { + case __ARM_NR_breakpoint: + case __ARM_NR_cacheflush: + case __ARM_NR_set_tls: + case __ARM_NR_usr26: + case __ARM_NR_usr32: + return true; + default: + return false; + } +} +#endif // defined(__arm__) + +#if defined(__mips__) +bool SyscallSets::IsMipsPrivate(int sysno) { + switch (sysno) { + case __NR_cacheflush: + case __NR_cachectl: + return true; + default: + return false; + } +} + +bool SyscallSets::IsMipsMisc(int sysno) { + switch (sysno) { + case __NR_sysmips: + case __NR_unused150: + return true; + default: + return false; + } +} +#endif // defined(__mips__) +} // namespace sandbox. diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h b/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h new file mode 100644 index 0000000000..5ba6335a95 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h @@ -0,0 +1,112 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_SETS_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_SETS_H_ + +#include "base/macros.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 +// nature, they don't have any clear semantics and are completely +// "implementation-defined". + +namespace sandbox { + +class SANDBOX_EXPORT SyscallSets { + public: + static bool IsKill(int sysno); + static bool IsAllowedGettime(int sysno); + static bool IsCurrentDirectory(int sysno); + static bool IsUmask(int sysno); + // System calls that directly access the file system. They might acquire + // a new file descriptor or otherwise perform an operation directly + // via a path. + static bool IsFileSystem(int sysno); + static bool IsAllowedFileSystemAccessViaFd(int sysno); + static bool IsDeniedFileSystemAccessViaFd(int sysno); + static bool IsGetSimpleId(int sysno); + static bool IsProcessPrivilegeChange(int sysno); + static bool IsProcessGroupOrSession(int sysno); + static bool IsAllowedSignalHandling(int sysno); + static bool IsAllowedOperationOnFd(int sysno); + static bool IsKernelInternalApi(int sysno); + // 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 IsAllowedFutex(int sysno); + static bool IsAllowedEpoll(int sysno); + static bool IsAllowedGetOrModifySocket(int sysno); + static bool IsDeniedGetOrModifySocket(int sysno); + +#if defined(__i386__) || defined(__mips__) + // Big multiplexing system call for sockets. + static bool IsSocketCall(int sysno); +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + static bool IsNetworkSocketInformation(int sysno); +#endif + + static bool IsAllowedAddressSpaceAccess(int sysno); + static bool IsAllowedGeneralIo(int sysno); + static bool IsPrctl(int sysno); + static bool IsSeccomp(int sysno); + static bool IsAllowedBasicScheduler(int sysno); + static bool IsAdminOperation(int sysno); + static bool IsKernelModule(int sysno); + static bool IsGlobalFSViewChange(int sysno); + static bool IsFsControl(int sysno); + static bool IsNuma(int sysno); + static bool IsMessageQueue(int sysno); + static bool IsGlobalProcessEnvironment(int sysno); + static bool IsDebug(int sysno); + static bool IsGlobalSystemStatus(int sysno); + static bool IsEventFd(int sysno); + // Asynchronous I/O API. + static bool IsAsyncIo(int sysno); + static bool IsKeyManagement(int sysno); +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + static bool IsSystemVSemaphores(int sysno); +#endif +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + // These give a lot of ambient authority and bypass the setuid sandbox. + static bool IsSystemVSharedMemory(int sysno); +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + static bool IsSystemVMessageQueue(int sysno); +#endif + +#if defined(__i386__) || defined(__mips__) + // Big system V multiplexing system call. + static bool IsSystemVIpc(int sysno); +#endif + + static bool IsAnySystemV(int sysno); + static bool IsAdvancedScheduler(int sysno); + static bool IsInotify(int sysno); + static bool IsFaNotify(int sysno); + static bool IsTimer(int sysno); + static bool IsAdvancedTimer(int sysno); + static bool IsExtendedAttributes(int sysno); + static bool IsMisc(int sysno); +#if defined(__arm__) + static bool IsArmPciConfig(int sysno); + static bool IsArmPrivate(int sysno); +#endif // defined(__arm__) +#if defined(__mips__) + static bool IsMipsPrivate(int sysno); + static bool IsMipsMisc(int sysno); +#endif // defined(__mips__) + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(SyscallSets); +}; + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_SETS_H_ diff --git a/sandbox/linux/seccomp-bpf/DEPS b/sandbox/linux/seccomp-bpf/DEPS new file mode 100644 index 0000000000..149c463b06 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+sandbox/linux/bpf_dsl", + "+sandbox/linux/services", + "+sandbox/linux/system_headers", +] diff --git a/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h b/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h new file mode 100644 index 0000000000..7736c1506f --- /dev/null +++ b/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h @@ -0,0 +1,54 @@ +// 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 "base/memory/scoped_ptr.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.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 Policy, class Aux> +class BPFTesterCompatibilityDelegate : public BPFTesterDelegate { + public: + typedef void (*TestFunction)(Aux*); + + explicit BPFTesterCompatibilityDelegate(TestFunction test_function) + : aux_(), test_function_(test_function) {} + + ~BPFTesterCompatibilityDelegate() override {} + + scoped_ptr<bpf_dsl::Policy> 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<bpf_dsl::Policy>(new Policy(&aux_)); + } + + 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: + Aux aux_; + TestFunction test_function_; + + DISALLOW_COPY_AND_ASSIGN(BPFTesterCompatibilityDelegate); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTER_COMPATIBILITY_DELEGATE_H_ diff --git a/sandbox/linux/seccomp-bpf/bpf_tests.h b/sandbox/linux/seccomp-bpf/bpf_tests.h new file mode 100644 index 0000000000..cc4debd4c3 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/bpf_tests.h @@ -0,0 +1,122 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTS_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTS_H__ + +#include "base/logging.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +// 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 +// Policy 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); \ + } + +// 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 Policy subclass. +// BPF_TEST() takes a C++ data type as an fourth parameter. A variable +// of this type will be allocated and a pointer to it will be +// available within the test function as "BPF_AUX". The pointer will +// also be passed as an argument to the policy's constructor. 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. The life-time of this object is the same as the life-time +// of the process running under the seccomp-bpf policy. +// |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) + +// 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(aux* BPF_AUX); \ + TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \ + sandbox::SandboxBPFTestRunner bpf_test_runner( \ + new sandbox::BPFTesterCompatibilityDelegate<policy, aux>( \ + BPF_TEST_##test_name)); \ + sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, death); \ + } \ + void BPF_TEST_##test_name(aux* 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: + explicit BPFTesterSimpleDelegate(void (*test_function)(void)) + : test_function_(test_function) {} + ~BPFTesterSimpleDelegate() override {} + + scoped_ptr<bpf_dsl::Policy> GetSandboxBPFPolicy() override { + return scoped_ptr<bpf_dsl::Policy>(new PolicyClass()); + } + void RunTestFunction() override { + DCHECK(test_function_); + test_function_(); + } + + private: + void (*test_function_)(void); + DISALLOW_COPY_AND_ASSIGN(BPFTesterSimpleDelegate); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTS_H__ diff --git a/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc b/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc new file mode 100644 index 0000000000..63e1814c90 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc @@ -0,0 +1,153 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/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/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::Error; +using sandbox::bpf_dsl::ResultExpr; + +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); +}; + +class EmptyClassTakingPolicy : public bpf_dsl::Policy { + public: + explicit EmptyClassTakingPolicy(FourtyTwo* fourty_two) { + BPF_ASSERT(fourty_two); + BPF_ASSERT(FourtyTwo::kMagicValue == fourty_two->value()); + } + ~EmptyClassTakingPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + return Allow(); + } +}; + +BPF_TEST(BPFTest, + BPFAUXPointsToClass, + EmptyClassTakingPolicy, + 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<EmptyClassTakingPolicy, FourtyTwo> + simple_delegate(DummyTestFunction); + } + { + // Test polymorphism. + scoped_ptr<BPFTesterDelegate> simple_delegate( + new BPFTesterCompatibilityDelegate<EmptyClassTakingPolicy, FourtyTwo>( + DummyTestFunction)); + } +} + +class EnosysPtracePolicy : public bpf_dsl::Policy { + public: + EnosysPtracePolicy() { my_pid_ = sys_getpid(); } + ~EnosysPtracePolicy() override { + // 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_, sys_getpid()); + } + + ResultExpr EvaluateSyscall(int system_call_number) const override { + CHECK(SandboxBPF::IsValidSyscallNumber(system_call_number)); + if (system_call_number == __NR_ptrace) { + // The EvaluateSyscall function should run in the process that created + // the current object. + BPF_ASSERT_EQ(my_pid_, sys_getpid()); + return Error(ENOSYS); + } else { + return Allow(); + } + } + + private: + pid_t my_pid_; + DISALLOW_COPY_AND_ASSIGN(EnosysPtracePolicy); +}; + +class BasicBPFTesterDelegate : public BPFTesterDelegate { + public: + BasicBPFTesterDelegate() {} + ~BasicBPFTesterDelegate() override {} + + scoped_ptr<bpf_dsl::Policy> GetSandboxBPFPolicy() override { + return scoped_ptr<bpf_dsl::Policy>(new EnosysPtracePolicy()); + } + 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); +} + +const char kHelloMessage[] = "Hello"; + +BPF_DEATH_TEST_C(BPFTest, + BPFDeathTestWithInlineTest, + DEATH_MESSAGE(kHelloMessage), + EnosysPtracePolicy) { + LOG(ERROR) << kHelloMessage; + _exit(1); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/die.cc b/sandbox/linux/seccomp-bpf/die.cc new file mode 100644 index 0000000000..3baf1f13d9 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/die.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/die.h" + +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include <string> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_signal.h" + +namespace sandbox { + +void Die::ExitGroup() { + // exit_group() should exit our program. After all, it is defined as a + // function that doesn't return. But things can theoretically go wrong. + // Especially, since we are dealing with system call filters. Continuing + // execution would be very bad in most cases where ExitGroup() gets called. + // So, we'll try a few other strategies too. + Syscall::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 + // to a defined state; but we have not way to verify whether we actually + // succeeded in doing so. Nonetheless, triggering a fatal signal could help + // us terminate. + struct sigaction sa = {}; + sa.sa_handler = LINUX_SIG_DFL; + sa.sa_flags = LINUX_SA_RESTART; + sys_sigaction(LINUX_SIGSEGV, &sa, nullptr); + Syscall::Call(__NR_prctl, PR_SET_DUMPABLE, (void*)0, (void*)0, (void*)0); + if (*(volatile char*)0) { + } + + // If there is no way for us to ask for the program to exit, the next + // best thing we can do is to loop indefinitely. Maybe, somebody will notice + // and file a bug... + // We in fact retry the system call inside of our loop so that it will + // stand out when somebody tries to diagnose the problem by using "strace". + for (;;) { + Syscall::Call(__NR_exit_group, 1); + } +} + +void Die::SandboxDie(const char* msg, const char* file, int line) { + if (simple_exit_) { + LogToStderr(msg, file, line); + } else { + logging::LogMessage(file, line, logging::LOG_FATAL).stream() << msg; + } + ExitGroup(); +} + +void Die::RawSandboxDie(const char* msg) { + if (!msg) + msg = ""; + RAW_LOG(FATAL, msg); + ExitGroup(); +} + +void Die::SandboxInfo(const char* msg, const char* file, int line) { + if (!suppress_info_) { + logging::LogMessage(file, line, logging::LOG_INFO).stream() << msg; + } +} + +void Die::LogToStderr(const char* msg, const char* file, int line) { + if (msg) { + char buf[40]; + snprintf(buf, sizeof(buf), "%d", line); + std::string s = std::string(file) + ":" + buf + ":" + msg + "\n"; + + // 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(Syscall::Call(__NR_write, 2, s.c_str(), s.length()))); + } +} + +bool Die::simple_exit_ = false; +bool Die::suppress_info_ = false; + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/die.h b/sandbox/linux/seccomp-bpf/die.h new file mode 100644 index 0000000000..b3f3f72c2f --- /dev/null +++ b/sandbox/linux/seccomp-bpf/die.h @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This is the main API for using this file. Prints a error message and +// exits with a fatal error. This is not async-signal safe. +#define SANDBOX_DIE(m) sandbox::Die::SandboxDie(m, __FILE__, __LINE__) + +// An async signal safe version of the same API. Won't print the filename +// and line numbers. +#define RAW_SANDBOX_DIE(m) sandbox::Die::RawSandboxDie(m) + +// Adds an informational message to the log file or stderr as appropriate. +#define SANDBOX_INFO(m) sandbox::Die::SandboxInfo(m, __FILE__, __LINE__) + +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. + // Most users would want to call SANDBOX_DIE() instead, as it logs extra + // information. But calling ExitGroup() is correct and in some rare cases + // preferable. So, we make it part of the public API. + static void ExitGroup() __attribute__((noreturn)); + + // This method gets called by SANDBOX_DIE(). There is normally no reason + // to call it directly unless you are defining your own exiting macro. + static void SandboxDie(const char* msg, const char* file, int line) + __attribute__((noreturn)); + + static void RawSandboxDie(const char* msg) __attribute__((noreturn)); + + // This method gets called by SANDBOX_INFO(). There is normally no reason + // to call it directly unless you are defining your own logging macro. + static void SandboxInfo(const char* msg, const char* file, int line); + + // Writes a message to stderr. Used as a fall-back choice, if we don't have + // any other way to report an error. + static void LogToStderr(const char* msg, const char* file, int line); + + // We generally want to run all exit handlers. This means, on SANDBOX_DIE() + // we should be calling LOG(FATAL). But there are some situations where + // we just need to print a message and then terminate. This would typically + // happen in cases where we consume the error message internally (e.g. in + // unit tests or in the supportsSeccompSandbox() method). + static void EnableSimpleExit() { simple_exit_ = true; } + + // Sometimes we need to disable all informational messages (e.g. from within + // unittests). + static void SuppressInfoMessages(bool flag) { suppress_info_ = flag; } + + private: + static bool simple_exit_; + static bool suppress_info_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(Die); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ diff --git a/sandbox/linux/seccomp-bpf/errorcode.cc b/sandbox/linux/seccomp-bpf/errorcode.cc new file mode 100644 index 0000000000..9bb3ddb648 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/errorcode.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/errorcode.h" + +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" + +namespace sandbox { + +ErrorCode::ErrorCode() : error_type_(ET_INVALID), err_(SECCOMP_RET_INVALID) { +} + +ErrorCode::ErrorCode(int err) { + switch (err) { + case ERR_ALLOWED: + err_ = SECCOMP_RET_ALLOW; + error_type_ = ET_SIMPLE; + break; + case ERR_MIN_ERRNO... ERR_MAX_ERRNO: + err_ = SECCOMP_RET_ERRNO + 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"); + } +} + +ErrorCode::ErrorCode(uint16_t trap_id, + Trap::TrapFnc fnc, + const void* aux, + bool safe) + : error_type_(ET_TRAP), + fnc_(fnc), + aux_(const_cast<void*>(aux)), + safe_(safe), + err_(SECCOMP_RET_TRAP + trap_id) { +} + +ErrorCode::ErrorCode(int argno, + ArgType width, + uint64_t mask, + uint64_t value, + const ErrorCode* passed, + const ErrorCode* failed) + : error_type_(ET_COND), + mask_(mask), + value_(value), + argno_(argno), + width_(width), + passed_(passed), + failed_(failed), + err_(SECCOMP_RET_INVALID) { +} + +bool ErrorCode::Equals(const ErrorCode& err) const { + if (error_type_ == ET_INVALID || err.error_type_ == ET_INVALID) { + SANDBOX_DIE("Dereferencing invalid ErrorCode"); + } + if (error_type_ != err.error_type_) { + return false; + } + if (error_type_ == ET_SIMPLE || error_type_ == ET_TRAP) { + return err_ == err.err_; + } else if (error_type_ == ET_COND) { + return mask_ == err.mask_ && value_ == err.value_ && argno_ == err.argno_ && + width_ == err.width_ && passed_->Equals(*err.passed_) && + failed_->Equals(*err.failed_); + } else { + SANDBOX_DIE("Corrupted ErrorCode"); + } +} + +bool ErrorCode::LessThan(const ErrorCode& err) const { + // Implementing a "LessThan()" operator allows us to use ErrorCode objects + // as keys in STL containers; most notably, it also allows us to put them + // into std::set<>. Actual ordering is not important as long as it is + // deterministic. + if (error_type_ == ET_INVALID || err.error_type_ == ET_INVALID) { + SANDBOX_DIE("Dereferencing invalid ErrorCode"); + } + if (error_type_ != err.error_type_) { + return error_type_ < err.error_type_; + } else { + if (error_type_ == ET_SIMPLE || error_type_ == ET_TRAP) { + return err_ < err.err_; + } else if (error_type_ == ET_COND) { + if (mask_ != err.mask_) { + return mask_ < err.mask_; + } else if (value_ != err.value_) { + return value_ < err.value_; + } else if (argno_ != err.argno_) { + return argno_ < err.argno_; + } else if (width_ != err.width_) { + return width_ < err.width_; + } else if (!passed_->Equals(*err.passed_)) { + return passed_->LessThan(*err.passed_); + } else if (!failed_->Equals(*err.failed_)) { + return failed_->LessThan(*err.failed_); + } else { + return false; + } + } else { + SANDBOX_DIE("Corrupted ErrorCode"); + } + } +} + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/errorcode.h b/sandbox/linux/seccomp-bpf/errorcode.h new file mode 100644 index 0000000000..d88777313e --- /dev/null +++ b/sandbox/linux/seccomp-bpf/errorcode.h @@ -0,0 +1,203 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_ERRORCODE_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_ERRORCODE_H__ + +#include "sandbox/linux/seccomp-bpf/trap.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { +class PolicyCompiler; +} + +// This class holds all the possible values that can be returned by a sandbox +// policy. +// We can either wrap a symbolic ErrorCode (i.e. ERR_XXX enum values), an +// errno value (in the range 0..4095), a pointer to a TrapFnc callback +// handling a SECCOMP_RET_TRAP trap, or a complex constraint. +// All of the commonly used values are stored in the "err_" field. So, code +// that is using the ErrorCode class typically operates on a single 32bit +// field. +// +// TODO(mdempsky): Nuke from orbit. The only reason this class still +// exists is for Verifier, which will eventually be replaced by a true +// BPF symbolic evaluator and constraint solver. +class SANDBOX_EXPORT ErrorCode { + public: + enum { + // Allow this system call. The value of ERR_ALLOWED is pretty much + // completely arbitrary. But we want to pick it so that is is unlikely + // to be passed in accidentally, when the user intended to return an + // "errno" (see below) value instead. + ERR_ALLOWED = 0x04000000, + + // 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. + // This is very different from return ERR_ALLOWED. + ERR_MIN_ERRNO = 0, +#if defined(__mips__) + // MIPS only supports errno up to 1133 + ERR_MAX_ERRNO = 1133, +#else + // TODO(markus): Android only supports errno up to 255 + // (crbug.com/181647). + ERR_MAX_ERRNO = 4095, +#endif + }; + + // While BPF filter programs always operate on 32bit quantities, the kernel + // always sees system call arguments as 64bit values. This statement is true + // no matter whether the host system is natively operating in 32bit or 64bit. + // The BPF compiler hides the fact that BPF instructions cannot directly + // access 64bit quantities. But policies are still advised to specify whether + // a system call expects a 32bit or a 64bit quantity. + enum ArgType { + // When passed as an argument to SandboxBPF::Cond(), TP_32BIT requests that + // the conditional test should operate on the 32bit part of the system call + // argument. + // On 64bit architectures, this verifies that user space did not pass + // a 64bit value as an argument to the system call. If it did, that will be + // interpreted as an attempt at breaking the sandbox and results in the + // program getting terminated. + // In other words, only perform a 32bit test, if you are sure this + // particular system call would never legitimately take a 64bit + // argument. + // Implementation detail: TP_32BIT does two things. 1) it restricts the + // conditional test to operating on the LSB only, and 2) it adds code to + // the BPF filter program verifying that the MSB the kernel received from + // user space is either 0, or 0xFFFFFFFF; the latter is acceptable, iff bit + // 31 was set in the system call argument. It deals with 32bit arguments + // having been sign extended. + TP_32BIT, + + // When passed as an argument to SandboxBPF::Cond(), TP_64BIT requests that + // the conditional test should operate on the full 64bit argument. It is + // generally harmless to perform a 64bit test on 32bit systems, as the + // kernel will always see the top 32 bits of all arguments as zero'd out. + // This approach has the desirable property that for tests of pointer + // values, we can always use TP_64BIT no matter the host architecture. + // But of course, that also means, it is possible to write conditional + // policies that turn into no-ops on 32bit systems; this is by design. + TP_64BIT, + }; + + // Deprecated. + enum Operation { + // Test whether the system call argument is equal to the operand. + OP_EQUAL, + + // Tests a system call argument against a bit mask. + // The "ALL_BITS" variant performs this test: "arg & mask == mask" + // This implies that a mask of zero always results in a passing test. + // The "ANY_BITS" variant performs this test: "arg & mask != 0" + // This implies that a mask of zero always results in a failing test. + OP_HAS_ALL_BITS, + OP_HAS_ANY_BITS, + }; + + enum ErrorType { + ET_INVALID, + ET_SIMPLE, + ET_TRAP, + ET_COND, + }; + + // We allow the default constructor, as it makes the ErrorCode class + // much easier to use. But if we ever encounter an invalid ErrorCode + // when compiling a BPF filter, we deliberately generate an invalid + // program that will get flagged both by our Verifier class and by + // the Linux kernel. + ErrorCode(); + explicit ErrorCode(int err); + + // For all practical purposes, ErrorCodes are treated as if they were + // structs. The copy constructor and assignment operator are trivial and + // we do not need to explicitly specify them. + // Most notably, it is in fact perfectly OK to directly copy the passed_ and + // failed_ field. They only ever get set by our private constructor, and the + // callers handle life-cycle management for these objects. + + // Destructor + ~ErrorCode() {} + + bool Equals(const ErrorCode& err) const; + bool LessThan(const ErrorCode& err) const; + + uint32_t err() const { return err_; } + ErrorType error_type() const { return error_type_; } + + bool safe() const { return safe_; } + + uint64_t mask() const { return mask_; } + uint64_t value() const { return value_; } + int argno() const { return argno_; } + ArgType width() const { return width_; } + const ErrorCode* passed() const { return passed_; } + const ErrorCode* failed() const { return failed_; } + + struct LessThan { + bool operator()(const ErrorCode& a, const ErrorCode& b) const { + return a.LessThan(b); + } + }; + + private: + friend bpf_dsl::PolicyCompiler; + friend class CodeGen; + friend class SandboxBPF; + friend class Trap; + + // If we are wrapping a callback, we must assign a unique id. This id is + // how the kernel tells us which one of our different SECCOMP_RET_TRAP + // cases has been triggered. + ErrorCode(uint16_t trap_id, Trap::TrapFnc fnc, const void* aux, bool safe); + + // Some system calls require inspection of arguments. This constructor + // allows us to specify additional constraints. + ErrorCode(int argno, + ArgType width, + uint64_t mask, + uint64_t value, + const ErrorCode* passed, + const ErrorCode* failed); + + ErrorType error_type_; + + union { + // Fields needed for SECCOMP_RET_TRAP callbacks + struct { + Trap::TrapFnc fnc_; // Callback function and arg, if trap was + void* aux_; // triggered by the kernel's BPF filter. + bool safe_; // Keep sandbox active while calling fnc_() + }; + + // Fields needed when inspecting additional arguments. + struct { + uint64_t mask_; // Mask that we are comparing under. + uint64_t value_; // Value that we are comparing with. + int argno_; // Syscall arg number that we are inspecting. + ArgType width_; // Whether we are looking at a 32/64bit value. + const ErrorCode* passed_; // Value to be returned if comparison passed, + const ErrorCode* failed_; // or if it failed. + }; + }; + + // 32bit field used for all possible types of ErrorCode values. This is + // the value that uniquely identifies any ErrorCode and it (typically) can + // be emitted directly into a BPF filter program. + uint32_t err_; +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_ERRORCODE_H__ diff --git a/sandbox/linux/seccomp-bpf/errorcode_unittest.cc b/sandbox/linux/seccomp-bpf/errorcode_unittest.cc new file mode 100644 index 0000000000..6b5491ee4a --- /dev/null +++ b/sandbox/linux/seccomp-bpf/errorcode_unittest.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/errorcode.h" + +#include <errno.h> + +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/seccomp-bpf/trap.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +namespace { + +class DummyPolicy : public bpf_dsl::Policy { + public: + DummyPolicy() {} + ~DummyPolicy() override {} + + bpf_dsl::ResultExpr EvaluateSyscall(int sysno) const override { + return bpf_dsl::Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(DummyPolicy); +}; + +SANDBOX_TEST(ErrorCode, ErrnoConstructor) { + ErrorCode e0; + SANDBOX_ASSERT(e0.err() == SECCOMP_RET_INVALID); + + ErrorCode e1(ErrorCode::ERR_ALLOWED); + SANDBOX_ASSERT(e1.err() == SECCOMP_RET_ALLOW); + + ErrorCode e2(EPERM); + SANDBOX_ASSERT(e2.err() == SECCOMP_RET_ERRNO + EPERM); + + DummyPolicy dummy_policy; + bpf_dsl::PolicyCompiler compiler(&dummy_policy, Trap::Registry()); + ErrorCode e3 = compiler.Trap(NULL, NULL, true /* safe */); + 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) { + DummyPolicy dummy_policy; + bpf_dsl::PolicyCompiler compiler(&dummy_policy, Trap::Registry()); + ErrorCode e0 = compiler.Trap(NULL, "a", true /* safe */); + ErrorCode e1 = compiler.Trap(NULL, "b", true /* safe */); + SANDBOX_ASSERT((e0.err() & SECCOMP_RET_DATA) + 1 == + (e1.err() & SECCOMP_RET_DATA)); + + ErrorCode e2 = compiler.Trap(NULL, "a", true /* safe */); + SANDBOX_ASSERT((e0.err() & SECCOMP_RET_DATA) == + (e2.err() & SECCOMP_RET_DATA)); +} + +SANDBOX_TEST(ErrorCode, Equals) { + ErrorCode e1(ErrorCode::ERR_ALLOWED); + ErrorCode e2(ErrorCode::ERR_ALLOWED); + SANDBOX_ASSERT(e1.Equals(e1)); + SANDBOX_ASSERT(e1.Equals(e2)); + SANDBOX_ASSERT(e2.Equals(e1)); + + ErrorCode e3(EPERM); + SANDBOX_ASSERT(!e1.Equals(e3)); + + DummyPolicy dummy_policy; + bpf_dsl::PolicyCompiler compiler(&dummy_policy, Trap::Registry()); + ErrorCode e4 = compiler.Trap(NULL, "a", true /* safe */); + ErrorCode e5 = compiler.Trap(NULL, "b", true /* safe */); + ErrorCode e6 = compiler.Trap(NULL, "a", true /* safe */); + SANDBOX_ASSERT(!e1.Equals(e4)); + SANDBOX_ASSERT(!e3.Equals(e4)); + SANDBOX_ASSERT(!e5.Equals(e4)); + SANDBOX_ASSERT( e6.Equals(e4)); +} + +SANDBOX_TEST(ErrorCode, LessThan) { + ErrorCode e1(ErrorCode::ERR_ALLOWED); + ErrorCode e2(ErrorCode::ERR_ALLOWED); + SANDBOX_ASSERT(!e1.LessThan(e1)); + SANDBOX_ASSERT(!e1.LessThan(e2)); + SANDBOX_ASSERT(!e2.LessThan(e1)); + + ErrorCode e3(EPERM); + SANDBOX_ASSERT(!e1.LessThan(e3)); + SANDBOX_ASSERT( e3.LessThan(e1)); + + DummyPolicy dummy_policy; + bpf_dsl::PolicyCompiler compiler(&dummy_policy, Trap::Registry()); + ErrorCode e4 = compiler.Trap(NULL, "a", true /* safe */); + ErrorCode e5 = compiler.Trap(NULL, "b", true /* safe */); + ErrorCode e6 = compiler.Trap(NULL, "a", true /* safe */); + SANDBOX_ASSERT(e1.LessThan(e4)); + SANDBOX_ASSERT(e3.LessThan(e4)); + SANDBOX_ASSERT(e4.LessThan(e5)); + SANDBOX_ASSERT(!e4.LessThan(e6)); + SANDBOX_ASSERT(!e6.LessThan(e4)); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc new file mode 100644 index 0000000000..239043eb27 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -0,0 +1,279 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" + +// Some headers on Android are missing cdefs: crbug.com/172337. +// (We can't use OS_ANDROID here since build_config.h is not included). +#if defined(ANDROID) +#include <sys/cdefs.h> +#endif + +#include <errno.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/compiler_specific.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/third_party/valgrind/valgrind.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/syscall_set.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/seccomp-bpf/trap.h" +#include "sandbox/linux/services/proc_util.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/services/thread_helpers.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { + +namespace { + +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + +bool IsSingleThreaded(int proc_fd) { + return ThreadHelpers::IsSingleThreaded(proc_fd); +} + +// Check if the kernel supports seccomp-filter (a.k.a. seccomp mode 2) via +// prctl(). +bool KernelSupportsSeccompBPF() { + errno = 0; + const int rv = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, nullptr); + + if (rv == -1 && EFAULT == errno) { + return true; + } + return false; +} + +// LG introduced a buggy syscall, sys_set_media_ext, with the same number as +// seccomp. Return true if the current kernel has this buggy syscall. +// +// We want this to work with upcoming versions of seccomp, so we pass bogus +// flags that are unlikely to ever be used by the kernel. A normal kernel would +// return -EINVAL, but a buggy LG kernel would return 1. +bool KernelHasLGBug() { +#if defined(OS_ANDROID) + // sys_set_media will see this as NULL, which should be a safe (non-crashing) + // way to invoke it. A genuine seccomp syscall will see it as + // SECCOMP_SET_MODE_STRICT. + const unsigned int operation = 0; + // Chosen by fair dice roll. Guaranteed to be random. + const unsigned int flags = 0xf7a46a5c; + const int rv = sys_seccomp(operation, flags, nullptr); + // A genuine kernel would return -EINVAL (which would set rv to -1 and errno + // to EINVAL), or at the very least return some kind of error (which would + // set rv to -1). Any other behavior indicates that whatever code received + // our syscall was not the real seccomp. + if (rv != -1) { + return true; + } +#endif // defined(OS_ANDROID) + + return false; +} + +// Check if the kernel supports seccomp-filter via the seccomp system call +// and the TSYNC feature to enable seccomp on all threads. +bool KernelSupportsSeccompTsync() { + if (KernelHasLGBug()) { + return false; + } + + errno = 0; + const int rv = + sys_seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, nullptr); + + if (rv == -1 && errno == EFAULT) { + return true; + } else { + // TODO(jln): turn these into DCHECK after 417888 is considered fixed. + CHECK_EQ(-1, rv); + CHECK(ENOSYS == errno || EINVAL == errno); + return false; + } +} + +uint64_t EscapePC() { + intptr_t rv = Syscall::Call(-1); + if (rv == -1 && errno == ENOSYS) { + return 0; + } + return static_cast<uint64_t>(static_cast<uintptr_t>(rv)); +} + +} // namespace + +SandboxBPF::SandboxBPF(bpf_dsl::Policy* policy) + : proc_fd_(), sandbox_has_started_(false), policy_(policy) { +} + +SandboxBPF::~SandboxBPF() { +} + +// static +bool SandboxBPF::SupportsSeccompSandbox(SeccompLevel level) { + // Never pretend to support seccomp with Valgrind, as it + // throws the tool off. + if (IsRunningOnValgrind()) { + return false; + } + + switch (level) { + case SeccompLevel::SINGLE_THREADED: + return KernelSupportsSeccompBPF(); + case SeccompLevel::MULTI_THREADED: + return KernelSupportsSeccompTsync(); + } + NOTREACHED(); + return false; +} + +bool SandboxBPF::StartSandbox(SeccompLevel seccomp_level) { + DCHECK(policy_); + CHECK(seccomp_level == SeccompLevel::SINGLE_THREADED || + seccomp_level == SeccompLevel::MULTI_THREADED); + + if (sandbox_has_started_) { + SANDBOX_DIE( + "Cannot repeatedly start sandbox. Create a separate Sandbox " + "object instead."); + return false; + } + + if (!proc_fd_.is_valid()) { + SetProcFd(ProcUtil::OpenProc()); + } + + const bool supports_tsync = KernelSupportsSeccompTsync(); + + if (seccomp_level == SeccompLevel::SINGLE_THREADED) { + // Wait for /proc/self/task/ to update if needed and assert the + // process is single threaded. + ThreadHelpers::AssertSingleThreaded(proc_fd_.get()); + } else if (seccomp_level == SeccompLevel::MULTI_THREADED) { + if (IsSingleThreaded(proc_fd_.get())) { + SANDBOX_DIE("Cannot start sandbox; " + "process may be single-threaded when reported as not"); + return false; + } + if (!supports_tsync) { + SANDBOX_DIE("Cannot start sandbox; kernel does not support synchronizing " + "filters for a threadgroup"); + return false; + } + } + + // We no longer need access to any files in /proc. We want to do this + // before installing the filters, just in case that our policy denies + // close(). + if (proc_fd_.is_valid()) { + proc_fd_.reset(); + } + + // Install the filters. + InstallFilter(supports_tsync || + seccomp_level == SeccompLevel::MULTI_THREADED); + + return true; +} + +void SandboxBPF::SetProcFd(base::ScopedFD proc_fd) { + proc_fd_.swap(proc_fd); +} + +// static +bool SandboxBPF::IsValidSyscallNumber(int sysnum) { + return SyscallSet::IsValid(sysnum); +} + +// static +bool SandboxBPF::IsRequiredForUnsafeTrap(int sysno) { + return bpf_dsl::PolicyCompiler::IsRequiredForUnsafeTrap(sysno); +} + +// static +intptr_t SandboxBPF::ForwardSyscall(const struct arch_seccomp_data& args) { + 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])); +} + +scoped_ptr<CodeGen::Program> SandboxBPF::AssembleFilter( + bool force_verification) { +#if !defined(NDEBUG) + force_verification = true; +#endif + DCHECK(policy_); + + bpf_dsl::PolicyCompiler compiler(policy_.get(), Trap::Registry()); + if (Trap::SandboxDebuggingAllowedByUser()) { + compiler.DangerousSetEscapePC(EscapePC()); + } + return compiler.Compile(force_verification); +} + +void SandboxBPF::InstallFilter(bool must_sync_threads) { + // We want to be very careful in not imposing any requirements on the + // policies that are set with SetSandboxPolicy(). This means, as soon as + // the sandbox is active, we shouldn't be relying on libraries that could + // be making system calls. This, for example, means we should avoid + // using the heap and we should avoid using STL functions. + // Temporarily copy the contents of the "program" vector into a + // stack-allocated array; and then explicitly destroy that object. + // This makes sure we don't ex- or implicitly call new/delete after we + // installed the BPF filter program in the kernel. Depending on the + // system memory allocator that is in effect, these operators can result + // in system calls to things like munmap() or brk(). + CodeGen::Program* program = AssembleFilter(false).release(); + + struct sock_filter bpf[program->size()]; + const struct sock_fprog prog = {static_cast<unsigned short>(program->size()), + bpf}; + memcpy(bpf, &(*program)[0], sizeof(bpf)); + delete program; + + // Make an attempt to release memory that is no longer needed here, rather + // than in the destructor. Try to avoid as much as possible to presume of + // what will be possible to do in the new (sandboxed) execution environment. + policy_.reset(); + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + SANDBOX_DIE("Kernel refuses to enable no-new-privs"); + } + + // Install BPF filter program. If the thread state indicates multi-threading + // support, then the kernel hass the seccomp system call. Otherwise, fall + // back on prctl, which requires the process to be single-threaded. + if (must_sync_threads) { + int rv = + sys_seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &prog); + if (rv) { + SANDBOX_DIE( + "Kernel refuses to turn on and synchronize threads for BPF filters"); + } + } else { + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { + SANDBOX_DIE("Kernel refuses to turn on BPF filters"); + } + } + + sandbox_has_started_ = true; +} + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h new file mode 100644 index 0000000000..96cceb5648 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -0,0 +1,118 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_ + +#include <stdint.h> + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +struct arch_seccomp_data; +namespace bpf_dsl { +class Policy; +} + +// This class can be used to apply a syscall sandboxing policy expressed in a +// bpf_dsl::Policy object to the current process. +// Syscall sandboxing policies get inherited by subprocesses and, once applied, +// can never be removed for the lifetime of the process. +class SANDBOX_EXPORT SandboxBPF { + public: + enum class SeccompLevel { + SINGLE_THREADED, + MULTI_THREADED, + }; + + // Ownership of |policy| is transfered here to the sandbox object. + // nullptr is allowed for unit tests. + explicit SandboxBPF(bpf_dsl::Policy* policy); + // NOTE: Setting a policy and starting the sandbox is a one-way operation. + // The kernel does not provide any option for unloading a loaded sandbox. The + // sandbox remains engaged even when the object is destructed. + ~SandboxBPF(); + + // Detect if the kernel supports the specified seccomp level. + // See StartSandbox() for a description of these. + static bool SupportsSeccompSandbox(SeccompLevel level); + + // This is the main public entry point. It sets up the resources needed by + // the sandbox, and enters Seccomp mode. + // The calling process must provide a |level| to tell the sandbox which type + // of kernel support it should engage. + // SINGLE_THREADED will only sandbox the calling thread. Since it would be a + // security risk, the sandbox will also check that the current process is + // single threaded and crash if it isn't the case. + // MULTI_THREADED requires more recent kernel support and allows to sandbox + // all the threads of the current process. Be mindful of potential races, + // with other threads using disallowed system calls either before or after + // the sandbox is engaged. + // + // It is possible to stack multiple sandboxes by creating separate "Sandbox" + // objects and calling "StartSandbox()" on each of them. Please note, that + // this requires special care, though, as newly stacked sandboxes can never + // relax restrictions imposed by earlier sandboxes. Furthermore, installing + // a new policy requires making system calls, that might already be + // disallowed. + // Finally, stacking does add more kernel overhead than having a single + // combined policy. So, it should only be used if there are no alternatives. + bool StartSandbox(SeccompLevel level) WARN_UNUSED_RESULT; + + // The sandbox needs to be able to access files in "/proc/self/". If + // this directory is not accessible when "StartSandbox()" gets called, the + // caller must provide an already opened file descriptor by calling + // "SetProcFd()". + // The sandbox becomes the new owner of this file descriptor and will + // close it when "StartSandbox()" executes or when the sandbox object + // disappears. + void SetProcFd(base::ScopedFD proc_fd); + + // Checks whether a particular system call number is valid on the current + // architecture. + static bool IsValidSyscallNumber(int sysnum); + + // UnsafeTraps require some syscalls to always be allowed. + // This helper function returns true for these calls. + static bool IsRequiredForUnsafeTrap(int sysno); + + // From within an UnsafeTrap() it is often useful to be able to execute + // the system call that triggered the trap. The ForwardSyscall() method + // makes this easy. It is more efficient than calling glibc's syscall() + // function, as it avoid the extra round-trip to the signal handler. And + // it automatically does the correct thing to report kernel-style error + // conditions, rather than setting errno. See the comments for TrapFnc for + // details. In other words, the return value from ForwardSyscall() is + // directly suitable as a return value for a trap handler. + static intptr_t ForwardSyscall(const struct arch_seccomp_data& args); + + // Assembles a BPF filter program from the current policy. After calling this + // function, you must not call any other sandboxing function. + // Typically, AssembleFilter() is only used by unit tests and by sandbox + // internals. It should not be used by production code. + // For performance reasons, we normally only run the assembled BPF program + // through the verifier, iff the program was built in debug mode. + // But by setting "force_verification", the caller can request that the + // verifier is run unconditionally. This is useful for unittests. + scoped_ptr<CodeGen::Program> AssembleFilter(bool force_verification); + + private: + // Assembles and installs a filter based on the policy that has previously + // been configured with SetSandboxPolicy(). + void InstallFilter(bool must_sync_threads); + + base::ScopedFD proc_fd_; + bool sandbox_has_started_; + scoped_ptr<bpf_dsl::Policy> policy_; + + DISALLOW_COPY_AND_ASSIGN(SandboxBPF); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_ diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc new file mode 100644 index 0000000000..321ea9a8ee --- /dev/null +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc @@ -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. + +#include "sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h" + +#include <fcntl.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/system_headers/linux_filter.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<bpf_dsl::Policy> policy = + bpf_tester_delegate_->GetSandboxBPFPolicy(); + + if (sandbox::SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::SINGLE_THREADED)) { + // Initialize and then start the sandbox with our custom policy + sandbox::SandboxBPF sandbox(policy.release()); + SANDBOX_ASSERT(sandbox.StartSandbox( + sandbox::SandboxBPF::SeccompLevel::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(policy.release()); + sandbox.AssembleFilter(true /* force_verification */); + 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/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h b/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h new file mode 100644 index 0000000000..fef6240d74 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h @@ -0,0 +1,61 @@ +// 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/macros.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/tests/sandbox_test_runner.h" + +namespace sandbox { +namespace bpf_dsl { +class Policy; +} + +// 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<bpf_dsl::Policy> 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); + ~SandboxBPFTestRunner() override; + + void Run() override; + + 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/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc new file mode 100644 index 0000000000..580cad2525 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc @@ -0,0 +1,85 @@ +// 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.h" + +#include <fcntl.h> +#include <unistd.h> + +#include <iostream> + +#include "base/files/scoped_file.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { +namespace { + +// NOTE: most tests for the SandboxBPF class are currently in +// integration_tests/. + +TEST(SandboxBPF, CreateDestroy) { + // Give an opportunity to dynamic tools to perform some simple testing. + SandboxBPF sandbox(nullptr); + SandboxBPF* sandbox_ptr = new SandboxBPF(nullptr); + delete sandbox_ptr; +} + +// This test should execute no matter whether we have kernel support. So, +// we make it a TEST() instead of a BPF_TEST(). +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 = SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::SINGLE_THREADED); + bool seccomp_bpf_tsync_supported = SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::MULTI_THREADED); + // We want to log whether or not seccomp BPF is actually supported + // since actual test coverage depends on it. + std::cout << "Seccomp BPF supported (single thread): " + << (seccomp_bpf_supported ? "true." : "false.") << "\n"; + std::cout << "Seccomp BPF supported (multi thread): " + << (seccomp_bpf_tsync_supported ? "true." : "false.") << "\n"; + std::cout << "Pointer size: " << sizeof(void*) << "\n"; +} + +SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(CallSupportsTwice)) { + bool single1 = SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::SINGLE_THREADED); + bool single2 = SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::SINGLE_THREADED); + ASSERT_EQ(single1, single2); + bool multi1 = SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::MULTI_THREADED); + bool multi2 = SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::MULTI_THREADED); + ASSERT_EQ(multi1, multi2); + + // Multi threaded support implies single threaded support. + if (multi1) { + ASSERT_TRUE(single1); + } +} + +TEST(SandboxBPF, ProcTaskFdDescriptorGetsClosed) { + int pipe_fds[2]; + ASSERT_EQ(0, pipe(pipe_fds)); + base::ScopedFD read_end(pipe_fds[0]); + base::ScopedFD write_end(pipe_fds[1]); + + { + SandboxBPF sandbox(nullptr); + sandbox.SetProcFd(write_end.Pass()); + } + + ASSERT_EQ(0, fcntl(read_end.get(), F_SETFL, O_NONBLOCK)); + char c; + // Check that the sandbox closed the write_end (read will EOF instead of + // returning EWOULDBLOCK). + ASSERT_EQ(0, read(read_end.get(), &c, 1)); +} + +} // namespace +} // sandbox diff --git a/sandbox/linux/seccomp-bpf/syscall.cc b/sandbox/linux/seccomp-bpf/syscall.cc new file mode 100644 index 0000000000..bc6461f117 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall.cc @@ -0,0 +1,421 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/syscall.h" + +#include <errno.h> +#include <stdint.h> + +#include "base/logging.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" + +namespace sandbox { + +namespace { + +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS_FAMILY) +// Number that's not currently used by any Linux kernel ABIs. +const int kInvalidSyscallNumber = 0x351d3; +#else +#error Unrecognized architecture +#endif + +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; .cfi_rel_offset esi, 0\n" + "push %edi; .cfi_adjust_cfa_offset 4; .cfi_rel_offset edi, 0\n" + "push %ebx; .cfi_adjust_cfa_offset 4; .cfi_rel_offset ebx, 0\n" + "push %ebp; .cfi_adjust_cfa_offset 4; .cfi_rel_offset ebp, 0\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_restore ebp; .cfi_adjust_cfa_offset -4\n" + "pop %ebx; .cfi_restore ebx; .cfi_adjust_cfa_offset -4\n" + "pop %edi; .cfi_restore edi; .cfi_adjust_cfa_offset -4\n" + "pop %esi; .cfi_restore 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 "%rdi" 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 %rdi, %rdi\n" + "jge 1f\n" + // Always make sure that our code is position-independent, or the + // linker will throw a hissy fit on x86-64. + "lea 2f(%rip), %rax\n" + "ret\n" + // Now we load the registers used to pass arguments to the system + // call: system call number in %rax, and arguments in %rdi, %rsi, + // %rdx, %r10, %r8, %r9. Note: These are all caller-save registers + // (only %rbx, %rbp, %rsp, and %r12-%r15 are callee-save), so no + // need to worry here about spilling registers or CFI directives. + "1:movq %rdi, %rax\n" + "movq 0(%rsi), %rdi\n" + "movq 16(%rsi), %rdx\n" + "movq 24(%rsi), %r10\n" + "movq 32(%rsi), %r8\n" + "movq 40(%rsi), %r9\n" + "movq 8(%rsi), %rsi\n" + // Enter the kernel. + "syscall\n" + // This is our "magic" return address that the BPF filter sees. + "2:ret\n" + ".cfi_endproc\n" + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#elif defined(__arm__) + // Throughout this file, we use the same mode (ARM vs. thumb) + // that the C++ compiler uses. This means, when transfering control + // from C++ to assembly code, we do not need to switch modes (e.g. + // by using the "bx" instruction). It also means that our assembly + // code should not be invoked directly from code that lives in + // other compilation units, as we don't bother implementing thumb + // interworking. That's OK, as we don't make any of the assembly + // symbols public. They are all local to this file. + ".text\n" + ".align 2\n" + ".type SyscallAsm, %function\n" +#if defined(__thumb__) + ".thumb_func\n" +#else + ".arm\n" +#endif + "SyscallAsm:\n" +#if !defined(__native_client_nonsfi__) + // .fnstart and .fnend pseudo operations creates unwind table. + // It also creates a reference to the symbol __aeabi_unwind_cpp_pr0, which + // is not provided by PNaCl toolchain. Disable it. + ".fnstart\n" +#endif + "@ 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" + ".save {r7, lr}\n" + ".cfi_offset 14, -4\n" + ".cfi_offset 7, -8\n" + ".cfi_def_cfa_offset 8\n" +#else + "stmfd sp!, {fp, lr}\n" + "add fp, sp, #4\n" +#endif + // Check if "r0" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "swi 0". This address can be + // used as a marker that BPF code inspects. + "cmp r0, #0\n" + "bge 1f\n" + "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" +#else + "2:ldmfd sp!, {fp, pc}\n" +#endif +#if !defined(__native_client_nonsfi__) + // Do not use .fnstart and .fnend for PNaCl toolchain. See above comment, + // for more details. + ".fnend\n" +#endif + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#elif defined(__mips__) + ".text\n" + ".align 4\n" + ".type SyscallAsm, @function\n" + "SyscallAsm:.ent SyscallAsm\n" + ".frame $sp, 40, $ra\n" + ".set push\n" + ".set noreorder\n" + "addiu $sp, $sp, -40\n" + "sw $ra, 36($sp)\n" + // Check if "v0" 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. + "bgez $v0, 1f\n" + " nop\n" + "la $v0, 2f\n" + "b 2f\n" + " nop\n" + // On MIPS first four arguments go to registers a0 - a3 and any + // argument after that goes to stack. We can go ahead and directly + // copy the entries from the arguments array into the appropriate + // CPU registers and on the stack. + "1:lw $a3, 28($a0)\n" + "lw $a2, 24($a0)\n" + "lw $a1, 20($a0)\n" + "lw $t0, 16($a0)\n" + "sw $a3, 28($sp)\n" + "sw $a2, 24($sp)\n" + "sw $a1, 20($sp)\n" + "sw $t0, 16($sp)\n" + "lw $a3, 12($a0)\n" + "lw $a2, 8($a0)\n" + "lw $a1, 4($a0)\n" + "lw $a0, 0($a0)\n" + // Enter the kernel + "syscall\n" + // This is our "magic" return address that the BPF filter sees. + // Restore the return address from the stack. + "2:lw $ra, 36($sp)\n" + "jr $ra\n" + " addiu $sp, $sp, 40\n" + ".set pop\n" + ".end SyscallAsm\n" + ".size SyscallAsm,.-SyscallAsm\n" +#elif defined(__aarch64__) + ".text\n" + ".align 2\n" + ".type SyscallAsm, %function\n" + "SyscallAsm:\n" + ".cfi_startproc\n" + "cmp x0, #0\n" + "b.ge 1f\n" + "adr x0,2f\n" + "b 2f\n" + "1:ldr x5, [x6, #40]\n" + "ldr x4, [x6, #32]\n" + "ldr x3, [x6, #24]\n" + "ldr x2, [x6, #16]\n" + "ldr x1, [x6, #8]\n" + "mov x8, x0\n" + "ldr x0, [x6, #0]\n" + // Enter the kernel + "svc 0\n" + "2:ret\n" + ".cfi_endproc\n" + ".size SyscallAsm, .-SyscallAsm\n" +#endif + ); // asm + +#if defined(__x86_64__) +extern "C" { +intptr_t SyscallAsm(intptr_t nr, const intptr_t args[6]); +} +#endif + +} // namespace + +intptr_t Syscall::InvalidCall() { + // Explicitly pass eight zero arguments just in case. + return Call(kInvalidSyscallNumber, 0, 0, 0, 0, 0, 0, 0, 0); +} + +intptr_t Syscall::Call(int nr, + intptr_t p0, + intptr_t p1, + intptr_t p2, + intptr_t p3, + intptr_t p4, + intptr_t p5, + intptr_t p6, + intptr_t p7) { + // We rely on "intptr_t" to be the exact size as a "void *". This is + // typically true, but just in case, we add a check. The language + // specification allows platforms some leeway in cases, where + // "sizeof(void *)" is not the same as "sizeof(void (*)())". We expect + // that this would only be an issue for IA64, which we are currently not + // planning on supporting. And it is even possible that this would work + // on IA64, but for lack of actual hardware, I cannot test. + static_assert(sizeof(void*) == sizeof(intptr_t), + "pointer types and intptr_t must be exactly the same size"); + + // TODO(nedeljko): Enable use of more than six parameters on architectures + // where that makes sense. +#if defined(__mips__) + const intptr_t args[8] = {p0, p1, p2, p3, p4, p5, p6, p7}; +#else + DCHECK_EQ(p6, 0) << " Support for syscalls with more than six arguments not " + "added for this architecture"; + DCHECK_EQ(p7, 0) << " Support for syscalls with more than six arguments not " + "added for this architecture"; + const intptr_t args[6] = {p0, p1, p2, p3, p4, p5}; +#endif // defined(__mips__) + +// 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"); +#elif defined(__x86_64__) + intptr_t ret = SyscallAsm(nr, args); +#elif defined(__arm__) + intptr_t ret; + { + register intptr_t inout __asm__("r0") = nr; + register const intptr_t* data __asm__("r6") = args; + asm volatile( + "bl SyscallAsm\n" + // N.B. These are not the calling conventions normally used by the ABI. + : "=r"(inout) + : "0"(inout), "r"(data) + : "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; + } +#elif defined(__mips__) + int err_status; + intptr_t ret = Syscall::SandboxSyscallRaw(nr, args, &err_status); + + if (err_status) { + // On error, MIPS returns errno from syscall instead of -errno. + // The purpose of this negation is for SandboxSyscall() to behave + // more like it would on other architectures. + ret = -ret; + } +#elif defined(__aarch64__) + intptr_t ret; + { + register intptr_t inout __asm__("x0") = nr; + register const intptr_t* data __asm__("x6") = args; + asm volatile("bl SyscallAsm\n" + : "=r"(inout) + : "0"(inout), "r"(data) + : "memory", "x1", "x2", "x3", "x4", "x5", "x8", "x30"); + ret = inout; + } + +#else +#error "Unimplemented architecture" +#endif + return ret; +} + +void Syscall::PutValueInUcontext(intptr_t ret_val, ucontext_t* ctx) { +#if defined(__mips__) + // Mips ABI states that on error a3 CPU register has non zero value and if + // there is no error, it should be zero. + if (ret_val <= -1 && ret_val >= -4095) { + // |ret_val| followes the Syscall::Call() convention of being -errno on + // errors. In order to write correct value to return register this sign + // needs to be changed back. + ret_val = -ret_val; + SECCOMP_PARM4(ctx) = 1; + } else + SECCOMP_PARM4(ctx) = 0; +#endif + SECCOMP_RESULT(ctx) = static_cast<greg_t>(ret_val); +} + +#if defined(__mips__) +intptr_t Syscall::SandboxSyscallRaw(int nr, + const intptr_t* args, + intptr_t* err_ret) { + register intptr_t ret __asm__("v0") = nr; + // a3 register becomes non zero on error. + register intptr_t err_stat __asm__("a3") = 0; + { + register const intptr_t* data __asm__("a0") = args; + asm volatile( + "la $t9, SyscallAsm\n" + "jalr $t9\n" + " nop\n" + : "=r"(ret), "=r"(err_stat) + : "0"(ret), + "r"(data) + // a2 is in the clober list so inline assembly can not change its + // value. + : "memory", "ra", "t9", "a2"); + } + + // Set an error status so it can be used outside of this function + *err_ret = err_stat; + + return ret; +} +#endif // defined(__mips__) + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/syscall.h b/sandbox/linux/seccomp-bpf/syscall.h new file mode 100644 index 0000000000..ccfc88dcb3 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall.h @@ -0,0 +1,166 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ + +#include <signal.h> +#include <stdint.h> + +#include "base/macros.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This purely static class can be used to perform system calls with some +// low-level control. +class SANDBOX_EXPORT Syscall { + public: + // InvalidCall() invokes Call() with a platform-appropriate syscall + // number that is guaranteed to not be implemented (i.e., normally + // returns -ENOSYS). + // This is primarily meant to be useful for writing sandbox policy + // unit tests. + static intptr_t InvalidCall(); + + // System calls can take up to six parameters (up to eight on some + // architectures). 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, + class T6, + class T7> + static inline intptr_t + Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7) { + return Call(nr, + (intptr_t)p0, + (intptr_t)p1, + (intptr_t)p2, + (intptr_t)p3, + (intptr_t)p4, + (intptr_t)p5, + (intptr_t)p6, + (intptr_t)p7); + } + + template <class T0, + class T1, + class T2, + class T3, + class T4, + class T5, + class T6> + static inline intptr_t + Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6) { + return Call(nr, + (intptr_t)p0, + (intptr_t)p1, + (intptr_t)p2, + (intptr_t)p3, + (intptr_t)p4, + (intptr_t)p5, + (intptr_t)p6, + 0); + } + + 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, + 0, + 0); + } + + 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, 0, 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, 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, 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, 0, 0); + } + + template <class T0> + static inline intptr_t Call(int nr, T0 p0) { + return Call(nr, p0, 0, 0, 0, 0, 0, 0, 0); + } + + static inline intptr_t Call(int nr) { + return Call(nr, 0, 0, 0, 0, 0, 0, 0, 0); + } + + // Set the registers in |ctx| to match what they would be after a system call + // returning |ret_val|. |ret_val| must follow the Syscall::Call() convention + // of being -errno on errors. + static void PutValueInUcontext(intptr_t ret_val, ucontext_t* ctx); + + private: + // This performs system call |nr| with the arguments p0 to p7 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, + intptr_t p6, + intptr_t p7); + +#if defined(__mips__) + // This function basically does on MIPS what SandboxSyscall() is doing on + // other architectures. However, because of specificity of MIPS regarding + // handling syscall errors, SandboxSyscall() is made as a wrapper for this + // function in order for SandboxSyscall() to behave more like on other + // architectures on places where return value from SandboxSyscall() is used + // directly (like in most tests). + // The syscall "nr" is called with arguments that are set in an array on which + // pointer "args" points to and an information weather there is an error or no + // is returned to SandboxSyscall() by err_stat. + static intptr_t SandboxSyscallRaw(int nr, + const intptr_t* args, + intptr_t* err_stat); +#endif // defined(__mips__) + + DISALLOW_IMPLICIT_CONSTRUCTORS(Syscall); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ diff --git a/sandbox/linux/seccomp-bpf/syscall_unittest.cc b/sandbox/linux/seccomp-bpf/syscall_unittest.cc new file mode 100644 index 0000000000..5fdee6c495 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall_unittest.cc @@ -0,0 +1,240 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/syscall.h" + +#include <asm/unistd.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <vector> + +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::ResultExpr; +using sandbox::bpf_dsl::Trap; + +namespace sandbox { + +namespace { + +// Different platforms use different symbols for the six-argument version +// of the mmap() system call. Test for the correct symbol at compile time. +#ifdef __NR_mmap2 +const int kMMapNr = __NR_mmap2; +#else +const int kMMapNr = __NR_mmap; +#endif + +TEST(Syscall, InvalidCallReturnsENOSYS) { + EXPECT_EQ(-ENOSYS, Syscall::InvalidCall()); +} + +TEST(Syscall, WellKnownEntryPoint) { +// 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__) && !defined(__aarch64__) + EXPECT_NE(Syscall::Call(-1), syscall(-1)); +#endif + +// If possible, test that Syscall::Call(-1) returns the address right +// after +// a kernel entry point. +#if defined(__i386__) + EXPECT_EQ(0x80CDu, ((uint16_t*)Syscall::Call(-1))[-1]); // INT 0x80 +#elif defined(__x86_64__) + EXPECT_EQ(0x050Fu, ((uint16_t*)Syscall::Call(-1))[-1]); // SYSCALL +#elif defined(__arm__) +#if defined(__thumb__) + EXPECT_EQ(0xDF00u, ((uint16_t*)Syscall::Call(-1))[-1]); // SWI 0 +#else + EXPECT_EQ(0xEF000000u, ((uint32_t*)Syscall::Call(-1))[-1]); // SVC 0 +#endif +#elif defined(__mips__) + // Opcode for MIPS sycall is in the lower 16-bits + EXPECT_EQ(0x0cu, (((uint32_t*)Syscall::Call(-1))[-1]) & 0x0000FFFF); +#elif defined(__aarch64__) + EXPECT_EQ(0xD4000001u, ((uint32_t*)Syscall::Call(-1))[-1]); // SVC 0 +#else +#warning Incomplete test case; need port for target platform +#endif +} + +TEST(Syscall, TrivialSyscallNoArgs) { + // Test that we can do basic system calls + 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 = 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 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); + seen_syscall_args->assign(args.args, args.args + arraysize(args.args)); + return -ENOMEM; +} + +class CopyAllArgsOnUnamePolicy : public bpf_dsl::Policy { + public: + explicit CopyAllArgsOnUnamePolicy(std::vector<uint64_t>* aux) : aux_(aux) {} + ~CopyAllArgsOnUnamePolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno == __NR_uname) { + return Trap(CopySyscallArgsToAux, aux_); + } else { + return Allow(); + } + } + + private: + std::vector<uint64_t>* aux_; + + DISALLOW_COPY_AND_ASSIGN(CopyAllArgsOnUnamePolicy); +}; + +// 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) */) { + const int kExpectedValue = 42; + // In this test we only pass integers to the kernel. We might want to make + // additional tests to try other types. What we will see depends on + // implementation details of kernel BPF filters and we will need to document + // the expected behavior very clearly. + int syscall_args[6]; + for (size_t i = 0; i < arraysize(syscall_args); ++i) { + syscall_args[i] = kExpectedValue + i; + } + + // We could use pretty much any system call we don't need here. uname() is + // nice because it doesn't have any dangerous side effects. + BPF_ASSERT(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); + + // Don't loop here so that we can see which argument does cause the failure + // easily from the failing line. + // uint64_t is the type passed to our SIGSYS handler. + BPF_ASSERT((*BPF_AUX)[0] == static_cast<uint64_t>(syscall_args[0])); + BPF_ASSERT((*BPF_AUX)[1] == static_cast<uint64_t>(syscall_args[1])); + BPF_ASSERT((*BPF_AUX)[2] == static_cast<uint64_t>(syscall_args[2])); + BPF_ASSERT((*BPF_AUX)[3] == static_cast<uint64_t>(syscall_args[3])); + BPF_ASSERT((*BPF_AUX)[4] == static_cast<uint64_t>(syscall_args[4])); + BPF_ASSERT((*BPF_AUX)[5] == static_cast<uint64_t>(syscall_args[5])); +} + +TEST(Syscall, ComplexSyscallSixArgs) { + int fd; + ASSERT_LE(0, + fd = Syscall::Call(__NR_openat, AT_FDCWD, "/dev/null", O_RDWR, 0L)); + + // Use mmap() to allocate some read-only memory + char* addr0; + 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*>( + 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, 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 = Syscall::Call(__NR_openat, AT_FDCWD, "/proc/self/exe", O_RDONLY, 0L), + 0); + char* addr2, *addr3; + ASSERT_NE((char*)NULL, + addr2 = reinterpret_cast<char*>(Syscall::Call( + kMMapNr, (void*)NULL, 8192L, PROT_READ, MAP_PRIVATE, fd, 0L))); + ASSERT_NE((char*)NULL, + addr3 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, + (void*)NULL, + 4096L, + PROT_READ, + MAP_PRIVATE, + fd, +#if defined(__NR_mmap2) + 1L +#else + 4096L +#endif + ))); + EXPECT_EQ(0, memcmp(addr2 + 4096, addr3, 4096)); + + // Just to be absolutely on the safe side, also verify that the file + // contents matches what we are getting from a read() operation. + char buf[8192]; + EXPECT_EQ(8192, Syscall::Call(__NR_read, fd, buf, 8192L)); + EXPECT_EQ(0, memcmp(addr2, buf, 8192)); + + // Clean up + 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 + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/trap.cc b/sandbox/linux/seccomp-bpf/trap.cc new file mode 100644 index 0000000000..8f559e53b1 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/trap.cc @@ -0,0 +1,390 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/trap.h" + +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <sys/syscall.h> + +#include <algorithm> +#include <limits> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/system_headers/linux_signal.h" + +namespace { + +struct arch_sigsys { + void* ip; + int nr; + unsigned int arch; +}; + +const int kCapacityIncrement = 20; + +// Unsafe traps can only be turned on, if the user explicitly allowed them +// by setting the CHROME_SANDBOX_DEBUGGING environment variable. +const char kSandboxDebuggingEnv[] = "CHROME_SANDBOX_DEBUGGING"; + +// We need to tell whether we are performing a "normal" callback, or +// whether we were called recursively from within a UnsafeTrap() callback. +// This is a little tricky to do, because we need to somehow get access to +// per-thread data from within a signal context. Normal TLS storage is not +// safely accessible at this time. We could roll our own, but that involves +// a lot of complexity. Instead, we co-opt one bit in the signal mask. +// If BUS is blocked, we assume that we have been called recursively. +// There is a possibility for collision with other code that needs to do +// this, but in practice the risks are low. +// If SIGBUS turns out to be a problem, we could instead co-opt one of the +// realtime signals. There are plenty of them. Unfortunately, there is no +// way to mark a signal as allocated. So, the potential for collision is +// possibly even worse. +bool GetIsInSigHandler(const ucontext_t* ctx) { + // Note: on Android, sigismember does not take a pointer to const. + return sigismember(const_cast<sigset_t*>(&ctx->uc_sigmask), LINUX_SIGBUS); +} + +void SetIsInSigHandler() { + sigset_t mask; + if (sigemptyset(&mask) || sigaddset(&mask, LINUX_SIGBUS) || + sandbox::sys_sigprocmask(LINUX_SIG_BLOCK, &mask, NULL)) { + SANDBOX_DIE("Failed to block SIGBUS"); + } +} + +bool IsDefaultSignalAction(const struct sigaction& sa) { + if (sa.sa_flags & SA_SIGINFO || sa.sa_handler != SIG_DFL) { + return false; + } + return true; +} + +} // namespace + +namespace sandbox { + +Trap::Trap() + : trap_array_(NULL), + trap_array_size_(0), + trap_array_capacity_(0), + has_unsafe_traps_(false) { + // Set new SIGSYS handler + struct sigaction sa = {}; + // In some toolchain, sa_sigaction is not declared in struct sigaction. + // So, here cast the pointer to the sa_handler's type. This works because + // |sa_handler| and |sa_sigaction| shares the same memory. + sa.sa_handler = reinterpret_cast<void (*)(int)>(SigSysAction); + sa.sa_flags = LINUX_SA_SIGINFO | LINUX_SA_NODEFER; + struct sigaction old_sa = {}; + if (sys_sigaction(LINUX_SIGSYS, &sa, &old_sa) < 0) { + SANDBOX_DIE("Failed to configure SIGSYS handler"); + } + + if (!IsDefaultSignalAction(old_sa)) { + 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 + sigset_t mask; + if (sigemptyset(&mask) || sigaddset(&mask, LINUX_SIGSYS) || + sys_sigprocmask(LINUX_SIG_UNBLOCK, &mask, NULL)) { + SANDBOX_DIE("Failed to configure SIGSYS handler"); + } +} + +bpf_dsl::TrapRegistry* Trap::Registry() { + // Note: This class is not thread safe. It is the caller's responsibility + // to avoid race conditions. Normally, this is a non-issue as the sandbox + // can only be initialized if there are no other threads present. + // Also, this is not a normal singleton. Once created, the global trap + // object must never be destroyed again. + if (!global_trap_) { + global_trap_ = new Trap(); + if (!global_trap_) { + SANDBOX_DIE("Failed to allocate global trap handler"); + } + } + return global_trap_; +} + +void Trap::SigSysAction(int nr, LinuxSigInfo* info, void* void_context) { + if (info) { + MSAN_UNPOISON(info, sizeof(*info)); + } + + // Obtain the signal context. This, most notably, gives us access to + // all CPU registers at the time of the signal. + ucontext_t* ctx = reinterpret_cast<ucontext_t*>(void_context); + if (ctx) { + MSAN_UNPOISON(ctx, sizeof(*ctx)); + } + + if (!global_trap_) { + RAW_SANDBOX_DIE( + "This can't happen. Found no global singleton instance " + "for Trap() handling."); + } + global_trap_->SigSys(nr, info, ctx); +} + +void Trap::SigSys(int nr, LinuxSigInfo* info, ucontext_t* ctx) { + // Signal handlers should always preserve "errno". Otherwise, we could + // trigger really subtle bugs. + const int old_errno = errno; + + // Various sanity checks to make sure we actually received a signal + // triggered by a BPF filter. If something else triggered SIGSYS + // (e.g. kill()), there is really nothing we can do with this signal. + if (nr != LINUX_SIGSYS || info->si_code != SYS_SECCOMP || !ctx || + info->si_errno <= 0 || + static_cast<size_t>(info->si_errno) > trap_array_size_) { + // ATI drivers seem to send SIGSYS, so this cannot be FATAL. + // See crbug.com/178166. + // TODO(jln): add a DCHECK or move back to FATAL. + RAW_LOG(ERROR, "Unexpected SIGSYS received."); + errno = old_errno; + return; + } + + + // Obtain the siginfo information that is specific to SIGSYS. Unfortunately, + // most versions of glibc don't include this information in siginfo_t. So, + // we need to explicitly copy it into a arch_sigsys structure. + struct arch_sigsys sigsys; + memcpy(&sigsys, &info->_sifields, sizeof(sigsys)); + +#if defined(__mips__) + // When indirect syscall (syscall(__NR_foo, ...)) is made on Mips, the + // number in register SECCOMP_SYSCALL(ctx) is always __NR_syscall and the + // real number of a syscall (__NR_foo) is in SECCOMP_PARM1(ctx) + bool sigsys_nr_is_bad = sigsys.nr != static_cast<int>(SECCOMP_SYSCALL(ctx)) && + sigsys.nr != static_cast<int>(SECCOMP_PARM1(ctx)); +#else + bool sigsys_nr_is_bad = sigsys.nr != static_cast<int>(SECCOMP_SYSCALL(ctx)); +#endif + + // Some more sanity checks. + if (sigsys.ip != reinterpret_cast<void*>(SECCOMP_IP(ctx)) || + sigsys_nr_is_bad || sigsys.arch != SECCOMP_ARCH) { + // TODO(markus): + // SANDBOX_DIE() can call LOG(FATAL). This is not normally async-signal + // safe and can lead to bugs. We should eventually implement a different + // logging and reporting mechanism that is safe to be called from + // the sigSys() handler. + RAW_SANDBOX_DIE("Sanity checks are failing after receiving SIGSYS."); + } + + intptr_t rc; + if (has_unsafe_traps_ && GetIsInSigHandler(ctx)) { + errno = old_errno; + if (sigsys.nr == __NR_clone) { + RAW_SANDBOX_DIE("Cannot call clone() from an UnsafeTrap() handler."); + } +#if defined(__mips__) + // Mips supports up to eight arguments for syscall. + // However, seccomp bpf can filter only up to six arguments, so using eight + // arguments has sense only when using UnsafeTrap() handler. + rc = Syscall::Call(SECCOMP_SYSCALL(ctx), + SECCOMP_PARM1(ctx), + SECCOMP_PARM2(ctx), + SECCOMP_PARM3(ctx), + SECCOMP_PARM4(ctx), + SECCOMP_PARM5(ctx), + SECCOMP_PARM6(ctx), + SECCOMP_PARM7(ctx), + SECCOMP_PARM8(ctx)); +#else + rc = Syscall::Call(SECCOMP_SYSCALL(ctx), + SECCOMP_PARM1(ctx), + SECCOMP_PARM2(ctx), + SECCOMP_PARM3(ctx), + SECCOMP_PARM4(ctx), + SECCOMP_PARM5(ctx), + SECCOMP_PARM6(ctx)); +#endif // defined(__mips__) + } else { + const TrapKey& trap = trap_array_[info->si_errno - 1]; + if (!trap.safe) { + SetIsInSigHandler(); + } + + // Copy the seccomp-specific data into a arch_seccomp_data structure. This + // is what we are showing to TrapFnc callbacks that the system call + // evaluator registered with the sandbox. + struct arch_seccomp_data data = { + static_cast<int>(SECCOMP_SYSCALL(ctx)), + SECCOMP_ARCH, + reinterpret_cast<uint64_t>(sigsys.ip), + {static_cast<uint64_t>(SECCOMP_PARM1(ctx)), + static_cast<uint64_t>(SECCOMP_PARM2(ctx)), + static_cast<uint64_t>(SECCOMP_PARM3(ctx)), + static_cast<uint64_t>(SECCOMP_PARM4(ctx)), + static_cast<uint64_t>(SECCOMP_PARM5(ctx)), + static_cast<uint64_t>(SECCOMP_PARM6(ctx))}}; + + // Now call the TrapFnc callback associated with this particular instance + // of SECCOMP_RET_TRAP. + rc = trap.fnc(data, const_cast<void*>(trap.aux)); + } + + // Update the CPU register that stores the return code of the system call + // that we just handled, and restore "errno" to the value that it had + // before entering the signal handler. + Syscall::PutValueInUcontext(rc, ctx); + errno = old_errno; + + return; +} + +bool Trap::TrapKey::operator<(const TrapKey& o) const { + if (fnc != o.fnc) { + return fnc < o.fnc; + } else if (aux != o.aux) { + return aux < o.aux; + } else { + return safe < o.safe; + } +} + +uint16_t Trap::Add(TrapFnc fnc, const void* aux, bool safe) { + if (!safe && !SandboxDebuggingAllowedByUser()) { + // Unless the user set the CHROME_SANDBOX_DEBUGGING environment variable, + // we never return an ErrorCode that is marked as "unsafe". This also + // means, the BPF compiler will never emit code that allow unsafe system + // calls to by-pass the filter (because they use the magic return address + // from 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 + // to understand. Removing the SANDBOX_DIE() allows callers to easily check + // whether unsafe traps are supported (by checking whether the returned + // ErrorCode is ET_INVALID). + SANDBOX_DIE( + "Cannot use unsafe traps unless CHROME_SANDBOX_DEBUGGING " + "is enabled"); + + return 0; + } + + // Each unique pair of TrapFnc and auxiliary data make up a distinct instance + // of a SECCOMP_RET_TRAP. + TrapKey key(fnc, aux, safe); + + // We return unique identifiers together with SECCOMP_RET_TRAP. This allows + // us to associate trap with the appropriate handler. The kernel allows us + // identifiers in the range from 0 to SECCOMP_RET_DATA (0xFFFF). We want to + // avoid 0, as it could be confused for a trap without any specific id. + // The nice thing about sequentially numbered identifiers is that we can also + // trivially look them up from our signal handler without making any system + // calls that might be async-signal-unsafe. + // In order to do so, we store all of our traps in a C-style trap_array_. + + TrapIds::const_iterator iter = trap_ids_.find(key); + if (iter != trap_ids_.end()) { + // We have seen this pair before. Return the same id that we assigned + // earlier. + return iter->second; + } + + // This is a new pair. Remember it and assign a new id. + if (trap_array_size_ >= SECCOMP_RET_DATA /* 0xFFFF */ || + trap_array_size_ >= std::numeric_limits<uint16_t>::max()) { + // In practice, this is pretty much impossible to trigger, as there + // are other kernel limitations that restrict overall BPF program sizes. + SANDBOX_DIE("Too many SECCOMP_RET_TRAP callback instances"); + } + + // Our callers ensure that there are no other threads accessing trap_array_ + // concurrently (typically this is done by ensuring that we are single- + // threaded while the sandbox is being set up). But we nonetheless are + // modifying a live data structure that could be accessed any time a + // system call is made; as system calls could be triggering SIGSYS. + // So, we have to be extra careful that we update trap_array_ atomically. + // In particular, this means we shouldn't be using realloc() to resize it. + // Instead, we allocate a new array, copy the values, and then switch the + // pointer. We only really care about the pointer being updated atomically + // and the data that is pointed to being valid, as these are the only + // values accessed from the signal handler. It is OK if trap_array_size_ + // is inconsistent with the pointer, as it is monotonously increasing. + // Also, we only care about compiler barriers, as the signal handler is + // triggered synchronously from a system call. We don't have to protect + // against issues with the memory model or with completely asynchronous + // events. + if (trap_array_size_ >= trap_array_capacity_) { + trap_array_capacity_ += kCapacityIncrement; + TrapKey* old_trap_array = trap_array_; + TrapKey* new_trap_array = new TrapKey[trap_array_capacity_]; + std::copy_n(old_trap_array, trap_array_size_, new_trap_array); + + // Language specs are unclear on whether the compiler is allowed to move + // the "delete[]" above our preceding assignments and/or memory moves, + // iff the compiler believes that "delete[]" doesn't have any other + // global side-effects. + // We insert optimization barriers to prevent this from happening. + // The first barrier is probably not needed, but better be explicit in + // what we want to tell the compiler. + // The clang developer mailing list couldn't answer whether this is a + // legitimate worry; but they at least thought that the barrier is + // sufficient to prevent the (so far hypothetical) problem of re-ordering + // of instructions by the compiler. + // + // TODO(mdempsky): Try to clean this up using base/atomicops or C++11 + // atomics; see crbug.com/414363. + asm volatile("" : "=r"(new_trap_array) : "0"(new_trap_array) : "memory"); + trap_array_ = new_trap_array; + asm volatile("" : "=r"(trap_array_) : "0"(trap_array_) : "memory"); + + delete[] old_trap_array; + } + + uint16_t id = trap_array_size_ + 1; + trap_ids_[key] = id; + trap_array_[trap_array_size_] = key; + trap_array_size_++; + return id; +} + +bool Trap::SandboxDebuggingAllowedByUser() { + const char* debug_flag = getenv(kSandboxDebuggingEnv); + return debug_flag && *debug_flag; +} + +bool Trap::EnableUnsafeTraps() { + if (!has_unsafe_traps_) { + // Unsafe traps are a one-way fuse. Once enabled, they can never be turned + // off again. + // We only allow enabling unsafe traps, if the user explicitly set an + // appropriate environment variable. This prevents bugs that accidentally + // disable all sandboxing for all users. + if (SandboxDebuggingAllowedByUser()) { + // We only ever print this message once, when we enable unsafe traps the + // first time. + SANDBOX_INFO("WARNING! Disabling sandbox for debugging purposes"); + has_unsafe_traps_ = true; + } else { + SANDBOX_INFO( + "Cannot disable sandbox and use unsafe traps unless " + "CHROME_SANDBOX_DEBUGGING is turned on first"); + } + } + // Returns the, possibly updated, value of has_unsafe_traps_. + return has_unsafe_traps_; +} + +Trap* Trap::global_trap_; + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/trap.h b/sandbox/linux/seccomp-bpf/trap.h new file mode 100644 index 0000000000..50ac3fd1c3 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/trap.h @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ + +#include <stdint.h> + +#include <map> + +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// The Trap class allows a BPF filter program to branch out to user space by +// raising a SIGSYS signal. +// N.B.: This class does not perform any synchronization operations. If +// modifications are made to any of the traps, it is the caller's +// responsibility to ensure that this happens in a thread-safe fashion. +// Preferably, that means that no other threads should be running at that +// time. For the purposes of our sandbox, this assertion should always be +// true. Threads are incompatible with the seccomp sandbox anyway. +class SANDBOX_EXPORT Trap : public bpf_dsl::TrapRegistry { + public: + uint16_t Add(TrapFnc fnc, const void* aux, bool safe) override; + + bool EnableUnsafeTraps() override; + + // Registry returns the trap registry used by Trap's SIGSYS handler, + // creating it if necessary. + static bpf_dsl::TrapRegistry* Registry(); + + // SandboxDebuggingAllowedByUser returns whether the + // "CHROME_SANDBOX_DEBUGGING" environment variable is set. + static bool SandboxDebuggingAllowedByUser(); + + private: + struct TrapKey { + TrapKey() : fnc(NULL), aux(NULL), safe(false) {} + TrapKey(TrapFnc f, const void* a, bool s) : fnc(f), aux(a), safe(s) {} + TrapFnc fnc; + const void* aux; + bool safe; + bool operator<(const TrapKey&) const; + }; + typedef std::map<TrapKey, uint16_t> TrapIds; + + // Our constructor is private. A shared global instance is created + // automatically as needed. + Trap(); + + // The destructor is unimplemented as destroying this object would + // break subsequent system calls that trigger a SIGSYS. + ~Trap() = delete; + + static void SigSysAction(int nr, LinuxSigInfo* info, void* void_context); + + // Make sure that SigSys is not inlined in order to get slightly better crash + // dumps. + void SigSys(int nr, LinuxSigInfo* info, ucontext_t* ctx) + __attribute__((noinline)); + // We have a global singleton that handles all of our SIGSYS traps. This + // variable must never be deallocated after it has been set up initially, as + // there is no way to reset in-kernel BPF filters that generate SIGSYS + // events. + static Trap* global_trap_; + + TrapIds trap_ids_; // Maps from TrapKeys to numeric ids + TrapKey* trap_array_; // Array of TrapKeys indexed by ids + size_t trap_array_size_; // Currently used size of array + size_t trap_array_capacity_; // Currently allocated capacity of array + bool has_unsafe_traps_; // Whether unsafe traps have been enabled + + // Copying and assigning is unimplemented. It doesn't make sense for a + // singleton. + DISALLOW_COPY_AND_ASSIGN(Trap); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ diff --git a/sandbox/linux/seccomp-bpf/trap_unittest.cc b/sandbox/linux/seccomp-bpf/trap_unittest.cc new file mode 100644 index 0000000000..99f94bfb3a --- /dev/null +++ b/sandbox/linux/seccomp-bpf/trap_unittest.cc @@ -0,0 +1,28 @@ +// Copyright 2015 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/trap.h" + +#include <signal.h> + +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { +namespace { + +SANDBOX_TEST_ALLOW_NOISE(Trap, SigSysAction) { + // This creates a global Trap instance, and registers the signal handler + // (Trap::SigSysAction). + Trap::Registry(); + + // Send SIGSYS to self. If signal handler (SigSysAction) is not registered, + // the process will be terminated with status code -SIGSYS. + // Note that, SigSysAction handler would output an error message + // "Unexpected SIGSYS received." so it is necessary to allow the noise. + raise(SIGSYS); +} + +} // namespace +} // namespace sandbox diff --git a/sandbox/linux/services/DEPS b/sandbox/linux/services/DEPS new file mode 100644 index 0000000000..70d9b18aa1 --- /dev/null +++ b/sandbox/linux/services/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+sandbox/linux/system_headers", +] diff --git a/sandbox/linux/services/credentials.cc b/sandbox/linux/services/credentials.cc new file mode 100644 index 0000000000..35bb4dcbd7 --- /dev/null +++ b/sandbox/linux/services/credentials.cc @@ -0,0 +1,299 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/credentials.h" + +#include <errno.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" +#include "base/template_util.h" +#include "base/third_party/valgrind/valgrind.h" +#include "build/build_config.h" +#include "sandbox/linux/services/namespace_utils.h" +#include "sandbox/linux/services/proc_util.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/services/thread_helpers.h" +#include "sandbox/linux/system_headers/capability.h" +#include "sandbox/linux/system_headers/linux_signal.h" + +namespace sandbox { + +namespace { + +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + +// Checks that the set of RES-uids and the set of RES-gids have +// one element each and return that element in |resuid| and |resgid| +// respectively. It's ok to pass NULL as one or both of the ids. +bool GetRESIds(uid_t* resuid, gid_t* resgid) { + uid_t ruid, euid, suid; + gid_t rgid, egid, sgid; + PCHECK(sys_getresuid(&ruid, &euid, &suid) == 0); + PCHECK(sys_getresgid(&rgid, &egid, &sgid) == 0); + const bool uids_are_equal = (ruid == euid) && (ruid == suid); + const bool gids_are_equal = (rgid == egid) && (rgid == sgid); + if (!uids_are_equal || !gids_are_equal) return false; + if (resuid) *resuid = euid; + if (resgid) *resgid = egid; + return true; +} + +const int kExitSuccess = 0; + +int ChrootToSelfFdinfo(void*) { + RAW_CHECK(sys_chroot("/proc/self/fdinfo/") == 0); + + // CWD is essentially an implicit file descriptor, so be careful to not + // leave it behind. + RAW_CHECK(chdir("/") == 0); + _exit(kExitSuccess); +} + +// chroot() to an empty dir that is "safe". To be safe, it must not contain +// any subdirectory (chroot-ing there would allow a chroot escape) and it must +// be impossible to create an empty directory there. +// We achieve this by doing the following: +// 1. We create a new process sharing file system information. +// 2. In the child, we chroot to /proc/self/fdinfo/ +// This is already "safe", since fdinfo/ does not contain another directory and +// one cannot create another directory there. +// 3. The process dies +// After (3) happens, the directory is not available anymore in /proc. +bool ChrootToSafeEmptyDir() { + // We need to chroot to a fdinfo that is unique to a process and have that + // process die. + // 1. We don't want to simply fork() because duplicating the page tables is + // slow with a big address space. + // 2. We do not use a regular thread (that would unshare CLONE_FILES) because + // when we are in a PID namespace, we cannot easily get a handle to the + // /proc/tid directory for the thread (since /proc may not be aware of the + // PID namespace). With a process, we can just use /proc/self. + pid_t pid = -1; + char stack_buf[PTHREAD_STACK_MIN]; +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS64_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY) + // The stack grows downward. + void* stack = stack_buf + sizeof(stack_buf); +#else +#error "Unsupported architecture" +#endif + + pid = clone(ChrootToSelfFdinfo, stack, + CLONE_VM | CLONE_VFORK | CLONE_FS | LINUX_SIGCHLD, nullptr, + nullptr, nullptr, nullptr); + PCHECK(pid != -1); + + int status = -1; + PCHECK(HANDLE_EINTR(waitpid(pid, &status, 0)) == pid); + + return WIFEXITED(status) && WEXITSTATUS(status) == kExitSuccess; +} + +// 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); +} + +// Converts a Capability to the corresponding Linux CAP_XXX value. +int CapabilityToKernelValue(Credentials::Capability cap) { + switch (cap) { + case Credentials::Capability::SYS_CHROOT: + return CAP_SYS_CHROOT; + case Credentials::Capability::SYS_ADMIN: + return CAP_SYS_ADMIN; + } + + LOG(FATAL) << "Invalid Capability: " << static_cast<int>(cap); + return 0; +} + +} // namespace. + +// static +bool Credentials::DropAllCapabilities(int proc_fd) { + if (!SetCapabilities(proc_fd, std::vector<Capability>())) { + return false; + } + + CHECK(!HasAnyCapability()); + return true; +} + +// static +bool Credentials::DropAllCapabilities() { + base::ScopedFD proc_fd(ProcUtil::OpenProc()); + return Credentials::DropAllCapabilities(proc_fd.get()); +} + +// static +bool Credentials::DropAllCapabilitiesOnCurrentThread() { + return SetCapabilitiesOnCurrentThread(std::vector<Capability>()); +} + +// static +bool Credentials::SetCapabilitiesOnCurrentThread( + const std::vector<Capability>& caps) { + struct cap_hdr hdr = {}; + hdr.version = _LINUX_CAPABILITY_VERSION_3; + struct cap_data data[_LINUX_CAPABILITY_U32S_3] = {{}}; + + // Initially, cap has no capability flags set. Enable the effective and + // permitted flags only for the requested capabilities. + for (const Capability cap : caps) { + const int cap_num = CapabilityToKernelValue(cap); + const size_t index = CAP_TO_INDEX(cap_num); + const uint32_t mask = CAP_TO_MASK(cap_num); + data[index].effective |= mask; + data[index].permitted |= mask; + } + + return sys_capset(&hdr, data) == 0; +} + +// static +bool Credentials::SetCapabilities(int proc_fd, + const std::vector<Capability>& caps) { + DCHECK_LE(0, proc_fd); + +#if !defined(THREAD_SANITIZER) + // With TSAN, accept to break the security model as it is a testing + // configuration. + CHECK(ThreadHelpers::IsSingleThreaded(proc_fd)); +#endif + + return SetCapabilitiesOnCurrentThread(caps); +} + +bool Credentials::HasAnyCapability() { + struct cap_hdr hdr = {}; + hdr.version = _LINUX_CAPABILITY_VERSION_3; + struct cap_data data[_LINUX_CAPABILITY_U32S_3] = {{}}; + + PCHECK(sys_capget(&hdr, data) == 0); + + for (size_t i = 0; i < arraysize(data); ++i) { + if (data[i].effective || data[i].permitted || data[i].inheritable) { + return true; + } + } + + return false; +} + +bool Credentials::HasCapability(Capability cap) { + struct cap_hdr hdr = {}; + hdr.version = _LINUX_CAPABILITY_VERSION_3; + struct cap_data data[_LINUX_CAPABILITY_U32S_3] = {{}}; + + PCHECK(sys_capget(&hdr, data) == 0); + + const int cap_num = CapabilityToKernelValue(cap); + const size_t index = CAP_TO_INDEX(cap_num); + const uint32_t mask = CAP_TO_MASK(cap_num); + + return (data[index].effective | data[index].permitted | + data[index].inheritable) & + mask; +} + +// static +bool Credentials::CanCreateProcessInNewUserNS() { + // Valgrind will let clone(2) pass-through, but doesn't support unshare(), + // so always consider UserNS unsupported there. + if (IsRunningOnValgrind()) { + return false; + } + +#if defined(THREAD_SANITIZER) + // With TSAN, processes will always have threads running and can never + // enter a new user namespace with MoveToNewUserNS(). + return false; +#endif + + // This is roughly a fork(). + const pid_t pid = sys_clone(CLONE_NEWUSER | SIGCHLD, 0, 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(kExitSuccess); + } + + // Always reap the child. + int status = -1; + PCHECK(HANDLE_EINTR(waitpid(pid, &status, 0)) == pid); + CHECK(WIFEXITED(status)); + CHECK_EQ(kExitSuccess, WEXITSTATUS(status)); + + // clone(2) succeeded, we can use CLONE_NEWUSER. + return true; +} + +bool Credentials::MoveToNewUserNS() { + uid_t uid; + gid_t gid; + if (!GetRESIds(&uid, &gid)) { + // If all the uids (or gids) are not equal to each other, the security + // model will most likely confuse the caller, abort. + DVLOG(1) << "uids or gids differ!"; + return false; + } + int ret = sys_unshare(CLONE_NEWUSER); + 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; + } + + if (NamespaceUtils::KernelSupportsDenySetgroups()) { + PCHECK(NamespaceUtils::DenySetgroups()); + } + + // 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)); + const char kGidMapFile[] = "/proc/self/gid_map"; + const char kUidMapFile[] = "/proc/self/uid_map"; + PCHECK(NamespaceUtils::WriteToIdMapFile(kGidMapFile, gid)); + PCHECK(NamespaceUtils::WriteToIdMapFile(kUidMapFile, uid)); + DCHECK(GetRESIds(NULL, NULL)); + return true; +} + +bool Credentials::DropFileSystemAccess(int proc_fd) { + CHECK_LE(0, proc_fd); + + CHECK(ChrootToSafeEmptyDir()); + CHECK(!base::DirectoryExists(base::FilePath("/proc"))); + CHECK(!ProcUtil::HasOpenDirectory(proc_fd)); + // We never let this function fail. + return true; +} + +} // namespace sandbox. diff --git a/sandbox/linux/services/credentials.h b/sandbox/linux/services/credentials.h new file mode 100644 index 0000000000..0001dc7328 --- /dev/null +++ b/sandbox/linux/services/credentials.h @@ -0,0 +1,104 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_CREDENTIALS_H_ +#define SANDBOX_LINUX_SERVICES_CREDENTIALS_H_ + +#include "build/build_config.h" +// Link errors are tedious to track, raise a compile-time error instead. +#if defined(OS_ANDROID) +#error "Android is not supported." +#endif // defined(OS_ANDROID). + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/system_headers/capability.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 SANDBOX_EXPORT Credentials { + public: + // For brevity, we only expose enums for the subset of capabilities we use. + // This can be expanded as the need arises. + enum class Capability { + SYS_CHROOT, + SYS_ADMIN, + }; + + // Drop all capabilities in the effective, inheritable and permitted sets for + // the current thread. For security reasons, since capabilities are + // per-thread, the caller is responsible for ensuring it is single-threaded + // when calling this API. + // |proc_fd| must be a file descriptor to /proc/ and remains owned by + // the caller. + static bool DropAllCapabilities(int proc_fd) WARN_UNUSED_RESULT; + // A similar API which assumes that it can open /proc/self/ by itself. + static bool DropAllCapabilities() WARN_UNUSED_RESULT; + // Sets the effective and permitted capability sets for the current thread to + // the list of capabiltiies in |caps|. All other capability flags are cleared. + static bool SetCapabilities(int proc_fd, + const std::vector<Capability>& caps) + WARN_UNUSED_RESULT; + + // Versions of the above functions which do not check that the process is + // single-threaded. After calling these functions, capabilities of other + // threads will not be changed. This is dangerous, do not use unless you nkow + // what you are doing. + static bool DropAllCapabilitiesOnCurrentThread() WARN_UNUSED_RESULT; + static bool SetCapabilitiesOnCurrentThread( + const std::vector<Capability>& caps) WARN_UNUSED_RESULT; + + // Returns true if the current thread has either the effective, permitted, or + // inheritable flag set for the given capability. + static bool HasCapability(Capability cap); + + // Return true iff there is any capability in any of the capabilities sets + // of the current thread. + static bool HasAnyCapability(); + + // 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 the + // ability to move to a user namespace ahead of time. + static bool CanCreateProcessInNewUserNS(); + + // 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 + // change. + // If this call succeeds, the current process will be granted a full set of + // capabilities in the new namespace. + // This will fail if the process is not mono-threaded. + static bool MoveToNewUserNS() WARN_UNUSED_RESULT; + + // Remove the ability of the process to access the file system. File + // descriptors which are already open prior to calling this API remain + // available. + // The implementation currently uses chroot(2) and requires CAP_SYS_CHROOT. + // CAP_SYS_CHROOT can be acquired by using the MoveToNewUserNS() API. + // |proc_fd| must be a file descriptor to /proc/ and must be the only open + // directory file descriptor of the process. + // + // CRITICAL: + // - the caller must close |proc_fd| eventually or access to the file + // system can be recovered. + // - DropAllCapabilities() must be called to prevent escapes. + static bool DropFileSystemAccess(int proc_fd) WARN_UNUSED_RESULT; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Credentials); +}; + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SERVICES_CREDENTIALS_H_ diff --git a/sandbox/linux/services/credentials_unittest.cc b/sandbox/linux/services/credentials_unittest.cc new file mode 100644 index 0000000000..6b93c86c3e --- /dev/null +++ b/sandbox/linux/services/credentials_unittest.cc @@ -0,0 +1,242 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/credentials.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/capability.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/services/proc_util.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/capability.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +struct CapFreeDeleter { + inline void operator()(cap_t cap) const { + int ret = cap_free(cap); + CHECK_EQ(0, ret); + } +}; + +// Wrapper to manage libcap2's cap_t type. +typedef scoped_ptr<typeof(*((cap_t)0)), CapFreeDeleter> ScopedCap; + +bool WorkingDirectoryIsRoot() { + char current_dir[PATH_MAX]; + char* cwd = getcwd(current_dir, sizeof(current_dir)); + PCHECK(cwd); + if (strcmp("/", cwd)) return false; + + // The current directory is the root. Add a few paranoid checks. + struct stat current; + CHECK_EQ(0, stat(".", ¤t)); + struct stat parrent; + CHECK_EQ(0, stat("..", &parrent)); + CHECK_EQ(current.st_dev, parrent.st_dev); + CHECK_EQ(current.st_ino, parrent.st_ino); + CHECK_EQ(current.st_mode, parrent.st_mode); + CHECK_EQ(current.st_uid, parrent.st_uid); + CHECK_EQ(current.st_gid, parrent.st_gid); + return true; +} + +SANDBOX_TEST(Credentials, DropAllCaps) { + CHECK(Credentials::DropAllCapabilities()); + CHECK(!Credentials::HasAnyCapability()); +} + +SANDBOX_TEST(Credentials, MoveToNewUserNS) { + CHECK(Credentials::DropAllCapabilities()); + bool moved_to_new_ns = Credentials::MoveToNewUserNS(); + fprintf(stdout, + "Unprivileged CLONE_NEWUSER supported: %s\n", + moved_to_new_ns ? "true." : "false."); + fflush(stdout); + if (!moved_to_new_ns) { + fprintf(stdout, "This kernel does not support unprivileged namespaces. " + "USERNS tests will succeed without running.\n"); + fflush(stdout); + return; + } + CHECK(Credentials::HasAnyCapability()); + CHECK(Credentials::DropAllCapabilities()); + CHECK(!Credentials::HasAnyCapability()); +} + +SANDBOX_TEST(Credentials, CanCreateProcessInNewUserNS) { + CHECK(Credentials::DropAllCapabilities()); + bool user_ns_supported = Credentials::CanCreateProcessInNewUserNS(); + bool moved_to_new_ns = Credentials::MoveToNewUserNS(); + CHECK_EQ(user_ns_supported, moved_to_new_ns); +} + +SANDBOX_TEST(Credentials, UidIsPreserved) { + CHECK(Credentials::DropAllCapabilities()); + uid_t old_ruid, old_euid, old_suid; + gid_t old_rgid, old_egid, old_sgid; + PCHECK(0 == getresuid(&old_ruid, &old_euid, &old_suid)); + PCHECK(0 == getresgid(&old_rgid, &old_egid, &old_sgid)); + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) return; + uid_t new_ruid, new_euid, new_suid; + PCHECK(0 == getresuid(&new_ruid, &new_euid, &new_suid)); + CHECK(old_ruid == new_ruid); + CHECK(old_euid == new_euid); + CHECK(old_suid == new_suid); + + gid_t new_rgid, new_egid, new_sgid; + PCHECK(0 == getresgid(&new_rgid, &new_egid, &new_sgid)); + CHECK(old_rgid == new_rgid); + CHECK(old_egid == new_egid); + CHECK(old_sgid == new_sgid); +} + +bool NewUserNSCycle() { + if (!Credentials::MoveToNewUserNS() || + !Credentials::HasAnyCapability() || + !Credentials::DropAllCapabilities() || + Credentials::HasAnyCapability()) { + return false; + } + return true; +} + +SANDBOX_TEST(Credentials, NestedUserNS) { + CHECK(Credentials::DropAllCapabilities()); + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) return; + CHECK(Credentials::DropAllCapabilities()); + // As of 3.12, the kernel has a limit of 32. See create_user_ns(). + const int kNestLevel = 10; + for (int i = 0; i < kNestLevel; ++i) { + CHECK(NewUserNSCycle()) << "Creating new user NS failed at iteration " + << i << "."; + } +} + +// Test the WorkingDirectoryIsRoot() helper. +SANDBOX_TEST(Credentials, CanDetectRoot) { + PCHECK(0 == chdir("/proc/")); + CHECK(!WorkingDirectoryIsRoot()); + PCHECK(0 == chdir("/")); + CHECK(WorkingDirectoryIsRoot()); +} + +// Disabled on ASAN because of crbug.com/451603. +SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(DropFileSystemAccessIsSafe)) { + CHECK(Credentials::DropAllCapabilities()); + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) return; + CHECK(Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get())); + CHECK(!base::DirectoryExists(base::FilePath("/proc"))); + CHECK(WorkingDirectoryIsRoot()); + CHECK(base::IsDirectoryEmpty(base::FilePath("/"))); + // We want the chroot to never have a subdirectory. A subdirectory + // could allow a chroot escape. + CHECK_NE(0, mkdir("/test", 0700)); +} + +// Check that after dropping filesystem access and dropping privileges +// it is not possible to regain capabilities. +SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(CannotRegainPrivileges)) { + base::ScopedFD proc_fd(ProcUtil::OpenProc()); + CHECK(Credentials::DropAllCapabilities(proc_fd.get())); + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) return; + CHECK(Credentials::DropFileSystemAccess(proc_fd.get())); + CHECK(Credentials::DropAllCapabilities(proc_fd.get())); + + // The kernel should now prevent us from regaining capabilities because we + // are in a chroot. + CHECK(!Credentials::CanCreateProcessInNewUserNS()); + CHECK(!Credentials::MoveToNewUserNS()); +} + +SANDBOX_TEST(Credentials, SetCapabilities) { + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) + return; + + base::ScopedFD proc_fd(ProcUtil::OpenProc()); + + CHECK(Credentials::HasCapability(Credentials::Capability::SYS_ADMIN)); + CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT)); + + std::vector<Credentials::Capability> caps; + caps.push_back(Credentials::Capability::SYS_CHROOT); + CHECK(Credentials::SetCapabilities(proc_fd.get(), caps)); + + CHECK(!Credentials::HasCapability(Credentials::Capability::SYS_ADMIN)); + CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT)); + + const std::vector<Credentials::Capability> no_caps; + CHECK(Credentials::SetCapabilities(proc_fd.get(), no_caps)); + CHECK(!Credentials::HasAnyCapability()); +} + +SANDBOX_TEST(Credentials, SetCapabilitiesAndChroot) { + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) + return; + + base::ScopedFD proc_fd(ProcUtil::OpenProc()); + + CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT)); + PCHECK(chroot("/") == 0); + + std::vector<Credentials::Capability> caps; + caps.push_back(Credentials::Capability::SYS_CHROOT); + CHECK(Credentials::SetCapabilities(proc_fd.get(), caps)); + PCHECK(chroot("/") == 0); + + CHECK(Credentials::DropAllCapabilities()); + PCHECK(chroot("/") == -1 && errno == EPERM); +} + +SANDBOX_TEST(Credentials, SetCapabilitiesMatchesLibCap2) { + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) + return; + + base::ScopedFD proc_fd(ProcUtil::OpenProc()); + + std::vector<Credentials::Capability> caps; + caps.push_back(Credentials::Capability::SYS_CHROOT); + CHECK(Credentials::SetCapabilities(proc_fd.get(), caps)); + + ScopedCap actual_cap(cap_get_proc()); + PCHECK(actual_cap != nullptr); + + ScopedCap expected_cap(cap_init()); + PCHECK(expected_cap != nullptr); + + const cap_value_t allowed_cap = CAP_SYS_CHROOT; + for (const cap_flag_t flag : {CAP_EFFECTIVE, CAP_PERMITTED}) { + PCHECK(cap_set_flag(expected_cap.get(), flag, 1, &allowed_cap, CAP_SET) == + 0); + } + + CHECK_EQ(0, cap_compare(expected_cap.get(), actual_cap.get())); +} + +} // namespace. + +} // namespace sandbox. diff --git a/sandbox/linux/services/init_process_reaper.cc b/sandbox/linux/services/init_process_reaper.cc new file mode 100644 index 0000000000..2e0b90b7b5 --- /dev/null +++ b/sandbox/linux/services/init_process_reaper.cc @@ -0,0 +1,101 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/init_process_reaper.h" + +#include <signal.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace sandbox { + +namespace { + +void DoNothingSignalHandler(int signal) {} + +} // namespace + +bool CreateInitProcessReaper(base::Closure* post_fork_parent_callback) { + int sync_fds[2]; + // We want to use send, so we can't use a pipe + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sync_fds)) { + PLOG(ERROR) << "Failed to create socketpair"; + return false; + } + pid_t child_pid = fork(); + if (child_pid == -1) { + int close_ret; + close_ret = IGNORE_EINTR(close(sync_fds[0])); + DPCHECK(!close_ret); + close_ret = IGNORE_EINTR(close(sync_fds[1])); + DPCHECK(!close_ret); + return false; + } + if (child_pid) { + // In the parent, assuming the role of an init process. + // The disposition for SIGCHLD cannot be SIG_IGN or wait() will only return + // once all of our childs are dead. Since we're init we need to reap childs + // as they come. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = &DoNothingSignalHandler; + CHECK(sigaction(SIGCHLD, &action, NULL) == 0); + + int close_ret; + close_ret = IGNORE_EINTR(close(sync_fds[0])); + DPCHECK(!close_ret); + close_ret = shutdown(sync_fds[1], SHUT_RD); + DPCHECK(!close_ret); + if (post_fork_parent_callback) + post_fork_parent_callback->Run(); + // Tell the child to continue + CHECK(HANDLE_EINTR(send(sync_fds[1], "C", 1, MSG_NOSIGNAL)) == 1); + close_ret = IGNORE_EINTR(close(sync_fds[1])); + DPCHECK(!close_ret); + + for (;;) { + // Loop until we have reaped our one natural child + siginfo_t reaped_child_info; + int wait_ret = + HANDLE_EINTR(waitid(P_ALL, 0, &reaped_child_info, WEXITED)); + if (wait_ret) + _exit(1); + if (reaped_child_info.si_pid == child_pid) { + int exit_code = 0; + // We're done waiting + if (reaped_child_info.si_code == CLD_EXITED) { + exit_code = reaped_child_info.si_status; + } + // Exit with the same exit code as our parent. Exit with 0 if we got + // signaled. + _exit(exit_code); + } + } + } else { + // The child needs to wait for the parent to run the callback to avoid a + // race condition. + int close_ret; + close_ret = IGNORE_EINTR(close(sync_fds[1])); + DPCHECK(!close_ret); + close_ret = shutdown(sync_fds[0], SHUT_WR); + DPCHECK(!close_ret); + char should_continue; + int read_ret = HANDLE_EINTR(read(sync_fds[0], &should_continue, 1)); + close_ret = IGNORE_EINTR(close(sync_fds[0])); + DPCHECK(!close_ret); + if (read_ret == 1) + return true; + else + return false; + } +} + +} // namespace sandbox. diff --git a/sandbox/linux/services/init_process_reaper.h b/sandbox/linux/services/init_process_reaper.h new file mode 100644 index 0000000000..840f6fcda7 --- /dev/null +++ b/sandbox/linux/services/init_process_reaper.h @@ -0,0 +1,25 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_INIT_PROCESS_REAPER_H_ +#define SANDBOX_LINUX_SERVICES_INIT_PROCESS_REAPER_H_ + +#include "base/callback_forward.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// The current process will fork(). The parent will become a process reaper +// like init(1). The child will continue normally (after this function +// returns). +// If not NULL, |post_fork_parent_callback| will run in the parent almost +// immediately after fork(). +// Since this function calls fork(), it's very important that the caller has +// only one thread running. +SANDBOX_EXPORT bool CreateInitProcessReaper( + base::Closure* post_fork_parent_callback); + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SERVICES_INIT_PROCESS_REAPER_H_ diff --git a/sandbox/linux/services/libc_urandom_override.cc b/sandbox/linux/services/libc_urandom_override.cc new file mode 100644 index 0000000000..33bb25d6b1 --- /dev/null +++ b/sandbox/linux/services/libc_urandom_override.cc @@ -0,0 +1,236 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/libc_urandom_override.h" + +#include <dlfcn.h> +#include <pthread.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/rand_util.h" + +// Note: this file is used by the zygote and nacl_helper. + +#if !defined(HAVE_XSTAT) && defined(LIBC_GLIBC) +#define HAVE_XSTAT 1 +#endif + +namespace sandbox { + +static bool g_override_urandom = false; + +// TODO(sergeyu): Currently InitLibcUrandomOverrides() doesn't work properly +// 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 + +void InitLibcUrandomOverrides() { + // Make sure /dev/urandom is open. + base::GetUrandomFD(); + g_override_urandom = true; + +#if !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) + CHECK_EQ(0, pthread_once(&g_libc_file_io_funcs_guard, + InitLibcFileIOFunctions)); +#endif +} + +#if !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) + +static const char kUrandomDevPath[] = "/dev/urandom"; + +typedef FILE* (*FopenFunction)(const char* path, const char* mode); + +static FopenFunction g_libc_fopen = NULL; +static FopenFunction g_libc_fopen64 = NULL; + +#if HAVE_XSTAT +typedef int (*XstatFunction)(int version, const char *path, struct stat *buf); +typedef int (*Xstat64Function)(int version, const char *path, + struct stat64 *buf); + +static XstatFunction g_libc_xstat = NULL; +static Xstat64Function g_libc_xstat64 = NULL; +#else +typedef int (*StatFunction)(const char *path, struct stat *buf); +typedef int (*Stat64Function)(const char *path, struct stat64 *buf); + +static StatFunction g_libc_stat = NULL; +static Stat64Function g_libc_stat64 = NULL; +#endif // HAVE_XSTAT + +// Find the libc's real fopen* and *stat* functions. This should only be +// called once, and should be guarded by g_libc_file_io_funcs_guard. +static void InitLibcFileIOFunctions() { + g_libc_fopen = reinterpret_cast<FopenFunction>( + dlsym(RTLD_NEXT, "fopen")); + g_libc_fopen64 = reinterpret_cast<FopenFunction>( + dlsym(RTLD_NEXT, "fopen64")); + + if (!g_libc_fopen) { + LOG(FATAL) << "Failed to get fopen() from libc."; + } else if (!g_libc_fopen64) { +#if !defined(OS_OPENBSD) && !defined(OS_FREEBSD) + LOG(WARNING) << "Failed to get fopen64() from libc. Using fopen() instead."; +#endif // !defined(OS_OPENBSD) && !defined(OS_FREEBSD) + g_libc_fopen64 = g_libc_fopen; + } + +#if HAVE_XSTAT + g_libc_xstat = reinterpret_cast<XstatFunction>( + dlsym(RTLD_NEXT, "__xstat")); + g_libc_xstat64 = reinterpret_cast<Xstat64Function>( + dlsym(RTLD_NEXT, "__xstat64")); + + if (!g_libc_xstat) { + LOG(FATAL) << "Failed to get __xstat() from libc."; + } + if (!g_libc_xstat64) { + LOG(FATAL) << "Failed to get __xstat64() from libc."; + } +#else + g_libc_stat = reinterpret_cast<StatFunction>( + dlsym(RTLD_NEXT, "stat")); + g_libc_stat64 = reinterpret_cast<Stat64Function>( + dlsym(RTLD_NEXT, "stat64")); + + if (!g_libc_stat) { + LOG(FATAL) << "Failed to get stat() from libc."; + } + if (!g_libc_stat64) { + LOG(FATAL) << "Failed to get stat64() from libc."; + } +#endif // HAVE_XSTAT +} + +// fopen() and fopen64() are intercepted here so that NSS can open +// /dev/urandom to seed its random number generator. NSS is used by +// remoting in the sendbox. + +// fopen() call may be redirected to fopen64() in stdio.h using +// __REDIRECT(), which sets asm name for fopen() to "fopen64". This +// means that we cannot override fopen() directly here. Instead the +// the code below defines fopen_override() function with asm name +// "fopen", so that all references to fopen() will resolve to this +// function. +__attribute__ ((__visibility__("default"))) +FILE* fopen_override(const char* path, const char* mode) __asm__ ("fopen"); + +__attribute__ ((__visibility__("default"))) +FILE* fopen_override(const char* path, const char* mode) { + if (g_override_urandom && strcmp(path, kUrandomDevPath) == 0) { + int fd = HANDLE_EINTR(dup(base::GetUrandomFD())); + if (fd < 0) { + PLOG(ERROR) << "dup() failed."; + return NULL; + } + return fdopen(fd, mode); + } else { + CHECK_EQ(0, pthread_once(&g_libc_file_io_funcs_guard, + InitLibcFileIOFunctions)); + return g_libc_fopen(path, mode); + } +} + +__attribute__ ((__visibility__("default"))) +FILE* fopen64(const char* path, const char* mode) { + if (g_override_urandom && strcmp(path, kUrandomDevPath) == 0) { + int fd = HANDLE_EINTR(dup(base::GetUrandomFD())); + if (fd < 0) { + PLOG(ERROR) << "dup() failed."; + return NULL; + } + return fdopen(fd, mode); + } else { + CHECK_EQ(0, pthread_once(&g_libc_file_io_funcs_guard, + InitLibcFileIOFunctions)); + return g_libc_fopen64(path, mode); + } +} + +// The stat() family of functions are subject to the same problem as +// fopen(), so we have to use the same trick to override them. + +#if HAVE_XSTAT + +__attribute__ ((__visibility__("default"))) +int xstat_override(int version, + const char *path, + struct stat *buf) __asm__ ("__xstat"); + +__attribute__ ((__visibility__("default"))) +int xstat_override(int version, const char *path, struct stat *buf) { + if (g_override_urandom && strcmp(path, kUrandomDevPath) == 0) { + int result = __fxstat(version, base::GetUrandomFD(), buf); + return result; + } else { + CHECK_EQ(0, pthread_once(&g_libc_file_io_funcs_guard, + InitLibcFileIOFunctions)); + return g_libc_xstat(version, path, buf); + } +} + +__attribute__ ((__visibility__("default"))) +int xstat64_override(int version, + const char *path, + struct stat64 *buf) __asm__ ("__xstat64"); + +__attribute__ ((__visibility__("default"))) +int xstat64_override(int version, const char *path, struct stat64 *buf) { + if (g_override_urandom && strcmp(path, kUrandomDevPath) == 0) { + int result = __fxstat64(version, base::GetUrandomFD(), buf); + return result; + } else { + CHECK_EQ(0, pthread_once(&g_libc_file_io_funcs_guard, + InitLibcFileIOFunctions)); + return g_libc_xstat64(version, path, buf); + } +} + +#else + +__attribute__ ((__visibility__("default"))) +int stat_override(const char *path, + struct stat *buf) __asm__ ("stat"); + +__attribute__ ((__visibility__("default"))) +int stat_override(const char *path, struct stat *buf) { + if (g_override_urandom && strcmp(path, kUrandomDevPath) == 0) { + int result = fstat(base::GetUrandomFD(), buf); + return result; + } else { + CHECK_EQ(0, pthread_once(&g_libc_file_io_funcs_guard, + InitLibcFileIOFunctions)); + return g_libc_stat(path, buf); + } +} + +__attribute__ ((__visibility__("default"))) +int stat64_override(const char *path, + struct stat64 *buf) __asm__ ("stat64"); + +__attribute__ ((__visibility__("default"))) +int stat64_override(const char *path, struct stat64 *buf) { + if (g_override_urandom && strcmp(path, kUrandomDevPath) == 0) { + int result = fstat64(base::GetUrandomFD(), buf); + return result; + } else { + CHECK_EQ(0, pthread_once(&g_libc_file_io_funcs_guard, + InitLibcFileIOFunctions)); + return g_libc_stat64(path, buf); + } +} + +#endif // HAVE_XSTAT + +#endif // !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) + +} // namespace content diff --git a/sandbox/linux/services/libc_urandom_override.h b/sandbox/linux/services/libc_urandom_override.h new file mode 100644 index 0000000000..86212f8bc4 --- /dev/null +++ b/sandbox/linux/services/libc_urandom_override.h @@ -0,0 +1,14 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_LIBC_URANDOM_OVERRIDE_H_ +#define SANDBOX_LINUX_SERVICES_LIBC_URANDOM_OVERRIDE_H_ + +namespace sandbox { + +void InitLibcUrandomOverrides(); + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_LIBC_URANDOM_OVERRIDE_H_ diff --git a/sandbox/linux/services/namespace_sandbox.cc b/sandbox/linux/services/namespace_sandbox.cc new file mode 100644 index 0000000000..23796446f3 --- /dev/null +++ b/sandbox/linux/services/namespace_sandbox.cc @@ -0,0 +1,208 @@ +// Copyright 2015 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/namespace_sandbox.h" + +#include <sched.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> +#include <utility> +#include <vector> + +#include "base/command_line.h" +#include "base/environment.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" +#include "base/process/process.h" +#include "sandbox/linux/services/credentials.h" +#include "sandbox/linux/services/namespace_utils.h" + +namespace sandbox { + +namespace { + +const char kSandboxUSERNSEnvironmentVarName[] = "SBX_USER_NS"; +const char kSandboxPIDNSEnvironmentVarName[] = "SBX_PID_NS"; +const char kSandboxNETNSEnvironmentVarName[] = "SBX_NET_NS"; + +#if !defined(OS_NACL_NONSFI) +class WriteUidGidMapDelegate : public base::LaunchOptions::PreExecDelegate { + public: + WriteUidGidMapDelegate() + : uid_(getuid()), + gid_(getgid()), + supports_deny_setgroups_( + NamespaceUtils::KernelSupportsDenySetgroups()) {} + + ~WriteUidGidMapDelegate() override {} + + void RunAsyncSafe() override { + if (supports_deny_setgroups_) { + RAW_CHECK(NamespaceUtils::DenySetgroups()); + } + RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/uid_map", uid_)); + RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/gid_map", gid_)); + } + + private: + const uid_t uid_; + const gid_t gid_; + const bool supports_deny_setgroups_; + DISALLOW_COPY_AND_ASSIGN(WriteUidGidMapDelegate); +}; + +void SetEnvironForNamespaceType(base::EnvironmentMap* environ, + base::NativeEnvironmentString env_var, + bool value) { + // An empty string causes the env var to be unset in the child process. + (*environ)[env_var] = value ? "1" : ""; +} + +// Linux supports up to 64 signals. This should be updated if that ever changes. +int g_signal_exit_codes[64]; + +void TerminationSignalHandler(int sig) { + // Return a special exit code so that the process is detected as terminated by + // a signal. + const size_t sig_idx = static_cast<size_t>(sig); + if (sig_idx < arraysize(g_signal_exit_codes)) { + _exit(g_signal_exit_codes[sig_idx]); + } + + _exit(NamespaceSandbox::kDefaultExitCode); +} +#endif // !defined(OS_NACL_NONSFI) + +} // namespace + +#if !defined(OS_NACL_NONSFI) +// static +base::Process NamespaceSandbox::LaunchProcess( + const base::CommandLine& cmdline, + const base::LaunchOptions& options) { + return LaunchProcess(cmdline.argv(), options); +} + +// static +base::Process NamespaceSandbox::LaunchProcess( + const std::vector<std::string>& argv, + const base::LaunchOptions& options) { + int clone_flags = 0; + int ns_types[] = {CLONE_NEWUSER, CLONE_NEWPID, CLONE_NEWNET}; + for (const int ns_type : ns_types) { + if (NamespaceUtils::KernelSupportsUnprivilegedNamespace(ns_type)) { + clone_flags |= ns_type; + } + } + CHECK(clone_flags & CLONE_NEWUSER); + + // These fields may not be set by the caller. + CHECK(options.pre_exec_delegate == nullptr); + CHECK_EQ(0, options.clone_flags); + + WriteUidGidMapDelegate write_uid_gid_map_delegate; + + base::LaunchOptions launch_options = options; + launch_options.pre_exec_delegate = &write_uid_gid_map_delegate; + launch_options.clone_flags = clone_flags; + + const std::pair<int, const char*> clone_flag_environ[] = { + std::make_pair(CLONE_NEWUSER, kSandboxUSERNSEnvironmentVarName), + std::make_pair(CLONE_NEWPID, kSandboxPIDNSEnvironmentVarName), + std::make_pair(CLONE_NEWNET, kSandboxNETNSEnvironmentVarName), + }; + + base::EnvironmentMap* environ = &launch_options.environ; + for (const auto& entry : clone_flag_environ) { + const int flag = entry.first; + const char* environ_name = entry.second; + SetEnvironForNamespaceType(environ, environ_name, clone_flags & flag); + } + + return base::LaunchProcess(argv, launch_options); +} + +// static +pid_t NamespaceSandbox::ForkInNewPidNamespace(bool drop_capabilities_in_child) { + const pid_t pid = + base::ForkWithFlags(CLONE_NEWPID | SIGCHLD, nullptr, nullptr); + if (pid < 0) { + return pid; + } + + if (pid == 0) { + DCHECK_EQ(1, getpid()); + if (drop_capabilities_in_child) { + // Since we just forked, we are single-threaded, so this should be safe. + CHECK(Credentials::DropAllCapabilitiesOnCurrentThread()); + } + return 0; + } + + return pid; +} + +// static +void NamespaceSandbox::InstallDefaultTerminationSignalHandlers() { + static const int kDefaultTermSignals[] = { + SIGHUP, SIGINT, SIGABRT, SIGQUIT, SIGPIPE, SIGTERM, SIGUSR1, SIGUSR2, + }; + + for (const int sig : kDefaultTermSignals) { + InstallTerminationSignalHandler(sig, kDefaultExitCode); + } +} + +// static +bool NamespaceSandbox::InstallTerminationSignalHandler( + int sig, + int exit_code) { + struct sigaction old_action; + PCHECK(sigaction(sig, nullptr, &old_action) == 0); + + if (old_action.sa_flags & SA_SIGINFO && + old_action.sa_sigaction != nullptr) { + return false; + } else if (old_action.sa_handler != SIG_DFL) { + return false; + } + + const size_t sig_idx = static_cast<size_t>(sig); + CHECK_LT(sig_idx, arraysize(g_signal_exit_codes)); + + DCHECK_GE(exit_code, 0); + DCHECK_LT(exit_code, 256); + + g_signal_exit_codes[sig_idx] = exit_code; + + struct sigaction action = {}; + action.sa_handler = &TerminationSignalHandler; + PCHECK(sigaction(sig, &action, nullptr) == 0); + return true; +} +#endif // !defined(OS_NACL_NONSFI) + +// static +bool NamespaceSandbox::InNewUserNamespace() { + return getenv(kSandboxUSERNSEnvironmentVarName) != nullptr; +} + +// static +bool NamespaceSandbox::InNewPidNamespace() { + return getenv(kSandboxPIDNSEnvironmentVarName) != nullptr; +} + +// static +bool NamespaceSandbox::InNewNetNamespace() { + return getenv(kSandboxNETNSEnvironmentVarName) != nullptr; +} + +} // namespace sandbox diff --git a/sandbox/linux/services/namespace_sandbox.h b/sandbox/linux/services/namespace_sandbox.h new file mode 100644 index 0000000000..80097fb16a --- /dev/null +++ b/sandbox/linux/services/namespace_sandbox.h @@ -0,0 +1,101 @@ +// Copyright 2015 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_NAMESPACE_SANDBOX_H_ +#define SANDBOX_LINUX_SERVICES_NAMESPACE_SANDBOX_H_ + +#include <sys/types.h> + +#include <string> +#include <vector> + +#include "base/command_line.h" +#include "base/macros.h" +#include "base/process/launch.h" +#include "base/process/process.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Helper class for starting a process inside a new user, PID, and network +// namespace. Before using a namespace sandbox, check for namespaces support +// using Credentials::CanCreateProcessInNewUserNS. +// +// A typical use for "A" launching a sandboxed process "B" would be: +// 1. A sets up a command line and launch options for process B. +// 2. A launches B with LaunchProcess. +// 3. B should be prepared to assume the role of init(1). In particular, apart +// from SIGKILL and SIGSTOP, B cannot receive any signal for which it does +// not have an explicit signal handler registered. +// 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(). +// 4. B chroots using Credentials::MoveToNewUserNS() and +// Credentials::DropFileSystemAccess() +// 5. B drops capabilities gained by entering the new user namespace with +// Credentials::DropAllCapabilities(). +class SANDBOX_EXPORT NamespaceSandbox { + public: +#if !defined(OS_NACL_NONSFI) + static const int kDefaultExitCode = 1; + + // Launch a new process inside its own user/PID/network namespaces (depending + // on kernel support). Requires at a minimum that user namespaces are + // supported (use Credentials::CanCreateProcessInNewUserNS to check this). + // + // pre_exec_delegate and clone_flags fields of LaunchOptions should be nullptr + // and 0, respectively, since this function makes a copy of options and + // overrides them. + static base::Process LaunchProcess(const base::CommandLine& cmdline, + const base::LaunchOptions& options); + static base::Process LaunchProcess(const std::vector<std::string>& argv, + const base::LaunchOptions& options); + + // Forks a process in its own PID namespace. The child process is the init + // process inside of the PID namespace, so if the child needs to fork further, + // it should call CreateInitProcessReaper, which turns the init process into a + // reaper process. + // + // Otherwise, the child should setup handlers for signals which should + // terminate the process using InstallDefaultTerminationSignalHandlers or + // InstallTerminationSignalHandler. This works around the fact that init + // processes ignore such signals unless they have an explicit handler set. + // + // This function requries CAP_SYS_ADMIN. If |drop_capabilities_in_child| is + // true, then capabilities are dropped in the child. + static pid_t ForkInNewPidNamespace(bool drop_capabilities_in_child); + + // Installs a signal handler for: + // + // SIGHUP, SIGINT, SIGABRT, SIGQUIT, SIGPIPE, SIGTERM, SIGUSR1, SIGUSR2 + // + // that exits with kDefaultExitCode. These are signals whose default action is + // to terminate the program (apart from SIGILL, SIGFPE, and SIGSEGV, which + // will still terminate the process if e.g. an illegal instruction is + // encountered, etc.). + // + // If any of these already had a signal handler installed, this function will + // not override them. + static void InstallDefaultTerminationSignalHandlers(); + + // Installs a signal handler for |sig| which exits with |exit_code|. If a + // signal handler was already present for |sig|, does nothing and returns + // false. + static bool InstallTerminationSignalHandler(int sig, int exit_code); +#endif // !defined(OS_NACL_NONSFI) + + // Returns whether the namespace sandbox created a new user, PID, and network + // namespace. In particular, InNewUserNamespace should return true iff the + // process was started via this class. + static bool InNewUserNamespace(); + static bool InNewPidNamespace(); + static bool InNewNetNamespace(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(NamespaceSandbox); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_NAMESPACE_SANDBOX_H_ diff --git a/sandbox/linux/services/namespace_sandbox_unittest.cc b/sandbox/linux/services/namespace_sandbox_unittest.cc new file mode 100644 index 0000000000..547ef6728c --- /dev/null +++ b/sandbox/linux/services/namespace_sandbox_unittest.cc @@ -0,0 +1,217 @@ +// Copyright 2015 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/namespace_sandbox.h" + +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <string> +#include <utility> + +#include "base/command_line.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/process/launch.h" +#include "base/process/process.h" +#include "base/test/multiprocess_test.h" +#include "sandbox/linux/services/credentials.h" +#include "sandbox/linux/services/namespace_utils.h" +#include "sandbox/linux/services/proc_util.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +namespace sandbox { + +namespace { + +bool RootDirectoryIsEmpty() { + base::FilePath root("/"); + int file_type = + base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES; + base::FileEnumerator enumerator_before(root, false, file_type); + return enumerator_before.Next().empty(); +} + +class NamespaceSandboxTest : public base::MultiProcessTest { + public: + void TestProc(const std::string& procname) { + if (!Credentials::CanCreateProcessInNewUserNS()) { + return; + } + + base::FileHandleMappingVector fds_to_remap = { + std::make_pair(STDOUT_FILENO, STDOUT_FILENO), + std::make_pair(STDERR_FILENO, STDERR_FILENO), + }; + base::LaunchOptions launch_options; + launch_options.fds_to_remap = &fds_to_remap; + + base::Process process = + NamespaceSandbox::LaunchProcess(MakeCmdLine(procname), launch_options); + ASSERT_TRUE(process.IsValid()); + + const int kDummyExitCode = 42; + int exit_code = kDummyExitCode; + EXPECT_TRUE(process.WaitForExit(&exit_code)); + EXPECT_EQ(0, exit_code); + } +}; + +MULTIPROCESS_TEST_MAIN(SimpleChildProcess) { + scoped_ptr<base::Environment> env(base::Environment::Create()); + bool in_user_ns = NamespaceSandbox::InNewUserNamespace(); + bool in_pid_ns = NamespaceSandbox::InNewPidNamespace(); + bool in_net_ns = NamespaceSandbox::InNewNetNamespace(); + CHECK(in_user_ns); + CHECK_EQ(in_pid_ns, + NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWPID)); + CHECK_EQ(in_net_ns, + NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWNET)); + if (in_pid_ns) { + CHECK_EQ(1, getpid()); + } + return 0; +} + +TEST_F(NamespaceSandboxTest, BasicUsage) { + TestProc("SimpleChildProcess"); +} + +MULTIPROCESS_TEST_MAIN(ChrootMe) { + CHECK(!RootDirectoryIsEmpty()); + CHECK(sandbox::Credentials::MoveToNewUserNS()); + CHECK(sandbox::Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get())); + CHECK(RootDirectoryIsEmpty()); + return 0; +} + +// Temporarily disabled on ASAN due to crbug.com/451603. +TEST_F(NamespaceSandboxTest, DISABLE_ON_ASAN(ChrootAndDropCapabilities)) { + TestProc("ChrootMe"); +} + +MULTIPROCESS_TEST_MAIN(NestedNamespaceSandbox) { + base::FileHandleMappingVector fds_to_remap = { + std::make_pair(STDOUT_FILENO, STDOUT_FILENO), + std::make_pair(STDERR_FILENO, STDERR_FILENO), + }; + base::LaunchOptions launch_options; + launch_options.fds_to_remap = &fds_to_remap; + base::Process process = NamespaceSandbox::LaunchProcess( + base::CommandLine(base::FilePath("/bin/true")), launch_options); + CHECK(process.IsValid()); + + const int kDummyExitCode = 42; + int exit_code = kDummyExitCode; + CHECK(process.WaitForExit(&exit_code)); + CHECK_EQ(0, exit_code); + return 0; +} + +TEST_F(NamespaceSandboxTest, NestedNamespaceSandbox) { + TestProc("NestedNamespaceSandbox"); +} + +const int kNormalExitCode = 0; +const int kSignalTerminationExitCode = 255; + +// Ensure that CHECK(false) is distinguishable from _exit(kNormalExitCode). +// Allowing noise since CHECK(false) will write a stack trace to stderr. +SANDBOX_TEST_ALLOW_NOISE(ForkInNewPidNamespace, CheckDoesNotReturnZero) { + if (!Credentials::CanCreateProcessInNewUserNS()) { + return; + } + + CHECK(sandbox::Credentials::MoveToNewUserNS()); + const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace( + /*drop_capabilities_in_child=*/true); + CHECK_GE(pid, 0); + + if (pid == 0) { + CHECK(false); + _exit(kNormalExitCode); + } + + int status; + PCHECK(waitpid(pid, &status, 0) == pid); + if (WIFEXITED(status)) { + CHECK_NE(kNormalExitCode, WEXITSTATUS(status)); + } +} + +SANDBOX_TEST(ForkInNewPidNamespace, BasicUsage) { + if (!Credentials::CanCreateProcessInNewUserNS()) { + return; + } + + CHECK(sandbox::Credentials::MoveToNewUserNS()); + const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace( + /*drop_capabilities_in_child=*/true); + CHECK_GE(pid, 0); + + if (pid == 0) { + CHECK_EQ(1, getpid()); + CHECK(!Credentials::HasAnyCapability()); + _exit(kNormalExitCode); + } + + int status; + PCHECK(waitpid(pid, &status, 0) == pid); + CHECK(WIFEXITED(status)); + CHECK_EQ(kNormalExitCode, WEXITSTATUS(status)); +} + +SANDBOX_TEST(ForkInNewPidNamespace, ExitWithSignal) { + if (!Credentials::CanCreateProcessInNewUserNS()) { + return; + } + + CHECK(sandbox::Credentials::MoveToNewUserNS()); + const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace( + /*drop_capabilities_in_child=*/true); + CHECK_GE(pid, 0); + + if (pid == 0) { + CHECK_EQ(1, getpid()); + CHECK(!Credentials::HasAnyCapability()); + CHECK(NamespaceSandbox::InstallTerminationSignalHandler( + SIGTERM, kSignalTerminationExitCode)); + while (true) { + raise(SIGTERM); + } + } + + int status; + PCHECK(waitpid(pid, &status, 0) == pid); + CHECK(WIFEXITED(status)); + CHECK_EQ(kSignalTerminationExitCode, WEXITSTATUS(status)); +} + +volatile sig_atomic_t signal_handler_called; +void ExitSuccessfully(int sig) { + signal_handler_called = 1; +} + +SANDBOX_TEST(InstallTerminationSignalHandler, DoesNotOverrideExistingHandlers) { + struct sigaction action = {}; + action.sa_handler = &ExitSuccessfully; + PCHECK(sigaction(SIGUSR1, &action, nullptr) == 0); + + NamespaceSandbox::InstallDefaultTerminationSignalHandlers(); + CHECK(!NamespaceSandbox::InstallTerminationSignalHandler( + SIGUSR1, kSignalTerminationExitCode)); + + raise(SIGUSR1); + CHECK_EQ(1, signal_handler_called); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/services/namespace_utils.cc b/sandbox/linux/services/namespace_utils.cc new file mode 100644 index 0000000000..82a544453f --- /dev/null +++ b/sandbox/linux/services/namespace_utils.cc @@ -0,0 +1,117 @@ +// Copyright 2015 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/namespace_utils.h" + +#include <fcntl.h> +#include <sched.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <string> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" +#include "base/strings/safe_sprintf.h" +#include "base/third_party/valgrind/valgrind.h" + +namespace sandbox { + +namespace { +bool IsRunningOnValgrind() { + return RUNNING_ON_VALGRIND; +} + +const char kProcSelfSetgroups[] = "/proc/self/setgroups"; +} // namespace + +// static +bool NamespaceUtils::WriteToIdMapFile(const char* map_file, generic_id_t id) { + // This function needs to be async-signal-safe, as it may be called in between + // fork and exec. + + int fd = HANDLE_EINTR(open(map_file, O_WRONLY)); + if (fd == -1) { + return false; + } + + const generic_id_t inside_id = id; + const generic_id_t outside_id = id; + + char mapping[64]; + const ssize_t len = + base::strings::SafeSPrintf(mapping, "%d %d 1\n", inside_id, outside_id); + const ssize_t rc = HANDLE_EINTR(write(fd, mapping, len)); + RAW_CHECK(IGNORE_EINTR(close(fd)) == 0); + return rc == len; +} + +// static +bool NamespaceUtils::KernelSupportsUnprivilegedNamespace(int type) { + // Valgrind will let clone(2) pass-through, but doesn't support unshare(), + // so always consider namespaces unsupported there. + if (IsRunningOnValgrind()) { + return false; + } + + // As of Linux 3.8, /proc/self/ns/* files exist for all namespace types. Since + // user namespaces were added in 3.8, it is OK to rely on the existence of + // /proc/self/ns/*. + if (!base::PathExists(base::FilePath("/proc/self/ns/user"))) { + return false; + } + + const char* path; + switch (type) { + case CLONE_NEWUSER: + return true; + case CLONE_NEWIPC: + path = "/proc/self/ns/ipc"; + break; + case CLONE_NEWNET: + path = "/proc/self/ns/net"; + break; + case CLONE_NEWNS: + path = "/proc/self/ns/mnt"; + break; + case CLONE_NEWPID: + path = "/proc/self/ns/pid"; + break; + case CLONE_NEWUTS: + path = "/proc/self/ns/uts"; + break; + default: + NOTREACHED(); + return false; + } + + return base::PathExists(base::FilePath(path)); +} + +// static +bool NamespaceUtils::KernelSupportsDenySetgroups() { + return base::PathExists(base::FilePath(kProcSelfSetgroups)); +} + +// static +bool NamespaceUtils::DenySetgroups() { + // This function needs to be async-signal-safe. + int fd = HANDLE_EINTR(open(kProcSelfSetgroups, O_WRONLY)); + if (fd == -1) { + return false; + } + + static const char kDeny[] = "deny"; + const ssize_t len = sizeof(kDeny) - 1; + const ssize_t rc = HANDLE_EINTR(write(fd, kDeny, len)); + RAW_CHECK(IGNORE_EINTR(close(fd)) == 0); + return rc == len; +} + +} // namespace sandbox diff --git a/sandbox/linux/services/namespace_utils.h b/sandbox/linux/services/namespace_utils.h new file mode 100644 index 0000000000..f3c88a9452 --- /dev/null +++ b/sandbox/linux/services/namespace_utils.h @@ -0,0 +1,53 @@ +// Copyright 2015 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_NAMESPACE_UTILS_H_ +#define SANDBOX_LINUX_SERVICES_NAMESPACE_UTILS_H_ + +#include <sys/types.h> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/template_util.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Utility functions for using Linux namepaces. +class SANDBOX_EXPORT NamespaceUtils { + public: + COMPILE_ASSERT((base::is_same<uid_t, gid_t>::value), UidAndGidAreSameType); + // generic_id_t can be used for either uid_t or gid_t. + typedef uid_t generic_id_t; + + // Write a uid or gid mapping from |id| to |id| in |map_file|. This function + // is async-signal-safe. + static bool WriteToIdMapFile(const char* map_file, + generic_id_t id) WARN_UNUSED_RESULT; + + // Returns true if unprivileged namespaces of type |type| is supported + // (meaning that both CLONE_NEWUSER and type are are supported). |type| must + // be one of CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWPID, + // CLONE_NEWUSER, or CLONE_NEWUTS. This relies on access to /proc, so it will + // not work from within a sandbox. + static bool KernelSupportsUnprivilegedNamespace(int type); + + // Returns true if the kernel supports denying setgroups in a user namespace. + // On kernels where this is supported, DenySetgroups must be called before a + // gid mapping can be added. + static bool KernelSupportsDenySetgroups(); + + // Disables setgroups() within the current user namespace. On Linux 3.18.2 and + // later, this is required in order to write to /proc/self/gid_map without + // having CAP_SETGID. Callers can determine whether is this needed with + // KernelSupportsDenySetgroups. This function is async-signal-safe. + static bool DenySetgroups() WARN_UNUSED_RESULT; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(NamespaceUtils); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_NAMESPACE_UTILS_H_ diff --git a/sandbox/linux/services/namespace_utils_unittest.cc b/sandbox/linux/services/namespace_utils_unittest.cc new file mode 100644 index 0000000000..41ed7e89a6 --- /dev/null +++ b/sandbox/linux/services/namespace_utils_unittest.cc @@ -0,0 +1,72 @@ +// Copyright 2015 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/namespace_utils.h" + +#include <errno.h> +#include <sched.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" +#include "sandbox/linux/services/credentials.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +SANDBOX_TEST(NamespaceUtils, KernelSupportsUnprivilegedNamespace) { + const bool can_create_user_ns = Credentials::CanCreateProcessInNewUserNS(); + const bool supports_user_ns = + NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWUSER); + // can_create_user_ns implies supports_user_ns, but the converse is not + // necessarily true, as creating a user namespace can fail for various + // reasons. + if (can_create_user_ns) { + SANDBOX_ASSERT(supports_user_ns); + } +} + +SANDBOX_TEST(NamespaceUtils, WriteToIdMapFile) { + if (!Credentials::CanCreateProcessInNewUserNS()) { + return; + } + + const uid_t uid = getuid(); + const gid_t gid = getgid(); + + const bool supports_deny_setgroups = + NamespaceUtils::KernelSupportsDenySetgroups(); + + const pid_t pid = + base::ForkWithFlags(CLONE_NEWUSER | SIGCHLD, nullptr, nullptr); + ASSERT_NE(-1, pid); + if (pid == 0) { + if (supports_deny_setgroups) { + RAW_CHECK(NamespaceUtils::DenySetgroups()); + } + + RAW_CHECK(getuid() != uid); + RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/uid_map", uid)); + RAW_CHECK(getuid() == uid); + + RAW_CHECK(getgid() != gid); + RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/gid_map", gid)); + RAW_CHECK(getgid() == gid); + + _exit(0); + } + + int status = 42; + SANDBOX_ASSERT_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0))); + SANDBOX_ASSERT_EQ(0, status); +} + +} // namespace. + +} // namespace sandbox. diff --git a/sandbox/linux/services/proc_util.cc b/sandbox/linux/services/proc_util.cc new file mode 100644 index 0000000000..d3f755f9a1 --- /dev/null +++ b/sandbox/linux/services/proc_util.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/proc_util.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/string_number_conversions.h" + +namespace sandbox { +namespace { + +struct DIRCloser { + void operator()(DIR* d) const { + DCHECK(d); + PCHECK(0 == closedir(d)); + } +}; + +typedef scoped_ptr<DIR, DIRCloser> ScopedDIR; + +base::ScopedFD OpenDirectory(const char* path) { + DCHECK(path); + base::ScopedFD directory_fd( + HANDLE_EINTR(open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC))); + PCHECK(directory_fd.is_valid()); + return directory_fd.Pass(); +} + +} // namespace + +int ProcUtil::CountOpenFds(int proc_fd) { + DCHECK_LE(0, proc_fd); + int proc_self_fd = HANDLE_EINTR( + openat(proc_fd, "self/fd/", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); + 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 ProcUtil::HasOpenDirectory(int proc_fd) { + DCHECK_LE(0, proc_fd); + int proc_self_fd = + openat(proc_fd, "self/fd/", O_DIRECTORY | O_RDONLY | O_CLOEXEC); + + 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); + + 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; + } + + struct stat s; + // It's OK to use proc_self_fd here, fstatat won't modify it. + CHECK(fstatat(proc_self_fd, e.d_name, &s, 0) == 0); + if (S_ISDIR(s.st_mode)) { + return true; + } + } + + // No open unmanaged directories found. + return false; +} + +bool ProcUtil::HasOpenDirectory() { + base::ScopedFD proc_fd( + HANDLE_EINTR(open("/proc/", O_DIRECTORY | O_RDONLY | O_CLOEXEC))); + return HasOpenDirectory(proc_fd.get()); +} + +//static +base::ScopedFD ProcUtil::OpenProc() { + return OpenDirectory("/proc/"); +} + +} // namespace sandbox diff --git a/sandbox/linux/services/proc_util.h b/sandbox/linux/services/proc_util.h new file mode 100644 index 0000000000..bc14c5ef2a --- /dev/null +++ b/sandbox/linux/services/proc_util.h @@ -0,0 +1,42 @@ +// 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_PROC_UTIL_H_ +#define SANDBOX_LINUX_SERVICES_PROC_UTIL_H_ + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +class SANDBOX_EXPORT ProcUtil { + public: + // Returns the number of file descriptors in the current process's FD + // table, excluding |proc_fd|, which should be a file descriptor for + // /proc/. + static 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 + // DropFileSystemAccess(). + // Sometimes it's useful to call HasOpenDirectory() after file system access + // has been dropped. In this case, |proc_fd| should be a file descriptor to + // /proc/. The file descriptor in |proc_fd| will be ignored by + // HasOpenDirectory() and remains owned by the caller. It is very important + // for the caller to close it. + static bool HasOpenDirectory(int proc_fd) WARN_UNUSED_RESULT; + static bool HasOpenDirectory() WARN_UNUSED_RESULT; + + // Open /proc/ or crash if not possible. + static base::ScopedFD OpenProc(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(ProcUtil); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_PROC_UTIL_H_ diff --git a/sandbox/linux/services/proc_util_unittest.cc b/sandbox/linux/services/proc_util_unittest.cc new file mode 100644 index 0000000000..bf25151956 --- /dev/null +++ b/sandbox/linux/services/proc_util_unittest.cc @@ -0,0 +1,62 @@ +// 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/proc_util.h" + +#include <fcntl.h> +#include <unistd.h> + +#include "base/files/scoped_file.h" +#include "base/posix/eintr_wrapper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +TEST(ProcUtil, CountOpenFds) { + base::ScopedFD proc_fd(open("/proc/", O_RDONLY | O_DIRECTORY)); + ASSERT_TRUE(proc_fd.is_valid()); + int fd_count = ProcUtil::CountOpenFds(proc_fd.get()); + int fd = open("/dev/null", O_RDONLY); + ASSERT_LE(0, fd); + EXPECT_EQ(fd_count + 1, ProcUtil::CountOpenFds(proc_fd.get())); + ASSERT_EQ(0, IGNORE_EINTR(close(fd))); + EXPECT_EQ(fd_count, ProcUtil::CountOpenFds(proc_fd.get())); +} + +TEST(ProcUtil, HasOpenDirectory) { + // No open directory should exist at startup. + EXPECT_FALSE(ProcUtil::HasOpenDirectory()); + { + // Have a "/proc" file descriptor around. + int proc_fd = open("/proc/", O_RDONLY | O_DIRECTORY); + base::ScopedFD proc_fd_closer(proc_fd); + EXPECT_TRUE(ProcUtil::HasOpenDirectory()); + } + EXPECT_FALSE(ProcUtil::HasOpenDirectory()); +} + +TEST(ProcUtil, HasOpenDirectoryWithFD) { + int proc_fd = open("/proc/", O_RDONLY | O_DIRECTORY); + base::ScopedFD proc_fd_closer(proc_fd); + ASSERT_LE(0, proc_fd); + + // Don't pass |proc_fd|, an open directory (proc_fd) should + // be detected. + EXPECT_TRUE(ProcUtil::HasOpenDirectory()); + // Pass |proc_fd| and no open directory should be detected. + EXPECT_FALSE(ProcUtil::HasOpenDirectory(proc_fd)); + + { + // Have a directory file descriptor around. + int open_directory_fd = open("/proc/self/", O_RDONLY | O_DIRECTORY); + base::ScopedFD open_directory_fd_closer(open_directory_fd); + EXPECT_TRUE(ProcUtil::HasOpenDirectory(proc_fd)); + } + + // The "/proc/" file descriptor should now be closed, |proc_fd| is the + // only directory file descriptor open. + EXPECT_FALSE(ProcUtil::HasOpenDirectory(proc_fd)); +} + +} // namespace sandbox diff --git a/sandbox/linux/services/resource_limits.cc b/sandbox/linux/services/resource_limits.cc new file mode 100644 index 0000000000..1ec11295d1 --- /dev/null +++ b/sandbox/linux/services/resource_limits.cc @@ -0,0 +1,26 @@ +// Copyright 2015 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/resource_limits.h" + +#include <sys/resource.h> +#include <sys/time.h> + +#include <algorithm> + +namespace sandbox { + +// static +bool ResourceLimits::Lower(int resource, rlim_t limit) { + struct rlimit old_rlimit; + if (getrlimit(resource, &old_rlimit)) + return false; + // Make sure we don't raise the existing limit. + const struct rlimit new_rlimit = {std::min(old_rlimit.rlim_cur, limit), + std::min(old_rlimit.rlim_max, limit)}; + int rc = setrlimit(resource, &new_rlimit); + return rc == 0; +} + +} // namespace sandbox diff --git a/sandbox/linux/services/resource_limits.h b/sandbox/linux/services/resource_limits.h new file mode 100644 index 0000000000..3464dab679 --- /dev/null +++ b/sandbox/linux/services/resource_limits.h @@ -0,0 +1,29 @@ +// Copyright 2015 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_RESOURCE_LIMITS_H_ +#define SANDBOX_LINUX_SERVICES_RESOURCE_LIMITS_H_ + +#include <sys/resource.h> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This class provides a small wrapper around setrlimit(). +class SANDBOX_EXPORT ResourceLimits { + public: + // Lower the soft and hard limit of |resource| to |limit|. If the current + // limit is lower than |limit|, keep it. + static bool Lower(int resource, rlim_t limit) WARN_UNUSED_RESULT; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(ResourceLimits); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_RESOURCE_LIMITS_H_ diff --git a/sandbox/linux/services/resource_limits_unittests.cc b/sandbox/linux/services/resource_limits_unittests.cc new file mode 100644 index 0000000000..910c740f7b --- /dev/null +++ b/sandbox/linux/services/resource_limits_unittests.cc @@ -0,0 +1,43 @@ +// Copyright 2015 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/resource_limits.h" + +#include <errno.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <unistd.h> + +#include "base/logging.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +// Fails on Android: crbug.com/459158 +#if !defined(OS_ANDROID) +#define MAYBE_NoFork DISABLE_ON_ASAN(NoFork) +#else +#define MAYBE_NoFork DISABLED_NoFork +#endif // OS_ANDROID + +// Not being able to fork breaks LeakSanitizer, so disable on +// all ASAN builds. +SANDBOX_TEST(ResourceLimits, MAYBE_NoFork) { + // Make sure that fork will fail with EAGAIN. + SANDBOX_ASSERT(ResourceLimits::Lower(RLIMIT_NPROC, 0)); + errno = 0; + pid_t pid = fork(); + // Reap any child if fork succeeded. + TestUtils::HandlePostForkReturn(pid); + SANDBOX_ASSERT_EQ(-1, pid); + CHECK_EQ(EAGAIN, errno); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/services/scoped_process.cc b/sandbox/linux/services/scoped_process.cc new file mode 100644 index 0000000000..65af4873a4 --- /dev/null +++ b/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/callback.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "sandbox/linux/services/syscall_wrappers.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()); +#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. + pid_t pid = sys_getpid(); + return pid == process_id_; +} + +} // namespace sandbox diff --git a/sandbox/linux/services/scoped_process.h b/sandbox/linux/services/scoped_process.h new file mode 100644 index 0000000000..bddbd5529b --- /dev/null +++ b/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/callback_forward.h" +#include "base/macros.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/sandbox/linux/services/scoped_process_unittest.cc b/sandbox/linux/services/scoped_process_unittest.cc new file mode 100644 index 0000000000..8bd2847997 --- /dev/null +++ b/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 "sandbox/linux/services/scoped_process.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/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/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/sandbox/linux/services/syscall_wrappers.cc b/sandbox/linux/services/syscall_wrappers.cc new file mode 100644 index 0000000000..264eb6842d --- /dev/null +++ b/sandbox/linux/services/syscall_wrappers.cc @@ -0,0 +1,246 @@ +// 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/syscall_wrappers.h" + +#include <pthread.h> +#include <sched.h> +#include <setjmp.h> +#include <sys/resource.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <cstring> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/third_party/valgrind/valgrind.h" +#include "build/build_config.h" +#include "sandbox/linux/system_headers/capability.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { + +pid_t sys_getpid(void) { + return syscall(__NR_getpid); +} + +pid_t sys_gettid(void) { + return syscall(__NR_gettid); +} + +long sys_clone(unsigned long flags, + decltype(nullptr) child_stack, + pid_t* ptid, + pid_t* ctid, + decltype(nullptr) tls) { + const bool clone_tls_used = flags & CLONE_SETTLS; + const bool invalid_ctid = + (flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) && !ctid; + const bool invalid_ptid = (flags & CLONE_PARENT_SETTID) && !ptid; + + // We do not support CLONE_VM. + const bool clone_vm_used = flags & CLONE_VM; + if (clone_tls_used || invalid_ctid || invalid_ptid || clone_vm_used) { + RAW_LOG(FATAL, "Invalid usage of sys_clone"); + } + + if (ptid) MSAN_UNPOISON(ptid, sizeof(*ptid)); + if (ctid) MSAN_UNPOISON(ctid, sizeof(*ctid)); + // See kernel/fork.c in Linux. There is different ordering of sys_clone + // parameters depending on CONFIG_CLONE_BACKWARDS* configuration options. +#if defined(ARCH_CPU_X86_64) + return syscall(__NR_clone, flags, child_stack, ptid, ctid, tls); +#elif defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS_FAMILY) || defined(ARCH_CPU_MIPS64_FAMILY) + // CONFIG_CLONE_BACKWARDS defined. + return syscall(__NR_clone, flags, child_stack, ptid, tls, ctid); +#endif +} + +long sys_clone(unsigned long flags) { + return sys_clone(flags, nullptr, nullptr, nullptr, nullptr); +} + +void sys_exit_group(int status) { + syscall(__NR_exit_group, status); +} + +int sys_seccomp(unsigned int operation, + unsigned int flags, + const struct sock_fprog* args) { + return syscall(__NR_seccomp, operation, flags, args); +} + +int sys_prlimit64(pid_t pid, + int resource, + const struct rlimit64* new_limit, + struct rlimit64* old_limit) { + int res = syscall(__NR_prlimit64, pid, resource, new_limit, old_limit); + if (res == 0 && old_limit) MSAN_UNPOISON(old_limit, sizeof(*old_limit)); + return res; +} + +int sys_capget(cap_hdr* hdrp, cap_data* datap) { + int res = syscall(__NR_capget, hdrp, datap); + if (res == 0) { + if (hdrp) MSAN_UNPOISON(hdrp, sizeof(*hdrp)); + if (datap) MSAN_UNPOISON(datap, sizeof(*datap)); + } + return res; +} + +int sys_capset(cap_hdr* hdrp, const cap_data* datap) { + return syscall(__NR_capset, hdrp, datap); +} + +int sys_getresuid(uid_t* ruid, uid_t* euid, uid_t* suid) { + int res; +#if defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARMEL) + // On 32-bit x86 or 32-bit arm, getresuid supports 16bit values only. + // Use getresuid32 instead. + res = syscall(__NR_getresuid32, ruid, euid, suid); +#else + res = syscall(__NR_getresuid, ruid, euid, suid); +#endif + if (res == 0) { + if (ruid) MSAN_UNPOISON(ruid, sizeof(*ruid)); + if (euid) MSAN_UNPOISON(euid, sizeof(*euid)); + if (suid) MSAN_UNPOISON(suid, sizeof(*suid)); + } + return res; +} + +int sys_getresgid(gid_t* rgid, gid_t* egid, gid_t* sgid) { + int res; +#if defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARMEL) + // On 32-bit x86 or 32-bit arm, getresgid supports 16bit values only. + // Use getresgid32 instead. + res = syscall(__NR_getresgid32, rgid, egid, sgid); +#else + res = syscall(__NR_getresgid, rgid, egid, sgid); +#endif + if (res == 0) { + if (rgid) MSAN_UNPOISON(rgid, sizeof(*rgid)); + if (egid) MSAN_UNPOISON(egid, sizeof(*egid)); + if (sgid) MSAN_UNPOISON(sgid, sizeof(*sgid)); + } + return res; +} + +int sys_chroot(const char* path) { + return syscall(__NR_chroot, path); +} + +int sys_unshare(int flags) { + return syscall(__NR_unshare, flags); +} + +int sys_sigprocmask(int how, const sigset_t* set, decltype(nullptr) oldset) { + // In some toolchain (in particular Android and PNaCl toolchain), + // sigset_t is 32 bits, but Linux ABI requires 64 bits. + uint64_t linux_value = 0; + std::memcpy(&linux_value, set, std::min(sizeof(sigset_t), sizeof(uint64_t))); + return syscall(__NR_rt_sigprocmask, how, &linux_value, nullptr, + sizeof(linux_value)); +} + +#if (defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \ + (defined(ARCH_CPU_X86_64) && !defined(__clang__))) && \ + !defined(OS_NACL_NONSFI) +// If MEMORY_SANITIZER or THREAD_SANITIZER is enabled, it is necessary to call +// sigaction() here, rather than the direct syscall (sys_sigaction() defined +// by ourselves). +// It is because, if MEMORY_SANITIZER or THREAD_SANITIZER is enabled, sigaction +// is wrapped, and |act->sa_handler| is injected in order to unpoisonize the +// memory passed via callback's arguments for MEMORY_SANITIZER, or handle +// signals to check thread consistency for THREAD_SANITIZER. Please see +// msan_interceptors.cc and tsan_interceptors.cc for more details. +// So, specifically, if MEMORY_SANITIZER is enabled while the direct syscall is +// used, as MEMORY_SANITIZER does not know about it, sigaction() invocation in +// other places would be broken (in more precise, returned |oldact| would have +// a broken |sa_handler| callback). +// Practically, it would break NaCl's signal handler installation. +// cf) native_client/src/trusted/service_runtime/linux/nacl_signal.c. +// As for THREAD_SANITIZER, the intercepted signal handlers are processed more +// in other libc functions' interceptors (such as for raise()), so that it +// would not work properly. +// +// Also on x86_64 architecture, we need naked function for rt_sigreturn. +// However, there is no simple way to define it with GCC. Note that the body +// of function is actually very small (only two instructions), but we need to +// define much debug information in addition, otherwise backtrace() used by +// base::StackTrace would not work so that some tests would fail. +// +// When this is built with PNaCl toolchain, we should always use sys_sigaction +// below, because sigaction() provided by the toolchain is incompatible with +// Linux's ABI. So, otherwise, it would just fail. Note that it is not +// necessary to think about sigaction() invocation in other places even with +// MEMORY_SANITIZER or THREAD_SANITIZER, because it would just fail there. +int sys_sigaction(int signum, + const struct sigaction* act, + struct sigaction* oldact) { + return sigaction(signum, act, oldact); +} +#else +// struct sigaction is different ABI from the Linux's. +struct KernelSigAction { + void (*kernel_handler)(int); + uint32_t sa_flags; + void (*sa_restorer)(void); + uint64_t sa_mask; +}; + +// On X86_64 arch, it is necessary to set sa_restorer always. +#if defined(ARCH_CPU_X86_64) +#if !defined(SA_RESTORER) +#define SA_RESTORER 0x04000000 +#endif + +// rt_sigreturn is a special system call that interacts with the user land +// stack. Thus, here prologue must not be created, which implies syscall() +// does not work properly, too. Note that rt_sigreturn will never return. +static __attribute__((naked)) void sys_rt_sigreturn() { + // Just invoke rt_sigreturn system call. + asm volatile ("syscall\n" + :: "a"(__NR_rt_sigreturn)); +} +#endif + +int sys_sigaction(int signum, + const struct sigaction* act, + struct sigaction* oldact) { + KernelSigAction kernel_act = {}; + if (act) { + kernel_act.kernel_handler = act->sa_handler; + std::memcpy(&kernel_act.sa_mask, &act->sa_mask, + std::min(sizeof(kernel_act.sa_mask), sizeof(act->sa_mask))); + kernel_act.sa_flags = act->sa_flags; + +#if defined(ARCH_CPU_X86_64) + if (!(kernel_act.sa_flags & SA_RESTORER)) { + kernel_act.sa_flags |= SA_RESTORER; + kernel_act.sa_restorer = sys_rt_sigreturn; + } +#endif + } + + KernelSigAction kernel_oldact = {}; + int result = syscall(__NR_rt_sigaction, signum, act ? &kernel_act : nullptr, + oldact ? &kernel_oldact : nullptr, sizeof(uint64_t)); + if (result == 0 && oldact) { + oldact->sa_handler = kernel_oldact.kernel_handler; + sigemptyset(&oldact->sa_mask); + std::memcpy(&oldact->sa_mask, &kernel_oldact.sa_mask, + std::min(sizeof(kernel_act.sa_mask), sizeof(act->sa_mask))); + oldact->sa_flags = kernel_oldact.sa_flags; + } + return result; +} + +#endif // defined(MEMORY_SANITIZER) + +} // namespace sandbox diff --git a/sandbox/linux/services/syscall_wrappers.h b/sandbox/linux/services/syscall_wrappers.h new file mode 100644 index 0000000000..581425a367 --- /dev/null +++ b/sandbox/linux/services/syscall_wrappers.h @@ -0,0 +1,83 @@ +// 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_SYSCALL_WRAPPERS_H_ +#define SANDBOX_LINUX_SERVICES_SYSCALL_WRAPPERS_H_ + +#include <signal.h> +#include <stdint.h> +#include <sys/types.h> + +#include "sandbox/sandbox_export.h" + +struct sock_fprog; +struct rlimit64; +struct cap_hdr; +struct cap_data; + +namespace sandbox { + +// Provide direct system call wrappers for a few common system calls. +// These are guaranteed to perform a system call and do not rely on things such +// as caching the current pid (c.f. getpid()) unless otherwise specified. + +SANDBOX_EXPORT pid_t sys_getpid(void); + +SANDBOX_EXPORT pid_t sys_gettid(void); + +SANDBOX_EXPORT long sys_clone(unsigned long flags); + +// |regs| is not supported and must be passed as nullptr. |child_stack| must be +// nullptr, since otherwise this function cannot safely return. As a +// consequence, this function does not support CLONE_VM. +SANDBOX_EXPORT long sys_clone(unsigned long flags, + decltype(nullptr) child_stack, + pid_t* ptid, + pid_t* ctid, + decltype(nullptr) regs); + +SANDBOX_EXPORT void sys_exit_group(int status); + +// The official system call takes |args| as void* (in order to be extensible), +// but add more typing for the cases that are currently used. +SANDBOX_EXPORT int sys_seccomp(unsigned int operation, + unsigned int flags, + const struct sock_fprog* args); + +// Some libcs do not expose a prlimit64 wrapper. +SANDBOX_EXPORT int sys_prlimit64(pid_t pid, + int resource, + const struct rlimit64* new_limit, + struct rlimit64* old_limit); + +// Some libcs do not expose capget/capset wrappers. We want to use these +// directly in order to avoid pulling in libcap2. +SANDBOX_EXPORT int sys_capget(struct cap_hdr* hdrp, struct cap_data* datap); +SANDBOX_EXPORT int sys_capset(struct cap_hdr* hdrp, + const struct cap_data* datap); + +// Some libcs do not expose getresuid/getresgid wrappers. +SANDBOX_EXPORT int sys_getresuid(uid_t* ruid, uid_t* euid, uid_t* suid); +SANDBOX_EXPORT int sys_getresgid(gid_t* rgid, gid_t* egid, gid_t* sgid); + +// Some libcs do not expose a chroot wrapper. +SANDBOX_EXPORT int sys_chroot(const char* path); + +// Some libcs do not expose a unshare wrapper. +SANDBOX_EXPORT int sys_unshare(int flags); + +// Some libcs do not expose a sigprocmask. Note that oldset must be a nullptr, +// because of some ABI gap between toolchain's and Linux's. +SANDBOX_EXPORT int sys_sigprocmask(int how, + const sigset_t* set, + decltype(nullptr) oldset); + +// Some libcs do not expose a sigaction(). +SANDBOX_EXPORT int sys_sigaction(int signum, + const struct sigaction* act, + struct sigaction* oldact); + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_SYSCALL_WRAPPERS_H_ diff --git a/sandbox/linux/services/syscall_wrappers_unittest.cc b/sandbox/linux/services/syscall_wrappers_unittest.cc new file mode 100644 index 0000000000..1878ff3fe7 --- /dev/null +++ b/sandbox/linux/services/syscall_wrappers_unittest.cc @@ -0,0 +1,99 @@ +// 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/syscall_wrappers.h" + +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <cstring> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/third_party/valgrind/valgrind.h" +#include "build/build_config.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +TEST(SyscallWrappers, BasicSyscalls) { + EXPECT_EQ(getpid(), sys_getpid()); +} + +TEST(SyscallWrappers, CloneBasic) { + pid_t child = sys_clone(SIGCHLD); + TestUtils::HandlePostForkReturn(child); + EXPECT_LT(0, child); +} + +TEST(SyscallWrappers, CloneParentSettid) { + pid_t ptid = 0; + pid_t child = sys_clone(CLONE_PARENT_SETTID | SIGCHLD, nullptr, &ptid, + nullptr, nullptr); + TestUtils::HandlePostForkReturn(child); + EXPECT_LT(0, child); + EXPECT_EQ(child, ptid); +} + +TEST(SyscallWrappers, CloneChildSettid) { + pid_t ctid = 0; + pid_t pid = + sys_clone(CLONE_CHILD_SETTID | SIGCHLD, nullptr, nullptr, &ctid, nullptr); + + const int kSuccessExit = 0; + if (0 == pid) { + // In child. + if (sys_getpid() == ctid) + _exit(kSuccessExit); + _exit(1); + } + + ASSERT_NE(-1, pid); + int status = 0; + ASSERT_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0))); + ASSERT_TRUE(WIFEXITED(status)); + EXPECT_EQ(kSuccessExit, WEXITSTATUS(status)); +} + +TEST(SyscallWrappers, GetRESUid) { + uid_t ruid, euid, suid; + uid_t sys_ruid, sys_euid, sys_suid; + ASSERT_EQ(0, getresuid(&ruid, &euid, &suid)); + ASSERT_EQ(0, sys_getresuid(&sys_ruid, &sys_euid, &sys_suid)); + EXPECT_EQ(ruid, sys_ruid); + EXPECT_EQ(euid, sys_euid); + EXPECT_EQ(suid, sys_suid); +} + +TEST(SyscallWrappers, GetRESGid) { + gid_t rgid, egid, sgid; + gid_t sys_rgid, sys_egid, sys_sgid; + ASSERT_EQ(0, getresgid(&rgid, &egid, &sgid)); + ASSERT_EQ(0, sys_getresgid(&sys_rgid, &sys_egid, &sys_sgid)); + EXPECT_EQ(rgid, sys_rgid); + EXPECT_EQ(egid, sys_egid); + EXPECT_EQ(sgid, sys_sgid); +} + +TEST(SyscallWrappers, LinuxSigSet) { + sigset_t sigset; + ASSERT_EQ(0, sigemptyset(&sigset)); + ASSERT_EQ(0, sigaddset(&sigset, LINUX_SIGSEGV)); + ASSERT_EQ(0, sigaddset(&sigset, LINUX_SIGBUS)); + uint64_t linux_sigset = 0; + std::memcpy(&linux_sigset, &sigset, + std::min(sizeof(sigset), sizeof(linux_sigset))); + EXPECT_EQ((1ULL << (LINUX_SIGSEGV - 1)) | (1ULL << (LINUX_SIGBUS - 1)), + linux_sigset); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/services/thread_helpers.cc b/sandbox/linux/services/thread_helpers.cc new file mode 100644 index 0000000000..80766a9bc5 --- /dev/null +++ b/sandbox/linux/services/thread_helpers.cc @@ -0,0 +1,157 @@ +// 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/thread_helpers.h" + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <string> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/string_number_conversions.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "sandbox/linux/services/proc_util.h" + +namespace sandbox { + +namespace { + +const char kAssertSingleThreadedError[] = + "Current process is not mono-threaded!"; + +bool IsSingleThreadedImpl(int proc_fd) { + CHECK_LE(0, proc_fd); + struct stat task_stat; + int fstat_ret = fstatat(proc_fd, "self/task/", &task_stat, 0); + PCHECK(0 == fstat_ret); + + // At least "..", "." and the current thread should be present. + CHECK_LE(3UL, task_stat.st_nlink); + // Counting threads via /proc/self/task could be racy. For the purpose of + // determining if the current proces is monothreaded it works: if at any + // time it becomes monothreaded, it'll stay so. + return task_stat.st_nlink == 3; +} + +bool IsThreadPresentInProcFS(int proc_fd, + const std::string& thread_id_dir_str) { + struct stat task_stat; + const int fstat_ret = + fstatat(proc_fd, thread_id_dir_str.c_str(), &task_stat, 0); + if (fstat_ret < 0) { + PCHECK(ENOENT == errno); + return false; + } + return true; +} + +// Run |cb| in a loop until it returns false. Every time |cb| runs, sleep +// for an exponentially increasing amount of time. |cb| is expected to return +// false very quickly and this will crash if it doesn't happen within ~64ms on +// Debug builds (2s on Release builds). +// This is guaranteed to not sleep more than twice as much as the bare minimum +// amount of time. +void RunWhileTrue(const base::Callback<bool(void)>& cb) { +#if defined(NDEBUG) + // In Release mode, crash after 30 iterations, which means having spent + // roughly 2s in + // nanosleep(2) cumulatively. + const unsigned int kMaxIterations = 30U; +#else + // In practice, this never goes through more than a couple iterations. In + // debug mode, crash after 64ms (+ eventually 25 times the granularity of + // the clock) in nanosleep(2). This ensures that this is not becoming too + // slow. + const unsigned int kMaxIterations = 25U; +#endif + + // Run |cb| with an exponential back-off, sleeping 2^iterations nanoseconds + // in nanosleep(2). + // Note: the clock may not allow for nanosecond granularity, in this case the + // first iterations would sleep a tiny bit more instead, which would not + // change the calculations significantly. + for (unsigned int i = 0; i < kMaxIterations; ++i) { + if (!cb.Run()) { + return; + } + + // Increase the waiting time exponentially. + struct timespec ts = {0, 1L << i /* nanoseconds */}; + PCHECK(0 == HANDLE_EINTR(nanosleep(&ts, &ts))); + } + + LOG(FATAL) << kAssertSingleThreadedError << " (iterations: " << kMaxIterations + << ")"; + + NOTREACHED(); +} + +bool IsMultiThreaded(int proc_fd) { + return !ThreadHelpers::IsSingleThreaded(proc_fd); +} + +} // namespace + +// static +bool ThreadHelpers::IsSingleThreaded(int proc_fd) { + DCHECK_LE(0, proc_fd); + return IsSingleThreadedImpl(proc_fd); +} + +// static +bool ThreadHelpers::IsSingleThreaded() { + base::ScopedFD task_fd(ProcUtil::OpenProc()); + return IsSingleThreaded(task_fd.get()); +} + +// static +void ThreadHelpers::AssertSingleThreaded(int proc_fd) { + DCHECK_LE(0, proc_fd); + const base::Callback<bool(void)> cb = base::Bind(&IsMultiThreaded, proc_fd); + RunWhileTrue(cb); +} + +void ThreadHelpers::AssertSingleThreaded() { + base::ScopedFD task_fd(ProcUtil::OpenProc()); + AssertSingleThreaded(task_fd.get()); +} + +// static +bool ThreadHelpers::StopThreadAndWatchProcFS(int proc_fd, + base::Thread* thread) { + DCHECK_LE(0, proc_fd); + DCHECK(thread); + const base::PlatformThreadId thread_id = thread->thread_id(); + const std::string thread_id_dir_str = + "self/task/" + base::IntToString(thread_id) + "/"; + + // The kernel is at liberty to wake the thread id futex before updating + // /proc. Following Stop(), the thread is joined, but entries in /proc may + // not have been updated. + thread->Stop(); + + const base::Callback<bool(void)> cb = + base::Bind(&IsThreadPresentInProcFS, proc_fd, thread_id_dir_str); + + RunWhileTrue(cb); + + return true; +} + +// static +const char* ThreadHelpers::GetAssertSingleThreadedErrorMessageForTests() { + return kAssertSingleThreadedError; +} + +} // namespace sandbox diff --git a/sandbox/linux/services/thread_helpers.h b/sandbox/linux/services/thread_helpers.h new file mode 100644 index 0000000000..f4abdffd03 --- /dev/null +++ b/sandbox/linux/services/thread_helpers.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_SERVICES_THREAD_HELPERS_H_ +#define SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_ + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace base { class Thread; } + +namespace sandbox { + +class SANDBOX_EXPORT ThreadHelpers { + public: + // Check whether the current process is single threaded. |proc_fd| + // must be a file descriptor to /proc/ and remains owned by the + // caller. + static bool IsSingleThreaded(int proc_fd); + static bool IsSingleThreaded(); + + // Crash if the current process is not single threaded. This will wait + // on /proc to be updated. In the case where this doesn't crash, this will + // return promptly. In the case where this does crash, this will first wait + // for a few ms in Debug mode, a few seconds in Release mode. + static void AssertSingleThreaded(int proc_fd); + static void AssertSingleThreaded(); + + // Stop |thread| and ensure that it does not have an entry in + // /proc/self/task/ from the point of view of the current thread. This is + // the way to stop threads before calling IsSingleThreaded(). + static bool StopThreadAndWatchProcFS(int proc_fd, base::Thread* thread); + + static const char* GetAssertSingleThreadedErrorMessageForTests(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(ThreadHelpers); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_ diff --git a/sandbox/linux/services/thread_helpers_unittests.cc b/sandbox/linux/services/thread_helpers_unittests.cc new file mode 100644 index 0000000000..7357a0cfa7 --- /dev/null +++ b/sandbox/linux/services/thread_helpers_unittests.cc @@ -0,0 +1,147 @@ +// 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/thread_helpers.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#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" + +using base::PlatformThread; + +namespace sandbox { + +namespace { + +// These tests fail under ThreadSanitizer, see http://crbug.com/342305 +#if !defined(THREAD_SANITIZER) + +int GetRaceTestIterations() { + if (IsRunningOnValgrind()) { + return 2; + } else { + return 1000; + } +} + +class ScopedProc { + public: + ScopedProc() : fd_(-1) { + fd_ = open("/proc/", O_RDONLY | O_DIRECTORY); + CHECK_LE(0, fd_); + } + + ~ScopedProc() { PCHECK(0 == IGNORE_EINTR(close(fd_))); } + + int fd() { return fd_; } + + private: + int fd_; + DISALLOW_COPY_AND_ASSIGN(ScopedProc); +}; + +TEST(ThreadHelpers, IsSingleThreadedBasic) { + ScopedProc proc_fd; + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded()); + + base::Thread thread("sandbox_tests"); + ASSERT_TRUE(thread.Start()); + ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + ASSERT_FALSE(ThreadHelpers::IsSingleThreaded()); + // Explicitly stop the thread here to not pollute the next test. + ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(proc_fd.fd(), &thread)); +} + +SANDBOX_TEST(ThreadHelpers, AssertSingleThreaded) { + ScopedProc proc_fd; + SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded()); + + ThreadHelpers::AssertSingleThreaded(proc_fd.fd()); + ThreadHelpers::AssertSingleThreaded(); +} + +TEST(ThreadHelpers, IsSingleThreadedIterated) { + ScopedProc proc_fd; + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + + // Iterate to check for race conditions. + for (int i = 0; i < GetRaceTestIterations(); ++i) { + base::Thread thread("sandbox_tests"); + ASSERT_TRUE(thread.Start()); + ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + // Explicitly stop the thread here to not pollute the next test. + ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(proc_fd.fd(), &thread)); + } +} + +TEST(ThreadHelpers, IsSingleThreadedStartAndStop) { + ScopedProc proc_fd; + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + + base::Thread thread("sandbox_tests"); + // This is testing for a race condition, so iterate. + // Manually, this has been tested with more that 1M iterations. + for (int i = 0; i < GetRaceTestIterations(); ++i) { + ASSERT_TRUE(thread.Start()); + ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + + ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(proc_fd.fd(), &thread)); + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + ASSERT_EQ(1, base::GetNumberOfThreads(base::GetCurrentProcessHandle())); + } +} + +SANDBOX_TEST(ThreadHelpers, AssertSingleThreadedAfterThreadStopped) { + SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded()); + + base::Thread thread1("sandbox_tests"); + base::Thread thread2("sandbox_tests"); + + for (int i = 0; i < GetRaceTestIterations(); ++i) { + SANDBOX_ASSERT(thread1.Start()); + SANDBOX_ASSERT(thread2.Start()); + SANDBOX_ASSERT(!ThreadHelpers::IsSingleThreaded()); + + thread1.Stop(); + thread2.Stop(); + // This will wait on /proc/ to reflect the state of threads in the + // process. + ThreadHelpers::AssertSingleThreaded(); + SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded()); + } +} + +// Only run this test in Debug mode, where AssertSingleThreaded() will return +// in less than 64ms. +#if !defined(NDEBUG) +SANDBOX_DEATH_TEST( + ThreadHelpers, + AssertSingleThreadedDies, + DEATH_MESSAGE( + ThreadHelpers::GetAssertSingleThreadedErrorMessageForTests())) { + base::Thread thread1("sandbox_tests"); + SANDBOX_ASSERT(thread1.Start()); + ThreadHelpers::AssertSingleThreaded(); +} +#endif // !defined(NDEBUG) + +#endif // !defined(THREAD_SANITIZER) + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/services/yama.cc b/sandbox/linux/services/yama.cc new file mode 100644 index 0000000000..151f4bd340 --- /dev/null +++ b/sandbox/linux/services/yama.cc @@ -0,0 +1,115 @@ +// 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/files/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/sandbox/linux/services/yama.h b/sandbox/linux/services/yama.h new file mode 100644 index 0000000000..e6c5c45b2a --- /dev/null +++ b/sandbox/linux/services/yama.h @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_YAMA_H_ +#define SANDBOX_LINUX_SERVICES_YAMA_H_ + +#include "base/macros.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/sandbox/linux/services/yama_unittests.cc b/sandbox/linux/services/yama_unittests.cc new file mode 100644 index 0000000000..204cfd6a44 --- /dev/null +++ b/sandbox/linux/services/yama_unittests.cc @@ -0,0 +1,172 @@ +// 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 "base/strings/string_util.h" +#include "base/sys_info.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 HasLinux32Bug() { +#if defined(__i386__) + // On 3.2 kernels, yama doesn't work for 32-bit binaries on 64-bit kernels. + // This is fixed in 3.4. + bool is_kernel_64bit = + base::SysInfo::OperatingSystemArchitecture() == "x86_64"; + bool is_linux = base::SysInfo::OperatingSystemName() == "Linux"; + bool is_3_dot_2 = base::StartsWithASCII( + base::SysInfo::OperatingSystemVersion(), "3.2", /*case_sensitive=*/false); + if (is_kernel_64bit && is_linux && is_3_dot_2) + return true; +#endif // defined(__i386__) + return false; +} + +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) { + if (HasLinux32Bug()) + return; + + 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() || HasLinux32Bug()) + 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/sandbox/linux/suid/client/DEPS b/sandbox/linux/suid/client/DEPS new file mode 100644 index 0000000000..99a337d772 --- /dev/null +++ b/sandbox/linux/suid/client/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+sandbox/linux/services", +] diff --git a/sandbox/linux/suid/client/setuid_sandbox_client.cc b/sandbox/linux/suid/client/setuid_sandbox_client.cc new file mode 100644 index 0000000000..12ef7f9f40 --- /dev/null +++ b/sandbox/linux/suid/client/setuid_sandbox_client.cc @@ -0,0 +1,151 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/suid/client/setuid_sandbox_client.h" + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <string> + +#include "base/environment.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/string_number_conversions.h" +#include "sandbox/linux/suid/common/sandbox.h" + +namespace { + +bool IsFileSystemAccessDenied() { + base::ScopedFD root_dir(HANDLE_EINTR(open("/", O_RDONLY))); + return !root_dir.is_valid(); +} + +int GetHelperApi(base::Environment* env) { + std::string api_string; + int api_number = 0; // Assume API version 0 if no environment was found. + if (env->GetVar(sandbox::kSandboxEnvironmentApiProvides, &api_string) && + !base::StringToInt(api_string, &api_number)) { + // It's an error if we could not convert the API number. + api_number = -1; + } + return api_number; +} + +// Convert |var_name| from the environment |env| to an int. +// Return -1 if the variable does not exist or the value cannot be converted. +int EnvToInt(base::Environment* env, const char* var_name) { + std::string var_string; + int var_value = -1; + if (env->GetVar(var_name, &var_string) && + !base::StringToInt(var_string, &var_value)) { + var_value = -1; + } + return var_value; +} + +pid_t GetHelperPID(base::Environment* env) { + return EnvToInt(env, sandbox::kSandboxHelperPidEnvironmentVarName); +} + +// Get the IPC file descriptor used to communicate with the setuid helper. +int GetIPCDescriptor(base::Environment* env) { + return EnvToInt(env, sandbox::kSandboxDescriptorEnvironmentVarName); +} + +} // namespace + +namespace sandbox { + +SetuidSandboxClient* SetuidSandboxClient::Create() { + base::Environment* environment(base::Environment::Create()); + CHECK(environment); + return new SetuidSandboxClient(environment); +} + +SetuidSandboxClient::SetuidSandboxClient(base::Environment* env) + : env_(env), sandboxed_(false) { +} + +SetuidSandboxClient::~SetuidSandboxClient() { +} + +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_.get()); + + if (ipc_fd < 0) { + LOG(ERROR) << "Failed to obtain the sandbox IPC descriptor"; + return false; + } + + if (HANDLE_EINTR(write(ipc_fd, &kMsgChrootMe, 1)) != 1) { + PLOG(ERROR) << "Failed to write to chroot pipe"; + return false; + } + + // We need to reap the chroot helper process in any event. + pid_t helper_pid = GetHelperPID(env_.get()); + // If helper_pid is -1 we wait for any child. + if (HANDLE_EINTR(waitpid(helper_pid, NULL, 0)) < 0) { + PLOG(ERROR) << "Failed to wait for setuid helper to die"; + return false; + } + + char reply; + if (HANDLE_EINTR(read(ipc_fd, &reply, 1)) != 1) { + PLOG(ERROR) << "Failed to read from chroot pipe"; + return false; + } + + if (reply != kMsgChrootSuccessful) { + LOG(ERROR) << "Error code reply from chroot helper"; + return false; + } + + // We now consider ourselves "fully sandboxed" as far as the + // setuid sandbox is concerned. + CHECK(IsFileSystemAccessDenied()); + sandboxed_ = true; + return true; +} + +bool SetuidSandboxClient::IsSuidSandboxUpToDate() const { + return GetHelperApi(env_.get()) == kSUIDSandboxApiNumber; +} + +bool SetuidSandboxClient::IsSuidSandboxChild() const { + return GetIPCDescriptor(env_.get()) >= 0; +} + +bool SetuidSandboxClient::IsInNewPIDNamespace() const { + return env_->HasVar(kSandboxPIDNSEnvironmentVarName); +} + +bool SetuidSandboxClient::IsInNewNETNamespace() const { + return env_->HasVar(kSandboxNETNSEnvironmentVarName); +} + +bool SetuidSandboxClient::IsSandboxed() const { + return sandboxed_; +} + +} // namespace sandbox diff --git a/sandbox/linux/suid/client/setuid_sandbox_client.h b/sandbox/linux/suid/client/setuid_sandbox_client.h new file mode 100644 index 0000000000..026894fc27 --- /dev/null +++ b/sandbox/linux/suid/client/setuid_sandbox_client.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SUID_SETUID_SANDBOX_CLIENT_H_ +#define SANDBOX_LINUX_SUID_SETUID_SANDBOX_CLIENT_H_ + +#include "base/environment.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Helper class to use the setuid sandbox. This class is to be used +// 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 for "A" launching a sandboxed process "B" would be: +// (Steps 1 through 4 are described in setuid_sandbox_host.h.) +// 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 +// sandbox::CreateInitProcessReaper(). +// 8. B requests being chroot-ed through ChrootMe() and +// requests other sandboxing status via the status functions. +class SANDBOX_EXPORT SetuidSandboxClient { + public: + // All instantation should go through this factory method. + static 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. + bool ChrootMe(); + + // Did we get launched through an up to date setuid binary ? + bool IsSuidSandboxUpToDate() const; + // Did we get launched through the setuid helper ? + bool IsSuidSandboxChild() const; + // Did the setuid helper create a new PID namespace ? + bool IsInNewPIDNamespace() const; + // Did the setuid helper create a new network namespace ? + bool IsInNewNETNamespace() const; + // Are we done and fully sandboxed ? + bool IsSandboxed() const; + + private: + explicit SetuidSandboxClient(base::Environment* env); + + // Holds the environment. Will never be NULL. + scoped_ptr<base::Environment> env_; + bool sandboxed_; + + DISALLOW_COPY_AND_ASSIGN(SetuidSandboxClient); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SUID_SETUID_SANDBOX_CLIENT_H_ diff --git a/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc b/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc new file mode 100644 index 0000000000..2acd8fb5fc --- /dev/null +++ b/sandbox/linux/suid/client/setuid_sandbox_client_unittest.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/suid/client/setuid_sandbox_client.h" + +#include "base/environment.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "sandbox/linux/suid/common/sandbox.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +TEST(SetuidSandboxClient, SandboxedClientAPI) { + scoped_ptr<base::Environment> env(base::Environment::Create()); + EXPECT_TRUE(env != NULL); + + scoped_ptr<SetuidSandboxClient> + sandbox_client(SetuidSandboxClient::Create()); + EXPECT_TRUE(sandbox_client != NULL); + + // Set-up a fake environment as if we went through the setuid sandbox. + EXPECT_TRUE(env->SetVar(kSandboxEnvironmentApiProvides, + base::IntToString(kSUIDSandboxApiNumber))); + EXPECT_TRUE(env->SetVar(kSandboxDescriptorEnvironmentVarName, "1")); + EXPECT_TRUE(env->SetVar(kSandboxPIDNSEnvironmentVarName, "1")); + EXPECT_TRUE(env->UnSetVar(kSandboxNETNSEnvironmentVarName)); + + // Check the API. + EXPECT_TRUE(sandbox_client->IsSuidSandboxUpToDate()); + EXPECT_TRUE(sandbox_client->IsSuidSandboxChild()); + EXPECT_TRUE(sandbox_client->IsInNewPIDNamespace()); + EXPECT_FALSE(sandbox_client->IsInNewNETNamespace()); + + // Forge an incorrect API version and check. + EXPECT_TRUE(env->SetVar(kSandboxEnvironmentApiProvides, + base::IntToString(kSUIDSandboxApiNumber + 1))); + EXPECT_FALSE(sandbox_client->IsSuidSandboxUpToDate()); + // We didn't go through the actual sandboxing mechanism as it is + // very hard in a unit test. + EXPECT_FALSE(sandbox_client->IsSandboxed()); +} + +} // namespace sandbox + diff --git a/sandbox/linux/suid/client/setuid_sandbox_host.cc b/sandbox/linux/suid/client/setuid_sandbox_host.cc new file mode 100644 index 0000000000..71171ebd4f --- /dev/null +++ b/sandbox/linux/suid/client/setuid_sandbox_host.cc @@ -0,0 +1,195 @@ +// Copyright 2015 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/suid/client/setuid_sandbox_host.h" + +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <string> +#include <utility> + +#include "base/command_line.h" +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.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/suid/common/sandbox.h" +#include "sandbox/linux/suid/common/suid_unsafe_environment_variables.h" + +namespace { + +// 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) { + env->SetVar(sandbox::kSandboxEnvironmentApiRequest, + 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. +std::string* CreateSavedVariableName(const char* env_var) { + char* const saved_env_var = SandboxSavedEnvironmentVariable(env_var); + if (!saved_env_var) + return NULL; + std::string* saved_env_var_copy = new std::string(saved_env_var); + // SandboxSavedEnvironmentVariable is the C function that we wrap and uses + // malloc() to allocate memory. + free(saved_env_var); + return saved_env_var_copy; +} + +// The ELF loader will clear many environment variables so we save them to +// different names here so that the SUID sandbox can resolve them for the +// renderer. +void SaveSUIDUnsafeEnvironmentVariables(base::Environment* env) { + for (unsigned i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) { + const char* env_var = kSUIDUnsafeEnvironmentVariables[i]; + // Get the saved environment variable corresponding to envvar. + scoped_ptr<std::string> saved_env_var(CreateSavedVariableName(env_var)); + if (saved_env_var == NULL) + continue; + + std::string value; + if (env->GetVar(env_var, &value)) + env->SetVar(saved_env_var->c_str(), value); + else + env->UnSetVar(saved_env_var->c_str()); + } +} + +const char* GetDevelSandboxPath() { + return getenv("CHROME_DEVEL_SANDBOX"); +} + +} // namespace + +namespace sandbox { + +SetuidSandboxHost* SetuidSandboxHost::Create() { + base::Environment* environment(base::Environment::Create()); + CHECK(environment); + return new SetuidSandboxHost(environment); +} + +SetuidSandboxHost::SetuidSandboxHost(base::Environment* env) : env_(env) { +} + +SetuidSandboxHost::~SetuidSandboxHost() { +} + +// Check if CHROME_DEVEL_SANDBOX is set but empty. This currently disables +// the setuid sandbox. TODO(jln): fix this (crbug.com/245376). +bool SetuidSandboxHost::IsDisabledViaEnvironment() { + const char* devel_sandbox_path = GetDevelSandboxPath(); + if (devel_sandbox_path && '\0' == *devel_sandbox_path) { + return true; + } + return false; +} + +base::FilePath SetuidSandboxHost::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 SetuidSandboxHost::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 SetuidSandboxHost::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 SetuidSandboxHost::SetupLaunchEnvironment() { + SaveSUIDUnsafeEnvironmentVariables(env_.get()); + SetSandboxAPIEnvironmentVariable(env_.get()); +} + +} // namespace sandbox diff --git a/sandbox/linux/suid/client/setuid_sandbox_host.h b/sandbox/linux/suid/client/setuid_sandbox_host.h new file mode 100644 index 0000000000..6788892441 --- /dev/null +++ b/sandbox/linux/suid/client/setuid_sandbox_host.h @@ -0,0 +1,70 @@ +// Copyright 2015 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_SUID_SETUID_SANDBOX_HOST_H_ +#define SANDBOX_LINUX_SUID_SETUID_SANDBOX_HOST_H_ + +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.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 +// before launching 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 for "A" launching a sandboxed process "B" would be: +// 1. A calls SetupLaunchEnvironment() +// 2. A sets up a base::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 +// base::CommandLine. +// (The remaining steps are described within setuid_sandbox_client.h.) +class SANDBOX_EXPORT SetuidSandboxHost { + public: + // All instantation should go through this factory method. + static SetuidSandboxHost* Create(); + ~SetuidSandboxHost(); + + // 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: + explicit SetuidSandboxHost(base::Environment* env); + + // Holds the environment. Will never be NULL. + scoped_ptr<base::Environment> env_; + + DISALLOW_COPY_AND_ASSIGN(SetuidSandboxHost); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SUID_SETUID_SANDBOX_HOST_H_ diff --git a/sandbox/linux/suid/client/setuid_sandbox_host_unittest.cc b/sandbox/linux/suid/client/setuid_sandbox_host_unittest.cc new file mode 100644 index 0000000000..8415abb064 --- /dev/null +++ b/sandbox/linux/suid/client/setuid_sandbox_host_unittest.cc @@ -0,0 +1,72 @@ +// Copyright 2015 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/suid/client/setuid_sandbox_host.h" + +#include <string> + +#include "base/environment.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "sandbox/linux/suid/common/sandbox.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +TEST(SetuidSandboxHost, SetupLaunchEnvironment) { + const char kTestValue[] = "This is a test"; + scoped_ptr<base::Environment> env(base::Environment::Create()); + EXPECT_TRUE(env != NULL); + + std::string saved_ld_preload; + bool environment_had_ld_preload; + // First, back-up the real LD_PRELOAD if any. + environment_had_ld_preload = env->GetVar("LD_PRELOAD", &saved_ld_preload); + // Setup environment variables to save or not save. + EXPECT_TRUE(env->SetVar("LD_PRELOAD", kTestValue)); + EXPECT_TRUE(env->UnSetVar("LD_ORIGIN_PATH")); + + scoped_ptr<SetuidSandboxHost> sandbox_host(SetuidSandboxHost::Create()); + EXPECT_TRUE(sandbox_host != NULL); + + // Make sure the environment is clean. + EXPECT_TRUE(env->UnSetVar(kSandboxEnvironmentApiRequest)); + EXPECT_TRUE(env->UnSetVar(kSandboxEnvironmentApiProvides)); + + sandbox_host->SetupLaunchEnvironment(); + + // Check if the requested API environment was set. + std::string api_request; + EXPECT_TRUE(env->GetVar(kSandboxEnvironmentApiRequest, &api_request)); + int api_request_num; + EXPECT_TRUE(base::StringToInt(api_request, &api_request_num)); + EXPECT_EQ(api_request_num, kSUIDSandboxApiNumber); + + // Now check if LD_PRELOAD was saved to SANDBOX_LD_PRELOAD. + std::string sandbox_ld_preload; + EXPECT_TRUE(env->GetVar("SANDBOX_LD_PRELOAD", &sandbox_ld_preload)); + EXPECT_EQ(sandbox_ld_preload, kTestValue); + + // Check that LD_ORIGIN_PATH was not saved. + EXPECT_FALSE(env->HasVar("SANDBOX_LD_ORIGIN_PATH")); + + // We should not forget to restore LD_PRELOAD at the end, or this environment + // variable will affect the next running tests! + if (environment_had_ld_preload) { + EXPECT_TRUE(env->SetVar("LD_PRELOAD", saved_ld_preload)); + } else { + EXPECT_TRUE(env->UnSetVar("LD_PRELOAD")); + } +} + +// This test doesn't accomplish much, but will make sure that analysis tools +// will run this codepath. +TEST(SetuidSandboxHost, GetSandboxBinaryPath) { + scoped_ptr<SetuidSandboxHost> setuid_sandbox_host( + SetuidSandboxHost::Create()); + ignore_result(setuid_sandbox_host->GetSandboxBinaryPath()); +} + +} // namespace sandbox diff --git a/sandbox/linux/suid/common/sandbox.h b/sandbox/linux/suid/common/sandbox.h new file mode 100644 index 0000000000..99eb7b5120 --- /dev/null +++ b/sandbox/linux/suid/common/sandbox.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SUID_SANDBOX_H_ +#define SANDBOX_LINUX_SUID_SANDBOX_H_ + +#if defined(__cplusplus) +namespace sandbox { +#endif + +// These are command line switches that may be used by other programs +// (e.g. Chrome) to construct a command line for the sandbox. +static const char kSuidSandboxGetApiSwitch[] = "--get-api"; +static const char kAdjustOOMScoreSwitch[] = "--adjust-oom-score"; + +static const char kSandboxDescriptorEnvironmentVarName[] = "SBX_D"; +static const char kSandboxHelperPidEnvironmentVarName[] = "SBX_HELPER_PID"; + +static const long kSUIDSandboxApiNumber = 1; +static const char kSandboxEnvironmentApiRequest[] = "SBX_CHROME_API_RQ"; +static const char kSandboxEnvironmentApiProvides[] = "SBX_CHROME_API_PRV"; + +// This number must be kept in sync with common/zygote_commands_linux.h +static const int kZygoteIdFd = 7; + +// These are the magic byte values which the sandboxed process uses to request +// that it be chrooted. +static const char kMsgChrootMe = 'C'; +static const char kMsgChrootSuccessful = 'O'; + +// These are set if we have respectively switched to a new PID or NET namespace +// by going through the setuid binary helper. +static const char kSandboxPIDNSEnvironmentVarName[] = "SBX_PID_NS"; +static const char kSandboxNETNSEnvironmentVarName[] = "SBX_NET_NS"; + +#if defined(__cplusplus) +} // namespace sandbox +#endif + +#endif // SANDBOX_LINUX_SUID_SANDBOX_H_ diff --git a/sandbox/linux/suid/common/suid_unsafe_environment_variables.h b/sandbox/linux/suid/common/suid_unsafe_environment_variables.h new file mode 100644 index 0000000000..33ba4b6ab7 --- /dev/null +++ b/sandbox/linux/suid/common/suid_unsafe_environment_variables.h @@ -0,0 +1,73 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is a list of environment variables which the ELF loader unsets when +// loading a SUID binary. Because they are unset rather than just ignored, they +// aren't passed to child processes of SUID processes either. +// +// We need to save these environment variables before running a SUID sandbox +// and restore them before running child processes (but after dropping root). +// +// List gathered from glibc sources (00ebd7ed58df389a78e41dece058048725cb585e): +// sysdeps/unix/sysv/linux/i386/dl-librecon.h +// sysdeps/generic/unsecvars.h + +#ifndef SANDBOX_LINUX_SUID_SUID_UNSAFE_ENVIRONMENT_VARIABLES_H_ +#define SANDBOX_LINUX_SUID_SUID_UNSAFE_ENVIRONMENT_VARIABLES_H_ + +#include <stdint.h> +#include <stdlib.h> // malloc +#include <string.h> // memcpy + +static const char* kSUIDUnsafeEnvironmentVariables[] = { + "LD_AOUT_LIBRARY_PATH", + "LD_AOUT_PRELOAD", + "GCONV_PATH", + "GETCONF_DIR", + "HOSTALIASES", + "LD_AUDIT", + "LD_DEBUG", + "LD_DEBUG_OUTPUT", + "LD_DYNAMIC_WEAK", + "LD_LIBRARY_PATH", + "LD_ORIGIN_PATH", + "LD_PRELOAD", + "LD_PROFILE", + "LD_SHOW_AUXV", + "LD_USE_LOAD_BIAS", + "LOCALDOMAIN", + "LOCPATH", + "MALLOC_TRACE", + "NIS_PATH", + "NLSPATH", + "RESOLV_HOST_CONF", + "RES_OPTIONS", + "TMPDIR", + "TZDIR", + NULL, +}; + +// Return a malloc allocated string containing the 'saved' environment variable +// name for a given environment variable. +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) + return NULL; + + const size_t saved_envvarlen = envvar_len + 1 /* NUL terminator */ + + 8 /* strlen("SANDBOX_") */; + char* const saved_envvar = (char*) malloc(saved_envvarlen); + if (!saved_envvar) + return NULL; + + memcpy(saved_envvar, "SANDBOX_", 8); + memcpy(saved_envvar + 8, envvar, envvar_len); + saved_envvar[8 + envvar_len] = 0; + + return saved_envvar; +} + +#endif // SANDBOX_LINUX_SUID_SUID_UNSAFE_ENVIRONMENT_VARIABLES_H_ diff --git a/sandbox/linux/suid/process_util.h b/sandbox/linux/suid/process_util.h new file mode 100644 index 0000000000..9fb9a8791a --- /dev/null +++ b/sandbox/linux/suid/process_util.h @@ -0,0 +1,30 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The following is duplicated from base/process_utils.h. +// We shouldn't link against C++ code in a setuid binary. + +#ifndef SANDBOX_LINUX_SUID_PROCESS_UTIL_H_ +#define SANDBOX_LINUX_SUID_PROCESS_UTIL_H_ + +#include <stdbool.h> +#include <sys/types.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. +// +// If the Linux system isn't new enough to use oom_score_adj, then we +// 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. +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. +bool AdjustLowMemoryMargin(int64_t margin_mb); + +#endif // SANDBOX_LINUX_SUID_PROCESS_UTIL_H_ diff --git a/sandbox/linux/suid/process_util_linux.c b/sandbox/linux/suid/process_util_linux.c new file mode 100644 index 0000000000..8d9a53c3a4 --- /dev/null +++ b/sandbox/linux/suid/process_util_linux.c @@ -0,0 +1,78 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// 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. + +// 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/process_util.h" + +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +// Ranges for the current (oom_score_adj) and previous (oom_adj) +// flavors of OOM score. +static const int kMaxOomScore = 1000; +static const int kMaxOldOomScore = 15; + +// NOTE: This is not the only version of this function in the source: +// the base library (in process_util_linux.cc) also has its own C++ version. +bool AdjustOOMScore(pid_t process, int score) { + if (score < 0 || score > kMaxOomScore) + return false; + + char oom_adj[27]; // "/proc/" + log_10(2**64) + "\0" + // 6 + 20 + 1 = 27 + snprintf(oom_adj, sizeof(oom_adj), "/proc/%" PRIdMAX, (intmax_t)process); + + const int dirfd = open(oom_adj, O_RDONLY | O_DIRECTORY); + if (dirfd < 0) + return false; + + struct stat statbuf; + if (fstat(dirfd, &statbuf) < 0) { + close(dirfd); + return false; + } + if (getuid() != statbuf.st_uid) { + close(dirfd); + return false; + } + + int fd = openat(dirfd, "oom_score_adj", O_WRONLY); + if (fd < 0) { + // We failed to open oom_score_adj, so let's try for the older + // oom_adj file instead. + fd = openat(dirfd, "oom_adj", O_WRONLY); + if (fd < 0) { + // Nope, that doesn't work either. + return false; + } else { + // If we're using the old oom_adj file, the allowed range is now + // [0, kMaxOldOomScore], so we scale the score. This may result in some + // aliasing of values, of course. + score = score * kMaxOldOomScore / kMaxOomScore; + } + } + close(dirfd); + + char buf[11]; // 0 <= |score| <= kMaxOomScore; using log_10(2**32) + 1 size + snprintf(buf, sizeof(buf), "%d", score); + size_t len = strlen(buf); + + ssize_t bytes_written = write(fd, buf, len); + close(fd); + return (bytes_written == (ssize_t)len); +} diff --git a/sandbox/linux/suid/sandbox.c b/sandbox/linux/suid/sandbox.c new file mode 100644 index 0000000000..3049ae5211 --- /dev/null +++ b/sandbox/linux/suid/sandbox.c @@ -0,0 +1,480 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// http://code.google.com/p/chromium/wiki/LinuxSUIDSandbox + +#include "sandbox/linux/suid/common/sandbox.h" + +#define _GNU_SOURCE +#include <asm/unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <sched.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/vfs.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "sandbox/linux/suid/common/suid_unsafe_environment_variables.h" +#include "sandbox/linux/suid/process_util.h" + +#if !defined(CLONE_NEWPID) +#define CLONE_NEWPID 0x20000000 +#endif +#if !defined(CLONE_NEWNET) +#define CLONE_NEWNET 0x40000000 +#endif + +static bool DropRoot(); + +#define HANDLE_EINTR(x) TEMP_FAILURE_RETRY(x) + +static void FatalError(const char* msg, ...) + __attribute__((noreturn, format(printf, 1, 2))); + +static void FatalError(const char* msg, ...) { + va_list ap; + va_start(ap, msg); + + vfprintf(stderr, msg, ap); + fprintf(stderr, ": %s\n", strerror(errno)); + fflush(stderr); + va_end(ap); + _exit(1); +} + +static void ExitWithErrorSignalHandler(int signal) { + const char msg[] = "\nThe setuid sandbox got signaled, exiting.\n"; + if (-1 == write(2, msg, sizeof(msg) - 1)) { + // Do nothing. + } + + _exit(1); +} + +// We will chroot() to the helper's /proc/self directory. Anything there will +// not exist anymore if we make sure to wait() for the helper. +// +// /proc/self/fdinfo or /proc/self/fd are especially safe and will be empty +// even if the helper survives as a zombie. +// +// There is very little reason to use fdinfo/ instead of fd/ but we are +// paranoid. fdinfo/ only exists since 2.6.22 so we allow fallback to fd/ +#define SAFE_DIR "/proc/self/fdinfo" +#define SAFE_DIR2 "/proc/self/fd" + +static bool SpawnChrootHelper() { + int sv[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { + perror("socketpair"); + return false; + } + + char* safedir = NULL; + struct stat sdir_stat; + 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; + } + + const pid_t pid = syscall(__NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); + + if (pid == -1) { + perror("clone"); + close(sv[0]); + close(sv[1]); + return false; + } + + if (pid == 0) { + // We share our files structure with an untrusted process. As a security in + // depth measure, we make sure that we can't open anything by mistake. + // TODO(agl): drop CAP_SYS_RESOURCE / use SECURE_NOROOT + + const struct rlimit nofile = {0, 0}; + if (setrlimit(RLIMIT_NOFILE, &nofile)) + FatalError("Setting RLIMIT_NOFILE"); + + if (close(sv[1])) + FatalError("close"); + + // wait for message + char msg; + ssize_t bytes; + do { + bytes = read(sv[0], &msg, 1); + } while (bytes == -1 && errno == EINTR); + + if (bytes == 0) + _exit(0); + if (bytes != 1) + FatalError("read"); + + // do chrooting + if (msg != kMsgChrootMe) + FatalError("Unknown message from sandboxed process"); + + // sanity check + if (chdir(safedir)) + FatalError("Cannot chdir into /proc/ directory"); + + if (chroot(safedir)) + FatalError("Cannot chroot into /proc/ directory"); + + if (chdir("/")) + FatalError("Cannot chdir to / after chroot"); + + const char reply = kMsgChrootSuccessful; + do { + bytes = write(sv[0], &reply, 1); + } while (bytes == -1 && errno == EINTR); + + if (bytes != 1) + FatalError("Writing reply"); + + _exit(0); + // We now become a zombie. /proc/self/fd(info) is now an empty dir and we + // are chrooted there. + // Our (unprivileged) parent should not even be able to open "." or "/" + // since they would need to pass the ptrace() check. If our parent wait() + // for us, our root directory will completely disappear. + } + + if (close(sv[0])) { + close(sv[1]); + perror("close"); + return false; + } + + // In the parent process, we install an environment variable containing the + // number of the file descriptor. + char desc_str[64]; + int printed = snprintf(desc_str, sizeof(desc_str), "%u", sv[1]); + if (printed < 0 || printed >= (int)sizeof(desc_str)) { + fprintf(stderr, "Failed to snprintf\n"); + return false; + } + + if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) { + perror("setenv"); + close(sv[1]); + return false; + } + + // We also install an environment variable containing the pid of the child + char helper_pid_str[64]; + printed = snprintf(helper_pid_str, sizeof(helper_pid_str), "%u", pid); + if (printed < 0 || printed >= (int)sizeof(helper_pid_str)) { + fprintf(stderr, "Failed to snprintf\n"); + return false; + } + + if (setenv(kSandboxHelperPidEnvironmentVarName, helper_pid_str, 1)) { + perror("setenv"); + close(sv[1]); + return false; + } + + return true; +} + +// Block until child_pid exits, then exit. Try to preserve the exit code. +static void WaitForChildAndExit(pid_t child_pid) { + int exit_code = -1; + siginfo_t reaped_child_info; + + // Don't "Core" on SIGABRT. SIGABRT is sent by the Chrome OS session manager + // when things are hanging. + // Here, the current process is going to waitid() and _exit(), so there is no + // point in generating a crash report. The child process is the one + // blocking us. + if (signal(SIGABRT, ExitWithErrorSignalHandler) == SIG_ERR) { + FatalError("Failed to change signal handler"); + } + + int wait_ret = + 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) { + exit_code = reaped_child_info.si_status; + } else { + // Exit with code 0 if the child got signaled. + exit_code = 0; + } + } + _exit(exit_code); +} + +static bool MoveToNewNamespaces() { + // These are the sets of flags which we'll try, in order. + 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; + int sync_fds[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sync_fds)) { + FatalError("Failed to create a socketpair"); + } + + 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()) { + FatalError("Could not drop privileges"); + } else { + if (close(sync_fds[0]) || shutdown(sync_fds[1], SHUT_RD)) + FatalError("Could not close socketpair"); + // The kZygoteIdFd needs to be closed in the parent before + // Zygote gets started. + if (close(kZygoteIdFd)) + FatalError("close"); + // Tell our child to continue + if (HANDLE_EINTR(send(sync_fds[1], "C", 1, MSG_NOSIGNAL)) != 1) + FatalError("send"); + if (close(sync_fds[1])) + FatalError("close"); + // We want to keep a full process tree and we don't want our childs to + // be reparented to (the outer PID namespace) init. So we wait for it. + WaitForChildAndExit(pid); + } + // NOTREACHED + FatalError("Not reached"); + } + + if (pid == 0) { + if (close(sync_fds[1]) || shutdown(sync_fds[0], SHUT_WR)) + FatalError("Could not close socketpair"); + + // Wait for the parent to confirm it closed kZygoteIdFd before we + // continue + char should_continue; + if (HANDLE_EINTR(read(sync_fds[0], &should_continue, 1)) != 1) + FatalError("Read on socketpair"); + if (close(sync_fds[0])) + FatalError("close"); + + if (kCloneExtraFlags[i] & CLONE_NEWPID) { + setenv(kSandboxPIDNSEnvironmentVarName, "", 1 /* overwrite */); + } else { + unsetenv(kSandboxPIDNSEnvironmentVarName); + } + + if (kCloneExtraFlags[i] & CLONE_NEWNET) { + setenv(kSandboxNETNSEnvironmentVarName, "", 1 /* overwrite */); + } else { + unsetenv(kSandboxNETNSEnvironmentVarName); + } + + 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) { + 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; + } + } + + // If the system doesn't support NEWPID then we carry on anyway. + return true; +} + +static bool DropRoot() { + if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0)) { + perror("prctl(PR_SET_DUMPABLE)"); + return false; + } + + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) { + perror("Still dumpable after prctl(PR_SET_DUMPABLE)"); + return false; + } + + gid_t rgid, egid, sgid; + if (getresgid(&rgid, &egid, &sgid)) { + perror("getresgid"); + return false; + } + + if (setresgid(rgid, rgid, rgid)) { + perror("setresgid"); + return false; + } + + uid_t ruid, euid, suid; + if (getresuid(&ruid, &euid, &suid)) { + perror("getresuid"); + return false; + } + + if (setresuid(ruid, ruid, ruid)) { + perror("setresuid"); + return false; + } + + return true; +} + +static bool SetupChildEnvironment() { + unsigned i; + + // ld.so may have cleared several environment variables because we are SUID. + // However, the child process might need them so zygote_host_linux.cc saves a + // copy in SANDBOX_$x. This is safe because we have dropped root by this + // point, so we can only exec a binary with the permissions of the user who + // ran us in the first place. + + for (i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) { + const char* const envvar = kSUIDUnsafeEnvironmentVariables[i]; + char* const saved_envvar = SandboxSavedEnvironmentVariable(envvar); + if (!saved_envvar) + return false; + + const char* const value = getenv(saved_envvar); + if (value) { + setenv(envvar, value, 1 /* overwrite */); + unsetenv(saved_envvar); + } + + free(saved_envvar); + } + + return true; +} + +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); + if (!api_string) { + api_number = 0; + } else { + errno = 0; + char* endptr = NULL; + api_number = strtol(api_string, &endptr, 10); + if (!endptr || *endptr || errno != 0) + return false; + } + + // 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); + } + + // 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); + if (setenv(kSandboxEnvironmentApiProvides, version_string, 1)) { + perror("setenv"); + return false; + } + + return true; +} + +int main(int argc, char** argv) { + if (argc <= 1) { + if (argc <= 0) { + return 1; + } + + fprintf(stderr, "Usage: %s <renderer process> <args...>\n", argv[0]); + return 1; + } + + // Allow someone to query our API version + if (argc == 2 && 0 == strcmp(argv[1], kSuidSandboxGetApiSwitch)) { + printf("%ld\n", kSUIDSandboxApiNumber); + return 0; + } + + // We cannot adjust /proc/pid/oom_adj for sandboxed renderers + // because those files are owned by root. So we need a helper here. + if (argc == 4 && (0 == strcmp(argv[1], kAdjustOOMScoreSwitch))) { + char* endptr = NULL; + long score; + errno = 0; + unsigned long pid_ul = strtoul(argv[2], &endptr, 10); + if (pid_ul == ULONG_MAX || !endptr || *endptr || errno != 0) + return 1; + pid_t pid = pid_ul; + endptr = NULL; + errno = 0; + score = strtol(argv[3], &endptr, 10); + if (score == LONG_MAX || score == LONG_MIN || !endptr || *endptr || + errno != 0) { + return 1; + } + return AdjustOOMScore(pid, score); + } + + // Protect the core setuid sandbox functionality with an API version + if (!CheckAndExportApiVersion()) { + 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()) + return 1; + if (!DropRoot()) + return 1; + if (!SetupChildEnvironment()) + return 1; + + execv(argv[1], &argv[1]); + FatalError("execv failed"); + + return 1; +} diff --git a/sandbox/linux/syscall_broker/DEPS b/sandbox/linux/syscall_broker/DEPS new file mode 100644 index 0000000000..70d9b18aa1 --- /dev/null +++ b/sandbox/linux/syscall_broker/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+sandbox/linux/system_headers", +] diff --git a/sandbox/linux/syscall_broker/broker_channel.cc b/sandbox/linux/syscall_broker/broker_channel.cc new file mode 100644 index 0000000000..fa0f7615fc --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_channel.cc @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/syscall_broker/broker_channel.h" + +#include <sys/socket.h> +#include <sys/types.h> + +#include "base/logging.h" + +namespace sandbox { + +namespace syscall_broker { + +// static +void BrokerChannel::CreatePair(EndPoint* reader, EndPoint* writer) { + DCHECK(reader); + DCHECK(writer); + int socket_pair[2]; + // Use SOCK_SEQPACKET, to preserve message boundaries but we also want to be + // notified (recvmsg should return and not block) when the connection has + // been broken which could mean that the other end has been closed. + PCHECK(0 == socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socket_pair)); + + reader->reset(socket_pair[0]); + PCHECK(0 == shutdown(reader->get(), SHUT_WR)); + + writer->reset(socket_pair[1]); + PCHECK(0 == shutdown(writer->get(), SHUT_RD)); +} + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_channel.h b/sandbox/linux/syscall_broker/broker_channel.h new file mode 100644 index 0000000000..2abdba413a --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_channel.h @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CHANNEL_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CHANNEL_H_ + +#include "base/files/scoped_file.h" +#include "base/macros.h" + +namespace sandbox { + +namespace syscall_broker { + +// A small class to create a pipe-like communication channel. It is based on a +// SOCK_SEQPACKET unix socket, which is connection-based and guaranteed to +// preserve message boundaries. +class BrokerChannel { + public: + typedef base::ScopedFD EndPoint; + static void CreatePair(EndPoint* reader, EndPoint* writer); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(BrokerChannel); +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CHANNEL_H_ diff --git a/sandbox/linux/syscall_broker/broker_client.cc b/sandbox/linux/syscall_broker/broker_client.cc new file mode 100644 index 0000000000..760cf59b3c --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_client.cc @@ -0,0 +1,144 @@ +// 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/syscall_broker/broker_client.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include "build/build_config.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "sandbox/linux/syscall_broker/broker_channel.h" +#include "sandbox/linux/syscall_broker/broker_common.h" +#include "sandbox/linux/syscall_broker/broker_policy.h" + +#if defined(OS_ANDROID) && !defined(MSG_CMSG_CLOEXEC) +#define MSG_CMSG_CLOEXEC 0x40000000 +#endif + +namespace sandbox { + +namespace syscall_broker { + +// Make a remote system call over IPC for syscalls that take a path and flags +// as arguments, currently open() and access(). +// Will return -errno like a real system call. +// This function needs to be async signal safe. +int BrokerClient::PathAndFlagsSyscall(IPCCommand syscall_type, + const char* pathname, + int flags) const { + int recvmsg_flags = 0; + RAW_CHECK(syscall_type == COMMAND_OPEN || syscall_type == COMMAND_ACCESS); + if (!pathname) + return -EFAULT; + + // For this "remote system call" to work, we need to handle any flag that + // cannot be sent over a Unix socket in a special way. + // See the comments around kCurrentProcessOpenFlagsMask. + if (syscall_type == COMMAND_OPEN && (flags & kCurrentProcessOpenFlagsMask)) { + // This implementation only knows about O_CLOEXEC, someone needs to look at + // this code if other flags are added. + RAW_CHECK(kCurrentProcessOpenFlagsMask == O_CLOEXEC); + recvmsg_flags |= MSG_CMSG_CLOEXEC; + flags &= ~O_CLOEXEC; + } + + // There is no point in forwarding a request that we know will be denied. + // Of course, the real security check needs to be on the other side of the + // IPC. + if (fast_check_in_client_) { + if (syscall_type == COMMAND_OPEN && + !broker_policy_.GetFileNameIfAllowedToOpen( + pathname, flags, NULL /* file_to_open */, + NULL /* unlink_after_open */)) { + return -broker_policy_.denied_errno(); + } + if (syscall_type == COMMAND_ACCESS && + !broker_policy_.GetFileNameIfAllowedToAccess(pathname, flags, NULL)) { + return -broker_policy_.denied_errno(); + } + } + + base::Pickle write_pickle; + write_pickle.WriteInt(syscall_type); + write_pickle.WriteString(pathname); + write_pickle.WriteInt(flags); + RAW_CHECK(write_pickle.size() <= kMaxMessageLength); + + int returned_fd = -1; + uint8_t reply_buf[kMaxMessageLength]; + + // Send a request (in write_pickle) as well that will include a new + // temporary socketpair (created internally by SendRecvMsg()). + // Then read the reply on this new socketpair in reply_buf and put an + // eventual attached file descriptor in |returned_fd|. + ssize_t msg_len = base::UnixDomainSocket::SendRecvMsgWithFlags( + ipc_channel_.get(), reply_buf, sizeof(reply_buf), recvmsg_flags, + &returned_fd, write_pickle); + if (msg_len <= 0) { + if (!quiet_failures_for_tests_) + RAW_LOG(ERROR, "Could not make request to broker process"); + return -ENOMEM; + } + + base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); + base::PickleIterator iter(read_pickle); + int return_value = -1; + // Now deserialize the return value and eventually return the file + // descriptor. + if (iter.ReadInt(&return_value)) { + switch (syscall_type) { + case COMMAND_ACCESS: + // We should never have a fd to return. + RAW_CHECK(returned_fd == -1); + return return_value; + case COMMAND_OPEN: + if (return_value < 0) { + RAW_CHECK(returned_fd == -1); + return return_value; + } else { + // We have a real file descriptor to return. + RAW_CHECK(returned_fd >= 0); + return returned_fd; + } + default: + RAW_LOG(ERROR, "Unsupported command"); + return -ENOSYS; + } + } else { + RAW_LOG(ERROR, "Could not read pickle"); + NOTREACHED(); + return -ENOMEM; + } +} + +BrokerClient::BrokerClient(const BrokerPolicy& broker_policy, + BrokerChannel::EndPoint ipc_channel, + bool fast_check_in_client, + bool quiet_failures_for_tests) + : broker_policy_(broker_policy), + ipc_channel_(ipc_channel.Pass()), + fast_check_in_client_(fast_check_in_client), + quiet_failures_for_tests_(quiet_failures_for_tests) { +} + +BrokerClient::~BrokerClient() { +} + +int BrokerClient::Access(const char* pathname, int mode) const { + return PathAndFlagsSyscall(COMMAND_ACCESS, pathname, mode); +} + +int BrokerClient::Open(const char* pathname, int flags) const { + return PathAndFlagsSyscall(COMMAND_OPEN, pathname, flags); +} + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_client.h b/sandbox/linux/syscall_broker/broker_client.h new file mode 100644 index 0000000000..2dfef8150c --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_client.h @@ -0,0 +1,75 @@ +// 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_SYSCALL_BROKER_BROKER_CLIENT_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CLIENT_H_ + +#include "base/macros.h" +#include "sandbox/linux/syscall_broker/broker_channel.h" +#include "sandbox/linux/syscall_broker/broker_common.h" + +namespace sandbox { + +namespace syscall_broker { + +class BrokerPolicy; + +// This class can be embedded in a sandboxed process and can be +// used to perform certain system calls in another, presumably +// non-sandboxed process (which embeds BrokerHost). +// A key feature of this class is the ability to use some of its methods in a +// thread-safe and async-signal safe way. The goal is to be able to use it to +// replace the open() or access() system calls happening anywhere in a process +// (as allowed for instance by seccomp-bpf's SIGSYS mechanism). +class BrokerClient { + public: + // |policy| needs to match the policy used by BrokerHost. This + // allows to predict some of the requests which will be denied + // and save an IPC round trip. + // |ipc_channel| needs to be a suitable SOCK_SEQPACKET unix socket. + // |fast_check_in_client| should be set to true and + // |quiet_failures_for_tests| to false unless you are writing tests. + BrokerClient(const BrokerPolicy& policy, + BrokerChannel::EndPoint ipc_channel, + bool fast_check_in_client, + bool quiet_failures_for_tests); + ~BrokerClient(); + + // Can be used in place of access(). + // X_OK will always return an error in practice since the broker process + // doesn't support execute permissions. + // It's similar to the access() system call and will return -errno on errors. + // This is async signal safe. + int Access(const char* pathname, int mode) const; + // Can be used in place of open(). + // The implementation only supports certain white listed flags and will + // return -EPERM on other flags. + // It's similar to the open() system call and will return -errno on errors. + // This is async signal safe. + int Open(const char* pathname, int flags) const; + + // Get the file descriptor used for IPC. This is used for tests. + int GetIPCDescriptor() const { return ipc_channel_.get(); } + + private: + const BrokerPolicy& broker_policy_; + const BrokerChannel::EndPoint ipc_channel_; + const bool fast_check_in_client_; // Whether to forward a request that we + // know will be denied to the broker. (Used + // for tests). + const bool quiet_failures_for_tests_; // Disable certain error message when + // testing for failures. + + int PathAndFlagsSyscall(IPCCommand syscall_type, + const char* pathname, + int flags) const; + + DISALLOW_COPY_AND_ASSIGN(BrokerClient); +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CLIENT_H_ diff --git a/sandbox/linux/syscall_broker/broker_common.h b/sandbox/linux/syscall_broker/broker_common.h new file mode 100644 index 0000000000..25aafa7ed2 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_common.h @@ -0,0 +1,41 @@ +// 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_SYSCALL_BROKER_BROKER_COMMON_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_COMMON_H_ + +#include <fcntl.h> +#include <stddef.h> + +namespace sandbox { + +namespace syscall_broker { + +const size_t kMaxMessageLength = 4096; + +// Some flags are local to the current process and cannot be sent over a Unix +// socket. They need special treatment from the client. +// O_CLOEXEC is tricky because in theory another thread could call execve() +// before special treatment is made on the client, so a client needs to call +// recvmsg(2) with MSG_CMSG_CLOEXEC. +// To make things worse, there are two CLOEXEC related flags, FD_CLOEXEC (see +// F_GETFD in fcntl(2)) and O_CLOEXEC (see F_GETFL in fcntl(2)). O_CLOEXEC +// doesn't affect the semantics on execve(), it's merely a note that the +// descriptor was originally opened with O_CLOEXEC as a flag. And it is sent +// over unix sockets just fine, so a receiver that would (incorrectly) look at +// O_CLOEXEC instead of FD_CLOEXEC may be tricked in thinking that the file +// descriptor will or won't be closed on execve(). +const int kCurrentProcessOpenFlagsMask = O_CLOEXEC; + +enum IPCCommand { + COMMAND_INVALID = 0, + COMMAND_OPEN, + COMMAND_ACCESS, +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_COMMON_H_ diff --git a/sandbox/linux/syscall_broker/broker_file_permission.cc b/sandbox/linux/syscall_broker/broker_file_permission.cc new file mode 100644 index 0000000000..beceda93f5 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_file_permission.cc @@ -0,0 +1,243 @@ +// 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/syscall_broker/broker_file_permission.h" + +#include <fcntl.h> +#include <string.h> + +#include <string> + +#include "base/logging.h" +#include "sandbox/linux/syscall_broker/broker_common.h" + +namespace sandbox { + +namespace syscall_broker { + +// Async signal safe +bool BrokerFilePermission::ValidatePath(const char* path) { + if (!path) + return false; + + const size_t len = strlen(path); + // No empty paths + if (len == 0) + return false; + // Paths must be absolute and not relative + if (path[0] != '/') + return false; + // No trailing / (but "/" is valid) + if (len > 1 && path[len - 1] == '/') + return false; + // No trailing /.. + if (len >= 3 && path[len - 3] == '/' && path[len - 2] == '.' && + path[len - 1] == '.') + return false; + // No /../ anywhere + for (size_t i = 0; i < len; i++) { + if (path[i] == '/' && (len - i) > 3) { + if (path[i + 1] == '.' && path[i + 2] == '.' && path[i + 3] == '/') { + return false; + } + } + } + return true; +} + +// Async signal safe +// Calls std::string::c_str(), strncmp and strlen. All these +// methods are async signal safe in common standard libs. +// TODO(leecam): remove dependency on std::string +bool BrokerFilePermission::MatchPath(const char* requested_filename) const { + const char* path = path_.c_str(); + if ((recursive_ && strncmp(requested_filename, path, strlen(path)) == 0)) { + // Note: This prefix match will allow any path under the whitelisted + // path, for any number of directory levels. E.g. if the whitelisted + // path is /good/ then the following will be permitted by the policy. + // /good/file1 + // /good/folder/file2 + // /good/folder/folder2/file3 + // If an attacker could make 'folder' a symlink to ../../ they would have + // access to the entire filesystem. + // Whitelisting with multiple depths is useful, e.g /proc/ but + // the system needs to ensure symlinks can not be created! + // That said if an attacker can convert any of the absolute paths + // to a symlink they can control any file on the system also. + return true; + } else if (strcmp(requested_filename, path) == 0) { + return true; + } + return false; +} + +// Async signal safe. +// External call to std::string::c_str() is +// called in MatchPath. +// TODO(leecam): remove dependency on std::string +bool BrokerFilePermission::CheckAccess(const char* requested_filename, + int mode, + const char** file_to_access) const { + // First, check if |mode| is existence, ability to read or ability + // to write. We do not support X_OK. + if (mode != F_OK && mode & ~(R_OK | W_OK)) { + return false; + } + + if (!ValidatePath(requested_filename)) + return false; + + if (!MatchPath(requested_filename)) { + return false; + } + bool allowed = false; + switch (mode) { + case F_OK: + if (allow_read_ || allow_write_) + allowed = true; + break; + case R_OK: + if (allow_read_) + allowed = true; + break; + case W_OK: + if (allow_write_) + allowed = true; + break; + case R_OK | W_OK: + if (allow_read_ && allow_write_) + allowed = true; + break; + default: + return false; + } + + if (allowed && file_to_access) { + if (!recursive_) + *file_to_access = path_.c_str(); + else + *file_to_access = requested_filename; + } + return allowed; +} + +// Async signal safe. +// External call to std::string::c_str() is +// called in MatchPath. +// TODO(leecam): remove dependency on std::string +bool BrokerFilePermission::CheckOpen(const char* requested_filename, + int flags, + const char** file_to_open, + bool* unlink_after_open) const { + if (!ValidatePath(requested_filename)) + return false; + + if (!MatchPath(requested_filename)) { + return false; + } + + // First, check the access mode is valid. + const int access_mode = flags & O_ACCMODE; + if (access_mode != O_RDONLY && access_mode != O_WRONLY && + access_mode != O_RDWR) { + return false; + } + + // Check if read is allowed + if (!allow_read_ && (access_mode == O_RDONLY || access_mode == O_RDWR)) { + return false; + } + + // Check if write is allowed + if (!allow_write_ && (access_mode == O_WRONLY || access_mode == O_RDWR)) { + return false; + } + + // Check if file creation is allowed. + if (!allow_create_ && (flags & O_CREAT)) { + return false; + } + + // If O_CREAT is present, ensure O_EXCL + if ((flags & O_CREAT) && !(flags & O_EXCL)) { + return false; + } + + // If this file is to be unlinked, ensure it's created. + if (unlink_ && !(flags & O_CREAT)) { + return false; + } + + // Some flags affect the behavior of the current process. We don't support + // them and don't allow them for now. + if (flags & kCurrentProcessOpenFlagsMask) { + return false; + } + + // Now check that all the flags are known to us. + const int creation_and_status_flags = flags & ~O_ACCMODE; + + const int known_flags = O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT | + O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME | + O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY | + O_SYNC | O_TRUNC; + + const int unknown_flags = ~known_flags; + const bool has_unknown_flags = creation_and_status_flags & unknown_flags; + + if (has_unknown_flags) + return false; + + if (file_to_open) { + if (!recursive_) + *file_to_open = path_.c_str(); + else + *file_to_open = requested_filename; + } + if (unlink_after_open) + *unlink_after_open = unlink_; + + return true; +} + +const char* BrokerFilePermission::GetErrorMessageForTests() { + static char kInvalidBrokerFileString[] = "Invalid BrokerFilePermission"; + return kInvalidBrokerFileString; +} + +BrokerFilePermission::BrokerFilePermission(const std::string& path, + bool recursive, + bool unlink, + bool allow_read, + bool allow_write, + bool allow_create) + : path_(path), + recursive_(recursive), + unlink_(unlink), + allow_read_(allow_read), + allow_write_(allow_write), + allow_create_(allow_create) { + // Validate this permission and die if invalid! + + // Must have enough length for a '/' + CHECK(path_.length() > 0) << GetErrorMessageForTests(); + // Whitelisted paths must be absolute. + CHECK(path_[0] == '/') << GetErrorMessageForTests(); + + // Don't allow unlinking on creation without create permission + if (unlink_) { + CHECK(allow_create) << GetErrorMessageForTests(); + } + const char last_char = *(path_.rbegin()); + // Recursive paths must have a trailing slash + if (recursive_) { + CHECK(last_char == '/') << GetErrorMessageForTests(); + } else { + CHECK(last_char != '/') << GetErrorMessageForTests(); + } +} + +} // namespace syscall_broker + +} // namespace sandbox
\ No newline at end of file diff --git a/sandbox/linux/syscall_broker/broker_file_permission.h b/sandbox/linux/syscall_broker/broker_file_permission.h new file mode 100644 index 0000000000..03300d1d74 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_file_permission.h @@ -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. + +#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_ + +#include <string> + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +namespace syscall_broker { + +// BrokerFilePermission defines a path for whitelisting. +// Pick the correct static factory method to create a permission. +// CheckOpen and CheckAccess are async signal safe. +// Constuction and Destruction are not async signal safe. +// |path| is the path to be whitelisted. +class SANDBOX_EXPORT BrokerFilePermission { + public: + ~BrokerFilePermission() {} + BrokerFilePermission(const BrokerFilePermission&) = default; + BrokerFilePermission& operator=(const BrokerFilePermission&) = default; + + static BrokerFilePermission ReadOnly(const std::string& path) { + return BrokerFilePermission(path, false, false, true, false, false); + } + + static BrokerFilePermission ReadOnlyRecursive(const std::string& path) { + return BrokerFilePermission(path, true, false, true, false, false); + } + + static BrokerFilePermission WriteOnly(const std::string& path) { + return BrokerFilePermission(path, false, false, false, true, false); + } + + static BrokerFilePermission ReadWrite(const std::string& path) { + return BrokerFilePermission(path, false, false, true, true, false); + } + + static BrokerFilePermission ReadWriteCreate(const std::string& path) { + return BrokerFilePermission(path, false, false, true, true, true); + } + + static BrokerFilePermission ReadWriteCreateUnlink(const std::string& path) { + return BrokerFilePermission(path, false, true, true, true, true); + } + + static BrokerFilePermission ReadWriteCreateUnlinkRecursive( + const std::string& path) { + return BrokerFilePermission(path, true, true, true, true, true); + } + + // Returns true if |requested_filename| is allowed to be opened + // by this permission. + // If |file_to_open| is not NULL it is set to point to either + // the |requested_filename| in the case of a recursive match, + // or a pointer the matched path in the whitelist if an absolute + // match. + // If not NULL |unlink_after_open| is set to point to true if the + // caller should unlink the path after openning. + // Async signal safe if |file_to_open| is NULL. + bool CheckOpen(const char* requested_filename, + int flags, + const char** file_to_open, + bool* unlink_after_open) const; + // Returns true if |requested_filename| is allowed to be accessed + // by this permission as per access(2). + // If |file_to_open| is not NULL it is set to point to either + // the |requested_filename| in the case of a recursive match, + // or a pointer to the matched path in the whitelist if an absolute + // match. + // |mode| is per mode argument of access(2). + // Async signal safe if |file_to_access| is NULL + bool CheckAccess(const char* requested_filename, + int mode, + const char** file_to_access) const; + + private: + friend class BrokerFilePermissionTester; + BrokerFilePermission(const std::string& path, + bool recursive, + bool unlink, + bool allow_read, + bool allow_write, + bool allow_create); + + // ValidatePath checks |path| and returns true if these conditions are met + // * Greater than 0 length + // * Is an absolute path + // * No trailing slash + // * No /../ path traversal + static bool ValidatePath(const char* path); + + // MatchPath returns true if |requested_filename| is covered by this instance + bool MatchPath(const char* requested_filename) const; + + // Used in by BrokerFilePermissionTester for tests. + static const char* GetErrorMessageForTests(); + + // These are not const as std::vector requires copy-assignment and this class + // is stored in vectors. All methods are marked const so + // the compiler will still enforce no changes outside of the constructor. + std::string path_; + bool recursive_; // Allow everything under this path. |path| must be a dir. + bool unlink_; // unlink after opening. + bool allow_read_; + bool allow_write_; + bool allow_create_; +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_
\ No newline at end of file diff --git a/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc b/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc new file mode 100644 index 0000000000..b58a901cde --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc @@ -0,0 +1,262 @@ +// 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/syscall_broker/broker_file_permission.h" + +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace syscall_broker { + +class BrokerFilePermissionTester { + public: + static bool ValidatePath(const char* path) { + return BrokerFilePermission::ValidatePath(path); + } + static const char* GetErrorMessage() { + return BrokerFilePermission::GetErrorMessageForTests(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BrokerFilePermissionTester); +}; + +namespace { + +// Creation tests are DEATH tests as a bad permission causes termination. +SANDBOX_TEST(BrokerFilePermission, CreateGood) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath); +} + +SANDBOX_TEST(BrokerFilePermission, CreateGoodRecursive) { + const char kPath[] = "/tmp/good/"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath); +} + +SANDBOX_DEATH_TEST( + BrokerFilePermission, + CreateBad, + DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) { + const char kPath[] = "/tmp/bad/"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath); +} + +SANDBOX_DEATH_TEST( + BrokerFilePermission, + CreateBadRecursive, + DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) { + const char kPath[] = "/tmp/bad"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath); +} + +SANDBOX_DEATH_TEST( + BrokerFilePermission, + CreateBadNotAbs, + DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) { + const char kPath[] = "tmp/bad"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath); +} + +SANDBOX_DEATH_TEST( + BrokerFilePermission, + CreateBadEmpty, + DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) { + const char kPath[] = ""; + BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath); +} + +// CheckPerm tests |path| against |perm| given |access_flags|. +// If |create| is true then file creation is tested for success. +void CheckPerm(const BrokerFilePermission& perm, + const char* path, + int access_flags, + bool create) { + const char* file_to_open = NULL; + + ASSERT_FALSE(perm.CheckAccess(path, X_OK, NULL)); + ASSERT_TRUE(perm.CheckAccess(path, F_OK, NULL)); + // check bad perms + switch (access_flags) { + case O_RDONLY: + ASSERT_TRUE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL)); + ASSERT_FALSE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL)); + ASSERT_FALSE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL)); + ASSERT_TRUE(perm.CheckAccess(path, R_OK, NULL)); + ASSERT_FALSE(perm.CheckAccess(path, W_OK, NULL)); + break; + case O_WRONLY: + ASSERT_FALSE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL)); + ASSERT_TRUE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL)); + ASSERT_FALSE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL)); + ASSERT_FALSE(perm.CheckAccess(path, R_OK, NULL)); + ASSERT_TRUE(perm.CheckAccess(path, W_OK, NULL)); + break; + case O_RDWR: + ASSERT_TRUE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL)); + ASSERT_TRUE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL)); + ASSERT_TRUE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL)); + ASSERT_TRUE(perm.CheckAccess(path, R_OK, NULL)); + ASSERT_TRUE(perm.CheckAccess(path, W_OK, NULL)); + break; + default: + // Bad test case + NOTREACHED(); + } + +// O_SYNC can be defined as (__O_SYNC|O_DSYNC) +#ifdef O_DSYNC + const int kSyncFlag = O_SYNC & ~O_DSYNC; +#else + const int kSyncFlag = O_SYNC; +#endif + + const int kNumberOfBitsInOAccMode = 2; + static_assert(O_ACCMODE == ((1 << kNumberOfBitsInOAccMode) - 1), + "incorrect number of bits"); + // check every possible flag and act accordingly. + // Skipping AccMode bits as they are present in every case. + for (int i = kNumberOfBitsInOAccMode; i < 32; i++) { + int flag = 1 << i; + switch (flag) { + case O_APPEND: + case O_ASYNC: + case O_DIRECT: + case O_DIRECTORY: +#ifdef O_DSYNC + case O_DSYNC: +#endif + case O_EXCL: + case O_LARGEFILE: + case O_NOATIME: + case O_NOCTTY: + case O_NOFOLLOW: + case O_NONBLOCK: +#if (O_NONBLOCK != O_NDELAY) + case O_NDELAY: +#endif + case kSyncFlag: + case O_TRUNC: + ASSERT_TRUE( + perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL)); + break; + case O_CLOEXEC: + case O_CREAT: + default: + ASSERT_FALSE( + perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL)); + } + } + if (create) { + bool unlink; + ASSERT_TRUE(perm.CheckOpen(path, O_CREAT | O_EXCL | access_flags, + &file_to_open, &unlink)); + ASSERT_FALSE(unlink); + } else { + ASSERT_FALSE(perm.CheckOpen(path, O_CREAT | O_EXCL | access_flags, + &file_to_open, NULL)); + } +} + +TEST(BrokerFilePermission, ReadOnly) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath); + CheckPerm(perm, kPath, O_RDONLY, false); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, ReadOnlyRecursive) { + const char kPath[] = "/tmp/good/"; + const char kPathFile[] = "/tmp/good/file"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath); + CheckPerm(perm, kPathFile, O_RDONLY, false); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, WriteOnly) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = BrokerFilePermission::WriteOnly(kPath); + CheckPerm(perm, kPath, O_WRONLY, false); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, ReadWrite) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = BrokerFilePermission::ReadWrite(kPath); + CheckPerm(perm, kPath, O_RDWR, false); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, ReadWriteCreate) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = BrokerFilePermission::ReadWriteCreate(kPath); + CheckPerm(perm, kPath, O_RDWR, true); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +void CheckUnlink(BrokerFilePermission& perm, + const char* path, + int access_flags) { + bool unlink; + ASSERT_FALSE(perm.CheckOpen(path, access_flags, NULL, &unlink)); + ASSERT_FALSE(perm.CheckOpen(path, access_flags | O_CREAT, NULL, &unlink)); + ASSERT_TRUE( + perm.CheckOpen(path, access_flags | O_CREAT | O_EXCL, NULL, &unlink)); + ASSERT_TRUE(unlink); +} + +TEST(BrokerFilePermission, ReadWriteCreateUnlink) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = + BrokerFilePermission::ReadWriteCreateUnlink(kPath); + CheckUnlink(perm, kPath, O_RDWR); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, ReadWriteCreateUnlinkRecursive) { + const char kPath[] = "/tmp/good/"; + const char kPathFile[] = "/tmp/good/file"; + BrokerFilePermission perm = + BrokerFilePermission::ReadWriteCreateUnlinkRecursive(kPath); + CheckUnlink(perm, kPathFile, O_RDWR); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, ValidatePath) { + EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/path")); + EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/")); + EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/..path")); + + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("bad")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("bad/")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/..")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/../bad")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/../bad")); +} + +} // namespace + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_host.cc b/sandbox/linux/syscall_broker/broker_host.cc new file mode 100644 index 0000000000..e5957ed224 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_host.cc @@ -0,0 +1,231 @@ +// 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/syscall_broker/broker_host.h" + +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "base/third_party/valgrind/valgrind.h" +#include "sandbox/linux/syscall_broker/broker_common.h" +#include "sandbox/linux/syscall_broker/broker_policy.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { + +namespace syscall_broker { + +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) { + // Hardcode mode to rw------- when creating files. + int mode; + if (flags & O_CREAT) { + mode = 0600; + } else { + 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); + } +} + +// Open |requested_filename| with |flags| if allowed by our policy. +// Write the syscall return value (-errno) to |write_pickle| and append +// a file descriptor to |opened_files| if relevant. +void OpenFileForIPC(const BrokerPolicy& policy, + const std::string& requested_filename, + int flags, + base::Pickle* write_pickle, + std::vector<int>* opened_files) { + DCHECK(write_pickle); + DCHECK(opened_files); + const char* file_to_open = NULL; + bool unlink_after_open = false; + const bool safe_to_open_file = policy.GetFileNameIfAllowedToOpen( + requested_filename.c_str(), flags, &file_to_open, &unlink_after_open); + + if (safe_to_open_file) { + CHECK(file_to_open); + int opened_fd = sys_open(file_to_open, flags); + if (opened_fd < 0) { + write_pickle->WriteInt(-errno); + } else { + // Success. + if (unlink_after_open) { + unlink(file_to_open); + } + opened_files->push_back(opened_fd); + write_pickle->WriteInt(0); + } + } else { + write_pickle->WriteInt(-policy.denied_errno()); + } +} + +// Perform access(2) on |requested_filename| with mode |mode| if allowed by our +// policy. Write the syscall return value (-errno) to |write_pickle|. +void AccessFileForIPC(const BrokerPolicy& policy, + const std::string& requested_filename, + int mode, + base::Pickle* write_pickle) { + DCHECK(write_pickle); + const char* file_to_access = NULL; + const bool safe_to_access_file = policy.GetFileNameIfAllowedToAccess( + requested_filename.c_str(), mode, &file_to_access); + + if (safe_to_access_file) { + CHECK(file_to_access); + int access_ret = access(file_to_access, mode); + int access_errno = errno; + if (!access_ret) + write_pickle->WriteInt(0); + else + write_pickle->WriteInt(-access_errno); + } else { + write_pickle->WriteInt(-policy.denied_errno()); + } +} + +// Handle a |command_type| request contained in |iter| and send the reply +// on |reply_ipc|. +// Currently COMMAND_OPEN and COMMAND_ACCESS are supported. +bool HandleRemoteCommand(const BrokerPolicy& policy, + IPCCommand command_type, + int reply_ipc, + base::PickleIterator iter) { + // Currently all commands have two arguments: filename and flags. + std::string requested_filename; + int flags = 0; + if (!iter.ReadString(&requested_filename) || !iter.ReadInt(&flags)) + return false; + + base::Pickle write_pickle; + std::vector<int> opened_files; + + switch (command_type) { + case COMMAND_ACCESS: + AccessFileForIPC(policy, requested_filename, flags, &write_pickle); + break; + case COMMAND_OPEN: + OpenFileForIPC( + policy, requested_filename, flags, &write_pickle, &opened_files); + break; + default: + LOG(ERROR) << "Invalid IPC command"; + break; + } + + CHECK_LE(write_pickle.size(), kMaxMessageLength); + ssize_t sent = base::UnixDomainSocket::SendMsg( + reply_ipc, write_pickle.data(), write_pickle.size(), opened_files); + + // Close anything we have opened in this process. + for (std::vector<int>::iterator it = opened_files.begin(); + it != opened_files.end(); + ++it) { + int ret = IGNORE_EINTR(close(*it)); + DCHECK(!ret) << "Could not close file descriptor"; + } + + if (sent <= 0) { + LOG(ERROR) << "Could not send IPC reply"; + return false; + } + return true; +} + +} // namespace + +BrokerHost::BrokerHost(const BrokerPolicy& broker_policy, + BrokerChannel::EndPoint ipc_channel) + : broker_policy_(broker_policy), ipc_channel_(ipc_channel.Pass()) { +} + +BrokerHost::~BrokerHost() { +} + +// Handle a request on the IPC channel ipc_channel_. +// A request should have a file descriptor attached on which we will reply and +// that we will then close. +// A request should start with an int that will be used as the command type. +BrokerHost::RequestStatus BrokerHost::HandleRequest() const { + ScopedVector<base::ScopedFD> fds; + char buf[kMaxMessageLength]; + errno = 0; + const ssize_t msg_len = base::UnixDomainSocket::RecvMsg( + ipc_channel_.get(), buf, sizeof(buf), &fds); + + if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) { + // EOF from the client, or the client died, we should die. + return RequestStatus::LOST_CLIENT; + } + + // The client should send exactly one file descriptor, on which we + // will write the reply. + // 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 RequestStatus::FAILURE; + } + + base::ScopedFD temporary_ipc(fds[0]->Pass()); + + base::Pickle pickle(buf, msg_len); + base::PickleIterator iter(pickle); + int command_type; + if (iter.ReadInt(&command_type)) { + bool command_handled = false; + // Go through all the possible IPC messages. + switch (command_type) { + case COMMAND_ACCESS: + case COMMAND_OPEN: + // We reply on the file descriptor sent to us via the IPC channel. + command_handled = HandleRemoteCommand( + broker_policy_, static_cast<IPCCommand>(command_type), + temporary_ipc.get(), iter); + break; + default: + NOTREACHED(); + break; + } + + if (command_handled) { + return RequestStatus::SUCCESS; + } else { + return RequestStatus::FAILURE; + } + + NOTREACHED(); + } + + LOG(ERROR) << "Error parsing IPC request"; + return RequestStatus::FAILURE; +} + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_host.h b/sandbox/linux/syscall_broker/broker_host.h new file mode 100644 index 0000000000..9866507d1c --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_host.h @@ -0,0 +1,41 @@ +// 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_SYSCALL_BROKER_BROKER_HOST_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_HOST_H_ + +#include "base/macros.h" +#include "sandbox/linux/syscall_broker/broker_channel.h" + +namespace sandbox { + +namespace syscall_broker { + +class BrokerPolicy; + +// The BrokerHost class should be embedded in a (presumably not sandboxed) +// process. It will honor IPC requests from a BrokerClient sent over +// |ipc_channel| according to |broker_policy|. +class BrokerHost { + public: + enum class RequestStatus { LOST_CLIENT = 0, SUCCESS, FAILURE }; + + BrokerHost(const BrokerPolicy& broker_policy, + BrokerChannel::EndPoint ipc_channel); + ~BrokerHost(); + + RequestStatus HandleRequest() const; + + private: + const BrokerPolicy& broker_policy_; + const BrokerChannel::EndPoint ipc_channel_; + + DISALLOW_COPY_AND_ASSIGN(BrokerHost); +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_HOST_H_ diff --git a/sandbox/linux/syscall_broker/broker_policy.cc b/sandbox/linux/syscall_broker/broker_policy.cc new file mode 100644 index 0000000000..d9f69e3b81 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_policy.cc @@ -0,0 +1,99 @@ +// 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/syscall_broker/broker_policy.h" + +#include <fcntl.h> +#include <stdint.h> +#include <string.h> + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "sandbox/linux/syscall_broker/broker_common.h" + +namespace sandbox { +namespace syscall_broker { + +BrokerPolicy::BrokerPolicy(int denied_errno, + const std::vector<BrokerFilePermission>& permissions) + : denied_errno_(denied_errno), + permissions_(permissions), + num_of_permissions_(permissions.size()) { + // The spec guarantees vectors store their elements contiguously + // so set up a pointer to array of element so it can be used + // in async signal safe code instead of vector operations. + if (num_of_permissions_ > 0) { + permissions_array_ = &permissions_[0]; + } else { + permissions_array_ = NULL; + } +} + +BrokerPolicy::~BrokerPolicy() { +} + +// Check if calling access() should be allowed on |requested_filename| with +// mode |requested_mode|. +// Note: access() being a system call to check permissions, this can get a bit +// confusing. We're checking if calling access() should even be allowed with +// the same policy we would use for open(). +// If |file_to_access| is not NULL, we will return the matching pointer from +// the whitelist. For paranoia a caller should then use |file_to_access|. See +// GetFileNameIfAllowedToOpen() for more explanation. +// return true if calling access() on this file should be allowed, false +// otherwise. +// Async signal safe if and only if |file_to_access| is NULL. +bool BrokerPolicy::GetFileNameIfAllowedToAccess( + const char* requested_filename, + int requested_mode, + const char** file_to_access) const { + if (file_to_access && *file_to_access) { + // Make sure that callers never pass a non-empty string. In case callers + // wrongly forget to check the return value and look at the string + // instead, this could catch bugs. + RAW_LOG(FATAL, "*file_to_access should be NULL"); + return false; + } + for (size_t i = 0; i < num_of_permissions_; i++) { + if (permissions_array_[i].CheckAccess(requested_filename, requested_mode, + file_to_access)) { + return true; + } + } + return false; +} + +// Check if |requested_filename| can be opened with flags |requested_flags|. +// If |file_to_open| is not NULL, we will return the matching pointer from the +// whitelist. For paranoia, a caller should then use |file_to_open| rather +// than |requested_filename|, so that it never attempts to open an +// attacker-controlled file name, even if an attacker managed to fool the +// string comparison mechanism. +// Return true if opening should be allowed, false otherwise. +// Async signal safe if and only if |file_to_open| is NULL. +bool BrokerPolicy::GetFileNameIfAllowedToOpen(const char* requested_filename, + int requested_flags, + const char** file_to_open, + bool* unlink_after_open) const { + if (file_to_open && *file_to_open) { + // Make sure that callers never pass a non-empty string. In case callers + // wrongly forget to check the return value and look at the string + // instead, this could catch bugs. + RAW_LOG(FATAL, "*file_to_open should be NULL"); + return false; + } + for (size_t i = 0; i < num_of_permissions_; i++) { + if (permissions_array_[i].CheckOpen(requested_filename, requested_flags, + file_to_open, unlink_after_open)) { + return true; + } + } + return false; +} + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_policy.h b/sandbox/linux/syscall_broker/broker_policy.h new file mode 100644 index 0000000000..d5146edc06 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_policy.h @@ -0,0 +1,87 @@ +// 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_SYSCALL_BROKER_BROKER_POLICY_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_POLICY_H_ + +#include <string> +#include <vector> + +#include "base/macros.h" + +#include "sandbox/linux/syscall_broker/broker_file_permission.h" + +namespace sandbox { +namespace syscall_broker { + +// BrokerPolicy allows to define the security policy enforced by a +// BrokerHost. The BrokerHost will evaluate requests sent over its +// IPC channel according to the BrokerPolicy. +// Some of the methods of this class can be used in an async-signal safe +// way. +class BrokerPolicy { + public: + // |denied_errno| is the error code returned when IPC requests for system + // calls such as open() or access() are denied because a file is not in the + // whitelist. EACCESS would be a typical value. + // |permissions| is a list of BrokerPermission objects that define + // what the broker will allow. + BrokerPolicy(int denied_errno, + const std::vector<BrokerFilePermission>& permissions); + + ~BrokerPolicy(); + + // Check if calling access() should be allowed on |requested_filename| with + // mode |requested_mode|. + // Note: access() being a system call to check permissions, this can get a bit + // confusing. We're checking if calling access() should even be allowed with + // If |file_to_open| is not NULL, a pointer to the path will be returned. + // In the case of a recursive match, this will be the requested_filename, + // otherwise it will return the matching pointer from the + // whitelist. For paranoia a caller should then use |file_to_access|. See + // GetFileNameIfAllowedToOpen() for more explanation. + // return true if calling access() on this file should be allowed, false + // otherwise. + // Async signal safe if and only if |file_to_access| is NULL. + bool GetFileNameIfAllowedToAccess(const char* requested_filename, + int requested_mode, + const char** file_to_access) const; + + // Check if |requested_filename| can be opened with flags |requested_flags|. + // If |file_to_open| is not NULL, a pointer to the path will be returned. + // In the case of a recursive match, this will be the requested_filename, + // otherwise it will return the matching pointer from the + // whitelist. For paranoia, a caller should then use |file_to_open| rather + // than |requested_filename|, so that it never attempts to open an + // attacker-controlled file name, even if an attacker managed to fool the + // string comparison mechanism. + // |unlink_after_open| if not NULL will be set to point to true if the + // policy requests the caller unlink the path after opening. + // Return true if opening should be allowed, false otherwise. + // Async signal safe if and only if |file_to_open| is NULL. + bool GetFileNameIfAllowedToOpen(const char* requested_filename, + int requested_flags, + const char** file_to_open, + bool* unlink_after_open) const; + int denied_errno() const { return denied_errno_; } + + private: + const int denied_errno_; + // The permissions_ vector is used as storage for the BrokerFilePermission + // objects but is not referenced outside of the constructor as + // vectors are unfriendly in async signal safe code. + const std::vector<BrokerFilePermission> permissions_; + // permissions_array_ is set up to point to the backing store of + // permissions_ and is used in async signal safe methods. + const BrokerFilePermission* permissions_array_; + const size_t num_of_permissions_; + + DISALLOW_COPY_AND_ASSIGN(BrokerPolicy); +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_POLICY_H_ diff --git a/sandbox/linux/syscall_broker/broker_process.cc b/sandbox/linux/syscall_broker/broker_process.cc new file mode 100644 index 0000000000..81131cc4e0 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_process.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/syscall_broker/broker_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 <algorithm> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/process_metrics.h" +#include "build/build_config.h" +#include "sandbox/linux/syscall_broker/broker_channel.h" +#include "sandbox/linux/syscall_broker/broker_client.h" +#include "sandbox/linux/syscall_broker/broker_host.h" + +namespace sandbox { + +namespace syscall_broker { + +BrokerProcess::BrokerProcess( + int denied_errno, + const std::vector<syscall_broker::BrokerFilePermission>& permissions, + bool fast_check_in_client, + bool quiet_failures_for_tests) + : initialized_(false), + fast_check_in_client_(fast_check_in_client), + quiet_failures_for_tests_(quiet_failures_for_tests), + broker_pid_(-1), + policy_(denied_errno, permissions) { +} + +BrokerProcess::~BrokerProcess() { + if (initialized_) { + if (broker_client_.get()) { + // Closing the socket should be enough to notify the child to die, + // unless it has been duplicated. + CloseChannel(); + } + 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( + const base::Callback<bool(void)>& broker_process_init_callback) { + CHECK(!initialized_); + BrokerChannel::EndPoint ipc_reader; + BrokerChannel::EndPoint ipc_writer; + BrokerChannel::CreatePair(&ipc_reader, &ipc_writer); + +#if !defined(THREAD_SANITIZER) + DCHECK_EQ(1, base::GetNumberOfThreads(base::GetCurrentProcessHandle())); +#endif + int child_pid = fork(); + if (child_pid == -1) { + return false; + } + if (child_pid) { + // We are the parent and we have just forked our broker process. + ipc_reader.reset(); + broker_pid_ = child_pid; + broker_client_.reset(new BrokerClient(policy_, ipc_writer.Pass(), + fast_check_in_client_, + quiet_failures_for_tests_)); + initialized_ = true; + return true; + } else { + // We are the broker process. Make sure to close the writer's end so that + // we get notified if the client disappears. + ipc_writer.reset(); + CHECK(broker_process_init_callback.Run()); + BrokerHost broker_host(policy_, ipc_reader.Pass()); + for (;;) { + switch (broker_host.HandleRequest()) { + case BrokerHost::RequestStatus::LOST_CLIENT: + _exit(1); + case BrokerHost::RequestStatus::SUCCESS: + case BrokerHost::RequestStatus::FAILURE: + continue; + } + } + _exit(1); + } + NOTREACHED(); + return false; +} + +void BrokerProcess::CloseChannel() { + broker_client_.reset(); +} + +int BrokerProcess::Access(const char* pathname, int mode) const { + RAW_CHECK(initialized_); + return broker_client_->Access(pathname, mode); +} + +int BrokerProcess::Open(const char* pathname, int flags) const { + RAW_CHECK(initialized_); + return broker_client_->Open(pathname, flags); +} + +} // namespace syscall_broker + +} // namespace sandbox. diff --git a/sandbox/linux/syscall_broker/broker_process.h b/sandbox/linux/syscall_broker/broker_process.h new file mode 100644 index 0000000000..8a512a0c12 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_process.h @@ -0,0 +1,94 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ +#define SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ + +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "base/process/process.h" +#include "sandbox/linux/syscall_broker/broker_policy.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +namespace syscall_broker { + +class BrokerClient; +class BrokerFilePermission; + +// Create a new "broker" process to which we can send requests via an IPC +// channel by forking the current process. +// This is a low level IPC mechanism that is suitable to be called from a +// signal handler. +// A process would typically create a broker process before entering +// sandboxing. +// 1. BrokerProcess open_broker(read_whitelist, write_whitelist); +// 2. CHECK(open_broker.Init(NULL)); +// 3. Enable sandbox. +// 4. Use open_broker.Open() to open files. +class 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 + // would be a typical value. + // |allowed_r_files| and |allowed_w_files| are white lists of files that can + // be opened later via the Open() API, respectively for reading and writing. + // A file available read-write should be listed in both. + // |fast_check_in_client| and |quiet_failures_for_tests| are reserved for + // unit tests, don't use it. + + BrokerProcess( + int denied_errno, + const std::vector<syscall_broker::BrokerFilePermission>& permissions, + bool fast_check_in_client = true, + bool quiet_failures_for_tests = false); + + ~BrokerProcess(); + // Will initialize the broker process. There should be no threads at this + // point, since we need to fork(). + // 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 + // doesn't support execute permissions. + // It's similar to the access() system call and will return -errno on errors. + int Access(const char* pathname, int mode) const; + // Can be used in place of open(). Will be async signal safe. + // The implementation only supports certain white listed flags and will + // return -EPERM on other flags. + // It's similar to the open() system call and will return -errno on errors. + int Open(const char* pathname, int flags) const; + + int broker_pid() const { return broker_pid_; } + + private: + friend class BrokerProcessTestHelper; + + // Close the IPC channel with the other party. This should only be used + // by tests an none of the class methods should be used afterwards. + void CloseChannel(); + + bool initialized_; // Whether we've been through Init() yet. + const bool fast_check_in_client_; + const bool quiet_failures_for_tests_; + pid_t broker_pid_; // The PID of the broker (child). + syscall_broker::BrokerPolicy policy_; // The sandboxing policy. + scoped_ptr<syscall_broker::BrokerClient> broker_client_; + + DISALLOW_COPY_AND_ASSIGN(BrokerProcess); +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ diff --git a/sandbox/linux/syscall_broker/broker_process_unittest.cc b/sandbox/linux/syscall_broker/broker_process_unittest.cc new file mode 100644 index 0000000000..9ad0e719de --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_process_unittest.cc @@ -0,0 +1,656 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/syscall_broker/broker_process.h" + +#include <errno.h> +#include <fcntl.h> +#include <poll.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/bind.h" +#include "base/files/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/syscall_broker/broker_client.h" +#include "sandbox/linux/tests/scoped_temporary_file.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace syscall_broker { + +class BrokerProcessTestHelper { + public: + static void CloseChannel(BrokerProcess* broker) { broker->CloseChannel(); } + // Get the client's IPC descriptor to send IPC requests directly. + // TODO(jln): refator tests to get rid of this. + static int GetIPCDescriptor(const BrokerProcess* broker) { + return broker->broker_client_->GetIPCDescriptor(); + } +}; + +namespace { + +bool NoOpCallback() { + return true; +} + +} // namespace + +TEST(BrokerProcess, CreateAndDestroy) { + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly("/proc/cpuinfo")); + + scoped_ptr<BrokerProcess> open_broker(new BrokerProcess(EPERM, permissions)); + ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + + ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); + // Destroy the broker and check it has exited properly. + open_broker.reset(); + ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); +} + +TEST(BrokerProcess, TestOpenAccessNull) { + std::vector<BrokerFilePermission> empty; + BrokerProcess open_broker(EPERM, empty); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + int fd = open_broker.Open(NULL, O_RDONLY); + ASSERT_EQ(fd, -EFAULT); + + int ret = open_broker.Access(NULL, F_OK); + ASSERT_EQ(ret, -EFAULT); +} + +void TestOpenFilePerms(bool fast_check_in_client, int denied_errno) { + const char kR_WhiteListed[] = "/proc/DOESNOTEXIST1"; + // We can't debug the init process, and shouldn't be able to access + // its auxv file. + const char kR_WhiteListedButDenied[] = "/proc/1/auxv"; + const char kW_WhiteListed[] = "/proc/DOESNOTEXIST2"; + const char kRW_WhiteListed[] = "/proc/DOESNOTEXIST3"; + const char k_NotWhitelisted[] = "/proc/DOESNOTEXIST4"; + + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(kR_WhiteListed)); + permissions.push_back( + BrokerFilePermission::ReadOnly(kR_WhiteListedButDenied)); + permissions.push_back(BrokerFilePermission::WriteOnly(kW_WhiteListed)); + permissions.push_back(BrokerFilePermission::ReadWrite(kRW_WhiteListed)); + + BrokerProcess open_broker(denied_errno, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + int fd = -1; + fd = open_broker.Open(kR_WhiteListed, O_RDONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kR_WhiteListed, O_WRONLY); + ASSERT_EQ(fd, -denied_errno); + fd = open_broker.Open(kR_WhiteListed, O_RDWR); + ASSERT_EQ(fd, -denied_errno); + int ret = -1; + ret = open_broker.Access(kR_WhiteListed, F_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kR_WhiteListed, R_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kR_WhiteListed, W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListed, R_OK | W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListed, X_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListed, R_OK | X_OK); + ASSERT_EQ(ret, -denied_errno); + + // Android sometimes runs tests as root. + // This part of the test requires a process that doesn't have + // CAP_DAC_OVERRIDE. We check against a root euid as a proxy for that. + if (geteuid()) { + fd = open_broker.Open(kR_WhiteListedButDenied, O_RDONLY); + // The broker process will allow this, but the normal permission system + // won't. + ASSERT_EQ(fd, -EACCES); + fd = open_broker.Open(kR_WhiteListedButDenied, O_WRONLY); + ASSERT_EQ(fd, -denied_errno); + fd = open_broker.Open(kR_WhiteListedButDenied, O_RDWR); + ASSERT_EQ(fd, -denied_errno); + ret = open_broker.Access(kR_WhiteListedButDenied, F_OK); + // The normal permission system will let us check that the file exists. + ASSERT_EQ(ret, 0); + ret = open_broker.Access(kR_WhiteListedButDenied, R_OK); + ASSERT_EQ(ret, -EACCES); + ret = open_broker.Access(kR_WhiteListedButDenied, W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListedButDenied, R_OK | W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListedButDenied, X_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListedButDenied, R_OK | X_OK); + ASSERT_EQ(ret, -denied_errno); + } + + fd = open_broker.Open(kW_WhiteListed, O_RDONLY); + ASSERT_EQ(fd, -denied_errno); + fd = open_broker.Open(kW_WhiteListed, O_WRONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kW_WhiteListed, O_RDWR); + ASSERT_EQ(fd, -denied_errno); + ret = open_broker.Access(kW_WhiteListed, F_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kW_WhiteListed, R_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kW_WhiteListed, W_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kW_WhiteListed, R_OK | W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kW_WhiteListed, X_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kW_WhiteListed, R_OK | X_OK); + ASSERT_EQ(ret, -denied_errno); + + fd = open_broker.Open(kRW_WhiteListed, O_RDONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kRW_WhiteListed, O_WRONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kRW_WhiteListed, O_RDWR); + ASSERT_EQ(fd, -ENOENT); + ret = open_broker.Access(kRW_WhiteListed, F_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kRW_WhiteListed, R_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kRW_WhiteListed, W_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kRW_WhiteListed, R_OK | W_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kRW_WhiteListed, X_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kRW_WhiteListed, R_OK | X_OK); + ASSERT_EQ(ret, -denied_errno); + + fd = open_broker.Open(k_NotWhitelisted, O_RDONLY); + ASSERT_EQ(fd, -denied_errno); + fd = open_broker.Open(k_NotWhitelisted, O_WRONLY); + ASSERT_EQ(fd, -denied_errno); + fd = open_broker.Open(k_NotWhitelisted, O_RDWR); + ASSERT_EQ(fd, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, F_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, R_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, R_OK | W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, X_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, R_OK | X_OK); + ASSERT_EQ(ret, -denied_errno); + + // We have some extra sanity check for clearly wrong values. + fd = open_broker.Open(kRW_WhiteListed, O_RDONLY | O_WRONLY | O_RDWR); + ASSERT_EQ(fd, -denied_errno); + + // It makes no sense to allow O_CREAT in a 2-parameters open. Ensure this + // is denied. + fd = open_broker.Open(kRW_WhiteListed, O_RDWR | O_CREAT); + ASSERT_EQ(fd, -denied_errno); +} + +// Run the same thing twice. The second time, we make sure that no security +// check is performed on the client. +TEST(BrokerProcess, OpenFilePermsWithClientCheck) { + TestOpenFilePerms(true /* fast_check_in_client */, EPERM); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenOpenFilePermsNoClientCheck) { + TestOpenFilePerms(false /* fast_check_in_client */, EPERM); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +// Run the same twice again, but with ENOENT instead of EPERM. +TEST(BrokerProcess, OpenFilePermsWithClientCheckNoEnt) { + TestOpenFilePerms(true /* fast_check_in_client */, ENOENT); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenOpenFilePermsNoClientCheckNoEnt) { + TestOpenFilePerms(false /* fast_check_in_client */, ENOENT); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +void TestBadPaths(bool fast_check_in_client) { + const char kFileCpuInfo[] = "/proc/cpuinfo"; + const char kNotAbsPath[] = "proc/cpuinfo"; + const char kDotDotStart[] = "/../proc/cpuinfo"; + const char kDotDotMiddle[] = "/proc/self/../cpuinfo"; + const char kDotDotEnd[] = "/proc/.."; + const char kTrailingSlash[] = "/proc/"; + + std::vector<BrokerFilePermission> permissions; + + permissions.push_back(BrokerFilePermission::ReadOnlyRecursive("/proc/")); + scoped_ptr<BrokerProcess> open_broker( + new BrokerProcess(EPERM, permissions, fast_check_in_client)); + ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + // Open cpuinfo via the broker. + int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); + base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); + ASSERT_GE(cpuinfo_fd, 0); + + int fd = -1; + int can_access; + + can_access = open_broker->Access(kNotAbsPath, R_OK); + ASSERT_EQ(can_access, -EPERM); + fd = open_broker->Open(kNotAbsPath, O_RDONLY); + ASSERT_EQ(fd, -EPERM); + + can_access = open_broker->Access(kDotDotStart, R_OK); + ASSERT_EQ(can_access, -EPERM); + fd = open_broker->Open(kDotDotStart, O_RDONLY); + ASSERT_EQ(fd, -EPERM); + + can_access = open_broker->Access(kDotDotMiddle, R_OK); + ASSERT_EQ(can_access, -EPERM); + fd = open_broker->Open(kDotDotMiddle, O_RDONLY); + ASSERT_EQ(fd, -EPERM); + + can_access = open_broker->Access(kDotDotEnd, R_OK); + ASSERT_EQ(can_access, -EPERM); + fd = open_broker->Open(kDotDotEnd, O_RDONLY); + ASSERT_EQ(fd, -EPERM); + + can_access = open_broker->Access(kTrailingSlash, R_OK); + ASSERT_EQ(can_access, -EPERM); + fd = open_broker->Open(kTrailingSlash, O_RDONLY); + ASSERT_EQ(fd, -EPERM); +} + +TEST(BrokerProcess, BadPathsClientCheck) { + TestBadPaths(true /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, BadPathsNoClientCheck) { + TestBadPaths(false /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +void TestOpenCpuinfo(bool fast_check_in_client, bool recursive) { + const char kFileCpuInfo[] = "/proc/cpuinfo"; + const char kDirProc[] = "/proc/"; + + std::vector<BrokerFilePermission> permissions; + if (recursive) + permissions.push_back(BrokerFilePermission::ReadOnlyRecursive(kDirProc)); + else + permissions.push_back(BrokerFilePermission::ReadOnly(kFileCpuInfo)); + + scoped_ptr<BrokerProcess> open_broker( + new BrokerProcess(EPERM, permissions, fast_check_in_client)); + ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + + int fd = -1; + fd = open_broker->Open(kFileCpuInfo, O_RDWR); + base::ScopedFD fd_closer(fd); + ASSERT_EQ(fd, -EPERM); + + // Check we can read /proc/cpuinfo. + int can_access = open_broker->Access(kFileCpuInfo, R_OK); + ASSERT_EQ(can_access, 0); + can_access = open_broker->Access(kFileCpuInfo, W_OK); + ASSERT_EQ(can_access, -EPERM); + // Check we can not write /proc/cpuinfo. + + // Open cpuinfo via the broker. + int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); + base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); + ASSERT_GE(cpuinfo_fd, 0); + char buf[3]; + memset(buf, 0, sizeof(buf)); + int read_len1 = read(cpuinfo_fd, buf, sizeof(buf)); + ASSERT_GT(read_len1, 0); + + // Open cpuinfo directly. + int cpuinfo_fd2 = open(kFileCpuInfo, O_RDONLY); + base::ScopedFD cpuinfo_fd2_closer(cpuinfo_fd2); + ASSERT_GE(cpuinfo_fd2, 0); + char buf2[3]; + memset(buf2, 1, sizeof(buf2)); + int read_len2 = read(cpuinfo_fd2, buf2, sizeof(buf2)); + ASSERT_GT(read_len1, 0); + + // The following is not guaranteed true, but will be in practice. + ASSERT_EQ(read_len1, read_len2); + // Compare the cpuinfo as returned by the broker with the one we opened + // ourselves. + ASSERT_EQ(memcmp(buf, buf2, read_len1), 0); + + ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); + open_broker.reset(); + ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); +} + +// Run this test 4 times. With and without the check in client +// and using a recursive path. +TEST(BrokerProcess, OpenCpuinfoWithClientCheck) { + TestOpenCpuinfo(true /* fast_check_in_client */, false /* not recursive */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenCpuinfoNoClientCheck) { + TestOpenCpuinfo(false /* fast_check_in_client */, false /* not recursive */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenCpuinfoWithClientCheckRecursive) { + TestOpenCpuinfo(true /* fast_check_in_client */, true /* recursive */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenCpuinfoNoClientCheckRecursive) { + TestOpenCpuinfo(false /* fast_check_in_client */, true /* recursive */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenFileRW) { + ScopedTemporaryFile tempfile; + const char* tempfile_name = tempfile.full_file_name(); + + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadWrite(tempfile_name)); + + BrokerProcess open_broker(EPERM, permissions); + 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); + ASSERT_EQ(can_access, 0); + + int tempfile2 = -1; + tempfile2 = open_broker.Open(tempfile_name, O_RDWR); + ASSERT_GE(tempfile2, 0); + + // Write to the descriptor opened by the broker. + char test_text[] = "TESTTESTTEST"; + ssize_t len = write(tempfile2, test_text, sizeof(test_text)); + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(test_text))); + + // Read back from the original file descriptor what we wrote through + // the descriptor provided by the broker. + char buf[1024]; + len = read(tempfile.fd(), buf, sizeof(buf)); + + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(test_text))); + ASSERT_EQ(memcmp(test_text, buf, sizeof(test_text)), 0); + + ASSERT_EQ(close(tempfile2), 0); +} + +// SANDBOX_TEST because the process could die with a SIGPIPE +// and we want this to happen in a subprocess. +SANDBOX_TEST(BrokerProcess, BrokerDied) { + const char kCpuInfo[] = "/proc/cpuinfo"; + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); + + BrokerProcess open_broker(EPERM, permissions, true /* fast_check_in_client */, + true /* quiet_failures_for_tests */); + 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 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(kCpuInfo, O_RDONLY) == -ENOMEM); + SANDBOX_ASSERT(open_broker.Access(kCpuInfo, O_RDONLY) == -ENOMEM); +} + +void TestOpenComplexFlags(bool fast_check_in_client) { + const char kCpuInfo[] = "/proc/cpuinfo"; + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); + + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + 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; + fd = open_broker.Open(kCpuInfo, O_RDONLY); + ASSERT_GE(fd, 0); + ret = fcntl(fd, F_GETFL); + ASSERT_NE(-1, ret); + // The descriptor shouldn't have the O_CLOEXEC attribute, nor O_NONBLOCK. + ASSERT_EQ(0, ret & (O_CLOEXEC | O_NONBLOCK)); + ASSERT_EQ(0, close(fd)); + + fd = open_broker.Open(kCpuInfo, O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ret = fcntl(fd, F_GETFD); + ASSERT_NE(-1, ret); + // Important: use F_GETFD, not F_GETFL. The O_CLOEXEC flag in F_GETFL + // is actually not used by the kernel. + ASSERT_TRUE(FD_CLOEXEC & ret); + ASSERT_EQ(0, close(fd)); + + fd = open_broker.Open(kCpuInfo, O_RDONLY | O_NONBLOCK); + ASSERT_GE(fd, 0); + ret = fcntl(fd, F_GETFL); + ASSERT_NE(-1, ret); + ASSERT_TRUE(O_NONBLOCK & ret); + ASSERT_EQ(0, close(fd)); +} + +TEST(BrokerProcess, OpenComplexFlagsWithClientCheck) { + TestOpenComplexFlags(true /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenComplexFlagsNoClientCheck) { + TestOpenComplexFlags(false /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +// We need to allow noise because the broker will log when it receives our +// bogus IPCs. +SANDBOX_TEST_ALLOW_NOISE(BrokerProcess, RecvMsgDescriptorLeak) { + // Android creates a socket on first use of the LOG call. + // We need to ensure this socket is open before we + // begin the test. + LOG(INFO) << "Ensure Android LOG socket is allocated"; + + // 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<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); + + BrokerProcess open_broker(EPERM, permissions); + SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback))); + + const int ipc_fd = BrokerProcessTestHelper::GetIPCDescriptor(&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( + base::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))); +} + +bool CloseFD(int fd) { + PCHECK(0 == IGNORE_EINTR(close(fd))); + return true; +} + +// Return true if the other end of the |reader| pipe was closed, +// false if |timeout_in_seconds| was reached or another event +// or error occured. +bool WaitForClosedPipeWriter(int reader, int timeout_in_ms) { + struct pollfd poll_fd = {reader, POLLIN | POLLRDHUP, 0}; + const int num_events = HANDLE_EINTR(poll(&poll_fd, 1, timeout_in_ms)); + if (1 == num_events && poll_fd.revents | POLLHUP) + return true; + return false; +} + +// Closing the broker client's IPC channel should terminate the broker +// process. +TEST(BrokerProcess, BrokerDiesOnClosedChannel) { + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly("/proc/cpuinfo")); + + // Get the writing end of a pipe into the broker (child) process so + // that we can reliably detect when it dies. + int lifeline_fds[2]; + PCHECK(0 == pipe(lifeline_fds)); + + BrokerProcess open_broker(EPERM, permissions, true /* fast_check_in_client */, + false /* quiet_failures_for_tests */); + ASSERT_TRUE(open_broker.Init(base::Bind(&CloseFD, lifeline_fds[0]))); + // Make sure the writing end only exists in the broker process. + CloseFD(lifeline_fds[1]); + base::ScopedFD reader(lifeline_fds[0]); + + const pid_t broker_pid = open_broker.broker_pid(); + + // This should cause the broker process to exit. + BrokerProcessTestHelper::CloseChannel(&open_broker); + + const int kTimeoutInMilliseconds = 5000; + const bool broker_lifeline_closed = + WaitForClosedPipeWriter(reader.get(), kTimeoutInMilliseconds); + // If the broker exited, its lifeline fd should be closed. + ASSERT_TRUE(broker_lifeline_closed); + // Now check that the broker has exited, but do not reap it. + siginfo_t process_info; + ASSERT_EQ(0, HANDLE_EINTR(waitid(P_PID, broker_pid, &process_info, + WEXITED | WNOWAIT))); + EXPECT_EQ(broker_pid, process_info.si_pid); + EXPECT_EQ(CLD_EXITED, process_info.si_code); + EXPECT_EQ(1, process_info.si_status); +} + +TEST(BrokerProcess, CreateFile) { + std::string temp_str; + { + ScopedTemporaryFile tmp_file; + temp_str = tmp_file.full_file_name(); + } + const char* tempfile_name = temp_str.c_str(); + + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadWriteCreate(tempfile_name)); + + BrokerProcess open_broker(EPERM, permissions); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + int fd = -1; + + // Try without O_EXCL + fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT); + ASSERT_EQ(fd, -EPERM); + + const char kTestText[] = "TESTTESTTEST"; + // Create a file + fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT | O_EXCL); + ASSERT_GE(fd, 0); + { + base::ScopedFD scoped_fd(fd); + + // Confirm fail if file exists + int bad_fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT | O_EXCL); + ASSERT_EQ(bad_fd, -EEXIST); + + // Write to the descriptor opened by the broker. + + ssize_t len = HANDLE_EINTR(write(fd, kTestText, sizeof(kTestText))); + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(kTestText))); + } + + int fd_check = open(tempfile_name, O_RDONLY); + ASSERT_GE(fd_check, 0); + { + base::ScopedFD scoped_fd(fd_check); + char buf[1024]; + ssize_t len = HANDLE_EINTR(read(fd_check, buf, sizeof(buf))); + + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(kTestText))); + ASSERT_EQ(memcmp(kTestText, buf, sizeof(kTestText)), 0); + } +} + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/system_headers/arm64_linux_syscalls.h b/sandbox/linux/system_headers/arm64_linux_syscalls.h new file mode 100644 index 0000000000..8acb2d1000 --- /dev/null +++ b/sandbox/linux/system_headers/arm64_linux_syscalls.h @@ -0,0 +1,1062 @@ +// 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_SYSTEM_HEADERS_ARM64_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_ARM64_LINUX_SYSCALLS_H_ + +#include <asm-generic/unistd.h> + +#if !defined(__NR_io_setup) +#define __NR_io_setup 0 +#endif + +#if !defined(__NR_io_destroy) +#define __NR_io_destroy 1 +#endif + +#if !defined(__NR_io_submit) +#define __NR_io_submit 2 +#endif + +#if !defined(__NR_io_cancel) +#define __NR_io_cancel 3 +#endif + +#if !defined(__NR_io_getevents) +#define __NR_io_getevents 4 +#endif + +#if !defined(__NR_setxattr) +#define __NR_setxattr 5 +#endif + +#if !defined(__NR_lsetxattr) +#define __NR_lsetxattr 6 +#endif + +#if !defined(__NR_fsetxattr) +#define __NR_fsetxattr 7 +#endif + +#if !defined(__NR_getxattr) +#define __NR_getxattr 8 +#endif + +#if !defined(__NR_lgetxattr) +#define __NR_lgetxattr 9 +#endif + +#if !defined(__NR_fgetxattr) +#define __NR_fgetxattr 10 +#endif + +#if !defined(__NR_listxattr) +#define __NR_listxattr 11 +#endif + +#if !defined(__NR_llistxattr) +#define __NR_llistxattr 12 +#endif + +#if !defined(__NR_flistxattr) +#define __NR_flistxattr 13 +#endif + +#if !defined(__NR_removexattr) +#define __NR_removexattr 14 +#endif + +#if !defined(__NR_lremovexattr) +#define __NR_lremovexattr 15 +#endif + +#if !defined(__NR_fremovexattr) +#define __NR_fremovexattr 16 +#endif + +#if !defined(__NR_getcwd) +#define __NR_getcwd 17 +#endif + +#if !defined(__NR_lookup_dcookie) +#define __NR_lookup_dcookie 18 +#endif + +#if !defined(__NR_eventfd2) +#define __NR_eventfd2 19 +#endif + +#if !defined(__NR_epoll_create1) +#define __NR_epoll_create1 20 +#endif + +#if !defined(__NR_epoll_ctl) +#define __NR_epoll_ctl 21 +#endif + +#if !defined(__NR_epoll_pwait) +#define __NR_epoll_pwait 22 +#endif + +#if !defined(__NR_dup) +#define __NR_dup 23 +#endif + +#if !defined(__NR_dup3) +#define __NR_dup3 24 +#endif + +#if !defined(__NR_fcntl) +#define __NR_fcntl 25 +#endif + +#if !defined(__NR_inotify_init1) +#define __NR_inotify_init1 26 +#endif + +#if !defined(__NR_inotify_add_watch) +#define __NR_inotify_add_watch 27 +#endif + +#if !defined(__NR_inotify_rm_watch) +#define __NR_inotify_rm_watch 28 +#endif + +#if !defined(__NR_ioctl) +#define __NR_ioctl 29 +#endif + +#if !defined(__NR_ioprio_set) +#define __NR_ioprio_set 30 +#endif + +#if !defined(__NR_ioprio_get) +#define __NR_ioprio_get 31 +#endif + +#if !defined(__NR_flock) +#define __NR_flock 32 +#endif + +#if !defined(__NR_mknodat) +#define __NR_mknodat 33 +#endif + +#if !defined(__NR_mkdirat) +#define __NR_mkdirat 34 +#endif + +#if !defined(__NR_unlinkat) +#define __NR_unlinkat 35 +#endif + +#if !defined(__NR_symlinkat) +#define __NR_symlinkat 36 +#endif + +#if !defined(__NR_linkat) +#define __NR_linkat 37 +#endif + +#if !defined(__NR_renameat) +#define __NR_renameat 38 +#endif + +#if !defined(__NR_umount2) +#define __NR_umount2 39 +#endif + +#if !defined(__NR_mount) +#define __NR_mount 40 +#endif + +#if !defined(__NR_pivot_root) +#define __NR_pivot_root 41 +#endif + +#if !defined(__NR_nfsservctl) +#define __NR_nfsservctl 42 +#endif + +#if !defined(__NR_statfs) +#define __NR_statfs 43 +#endif + +#if !defined(__NR_fstatfs) +#define __NR_fstatfs 44 +#endif + +#if !defined(__NR_truncate) +#define __NR_truncate 45 +#endif + +#if !defined(__NR_ftruncate) +#define __NR_ftruncate 46 +#endif + +#if !defined(__NR_fallocate) +#define __NR_fallocate 47 +#endif + +#if !defined(__NR_faccessat) +#define __NR_faccessat 48 +#endif + +#if !defined(__NR_chdir) +#define __NR_chdir 49 +#endif + +#if !defined(__NR_fchdir) +#define __NR_fchdir 50 +#endif + +#if !defined(__NR_chroot) +#define __NR_chroot 51 +#endif + +#if !defined(__NR_fchmod) +#define __NR_fchmod 52 +#endif + +#if !defined(__NR_fchmodat) +#define __NR_fchmodat 53 +#endif + +#if !defined(__NR_fchownat) +#define __NR_fchownat 54 +#endif + +#if !defined(__NR_fchown) +#define __NR_fchown 55 +#endif + +#if !defined(__NR_openat) +#define __NR_openat 56 +#endif + +#if !defined(__NR_close) +#define __NR_close 57 +#endif + +#if !defined(__NR_vhangup) +#define __NR_vhangup 58 +#endif + +#if !defined(__NR_pipe2) +#define __NR_pipe2 59 +#endif + +#if !defined(__NR_quotactl) +#define __NR_quotactl 60 +#endif + +#if !defined(__NR_getdents64) +#define __NR_getdents64 61 +#endif + +#if !defined(__NR_lseek) +#define __NR_lseek 62 +#endif + +#if !defined(__NR_read) +#define __NR_read 63 +#endif + +#if !defined(__NR_write) +#define __NR_write 64 +#endif + +#if !defined(__NR_readv) +#define __NR_readv 65 +#endif + +#if !defined(__NR_writev) +#define __NR_writev 66 +#endif + +#if !defined(__NR_pread64) +#define __NR_pread64 67 +#endif + +#if !defined(__NR_pwrite64) +#define __NR_pwrite64 68 +#endif + +#if !defined(__NR_preadv) +#define __NR_preadv 69 +#endif + +#if !defined(__NR_pwritev) +#define __NR_pwritev 70 +#endif + +#if !defined(__NR_sendfile) +#define __NR_sendfile 71 +#endif + +#if !defined(__NR_pselect6) +#define __NR_pselect6 72 +#endif + +#if !defined(__NR_ppoll) +#define __NR_ppoll 73 +#endif + +#if !defined(__NR_signalfd4) +#define __NR_signalfd4 74 +#endif + +#if !defined(__NR_vmsplice) +#define __NR_vmsplice 75 +#endif + +#if !defined(__NR_splice) +#define __NR_splice 76 +#endif + +#if !defined(__NR_tee) +#define __NR_tee 77 +#endif + +#if !defined(__NR_readlinkat) +#define __NR_readlinkat 78 +#endif + +#if !defined(__NR_newfstatat) +#define __NR_newfstatat 79 +#endif + +#if !defined(__NR_fstat) +#define __NR_fstat 80 +#endif + +#if !defined(__NR_sync) +#define __NR_sync 81 +#endif + +#if !defined(__NR_fsync) +#define __NR_fsync 82 +#endif + +#if !defined(__NR_fdatasync) +#define __NR_fdatasync 83 +#endif + +#if !defined(__NR_sync_file_range) +#define __NR_sync_file_range 84 +#endif + +#if !defined(__NR_timerfd_create) +#define __NR_timerfd_create 85 +#endif + +#if !defined(__NR_timerfd_settime) +#define __NR_timerfd_settime 86 +#endif + +#if !defined(__NR_timerfd_gettime) +#define __NR_timerfd_gettime 87 +#endif + +#if !defined(__NR_utimensat) +#define __NR_utimensat 88 +#endif + +#if !defined(__NR_acct) +#define __NR_acct 89 +#endif + +#if !defined(__NR_capget) +#define __NR_capget 90 +#endif + +#if !defined(__NR_capset) +#define __NR_capset 91 +#endif + +#if !defined(__NR_personality) +#define __NR_personality 92 +#endif + +#if !defined(__NR_exit) +#define __NR_exit 93 +#endif + +#if !defined(__NR_exit_group) +#define __NR_exit_group 94 +#endif + +#if !defined(__NR_waitid) +#define __NR_waitid 95 +#endif + +#if !defined(__NR_set_tid_address) +#define __NR_set_tid_address 96 +#endif + +#if !defined(__NR_unshare) +#define __NR_unshare 97 +#endif + +#if !defined(__NR_futex) +#define __NR_futex 98 +#endif + +#if !defined(__NR_set_robust_list) +#define __NR_set_robust_list 99 +#endif + +#if !defined(__NR_get_robust_list) +#define __NR_get_robust_list 100 +#endif + +#if !defined(__NR_nanosleep) +#define __NR_nanosleep 101 +#endif + +#if !defined(__NR_getitimer) +#define __NR_getitimer 102 +#endif + +#if !defined(__NR_setitimer) +#define __NR_setitimer 103 +#endif + +#if !defined(__NR_kexec_load) +#define __NR_kexec_load 104 +#endif + +#if !defined(__NR_init_module) +#define __NR_init_module 105 +#endif + +#if !defined(__NR_delete_module) +#define __NR_delete_module 106 +#endif + +#if !defined(__NR_timer_create) +#define __NR_timer_create 107 +#endif + +#if !defined(__NR_timer_gettime) +#define __NR_timer_gettime 108 +#endif + +#if !defined(__NR_timer_getoverrun) +#define __NR_timer_getoverrun 109 +#endif + +#if !defined(__NR_timer_settime) +#define __NR_timer_settime 110 +#endif + +#if !defined(__NR_timer_delete) +#define __NR_timer_delete 111 +#endif + +#if !defined(__NR_clock_settime) +#define __NR_clock_settime 112 +#endif + +#if !defined(__NR_clock_gettime) +#define __NR_clock_gettime 113 +#endif + +#if !defined(__NR_clock_getres) +#define __NR_clock_getres 114 +#endif + +#if !defined(__NR_clock_nanosleep) +#define __NR_clock_nanosleep 115 +#endif + +#if !defined(__NR_syslog) +#define __NR_syslog 116 +#endif + +#if !defined(__NR_ptrace) +#define __NR_ptrace 117 +#endif + +#if !defined(__NR_sched_setparam) +#define __NR_sched_setparam 118 +#endif + +#if !defined(__NR_sched_setscheduler) +#define __NR_sched_setscheduler 119 +#endif + +#if !defined(__NR_sched_getscheduler) +#define __NR_sched_getscheduler 120 +#endif + +#if !defined(__NR_sched_getparam) +#define __NR_sched_getparam 121 +#endif + +#if !defined(__NR_sched_setaffinity) +#define __NR_sched_setaffinity 122 +#endif + +#if !defined(__NR_sched_getaffinity) +#define __NR_sched_getaffinity 123 +#endif + +#if !defined(__NR_sched_yield) +#define __NR_sched_yield 124 +#endif + +#if !defined(__NR_sched_get_priority_max) +#define __NR_sched_get_priority_max 125 +#endif + +#if !defined(__NR_sched_get_priority_min) +#define __NR_sched_get_priority_min 126 +#endif + +#if !defined(__NR_sched_rr_get_interval) +#define __NR_sched_rr_get_interval 127 +#endif + +#if !defined(__NR_restart_syscall) +#define __NR_restart_syscall 128 +#endif + +#if !defined(__NR_kill) +#define __NR_kill 129 +#endif + +#if !defined(__NR_tkill) +#define __NR_tkill 130 +#endif + +#if !defined(__NR_tgkill) +#define __NR_tgkill 131 +#endif + +#if !defined(__NR_sigaltstack) +#define __NR_sigaltstack 132 +#endif + +#if !defined(__NR_rt_sigsuspend) +#define __NR_rt_sigsuspend 133 +#endif + +#if !defined(__NR_rt_sigaction) +#define __NR_rt_sigaction 134 +#endif + +#if !defined(__NR_rt_sigprocmask) +#define __NR_rt_sigprocmask 135 +#endif + +#if !defined(__NR_rt_sigpending) +#define __NR_rt_sigpending 136 +#endif + +#if !defined(__NR_rt_sigtimedwait) +#define __NR_rt_sigtimedwait 137 +#endif + +#if !defined(__NR_rt_sigqueueinfo) +#define __NR_rt_sigqueueinfo 138 +#endif + +#if !defined(__NR_rt_sigreturn) +#define __NR_rt_sigreturn 139 +#endif + +#if !defined(__NR_setpriority) +#define __NR_setpriority 140 +#endif + +#if !defined(__NR_getpriority) +#define __NR_getpriority 141 +#endif + +#if !defined(__NR_reboot) +#define __NR_reboot 142 +#endif + +#if !defined(__NR_setregid) +#define __NR_setregid 143 +#endif + +#if !defined(__NR_setgid) +#define __NR_setgid 144 +#endif + +#if !defined(__NR_setreuid) +#define __NR_setreuid 145 +#endif + +#if !defined(__NR_setuid) +#define __NR_setuid 146 +#endif + +#if !defined(__NR_setresuid) +#define __NR_setresuid 147 +#endif + +#if !defined(__NR_getresuid) +#define __NR_getresuid 148 +#endif + +#if !defined(__NR_setresgid) +#define __NR_setresgid 149 +#endif + +#if !defined(__NR_getresgid) +#define __NR_getresgid 150 +#endif + +#if !defined(__NR_setfsuid) +#define __NR_setfsuid 151 +#endif + +#if !defined(__NR_setfsgid) +#define __NR_setfsgid 152 +#endif + +#if !defined(__NR_times) +#define __NR_times 153 +#endif + +#if !defined(__NR_setpgid) +#define __NR_setpgid 154 +#endif + +#if !defined(__NR_getpgid) +#define __NR_getpgid 155 +#endif + +#if !defined(__NR_getsid) +#define __NR_getsid 156 +#endif + +#if !defined(__NR_setsid) +#define __NR_setsid 157 +#endif + +#if !defined(__NR_getgroups) +#define __NR_getgroups 158 +#endif + +#if !defined(__NR_setgroups) +#define __NR_setgroups 159 +#endif + +#if !defined(__NR_uname) +#define __NR_uname 160 +#endif + +#if !defined(__NR_sethostname) +#define __NR_sethostname 161 +#endif + +#if !defined(__NR_setdomainname) +#define __NR_setdomainname 162 +#endif + +#if !defined(__NR_getrlimit) +#define __NR_getrlimit 163 +#endif + +#if !defined(__NR_setrlimit) +#define __NR_setrlimit 164 +#endif + +#if !defined(__NR_getrusage) +#define __NR_getrusage 165 +#endif + +#if !defined(__NR_umask) +#define __NR_umask 166 +#endif + +#if !defined(__NR_prctl) +#define __NR_prctl 167 +#endif + +#if !defined(__NR_getcpu) +#define __NR_getcpu 168 +#endif + +#if !defined(__NR_gettimeofday) +#define __NR_gettimeofday 169 +#endif + +#if !defined(__NR_settimeofday) +#define __NR_settimeofday 170 +#endif + +#if !defined(__NR_adjtimex) +#define __NR_adjtimex 171 +#endif + +#if !defined(__NR_getpid) +#define __NR_getpid 172 +#endif + +#if !defined(__NR_getppid) +#define __NR_getppid 173 +#endif + +#if !defined(__NR_getuid) +#define __NR_getuid 174 +#endif + +#if !defined(__NR_geteuid) +#define __NR_geteuid 175 +#endif + +#if !defined(__NR_getgid) +#define __NR_getgid 176 +#endif + +#if !defined(__NR_getegid) +#define __NR_getegid 177 +#endif + +#if !defined(__NR_gettid) +#define __NR_gettid 178 +#endif + +#if !defined(__NR_sysinfo) +#define __NR_sysinfo 179 +#endif + +#if !defined(__NR_mq_open) +#define __NR_mq_open 180 +#endif + +#if !defined(__NR_mq_unlink) +#define __NR_mq_unlink 181 +#endif + +#if !defined(__NR_mq_timedsend) +#define __NR_mq_timedsend 182 +#endif + +#if !defined(__NR_mq_timedreceive) +#define __NR_mq_timedreceive 183 +#endif + +#if !defined(__NR_mq_notify) +#define __NR_mq_notify 184 +#endif + +#if !defined(__NR_mq_getsetattr) +#define __NR_mq_getsetattr 185 +#endif + +#if !defined(__NR_msgget) +#define __NR_msgget 186 +#endif + +#if !defined(__NR_msgctl) +#define __NR_msgctl 187 +#endif + +#if !defined(__NR_msgrcv) +#define __NR_msgrcv 188 +#endif + +#if !defined(__NR_msgsnd) +#define __NR_msgsnd 189 +#endif + +#if !defined(__NR_semget) +#define __NR_semget 190 +#endif + +#if !defined(__NR_semctl) +#define __NR_semctl 191 +#endif + +#if !defined(__NR_semtimedop) +#define __NR_semtimedop 192 +#endif + +#if !defined(__NR_semop) +#define __NR_semop 193 +#endif + +#if !defined(__NR_shmget) +#define __NR_shmget 194 +#endif + +#if !defined(__NR_shmctl) +#define __NR_shmctl 195 +#endif + +#if !defined(__NR_shmat) +#define __NR_shmat 196 +#endif + +#if !defined(__NR_shmdt) +#define __NR_shmdt 197 +#endif + +#if !defined(__NR_socket) +#define __NR_socket 198 +#endif + +#if !defined(__NR_socketpair) +#define __NR_socketpair 199 +#endif + +#if !defined(__NR_bind) +#define __NR_bind 200 +#endif + +#if !defined(__NR_listen) +#define __NR_listen 201 +#endif + +#if !defined(__NR_accept) +#define __NR_accept 202 +#endif + +#if !defined(__NR_connect) +#define __NR_connect 203 +#endif + +#if !defined(__NR_getsockname) +#define __NR_getsockname 204 +#endif + +#if !defined(__NR_getpeername) +#define __NR_getpeername 205 +#endif + +#if !defined(__NR_sendto) +#define __NR_sendto 206 +#endif + +#if !defined(__NR_recvfrom) +#define __NR_recvfrom 207 +#endif + +#if !defined(__NR_setsockopt) +#define __NR_setsockopt 208 +#endif + +#if !defined(__NR_getsockopt) +#define __NR_getsockopt 209 +#endif + +#if !defined(__NR_shutdown) +#define __NR_shutdown 210 +#endif + +#if !defined(__NR_sendmsg) +#define __NR_sendmsg 211 +#endif + +#if !defined(__NR_recvmsg) +#define __NR_recvmsg 212 +#endif + +#if !defined(__NR_readahead) +#define __NR_readahead 213 +#endif + +#if !defined(__NR_brk) +#define __NR_brk 214 +#endif + +#if !defined(__NR_munmap) +#define __NR_munmap 215 +#endif + +#if !defined(__NR_mremap) +#define __NR_mremap 216 +#endif + +#if !defined(__NR_add_key) +#define __NR_add_key 217 +#endif + +#if !defined(__NR_request_key) +#define __NR_request_key 218 +#endif + +#if !defined(__NR_keyctl) +#define __NR_keyctl 219 +#endif + +#if !defined(__NR_clone) +#define __NR_clone 220 +#endif + +#if !defined(__NR_execve) +#define __NR_execve 221 +#endif + +#if !defined(__NR_mmap) +#define __NR_mmap 222 +#endif + +#if !defined(__NR_fadvise64) +#define __NR_fadvise64 223 +#endif + +#if !defined(__NR_swapon) +#define __NR_swapon 224 +#endif + +#if !defined(__NR_swapoff) +#define __NR_swapoff 225 +#endif + +#if !defined(__NR_mprotect) +#define __NR_mprotect 226 +#endif + +#if !defined(__NR_msync) +#define __NR_msync 227 +#endif + +#if !defined(__NR_mlock) +#define __NR_mlock 228 +#endif + +#if !defined(__NR_munlock) +#define __NR_munlock 229 +#endif + +#if !defined(__NR_mlockall) +#define __NR_mlockall 230 +#endif + +#if !defined(__NR_munlockall) +#define __NR_munlockall 231 +#endif + +#if !defined(__NR_mincore) +#define __NR_mincore 232 +#endif + +#if !defined(__NR_madvise) +#define __NR_madvise 233 +#endif + +#if !defined(__NR_remap_file_pages) +#define __NR_remap_file_pages 234 +#endif + +#if !defined(__NR_mbind) +#define __NR_mbind 235 +#endif + +#if !defined(__NR_get_mempolicy) +#define __NR_get_mempolicy 236 +#endif + +#if !defined(__NR_set_mempolicy) +#define __NR_set_mempolicy 237 +#endif + +#if !defined(__NR_migrate_pages) +#define __NR_migrate_pages 238 +#endif + +#if !defined(__NR_move_pages) +#define __NR_move_pages 239 +#endif + +#if !defined(__NR_rt_tgsigqueueinfo) +#define __NR_rt_tgsigqueueinfo 240 +#endif + +#if !defined(__NR_perf_event_open) +#define __NR_perf_event_open 241 +#endif + +#if !defined(__NR_accept4) +#define __NR_accept4 242 +#endif + +#if !defined(__NR_recvmmsg) +#define __NR_recvmmsg 243 +#endif + +#if !defined(__NR_wait4) +#define __NR_wait4 260 +#endif + +#if !defined(__NR_prlimit64) +#define __NR_prlimit64 261 +#endif + +#if !defined(__NR_fanotify_init) +#define __NR_fanotify_init 262 +#endif + +#if !defined(__NR_fanotify_mark) +#define __NR_fanotify_mark 263 +#endif + +#if !defined(__NR_name_to_handle_at) +#define __NR_name_to_handle_at 264 +#endif + +#if !defined(__NR_open_by_handle_at) +#define __NR_open_by_handle_at 265 +#endif + +#if !defined(__NR_clock_adjtime) +#define __NR_clock_adjtime 266 +#endif + +#if !defined(__NR_syncfs) +#define __NR_syncfs 267 +#endif + +#if !defined(__NR_setns) +#define __NR_setns 268 +#endif + +#if !defined(__NR_sendmmsg) +#define __NR_sendmmsg 269 +#endif + +#if !defined(__NR_process_vm_readv) +#define __NR_process_vm_readv 270 +#endif + +#if !defined(__NR_process_vm_writev) +#define __NR_process_vm_writev 271 +#endif + +#if !defined(__NR_kcmp) +#define __NR_kcmp 272 +#endif + +#if !defined(__NR_finit_module) +#define __NR_finit_module 273 +#endif + +#if !defined(__NR_sched_setattr) +#define __NR_sched_setattr 274 +#endif + +#if !defined(__NR_sched_getattr) +#define __NR_sched_getattr 275 +#endif + +#if !defined(__NR_renameat2) +#define __NR_renameat2 276 +#endif + +#if !defined(__NR_seccomp) +#define __NR_seccomp 277 +#endif + +#if !defined(__NR_getrandom) +#define __NR_getrandom 278 +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_ARM64_LINUX_SYSCALLS_H_ diff --git a/sandbox/linux/system_headers/arm64_linux_ucontext.h b/sandbox/linux/system_headers/arm64_linux_ucontext.h new file mode 100644 index 0000000000..46e0407599 --- /dev/null +++ b/sandbox/linux/system_headers/arm64_linux_ucontext.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_ARM64_LINUX_UCONTEXT_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_ARM64_LINUX_UCONTEXT_H_ + +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +#include <asm/sigcontext.h> +#include <signal.h> +// We also need greg_t for the sandbox, include it in this header as well. +typedef uint64_t greg_t; + +struct ucontext_t { + unsigned long uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + sigset_t uc_sigmask; + /* glibc uses a 1024-bit sigset_t */ + uint8_t unused[1024 / 8 - sizeof(sigset_t)]; + /* last for future expansion */ + struct sigcontext uc_mcontext; +}; + +#else +#include <sys/ucontext.h> +#endif // __BIONIC_HAVE_UCONTEXT_T + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_ARM64_LINUX_UCONTEXT_H_ diff --git a/sandbox/linux/system_headers/arm_linux_syscalls.h b/sandbox/linux/system_headers/arm_linux_syscalls.h new file mode 100644 index 0000000000..1addd53843 --- /dev/null +++ b/sandbox/linux/system_headers/arm_linux_syscalls.h @@ -0,0 +1,1418 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Generated from the Linux kernel's calls.S. +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_SYSCALLS_H_ + +#if !defined(__arm__) || !defined(__ARM_EABI__) +#error "Including header on wrong architecture" +#endif + +#if !defined(__NR_SYSCALL_BASE) +// On ARM EABI arch, __NR_SYSCALL_BASE is 0. +#define __NR_SYSCALL_BASE 0 +#endif + +// This syscall list has holes, because ARM EABI makes some syscalls obsolete. + +#if !defined(__NR_restart_syscall) +#define __NR_restart_syscall (__NR_SYSCALL_BASE+0) +#endif + +#if !defined(__NR_exit) +#define __NR_exit (__NR_SYSCALL_BASE+1) +#endif + +#if !defined(__NR_fork) +#define __NR_fork (__NR_SYSCALL_BASE+2) +#endif + +#if !defined(__NR_read) +#define __NR_read (__NR_SYSCALL_BASE+3) +#endif + +#if !defined(__NR_write) +#define __NR_write (__NR_SYSCALL_BASE+4) +#endif + +#if !defined(__NR_open) +#define __NR_open (__NR_SYSCALL_BASE+5) +#endif + +#if !defined(__NR_close) +#define __NR_close (__NR_SYSCALL_BASE+6) +#endif + +#if !defined(__NR_creat) +#define __NR_creat (__NR_SYSCALL_BASE+8) +#endif + +#if !defined(__NR_link) +#define __NR_link (__NR_SYSCALL_BASE+9) +#endif + +#if !defined(__NR_unlink) +#define __NR_unlink (__NR_SYSCALL_BASE+10) +#endif + +#if !defined(__NR_execve) +#define __NR_execve (__NR_SYSCALL_BASE+11) +#endif + +#if !defined(__NR_chdir) +#define __NR_chdir (__NR_SYSCALL_BASE+12) +#endif + +#if !defined(__NR_mknod) +#define __NR_mknod (__NR_SYSCALL_BASE+14) +#endif + +#if !defined(__NR_chmod) +#define __NR_chmod (__NR_SYSCALL_BASE+15) +#endif + +#if !defined(__NR_lchown) +#define __NR_lchown (__NR_SYSCALL_BASE+16) +#endif + +#if !defined(__NR_lseek) +#define __NR_lseek (__NR_SYSCALL_BASE+19) +#endif + +#if !defined(__NR_getpid) +#define __NR_getpid (__NR_SYSCALL_BASE+20) +#endif + +#if !defined(__NR_mount) +#define __NR_mount (__NR_SYSCALL_BASE+21) +#endif + +#if !defined(__NR_setuid) +#define __NR_setuid (__NR_SYSCALL_BASE+23) +#endif + +#if !defined(__NR_getuid) +#define __NR_getuid (__NR_SYSCALL_BASE+24) +#endif + +#if !defined(__NR_ptrace) +#define __NR_ptrace (__NR_SYSCALL_BASE+26) +#endif + +#if !defined(__NR_pause) +#define __NR_pause (__NR_SYSCALL_BASE+29) +#endif + +#if !defined(__NR_access) +#define __NR_access (__NR_SYSCALL_BASE+33) +#endif + +#if !defined(__NR_nice) +#define __NR_nice (__NR_SYSCALL_BASE+34) +#endif + +#if !defined(__NR_sync) +#define __NR_sync (__NR_SYSCALL_BASE+36) +#endif + +#if !defined(__NR_kill) +#define __NR_kill (__NR_SYSCALL_BASE+37) +#endif + +#if !defined(__NR_rename) +#define __NR_rename (__NR_SYSCALL_BASE+38) +#endif + +#if !defined(__NR_mkdir) +#define __NR_mkdir (__NR_SYSCALL_BASE+39) +#endif + +#if !defined(__NR_rmdir) +#define __NR_rmdir (__NR_SYSCALL_BASE+40) +#endif + +#if !defined(__NR_dup) +#define __NR_dup (__NR_SYSCALL_BASE+41) +#endif + +#if !defined(__NR_pipe) +#define __NR_pipe (__NR_SYSCALL_BASE+42) +#endif + +#if !defined(__NR_times) +#define __NR_times (__NR_SYSCALL_BASE+43) +#endif + +#if !defined(__NR_brk) +#define __NR_brk (__NR_SYSCALL_BASE+45) +#endif + +#if !defined(__NR_setgid) +#define __NR_setgid (__NR_SYSCALL_BASE+46) +#endif + +#if !defined(__NR_getgid) +#define __NR_getgid (__NR_SYSCALL_BASE+47) +#endif + +#if !defined(__NR_geteuid) +#define __NR_geteuid (__NR_SYSCALL_BASE+49) +#endif + +#if !defined(__NR_getegid) +#define __NR_getegid (__NR_SYSCALL_BASE+50) +#endif + +#if !defined(__NR_acct) +#define __NR_acct (__NR_SYSCALL_BASE+51) +#endif + +#if !defined(__NR_umount2) +#define __NR_umount2 (__NR_SYSCALL_BASE+52) +#endif + +#if !defined(__NR_ioctl) +#define __NR_ioctl (__NR_SYSCALL_BASE+54) +#endif + +#if !defined(__NR_fcntl) +#define __NR_fcntl (__NR_SYSCALL_BASE+55) +#endif + +#if !defined(__NR_setpgid) +#define __NR_setpgid (__NR_SYSCALL_BASE+57) +#endif + +#if !defined(__NR_umask) +#define __NR_umask (__NR_SYSCALL_BASE+60) +#endif + +#if !defined(__NR_chroot) +#define __NR_chroot (__NR_SYSCALL_BASE+61) +#endif + +#if !defined(__NR_ustat) +#define __NR_ustat (__NR_SYSCALL_BASE+62) +#endif + +#if !defined(__NR_dup2) +#define __NR_dup2 (__NR_SYSCALL_BASE+63) +#endif + +#if !defined(__NR_getppid) +#define __NR_getppid (__NR_SYSCALL_BASE+64) +#endif + +#if !defined(__NR_getpgrp) +#define __NR_getpgrp (__NR_SYSCALL_BASE+65) +#endif + +#if !defined(__NR_setsid) +#define __NR_setsid (__NR_SYSCALL_BASE+66) +#endif + +#if !defined(__NR_sigaction) +#define __NR_sigaction (__NR_SYSCALL_BASE+67) +#endif + +#if !defined(__NR_setreuid) +#define __NR_setreuid (__NR_SYSCALL_BASE+70) +#endif + +#if !defined(__NR_setregid) +#define __NR_setregid (__NR_SYSCALL_BASE+71) +#endif + +#if !defined(__NR_sigsuspend) +#define __NR_sigsuspend (__NR_SYSCALL_BASE+72) +#endif + +#if !defined(__NR_sigpending) +#define __NR_sigpending (__NR_SYSCALL_BASE+73) +#endif + +#if !defined(__NR_sethostname) +#define __NR_sethostname (__NR_SYSCALL_BASE+74) +#endif + +#if !defined(__NR_setrlimit) +#define __NR_setrlimit (__NR_SYSCALL_BASE+75) +#endif + +#if !defined(__NR_getrusage) +#define __NR_getrusage (__NR_SYSCALL_BASE+77) +#endif + +#if !defined(__NR_gettimeofday) +#define __NR_gettimeofday (__NR_SYSCALL_BASE+78) +#endif + +#if !defined(__NR_settimeofday) +#define __NR_settimeofday (__NR_SYSCALL_BASE+79) +#endif + +#if !defined(__NR_getgroups) +#define __NR_getgroups (__NR_SYSCALL_BASE+80) +#endif + +#if !defined(__NR_setgroups) +#define __NR_setgroups (__NR_SYSCALL_BASE+81) +#endif + +#if !defined(__NR_symlink) +#define __NR_symlink (__NR_SYSCALL_BASE+83) +#endif + +#if !defined(__NR_readlink) +#define __NR_readlink (__NR_SYSCALL_BASE+85) +#endif + +#if !defined(__NR_uselib) +#define __NR_uselib (__NR_SYSCALL_BASE+86) +#endif + +#if !defined(__NR_swapon) +#define __NR_swapon (__NR_SYSCALL_BASE+87) +#endif + +#if !defined(__NR_reboot) +#define __NR_reboot (__NR_SYSCALL_BASE+88) +#endif + +#if !defined(__NR_munmap) +#define __NR_munmap (__NR_SYSCALL_BASE+91) +#endif + +#if !defined(__NR_truncate) +#define __NR_truncate (__NR_SYSCALL_BASE+92) +#endif + +#if !defined(__NR_ftruncate) +#define __NR_ftruncate (__NR_SYSCALL_BASE+93) +#endif + +#if !defined(__NR_fchmod) +#define __NR_fchmod (__NR_SYSCALL_BASE+94) +#endif + +#if !defined(__NR_fchown) +#define __NR_fchown (__NR_SYSCALL_BASE+95) +#endif + +#if !defined(__NR_getpriority) +#define __NR_getpriority (__NR_SYSCALL_BASE+96) +#endif + +#if !defined(__NR_setpriority) +#define __NR_setpriority (__NR_SYSCALL_BASE+97) +#endif + +#if !defined(__NR_statfs) +#define __NR_statfs (__NR_SYSCALL_BASE+99) +#endif + +#if !defined(__NR_fstatfs) +#define __NR_fstatfs (__NR_SYSCALL_BASE+100) +#endif + +#if !defined(__NR_syslog) +#define __NR_syslog (__NR_SYSCALL_BASE+103) +#endif + +#if !defined(__NR_setitimer) +#define __NR_setitimer (__NR_SYSCALL_BASE+104) +#endif + +#if !defined(__NR_getitimer) +#define __NR_getitimer (__NR_SYSCALL_BASE+105) +#endif + +#if !defined(__NR_stat) +#define __NR_stat (__NR_SYSCALL_BASE+106) +#endif + +#if !defined(__NR_lstat) +#define __NR_lstat (__NR_SYSCALL_BASE+107) +#endif + +#if !defined(__NR_fstat) +#define __NR_fstat (__NR_SYSCALL_BASE+108) +#endif + +#if !defined(__NR_vhangup) +#define __NR_vhangup (__NR_SYSCALL_BASE+111) +#endif + +#if !defined(__NR_wait4) +#define __NR_wait4 (__NR_SYSCALL_BASE+114) +#endif + +#if !defined(__NR_swapoff) +#define __NR_swapoff (__NR_SYSCALL_BASE+115) +#endif + +#if !defined(__NR_sysinfo) +#define __NR_sysinfo (__NR_SYSCALL_BASE+116) +#endif + +#if !defined(__NR_fsync) +#define __NR_fsync (__NR_SYSCALL_BASE+118) +#endif + +#if !defined(__NR_sigreturn) +#define __NR_sigreturn (__NR_SYSCALL_BASE+119) +#endif + +#if !defined(__NR_clone) +#define __NR_clone (__NR_SYSCALL_BASE+120) +#endif + +#if !defined(__NR_setdomainname) +#define __NR_setdomainname (__NR_SYSCALL_BASE+121) +#endif + +#if !defined(__NR_uname) +#define __NR_uname (__NR_SYSCALL_BASE+122) +#endif + +#if !defined(__NR_adjtimex) +#define __NR_adjtimex (__NR_SYSCALL_BASE+124) +#endif + +#if !defined(__NR_mprotect) +#define __NR_mprotect (__NR_SYSCALL_BASE+125) +#endif + +#if !defined(__NR_sigprocmask) +#define __NR_sigprocmask (__NR_SYSCALL_BASE+126) +#endif + +#if !defined(__NR_init_module) +#define __NR_init_module (__NR_SYSCALL_BASE+128) +#endif + +#if !defined(__NR_delete_module) +#define __NR_delete_module (__NR_SYSCALL_BASE+129) +#endif + +#if !defined(__NR_quotactl) +#define __NR_quotactl (__NR_SYSCALL_BASE+131) +#endif + +#if !defined(__NR_getpgid) +#define __NR_getpgid (__NR_SYSCALL_BASE+132) +#endif + +#if !defined(__NR_fchdir) +#define __NR_fchdir (__NR_SYSCALL_BASE+133) +#endif + +#if !defined(__NR_bdflush) +#define __NR_bdflush (__NR_SYSCALL_BASE+134) +#endif + +#if !defined(__NR_sysfs) +#define __NR_sysfs (__NR_SYSCALL_BASE+135) +#endif + +#if !defined(__NR_personality) +#define __NR_personality (__NR_SYSCALL_BASE+136) +#endif + +#if !defined(__NR_setfsuid) +#define __NR_setfsuid (__NR_SYSCALL_BASE+138) +#endif + +#if !defined(__NR_setfsgid) +#define __NR_setfsgid (__NR_SYSCALL_BASE+139) +#endif + +#if !defined(__NR__llseek) +#define __NR__llseek (__NR_SYSCALL_BASE+140) +#endif + +#if !defined(__NR_getdents) +#define __NR_getdents (__NR_SYSCALL_BASE+141) +#endif + +#if !defined(__NR__newselect) +#define __NR__newselect (__NR_SYSCALL_BASE+142) +#endif + +#if !defined(__NR_flock) +#define __NR_flock (__NR_SYSCALL_BASE+143) +#endif + +#if !defined(__NR_msync) +#define __NR_msync (__NR_SYSCALL_BASE+144) +#endif + +#if !defined(__NR_readv) +#define __NR_readv (__NR_SYSCALL_BASE+145) +#endif + +#if !defined(__NR_writev) +#define __NR_writev (__NR_SYSCALL_BASE+146) +#endif + +#if !defined(__NR_getsid) +#define __NR_getsid (__NR_SYSCALL_BASE+147) +#endif + +#if !defined(__NR_fdatasync) +#define __NR_fdatasync (__NR_SYSCALL_BASE+148) +#endif + +#if !defined(__NR__sysctl) +#define __NR__sysctl (__NR_SYSCALL_BASE+149) +#endif + +#if !defined(__NR_mlock) +#define __NR_mlock (__NR_SYSCALL_BASE+150) +#endif + +#if !defined(__NR_munlock) +#define __NR_munlock (__NR_SYSCALL_BASE+151) +#endif + +#if !defined(__NR_mlockall) +#define __NR_mlockall (__NR_SYSCALL_BASE+152) +#endif + +#if !defined(__NR_munlockall) +#define __NR_munlockall (__NR_SYSCALL_BASE+153) +#endif + +#if !defined(__NR_sched_setparam) +#define __NR_sched_setparam (__NR_SYSCALL_BASE+154) +#endif + +#if !defined(__NR_sched_getparam) +#define __NR_sched_getparam (__NR_SYSCALL_BASE+155) +#endif + +#if !defined(__NR_sched_setscheduler) +#define __NR_sched_setscheduler (__NR_SYSCALL_BASE+156) +#endif + +#if !defined(__NR_sched_getscheduler) +#define __NR_sched_getscheduler (__NR_SYSCALL_BASE+157) +#endif + +#if !defined(__NR_sched_yield) +#define __NR_sched_yield (__NR_SYSCALL_BASE+158) +#endif + +#if !defined(__NR_sched_get_priority_max) +#define __NR_sched_get_priority_max (__NR_SYSCALL_BASE+159) +#endif + +#if !defined(__NR_sched_get_priority_min) +#define __NR_sched_get_priority_min (__NR_SYSCALL_BASE+160) +#endif + +#if !defined(__NR_sched_rr_get_interval) +#define __NR_sched_rr_get_interval (__NR_SYSCALL_BASE+161) +#endif + +#if !defined(__NR_nanosleep) +#define __NR_nanosleep (__NR_SYSCALL_BASE+162) +#endif + +#if !defined(__NR_mremap) +#define __NR_mremap (__NR_SYSCALL_BASE+163) +#endif + +#if !defined(__NR_setresuid) +#define __NR_setresuid (__NR_SYSCALL_BASE+164) +#endif + +#if !defined(__NR_getresuid) +#define __NR_getresuid (__NR_SYSCALL_BASE+165) +#endif + +#if !defined(__NR_poll) +#define __NR_poll (__NR_SYSCALL_BASE+168) +#endif + +#if !defined(__NR_nfsservctl) +#define __NR_nfsservctl (__NR_SYSCALL_BASE+169) +#endif + +#if !defined(__NR_setresgid) +#define __NR_setresgid (__NR_SYSCALL_BASE+170) +#endif + +#if !defined(__NR_getresgid) +#define __NR_getresgid (__NR_SYSCALL_BASE+171) +#endif + +#if !defined(__NR_prctl) +#define __NR_prctl (__NR_SYSCALL_BASE+172) +#endif + +#if !defined(__NR_rt_sigreturn) +#define __NR_rt_sigreturn (__NR_SYSCALL_BASE+173) +#endif + +#if !defined(__NR_rt_sigaction) +#define __NR_rt_sigaction (__NR_SYSCALL_BASE+174) +#endif + +#if !defined(__NR_rt_sigprocmask) +#define __NR_rt_sigprocmask (__NR_SYSCALL_BASE+175) +#endif + +#if !defined(__NR_rt_sigpending) +#define __NR_rt_sigpending (__NR_SYSCALL_BASE+176) +#endif + +#if !defined(__NR_rt_sigtimedwait) +#define __NR_rt_sigtimedwait (__NR_SYSCALL_BASE+177) +#endif + +#if !defined(__NR_rt_sigqueueinfo) +#define __NR_rt_sigqueueinfo (__NR_SYSCALL_BASE+178) +#endif + +#if !defined(__NR_rt_sigsuspend) +#define __NR_rt_sigsuspend (__NR_SYSCALL_BASE+179) +#endif + +#if !defined(__NR_pread64) +#define __NR_pread64 (__NR_SYSCALL_BASE+180) +#endif + +#if !defined(__NR_pwrite64) +#define __NR_pwrite64 (__NR_SYSCALL_BASE+181) +#endif + +#if !defined(__NR_chown) +#define __NR_chown (__NR_SYSCALL_BASE+182) +#endif + +#if !defined(__NR_getcwd) +#define __NR_getcwd (__NR_SYSCALL_BASE+183) +#endif + +#if !defined(__NR_capget) +#define __NR_capget (__NR_SYSCALL_BASE+184) +#endif + +#if !defined(__NR_capset) +#define __NR_capset (__NR_SYSCALL_BASE+185) +#endif + +#if !defined(__NR_sigaltstack) +#define __NR_sigaltstack (__NR_SYSCALL_BASE+186) +#endif + +#if !defined(__NR_sendfile) +#define __NR_sendfile (__NR_SYSCALL_BASE+187) +#endif + +#if !defined(__NR_vfork) +#define __NR_vfork (__NR_SYSCALL_BASE+190) +#endif + +#if !defined(__NR_ugetrlimit) +#define __NR_ugetrlimit (__NR_SYSCALL_BASE+191) +#endif + +#if !defined(__NR_mmap2) +#define __NR_mmap2 (__NR_SYSCALL_BASE+192) +#endif + +#if !defined(__NR_truncate64) +#define __NR_truncate64 (__NR_SYSCALL_BASE+193) +#endif + +#if !defined(__NR_ftruncate64) +#define __NR_ftruncate64 (__NR_SYSCALL_BASE+194) +#endif + +#if !defined(__NR_stat64) +#define __NR_stat64 (__NR_SYSCALL_BASE+195) +#endif + +#if !defined(__NR_lstat64) +#define __NR_lstat64 (__NR_SYSCALL_BASE+196) +#endif + +#if !defined(__NR_fstat64) +#define __NR_fstat64 (__NR_SYSCALL_BASE+197) +#endif + +#if !defined(__NR_lchown32) +#define __NR_lchown32 (__NR_SYSCALL_BASE+198) +#endif + +#if !defined(__NR_getuid32) +#define __NR_getuid32 (__NR_SYSCALL_BASE+199) +#endif + +#if !defined(__NR_getgid32) +#define __NR_getgid32 (__NR_SYSCALL_BASE+200) +#endif + +#if !defined(__NR_geteuid32) +#define __NR_geteuid32 (__NR_SYSCALL_BASE+201) +#endif + +#if !defined(__NR_getegid32) +#define __NR_getegid32 (__NR_SYSCALL_BASE+202) +#endif + +#if !defined(__NR_setreuid32) +#define __NR_setreuid32 (__NR_SYSCALL_BASE+203) +#endif + +#if !defined(__NR_setregid32) +#define __NR_setregid32 (__NR_SYSCALL_BASE+204) +#endif + +#if !defined(__NR_getgroups32) +#define __NR_getgroups32 (__NR_SYSCALL_BASE+205) +#endif + +#if !defined(__NR_setgroups32) +#define __NR_setgroups32 (__NR_SYSCALL_BASE+206) +#endif + +#if !defined(__NR_fchown32) +#define __NR_fchown32 (__NR_SYSCALL_BASE+207) +#endif + +#if !defined(__NR_setresuid32) +#define __NR_setresuid32 (__NR_SYSCALL_BASE+208) +#endif + +#if !defined(__NR_getresuid32) +#define __NR_getresuid32 (__NR_SYSCALL_BASE+209) +#endif + +#if !defined(__NR_setresgid32) +#define __NR_setresgid32 (__NR_SYSCALL_BASE+210) +#endif + +#if !defined(__NR_getresgid32) +#define __NR_getresgid32 (__NR_SYSCALL_BASE+211) +#endif + +#if !defined(__NR_chown32) +#define __NR_chown32 (__NR_SYSCALL_BASE+212) +#endif + +#if !defined(__NR_setuid32) +#define __NR_setuid32 (__NR_SYSCALL_BASE+213) +#endif + +#if !defined(__NR_setgid32) +#define __NR_setgid32 (__NR_SYSCALL_BASE+214) +#endif + +#if !defined(__NR_setfsuid32) +#define __NR_setfsuid32 (__NR_SYSCALL_BASE+215) +#endif + +#if !defined(__NR_setfsgid32) +#define __NR_setfsgid32 (__NR_SYSCALL_BASE+216) +#endif + +#if !defined(__NR_getdents64) +#define __NR_getdents64 (__NR_SYSCALL_BASE+217) +#endif + +#if !defined(__NR_pivot_root) +#define __NR_pivot_root (__NR_SYSCALL_BASE+218) +#endif + +#if !defined(__NR_mincore) +#define __NR_mincore (__NR_SYSCALL_BASE+219) +#endif + +#if !defined(__NR_madvise) +#define __NR_madvise (__NR_SYSCALL_BASE+220) +#endif + +#if !defined(__NR_fcntl64) +#define __NR_fcntl64 (__NR_SYSCALL_BASE+221) +#endif + +#if !defined(__NR_gettid) +#define __NR_gettid (__NR_SYSCALL_BASE+224) +#endif + +#if !defined(__NR_readahead) +#define __NR_readahead (__NR_SYSCALL_BASE+225) +#endif + +#if !defined(__NR_setxattr) +#define __NR_setxattr (__NR_SYSCALL_BASE+226) +#endif + +#if !defined(__NR_lsetxattr) +#define __NR_lsetxattr (__NR_SYSCALL_BASE+227) +#endif + +#if !defined(__NR_fsetxattr) +#define __NR_fsetxattr (__NR_SYSCALL_BASE+228) +#endif + +#if !defined(__NR_getxattr) +#define __NR_getxattr (__NR_SYSCALL_BASE+229) +#endif + +#if !defined(__NR_lgetxattr) +#define __NR_lgetxattr (__NR_SYSCALL_BASE+230) +#endif + +#if !defined(__NR_fgetxattr) +#define __NR_fgetxattr (__NR_SYSCALL_BASE+231) +#endif + +#if !defined(__NR_listxattr) +#define __NR_listxattr (__NR_SYSCALL_BASE+232) +#endif + +#if !defined(__NR_llistxattr) +#define __NR_llistxattr (__NR_SYSCALL_BASE+233) +#endif + +#if !defined(__NR_flistxattr) +#define __NR_flistxattr (__NR_SYSCALL_BASE+234) +#endif + +#if !defined(__NR_removexattr) +#define __NR_removexattr (__NR_SYSCALL_BASE+235) +#endif + +#if !defined(__NR_lremovexattr) +#define __NR_lremovexattr (__NR_SYSCALL_BASE+236) +#endif + +#if !defined(__NR_fremovexattr) +#define __NR_fremovexattr (__NR_SYSCALL_BASE+237) +#endif + +#if !defined(__NR_tkill) +#define __NR_tkill (__NR_SYSCALL_BASE+238) +#endif + +#if !defined(__NR_sendfile64) +#define __NR_sendfile64 (__NR_SYSCALL_BASE+239) +#endif + +#if !defined(__NR_futex) +#define __NR_futex (__NR_SYSCALL_BASE+240) +#endif + +#if !defined(__NR_sched_setaffinity) +#define __NR_sched_setaffinity (__NR_SYSCALL_BASE+241) +#endif + +#if !defined(__NR_sched_getaffinity) +#define __NR_sched_getaffinity (__NR_SYSCALL_BASE+242) +#endif + +#if !defined(__NR_io_setup) +#define __NR_io_setup (__NR_SYSCALL_BASE+243) +#endif + +#if !defined(__NR_io_destroy) +#define __NR_io_destroy (__NR_SYSCALL_BASE+244) +#endif + +#if !defined(__NR_io_getevents) +#define __NR_io_getevents (__NR_SYSCALL_BASE+245) +#endif + +#if !defined(__NR_io_submit) +#define __NR_io_submit (__NR_SYSCALL_BASE+246) +#endif + +#if !defined(__NR_io_cancel) +#define __NR_io_cancel (__NR_SYSCALL_BASE+247) +#endif + +#if !defined(__NR_exit_group) +#define __NR_exit_group (__NR_SYSCALL_BASE+248) +#endif + +#if !defined(__NR_lookup_dcookie) +#define __NR_lookup_dcookie (__NR_SYSCALL_BASE+249) +#endif + +#if !defined(__NR_epoll_create) +#define __NR_epoll_create (__NR_SYSCALL_BASE+250) +#endif + +#if !defined(__NR_epoll_ctl) +#define __NR_epoll_ctl (__NR_SYSCALL_BASE+251) +#endif + +#if !defined(__NR_epoll_wait) +#define __NR_epoll_wait (__NR_SYSCALL_BASE+252) +#endif + +#if !defined(__NR_remap_file_pages) +#define __NR_remap_file_pages (__NR_SYSCALL_BASE+253) +#endif + +#if !defined(__NR_set_tid_address) +#define __NR_set_tid_address (__NR_SYSCALL_BASE+256) +#endif + +#if !defined(__NR_timer_create) +#define __NR_timer_create (__NR_SYSCALL_BASE+257) +#endif + +#if !defined(__NR_timer_settime) +#define __NR_timer_settime (__NR_SYSCALL_BASE+258) +#endif + +#if !defined(__NR_timer_gettime) +#define __NR_timer_gettime (__NR_SYSCALL_BASE+259) +#endif + +#if !defined(__NR_timer_getoverrun) +#define __NR_timer_getoverrun (__NR_SYSCALL_BASE+260) +#endif + +#if !defined(__NR_timer_delete) +#define __NR_timer_delete (__NR_SYSCALL_BASE+261) +#endif + +#if !defined(__NR_clock_settime) +#define __NR_clock_settime (__NR_SYSCALL_BASE+262) +#endif + +#if !defined(__NR_clock_gettime) +#define __NR_clock_gettime (__NR_SYSCALL_BASE+263) +#endif + +#if !defined(__NR_clock_getres) +#define __NR_clock_getres (__NR_SYSCALL_BASE+264) +#endif + +#if !defined(__NR_clock_nanosleep) +#define __NR_clock_nanosleep (__NR_SYSCALL_BASE+265) +#endif + +#if !defined(__NR_statfs64) +#define __NR_statfs64 (__NR_SYSCALL_BASE+266) +#endif + +#if !defined(__NR_fstatfs64) +#define __NR_fstatfs64 (__NR_SYSCALL_BASE+267) +#endif + +#if !defined(__NR_tgkill) +#define __NR_tgkill (__NR_SYSCALL_BASE+268) +#endif + +#if !defined(__NR_utimes) +#define __NR_utimes (__NR_SYSCALL_BASE+269) +#endif + +#if !defined(__NR_arm_fadvise64_64) +#define __NR_arm_fadvise64_64 (__NR_SYSCALL_BASE+270) +#endif + +#if !defined(__NR_pciconfig_iobase) +#define __NR_pciconfig_iobase (__NR_SYSCALL_BASE+271) +#endif + +#if !defined(__NR_pciconfig_read) +#define __NR_pciconfig_read (__NR_SYSCALL_BASE+272) +#endif + +#if !defined(__NR_pciconfig_write) +#define __NR_pciconfig_write (__NR_SYSCALL_BASE+273) +#endif + +#if !defined(__NR_mq_open) +#define __NR_mq_open (__NR_SYSCALL_BASE+274) +#endif + +#if !defined(__NR_mq_unlink) +#define __NR_mq_unlink (__NR_SYSCALL_BASE+275) +#endif + +#if !defined(__NR_mq_timedsend) +#define __NR_mq_timedsend (__NR_SYSCALL_BASE+276) +#endif + +#if !defined(__NR_mq_timedreceive) +#define __NR_mq_timedreceive (__NR_SYSCALL_BASE+277) +#endif + +#if !defined(__NR_mq_notify) +#define __NR_mq_notify (__NR_SYSCALL_BASE+278) +#endif + +#if !defined(__NR_mq_getsetattr) +#define __NR_mq_getsetattr (__NR_SYSCALL_BASE+279) +#endif + +#if !defined(__NR_waitid) +#define __NR_waitid (__NR_SYSCALL_BASE+280) +#endif + +#if !defined(__NR_socket) +#define __NR_socket (__NR_SYSCALL_BASE+281) +#endif + +#if !defined(__NR_bind) +#define __NR_bind (__NR_SYSCALL_BASE+282) +#endif + +#if !defined(__NR_connect) +#define __NR_connect (__NR_SYSCALL_BASE+283) +#endif + +#if !defined(__NR_listen) +#define __NR_listen (__NR_SYSCALL_BASE+284) +#endif + +#if !defined(__NR_accept) +#define __NR_accept (__NR_SYSCALL_BASE+285) +#endif + +#if !defined(__NR_getsockname) +#define __NR_getsockname (__NR_SYSCALL_BASE+286) +#endif + +#if !defined(__NR_getpeername) +#define __NR_getpeername (__NR_SYSCALL_BASE+287) +#endif + +#if !defined(__NR_socketpair) +#define __NR_socketpair (__NR_SYSCALL_BASE+288) +#endif + +#if !defined(__NR_send) +#define __NR_send (__NR_SYSCALL_BASE+289) +#endif + +#if !defined(__NR_sendto) +#define __NR_sendto (__NR_SYSCALL_BASE+290) +#endif + +#if !defined(__NR_recv) +#define __NR_recv (__NR_SYSCALL_BASE+291) +#endif + +#if !defined(__NR_recvfrom) +#define __NR_recvfrom (__NR_SYSCALL_BASE+292) +#endif + +#if !defined(__NR_shutdown) +#define __NR_shutdown (__NR_SYSCALL_BASE+293) +#endif + +#if !defined(__NR_setsockopt) +#define __NR_setsockopt (__NR_SYSCALL_BASE+294) +#endif + +#if !defined(__NR_getsockopt) +#define __NR_getsockopt (__NR_SYSCALL_BASE+295) +#endif + +#if !defined(__NR_sendmsg) +#define __NR_sendmsg (__NR_SYSCALL_BASE+296) +#endif + +#if !defined(__NR_recvmsg) +#define __NR_recvmsg (__NR_SYSCALL_BASE+297) +#endif + +#if !defined(__NR_semop) +#define __NR_semop (__NR_SYSCALL_BASE+298) +#endif + +#if !defined(__NR_semget) +#define __NR_semget (__NR_SYSCALL_BASE+299) +#endif + +#if !defined(__NR_semctl) +#define __NR_semctl (__NR_SYSCALL_BASE+300) +#endif + +#if !defined(__NR_msgsnd) +#define __NR_msgsnd (__NR_SYSCALL_BASE+301) +#endif + +#if !defined(__NR_msgrcv) +#define __NR_msgrcv (__NR_SYSCALL_BASE+302) +#endif + +#if !defined(__NR_msgget) +#define __NR_msgget (__NR_SYSCALL_BASE+303) +#endif + +#if !defined(__NR_msgctl) +#define __NR_msgctl (__NR_SYSCALL_BASE+304) +#endif + +#if !defined(__NR_shmat) +#define __NR_shmat (__NR_SYSCALL_BASE+305) +#endif + +#if !defined(__NR_shmdt) +#define __NR_shmdt (__NR_SYSCALL_BASE+306) +#endif + +#if !defined(__NR_shmget) +#define __NR_shmget (__NR_SYSCALL_BASE+307) +#endif + +#if !defined(__NR_shmctl) +#define __NR_shmctl (__NR_SYSCALL_BASE+308) +#endif + +#if !defined(__NR_add_key) +#define __NR_add_key (__NR_SYSCALL_BASE+309) +#endif + +#if !defined(__NR_request_key) +#define __NR_request_key (__NR_SYSCALL_BASE+310) +#endif + +#if !defined(__NR_keyctl) +#define __NR_keyctl (__NR_SYSCALL_BASE+311) +#endif + +#if !defined(__NR_semtimedop) +#define __NR_semtimedop (__NR_SYSCALL_BASE+312) +#endif + +#if !defined(__NR_vserver) +#define __NR_vserver (__NR_SYSCALL_BASE+313) +#endif + +#if !defined(__NR_ioprio_set) +#define __NR_ioprio_set (__NR_SYSCALL_BASE+314) +#endif + +#if !defined(__NR_ioprio_get) +#define __NR_ioprio_get (__NR_SYSCALL_BASE+315) +#endif + +#if !defined(__NR_inotify_init) +#define __NR_inotify_init (__NR_SYSCALL_BASE+316) +#endif + +#if !defined(__NR_inotify_add_watch) +#define __NR_inotify_add_watch (__NR_SYSCALL_BASE+317) +#endif + +#if !defined(__NR_inotify_rm_watch) +#define __NR_inotify_rm_watch (__NR_SYSCALL_BASE+318) +#endif + +#if !defined(__NR_mbind) +#define __NR_mbind (__NR_SYSCALL_BASE+319) +#endif + +#if !defined(__NR_get_mempolicy) +#define __NR_get_mempolicy (__NR_SYSCALL_BASE+320) +#endif + +#if !defined(__NR_set_mempolicy) +#define __NR_set_mempolicy (__NR_SYSCALL_BASE+321) +#endif + +#if !defined(__NR_openat) +#define __NR_openat (__NR_SYSCALL_BASE+322) +#endif + +#if !defined(__NR_mkdirat) +#define __NR_mkdirat (__NR_SYSCALL_BASE+323) +#endif + +#if !defined(__NR_mknodat) +#define __NR_mknodat (__NR_SYSCALL_BASE+324) +#endif + +#if !defined(__NR_fchownat) +#define __NR_fchownat (__NR_SYSCALL_BASE+325) +#endif + +#if !defined(__NR_futimesat) +#define __NR_futimesat (__NR_SYSCALL_BASE+326) +#endif + +#if !defined(__NR_fstatat64) +#define __NR_fstatat64 (__NR_SYSCALL_BASE+327) +#endif + +#if !defined(__NR_unlinkat) +#define __NR_unlinkat (__NR_SYSCALL_BASE+328) +#endif + +#if !defined(__NR_renameat) +#define __NR_renameat (__NR_SYSCALL_BASE+329) +#endif + +#if !defined(__NR_linkat) +#define __NR_linkat (__NR_SYSCALL_BASE+330) +#endif + +#if !defined(__NR_symlinkat) +#define __NR_symlinkat (__NR_SYSCALL_BASE+331) +#endif + +#if !defined(__NR_readlinkat) +#define __NR_readlinkat (__NR_SYSCALL_BASE+332) +#endif + +#if !defined(__NR_fchmodat) +#define __NR_fchmodat (__NR_SYSCALL_BASE+333) +#endif + +#if !defined(__NR_faccessat) +#define __NR_faccessat (__NR_SYSCALL_BASE+334) +#endif + +#if !defined(__NR_pselect6) +#define __NR_pselect6 (__NR_SYSCALL_BASE+335) +#endif + +#if !defined(__NR_ppoll) +#define __NR_ppoll (__NR_SYSCALL_BASE+336) +#endif + +#if !defined(__NR_unshare) +#define __NR_unshare (__NR_SYSCALL_BASE+337) +#endif + +#if !defined(__NR_set_robust_list) +#define __NR_set_robust_list (__NR_SYSCALL_BASE+338) +#endif + +#if !defined(__NR_get_robust_list) +#define __NR_get_robust_list (__NR_SYSCALL_BASE+339) +#endif + +#if !defined(__NR_splice) +#define __NR_splice (__NR_SYSCALL_BASE+340) +#endif + +#if !defined(__NR_arm_sync_file_range) +#define __NR_arm_sync_file_range (__NR_SYSCALL_BASE+341) +#endif + +#if !defined(__NR_sync_file_range2) +#define __NR_sync_file_range2 (__NR_SYSCALL_BASE+341) +#endif + +#if !defined(__NR_tee) +#define __NR_tee (__NR_SYSCALL_BASE+342) +#endif + +#if !defined(__NR_vmsplice) +#define __NR_vmsplice (__NR_SYSCALL_BASE+343) +#endif + +#if !defined(__NR_move_pages) +#define __NR_move_pages (__NR_SYSCALL_BASE+344) +#endif + +#if !defined(__NR_getcpu) +#define __NR_getcpu (__NR_SYSCALL_BASE+345) +#endif + +#if !defined(__NR_epoll_pwait) +#define __NR_epoll_pwait (__NR_SYSCALL_BASE+346) +#endif + +#if !defined(__NR_kexec_load) +#define __NR_kexec_load (__NR_SYSCALL_BASE+347) +#endif + +#if !defined(__NR_utimensat) +#define __NR_utimensat (__NR_SYSCALL_BASE+348) +#endif + +#if !defined(__NR_signalfd) +#define __NR_signalfd (__NR_SYSCALL_BASE+349) +#endif + +#if !defined(__NR_timerfd_create) +#define __NR_timerfd_create (__NR_SYSCALL_BASE+350) +#endif + +#if !defined(__NR_eventfd) +#define __NR_eventfd (__NR_SYSCALL_BASE+351) +#endif + +#if !defined(__NR_fallocate) +#define __NR_fallocate (__NR_SYSCALL_BASE+352) +#endif + +#if !defined(__NR_timerfd_settime) +#define __NR_timerfd_settime (__NR_SYSCALL_BASE+353) +#endif + +#if !defined(__NR_timerfd_gettime) +#define __NR_timerfd_gettime (__NR_SYSCALL_BASE+354) +#endif + +#if !defined(__NR_signalfd4) +#define __NR_signalfd4 (__NR_SYSCALL_BASE+355) +#endif + +#if !defined(__NR_eventfd2) +#define __NR_eventfd2 (__NR_SYSCALL_BASE+356) +#endif + +#if !defined(__NR_epoll_create1) +#define __NR_epoll_create1 (__NR_SYSCALL_BASE+357) +#endif + +#if !defined(__NR_dup3) +#define __NR_dup3 (__NR_SYSCALL_BASE+358) +#endif + +#if !defined(__NR_pipe2) +#define __NR_pipe2 (__NR_SYSCALL_BASE+359) +#endif + +#if !defined(__NR_inotify_init1) +#define __NR_inotify_init1 (__NR_SYSCALL_BASE+360) +#endif + +#if !defined(__NR_preadv) +#define __NR_preadv (__NR_SYSCALL_BASE+361) +#endif + +#if !defined(__NR_pwritev) +#define __NR_pwritev (__NR_SYSCALL_BASE+362) +#endif + +#if !defined(__NR_rt_tgsigqueueinfo) +#define __NR_rt_tgsigqueueinfo (__NR_SYSCALL_BASE+363) +#endif + +#if !defined(__NR_perf_event_open) +#define __NR_perf_event_open (__NR_SYSCALL_BASE+364) +#endif + +#if !defined(__NR_recvmmsg) +#define __NR_recvmmsg (__NR_SYSCALL_BASE+365) +#endif + +#if !defined(__NR_accept4) +#define __NR_accept4 (__NR_SYSCALL_BASE+366) +#endif + +#if !defined(__NR_fanotify_init) +#define __NR_fanotify_init (__NR_SYSCALL_BASE+367) +#endif + +#if !defined(__NR_fanotify_mark) +#define __NR_fanotify_mark (__NR_SYSCALL_BASE+368) +#endif + +#if !defined(__NR_prlimit64) +#define __NR_prlimit64 (__NR_SYSCALL_BASE+369) +#endif + +#if !defined(__NR_name_to_handle_at) +#define __NR_name_to_handle_at (__NR_SYSCALL_BASE+370) +#endif + +#if !defined(__NR_open_by_handle_at) +#define __NR_open_by_handle_at (__NR_SYSCALL_BASE+371) +#endif + +#if !defined(__NR_clock_adjtime) +#define __NR_clock_adjtime (__NR_SYSCALL_BASE+372) +#endif + +#if !defined(__NR_syncfs) +#define __NR_syncfs (__NR_SYSCALL_BASE+373) +#endif + +#if !defined(__NR_sendmmsg) +#define __NR_sendmmsg (__NR_SYSCALL_BASE+374) +#endif + +#if !defined(__NR_setns) +#define __NR_setns (__NR_SYSCALL_BASE+375) +#endif + +#if !defined(__NR_process_vm_readv) +#define __NR_process_vm_readv (__NR_SYSCALL_BASE+376) +#endif + +#if !defined(__NR_process_vm_writev) +#define __NR_process_vm_writev (__NR_SYSCALL_BASE+377) +#endif + +#if !defined(__NR_kcmp) +#define __NR_kcmp (__NR_SYSCALL_BASE+378) +#endif + +#if !defined(__NR_finit_module) +#define __NR_finit_module (__NR_SYSCALL_BASE+379) +#endif + +#if !defined(__NR_sched_setattr) +#define __NR_sched_setattr (__NR_SYSCALL_BASE+380) +#endif + +#if !defined(__NR_sched_getattr) +#define __NR_sched_getattr (__NR_SYSCALL_BASE+381) +#endif + +#if !defined(__NR_renameat2) +#define __NR_renameat2 (__NR_SYSCALL_BASE+382) +#endif + +#if !defined(__NR_seccomp) +#define __NR_seccomp (__NR_SYSCALL_BASE+383) +#endif + +#if !defined(__NR_getrandom) +#define __NR_getrandom (__NR_SYSCALL_BASE+384) +#endif + +#if !defined(__NR_memfd_create) +#define __NR_memfd_create (__NR_SYSCALL_BASE+385) +#endif + +// ARM private syscalls. +#if !defined(__ARM_NR_BASE) +#define __ARM_NR_BASE (__NR_SYSCALL_BASE + 0xF0000) +#endif + +#if !defined(__ARM_NR_breakpoint) +#define __ARM_NR_breakpoint (__ARM_NR_BASE+1) +#endif + +#if !defined(__ARM_NR_cacheflush) +#define __ARM_NR_cacheflush (__ARM_NR_BASE+2) +#endif + +#if !defined(__ARM_NR_usr26) +#define __ARM_NR_usr26 (__ARM_NR_BASE+3) +#endif + +#if !defined(__ARM_NR_usr32) +#define __ARM_NR_usr32 (__ARM_NR_BASE+4) +#endif + +#if !defined(__ARM_NR_set_tls) +#define __ARM_NR_set_tls (__ARM_NR_BASE+5) +#endif + +// ARM kernel private syscall. +#if !defined(__ARM_NR_cmpxchg) +#define __ARM_NR_cmpxchg (__ARM_NR_BASE+0x00fff0) +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_SYSCALLS_H_ diff --git a/sandbox/linux/system_headers/arm_linux_ucontext.h b/sandbox/linux/system_headers/arm_linux_ucontext.h new file mode 100644 index 0000000000..0eb723a236 --- /dev/null +++ b/sandbox/linux/system_headers/arm_linux_ucontext.h @@ -0,0 +1,67 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_UCONTEXT_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_UCONTEXT_H_ + +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +#if !defined(__native_client_nonsfi__) +#include <asm/sigcontext.h> +#else +// In PNaCl toolchain, sigcontext and stack_t is not defined. So here declare +// them. +struct sigcontext { + unsigned long trap_no; + unsigned long error_code; + unsigned long oldmask; + unsigned long arm_r0; + unsigned long arm_r1; + unsigned long arm_r2; + unsigned long arm_r3; + unsigned long arm_r4; + unsigned long arm_r5; + unsigned long arm_r6; + unsigned long arm_r7; + unsigned long arm_r8; + unsigned long arm_r9; + unsigned long arm_r10; + unsigned long arm_fp; + unsigned long arm_ip; + unsigned long arm_sp; + unsigned long arm_lr; + unsigned long arm_pc; + unsigned long arm_cpsr; + unsigned long fault_address; +}; + +typedef struct sigaltstack { + void* ss_sp; + int ss_flags; + size_t ss_size; +} stack_t; + +#endif + +// We also need greg_t for the sandbox, include it in this header as well. +typedef unsigned long greg_t; + +// typedef unsigned long sigset_t; +typedef struct ucontext { + unsigned long uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + struct sigcontext uc_mcontext; + sigset_t uc_sigmask; + /* Allow for uc_sigmask growth. Glibc uses a 1024-bit sigset_t. */ + int __not_used[32 - (sizeof(sigset_t) / sizeof(int))]; + /* Last for extensibility. Eight byte aligned because some + coprocessors require eight byte alignment. */ + unsigned long uc_regspace[128] __attribute__((__aligned__(8))); +} ucontext_t; + +#else +#include <sys/ucontext.h> +#endif // __BIONIC_HAVE_UCONTEXT_T + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_UCONTEXT_H_ diff --git a/sandbox/linux/system_headers/capability.h b/sandbox/linux/system_headers/capability.h new file mode 100644 index 0000000000..f91fcf78ac --- /dev/null +++ b/sandbox/linux/system_headers/capability.h @@ -0,0 +1,42 @@ +// Copyright 2015 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_SYSTEM_HEADERS_LINUX_CAPABILITY_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_CAPABILITY_H_ + +#include <stdint.h> + +// The following macros are taken from linux/capability.h. +// We only support capability version 3, which was introduced in Linux 2.6.26. +#ifndef _LINUX_CAPABILITY_VERSION_3 +#define _LINUX_CAPABILITY_VERSION_3 0x20080522 +#endif +#ifndef _LINUX_CAPABILITY_U32S_3 +#define _LINUX_CAPABILITY_U32S_3 2 +#endif +#ifndef CAP_TO_INDEX +#define CAP_TO_INDEX(x) ((x) >> 5) // 1 << 5 == bits in __u32 +#endif +#ifndef CAP_TO_MASK +#define CAP_TO_MASK(x) (1 << ((x) & 31)) // mask for indexed __u32 +#endif +#ifndef CAP_SYS_CHROOT +#define CAP_SYS_CHROOT 18 +#endif +#ifndef CAP_SYS_ADMIN +#define CAP_SYS_ADMIN 21 +#endif + +struct cap_hdr { + uint32_t version; + int pid; +}; + +struct cap_data { + uint32_t effective; + uint32_t permitted; + uint32_t inheritable; +}; + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_CAPABILITY_H_ diff --git a/sandbox/linux/system_headers/i386_linux_ucontext.h b/sandbox/linux/system_headers/i386_linux_ucontext.h new file mode 100644 index 0000000000..61d9f7a9b8 --- /dev/null +++ b/sandbox/linux/system_headers/i386_linux_ucontext.h @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_ANDROID_I386_UCONTEXT_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_ANDROID_I386_UCONTEXT_H_ + +// We do something compatible with glibc. Hopefully, at some point Android will +// provide that for us, and __BIONIC_HAVE_UCONTEXT_T should be defined. +// This is mostly copied from breakpad (common/android/include/sys/ucontext.h), +// except we do use sigset_t for uc_sigmask instead of a custom type. + +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +#if !defined(__native_client_nonsfi__) +#include <asm/sigcontext.h> +#else +// In PNaCl toolchain, sigcontext is not defined. So here declare it. +typedef struct sigaltstack { + void* ss_sp; + int ss_flags; + size_t ss_size; +} stack_t; +#endif + +/* 80-bit floating-point register */ +struct _libc_fpreg { + unsigned short significand[4]; + unsigned short exponent; +}; + +/* Simple floating-point state, see FNSTENV instruction */ +struct _libc_fpstate { + unsigned long cw; + unsigned long sw; + unsigned long tag; + unsigned long ipoff; + unsigned long cssel; + unsigned long dataoff; + unsigned long datasel; + struct _libc_fpreg _st[8]; + unsigned long status; +}; + +typedef uint32_t greg_t; + +typedef struct { + uint32_t gregs[19]; + struct _libc_fpstate* fpregs; + uint32_t oldmask; + uint32_t cr2; +} mcontext_t; + +enum { + REG_GS = 0, + REG_FS, + REG_ES, + REG_DS, + REG_EDI, + REG_ESI, + REG_EBP, + REG_ESP, + REG_EBX, + REG_EDX, + REG_ECX, + REG_EAX, + REG_TRAPNO, + REG_ERR, + REG_EIP, + REG_CS, + REG_EFL, + REG_UESP, + REG_SS, +}; + +typedef struct ucontext { + uint32_t uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + mcontext_t uc_mcontext; + // Android and PNaCl toolchain's sigset_t has only 32 bits, though Linux + // ABI requires 64 bits. + union { + sigset_t uc_sigmask; + uint32_t kernel_sigmask[2]; + }; + struct _libc_fpstate __fpregs_mem; +} ucontext_t; + +#else +#include <sys/ucontext.h> +#endif // __BIONIC_HAVE_UCONTEXT_T + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_ANDROID_I386_UCONTEXT_H_ diff --git a/sandbox/linux/system_headers/linux_filter.h b/sandbox/linux/system_headers/linux_filter.h new file mode 100644 index 0000000000..b23b6eb0c1 --- /dev/null +++ b/sandbox/linux/system_headers/linux_filter.h @@ -0,0 +1,140 @@ +// Copyright 2015 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_SYSTEM_HEADERS_LINUX_FILTER_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FILTER_H_ + +#include <stdint.h> + +// The following structs and macros are taken from linux/filter.h, +// as some toolchain does not expose them. +struct sock_filter { + uint16_t code; + uint8_t jt; + uint8_t jf; + uint32_t k; +}; + +struct sock_fprog { + uint16_t len; + struct sock_filter *filter; +}; + +#ifndef BPF_CLASS +#define BPF_CLASS(code) ((code) & 0x07) +#endif + +#ifndef BPF_LD +#define BPF_LD 0x00 +#endif + +#ifndef BPF_ALU +#define BPF_ALU 0x04 +#endif + +#ifndef BPF_JMP +#define BPF_JMP 0x05 +#endif + +#ifndef BPF_RET +#define BPF_RET 0x06 +#endif + +#ifndef BPF_SIZE +#define BPF_SIZE(code) ((code) & 0x18) +#endif + +#ifndef BPF_W +#define BPF_W 0x00 +#endif + +#ifndef BPF_MODE +#define BPF_MODE(code) ((code) & 0xe0) +#endif + +#ifndef BPF_ABS +#define BPF_ABS 0x20 +#endif + +#ifndef BPF_OP +#define BPF_OP(code) ((code) & 0xf0) +#endif + +#ifndef BPF_ADD +#define BPF_ADD 0x00 +#endif + +#ifndef BPF_SUB +#define BPF_SUB 0x10 +#endif + +#ifndef BPF_MUL +#define BPF_MUL 0x20 +#endif + +#ifndef BPF_DIV +#define BPF_DIV 0x30 +#endif + +#ifndef BPF_OR +#define BPF_OR 0x40 +#endif + +#ifndef BPF_AND +#define BPF_AND 0x50 +#endif + +#ifndef BPF_LSH +#define BPF_LSH 0x60 +#endif + +#ifndef BPF_RSH +#define BPF_RSH 0x70 +#endif + +#ifndef BPF_NEG +#define BPF_NEG 0x80 +#endif + +#ifndef BPF_MOD +#define BPF_MOD 0x90 +#endif + +#ifndef BPF_XOR +#define BPF_XOR 0xA0 +#endif + +#ifndef BPF_JA +#define BPF_JA 0x00 +#endif + +#ifndef BPF_JEQ +#define BPF_JEQ 0x10 +#endif + +#ifndef BPF_JGT +#define BPF_JGT 0x20 +#endif + +#ifndef BPF_JGE +#define BPF_JGE 0x30 +#endif + +#ifndef BPF_JSET +#define BPF_JSET 0x40 +#endif + +#ifndef BPF_SRC +#define BPF_SRC(code) ((code) & 0x08) +#endif + +#ifndef BPF_K +#define BPF_K 0x00 +#endif + +#ifndef BPF_MAXINSNS +#define BPF_MAXINSNS 4096 +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FILTER_H_ diff --git a/sandbox/linux/system_headers/linux_futex.h b/sandbox/linux/system_headers/linux_futex.h new file mode 100644 index 0000000000..4e28403336 --- /dev/null +++ b/sandbox/linux/system_headers/linux_futex.h @@ -0,0 +1,84 @@ +// 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_SYSTEM_HEADERS_LINUX_FUTEX_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FUTEX_H_ + +#if !defined(__native_client_nonsfi__) +#include <linux/futex.h> +#endif // !defined(__native_client_nonsfi__) + +#if !defined(FUTEX_WAIT) +#define FUTEX_WAIT 0 +#endif + +#if !defined(FUTEX_WAKE) +#define FUTEX_WAKE 1 +#endif + +#if !defined(FUTEX_FD) +#define FUTEX_FD 2 +#endif + +#if !defined(FUTEX_REQUEUE) +#define FUTEX_REQUEUE 3 +#endif + +#if !defined(FUTEX_CMP_REQUEUE) +#define FUTEX_CMP_REQUEUE 4 +#endif + +#if !defined(FUTEX_WAKE_OP) +#define FUTEX_WAKE_OP 5 +#endif + +#if !defined(FUTEX_LOCK_PI) +#define FUTEX_LOCK_PI 6 +#endif + +#if !defined(FUTEX_UNLOCK_PI) +#define FUTEX_UNLOCK_PI 7 +#endif + +#if !defined(FUTEX_TRYLOCK_PI) +#define FUTEX_TRYLOCK_PI 8 +#endif + +#if !defined(FUTEX_WAIT_BITSET) +#define FUTEX_WAIT_BITSET 9 +#endif + +#if !defined(FUTEX_WAKE_BITSET) +#define FUTEX_WAKE_BITSET 10 +#endif + +#if !defined(FUTEX_WAIT_REQUEUE_PI) +#define FUTEX_WAIT_REQUEUE_PI 11 +#endif + +#if !defined(FUTEX_CMP_REQUEUE_PI) +#define FUTEX_CMP_REQUEUE_PI 12 +#endif + +#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_CMD_MASK) +#define FUTEX_CMD_MASK ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME) +#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_UNLOCK_PI_PRIVATE) +#define FUTEX_UNLOCK_PI_PRIVATE (FUTEX_UNLOCK_PI | FUTEX_PRIVATE_FLAG) +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FUTEX_H_ diff --git a/sandbox/linux/system_headers/linux_seccomp.h b/sandbox/linux/system_headers/linux_seccomp.h new file mode 100644 index 0000000000..3deb3d2253 --- /dev/null +++ b/sandbox/linux/system_headers/linux_seccomp.h @@ -0,0 +1,107 @@ +// Copyright 2015 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_SYSTEM_HEADERS_LINUX_SECCOMP_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SECCOMP_H_ + +// The Seccomp2 kernel ABI is not part of older versions of glibc. +// As we can't break compilation with these versions of the library, +// we explicitly define all missing symbols. +// If we ever decide that we can now rely on system headers, the following +// include files should be enabled: +// #include <linux/audit.h> +// #include <linux/seccomp.h> + +// For audit.h +#ifndef EM_ARM +#define EM_ARM 40 +#endif +#ifndef EM_386 +#define EM_386 3 +#endif +#ifndef EM_X86_64 +#define EM_X86_64 62 +#endif +#ifndef EM_MIPS +#define EM_MIPS 8 +#endif +#ifndef EM_AARCH64 +#define EM_AARCH64 183 +#endif + +#ifndef __AUDIT_ARCH_64BIT +#define __AUDIT_ARCH_64BIT 0x80000000 +#endif +#ifndef __AUDIT_ARCH_LE +#define __AUDIT_ARCH_LE 0x40000000 +#endif +#ifndef AUDIT_ARCH_ARM +#define AUDIT_ARCH_ARM (EM_ARM|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_I386 +#define AUDIT_ARCH_I386 (EM_386|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_X86_64 +#define AUDIT_ARCH_X86_64 (EM_X86_64|__AUDIT_ARCH_64BIT|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_MIPSEL +#define AUDIT_ARCH_MIPSEL (EM_MIPS|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_AARCH64 +#define AUDIT_ARCH_AARCH64 (EM_AARCH64 | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE) +#endif + +// For prctl.h +#ifndef PR_SET_SECCOMP +#define PR_SET_SECCOMP 22 +#define PR_GET_SECCOMP 21 +#endif +#ifndef PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#define PR_GET_NO_NEW_PRIVS 39 +#endif +#ifndef IPC_64 +#define IPC_64 0x0100 +#endif + +// In order to build will older tool chains, we currently have to avoid +// including <linux/seccomp.h>. Until that can be fixed (if ever). Rely on +// our own definitions of the seccomp kernel ABI. +#ifndef SECCOMP_MODE_FILTER +#define SECCOMP_MODE_DISABLED 0 +#define SECCOMP_MODE_STRICT 1 +#define SECCOMP_MODE_FILTER 2 // User user-supplied filter +#endif + +#ifndef SECCOMP_SET_MODE_STRICT +#define SECCOMP_SET_MODE_STRICT 0 +#endif +#ifndef SECCOMP_SET_MODE_FILTER +#define SECCOMP_SET_MODE_FILTER 1 +#endif +#ifndef SECCOMP_FILTER_FLAG_TSYNC +#define SECCOMP_FILTER_FLAG_TSYNC 1 +#endif + +#ifndef SECCOMP_RET_KILL +// Return values supported for BPF filter programs. Please note that the +// "illegal" SECCOMP_RET_INVALID is not supported by the kernel, should only +// ever be used internally, and would result in the kernel killing our process. +#define SECCOMP_RET_KILL 0x00000000U // Kill the task immediately +#define SECCOMP_RET_INVALID 0x00010000U // Illegal return value +#define SECCOMP_RET_TRAP 0x00030000U // Disallow and force a SIGSYS +#define SECCOMP_RET_ERRNO 0x00050000U // Returns an errno +#define SECCOMP_RET_TRACE 0x7ff00000U // Pass to a tracer or disallow +#define SECCOMP_RET_ALLOW 0x7fff0000U // Allow +#define SECCOMP_RET_ACTION 0xffff0000U // Masks for the return value +#define SECCOMP_RET_DATA 0x0000ffffU // sections +#else +#define SECCOMP_RET_INVALID 0x00010000U // Illegal return value +#endif + +#ifndef SYS_SECCOMP +#define SYS_SECCOMP 1 +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SECCOMP_H_ diff --git a/sandbox/linux/system_headers/linux_signal.h b/sandbox/linux/system_headers/linux_signal.h new file mode 100644 index 0000000000..5db7fc5ea1 --- /dev/null +++ b/sandbox/linux/system_headers/linux_signal.h @@ -0,0 +1,73 @@ +// Copyright 2015 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_SYSTEM_HEADERS_LINUX_SIGNAL_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SIGNAL_H_ + +// NOTE: On some toolchains, signal related ABI is incompatible with Linux's +// (not undefined, but defined different values and in different memory +// layouts). So, fill the gap here. + +#if defined(__native_client_nonsfi__) +#if !defined(__i386__) && !defined(__arm__) +#error "Unsupported platform" +#endif + +#include <signal.h> + +#define LINUX_SIGBUS 7 // 10 in PNaCl toolchain. +#define LINUX_SIGSEGV 11 // 11 in PNaCl toolchain. Defined for consistency. +#define LINUX_SIGCHLD 17 // 20 in PNaCl toolchain. +#define LINUX_SIGSYS 31 // 12 in PNaCl toolchain. + +#define LINUX_SIG_BLOCK 0 // 1 in PNaCl toolchain. +#define LINUX_SIG_UNBLOCK 1 // 2 in PNaCl toolchain. + +#define LINUX_SA_SIGINFO 4 // 2 in PNaCl toolchain. +#define LINUX_SA_NODEFER 0x40000000 // Undefined in PNaCl toolchain. +#define LINUX_SA_RESTART 0x10000000 // Undefined in PNaCl toolchain. + +#define LINUX_SIG_DFL 0 // In PNaCl toolchain, unneeded cast is applied. + +struct LinuxSigInfo { + int si_signo; + int si_errno; + int si_code; + + // Extra data is followed by the |si_code|. The length depends on the + // signal number. + char _sifields[1]; +}; + +#include "sandbox/linux/system_headers/linux_ucontext.h" + +#else // !defined(__native_client_nonsfi__) + +// Just alias the toolchain's value. +#include <signal.h> + +#define LINUX_SIGBUS SIGBUS +#define LINUX_SIGSEGV SIGSEGV +#define LINUX_SIGCHLD SIGCHLD +#define LINUX_SIGSYS SIGSYS + +#define LINUX_SIG_BLOCK SIG_BLOCK +#define LINUX_SIG_UNBLOCK SIG_UNBLOCK + +#define LINUX_SA_SIGINFO SA_SIGINFO +#define LINUX_SA_NODEFER SA_NODEFER +#define LINUX_SA_RESTART SA_RESTART + +#define LINUX_SIG_DFL SIG_DFL + +typedef siginfo_t LinuxSigInfo; + +#if defined(__ANDROID__) +// Android's signal.h doesn't define ucontext etc. +#include "sandbox/linux/system_headers/linux_ucontext.h" +#endif // defined(__ANDROID__) + +#endif // !defined(__native_client_nonsfi__) + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SIGNAL_H_ diff --git a/sandbox/linux/system_headers/linux_syscalls.h b/sandbox/linux/system_headers/linux_syscalls.h new file mode 100644 index 0000000000..2b441e47ea --- /dev/null +++ b/sandbox/linux/system_headers/linux_syscalls.h @@ -0,0 +1,37 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This header will be kept up to date so that we can compile system-call +// policies even when system headers are old. +// System call numbers are accessible through __NR_syscall_name. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SYSCALLS_H_ + +#if defined(__x86_64__) +#include "sandbox/linux/system_headers/x86_64_linux_syscalls.h" +#endif + +#if defined(__i386__) +#include "sandbox/linux/system_headers/x86_32_linux_syscalls.h" +#endif + +#if defined(__arm__) && defined(__ARM_EABI__) +#include "sandbox/linux/system_headers/arm_linux_syscalls.h" +#endif + +#if defined(__mips__) && (_MIPS_SIM == _ABIO32) +#include "sandbox/linux/system_headers/mips_linux_syscalls.h" +#endif + +#if defined(__mips__) && (_MIPS_SIM == _ABI64) +#include "sandbox/linux/system_headers/mips64_linux_syscalls.h" +#endif + +#if defined(__aarch64__) +#include "sandbox/linux/system_headers/arm64_linux_syscalls.h" +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SYSCALLS_H_ + diff --git a/sandbox/linux/system_headers/linux_time.h b/sandbox/linux/system_headers/linux_time.h new file mode 100644 index 0000000000..e6c8112b86 --- /dev/null +++ b/sandbox/linux/system_headers/linux_time.h @@ -0,0 +1,18 @@ +// Copyright 2015 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_SYSTEM_HEADERS_LINUX_TIME_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_TIME_H_ + +#include <time.h> + +#if !defined(CLOCK_REALTIME_COARSE) +#define CLOCK_REALTIME_COARSE 5 +#endif + +#if !defined(CLOCK_MONOTONIC_COARSE) +#define CLOCK_MONOTONIC_COARSE 6 +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_TIME_H_ diff --git a/sandbox/linux/system_headers/linux_ucontext.h b/sandbox/linux/system_headers/linux_ucontext.h new file mode 100644 index 0000000000..ea4d8a6c1f --- /dev/null +++ b/sandbox/linux/system_headers/linux_ucontext.h @@ -0,0 +1,28 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_UCONTEXT_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_UCONTEXT_H_ + +#if defined(__ANDROID__) || defined(__native_client_nonsfi__) + +#if defined(__arm__) +#include "sandbox/linux/system_headers/arm_linux_ucontext.h" +#elif defined(__i386__) +#include "sandbox/linux/system_headers/i386_linux_ucontext.h" +#elif defined(__x86_64__) +#include "sandbox/linux/system_headers/x86_64_linux_ucontext.h" +#elif defined(__mips__) +#include "sandbox/linux/system_headers/mips_linux_ucontext.h" +#elif defined(__aarch64__) +#include "sandbox/linux/system_headers/arm64_linux_ucontext.h" +#else +#error "No support for your architecture in Android or PNaCl header" +#endif + +#else // defined(__ANDROID__) || defined(__native_client_nonsfi__) +#error "The header file included on non Android and non PNaCl." +#endif // defined(__ANDROID__) || defined(__native_client_nonsfi__) + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_UCONTEXT_H_ diff --git a/sandbox/linux/system_headers/mips64_linux_syscalls.h b/sandbox/linux/system_headers/mips64_linux_syscalls.h new file mode 100644 index 0000000000..d003124284 --- /dev/null +++ b/sandbox/linux/system_headers/mips64_linux_syscalls.h @@ -0,0 +1,1266 @@ +// 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. + +// Generated from the Linux kernel's calls.S. +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_MIPS64_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_MIPS64_LINUX_SYSCALLS_H_ + +#if !defined(__mips__) || (_MIPS_SIM != _ABI64) +#error "Including header on wrong architecture" +#endif + +// __NR_Linux, is defined in <asm/unistd.h>. +#include <asm/unistd.h> + +#if !defined(__NR_read) +#define __NR_read (__NR_Linux + 0) +#endif + +#if !defined(__NR_write) +#define __NR_write (__NR_Linux + 1) +#endif + +#if !defined(__NR_open) +#define __NR_open (__NR_Linux + 2) +#endif + +#if !defined(__NR_close) +#define __NR_close (__NR_Linux + 3) +#endif + +#if !defined(__NR_stat) +#define __NR_stat (__NR_Linux + 4) +#endif + +#if !defined(__NR_fstat) +#define __NR_fstat (__NR_Linux + 5) +#endif + +#if !defined(__NR_lstat) +#define __NR_lstat (__NR_Linux + 6) +#endif + +#if !defined(__NR_poll) +#define __NR_poll (__NR_Linux + 7) +#endif + +#if !defined(__NR_lseek) +#define __NR_lseek (__NR_Linux + 8) +#endif + +#if !defined(__NR_mmap) +#define __NR_mmap (__NR_Linux + 9) +#endif + +#if !defined(__NR_mprotect) +#define __NR_mprotect (__NR_Linux + 10) +#endif + +#if !defined(__NR_munmap) +#define __NR_munmap (__NR_Linux + 11) +#endif + +#if !defined(__NR_brk) +#define __NR_brk (__NR_Linux + 12) +#endif + +#if !defined(__NR_rt_sigaction) +#define __NR_rt_sigaction (__NR_Linux + 13) +#endif + +#if !defined(__NR_rt_sigprocmask) +#define __NR_rt_sigprocmask (__NR_Linux + 14) +#endif + +#if !defined(__NR_ioctl) +#define __NR_ioctl (__NR_Linux + 15) +#endif + +#if !defined(__NR_pread64) +#define __NR_pread64 (__NR_Linux + 16) +#endif + +#if !defined(__NR_pwrite64) +#define __NR_pwrite64 (__NR_Linux + 17) +#endif + +#if !defined(__NR_readv) +#define __NR_readv (__NR_Linux + 18) +#endif + +#if !defined(__NR_writev) +#define __NR_writev (__NR_Linux + 19) +#endif + +#if !defined(__NR_access) +#define __NR_access (__NR_Linux + 20) +#endif + +#if !defined(__NR_pipe) +#define __NR_pipe (__NR_Linux + 21) +#endif + +#if !defined(__NR__newselect) +#define __NR__newselect (__NR_Linux + 22) +#endif + +#if !defined(__NR_sched_yield) +#define __NR_sched_yield (__NR_Linux + 23) +#endif + +#if !defined(__NR_mremap) +#define __NR_mremap (__NR_Linux + 24) +#endif + +#if !defined(__NR_msync) +#define __NR_msync (__NR_Linux + 25) +#endif + +#if !defined(__NR_mincore) +#define __NR_mincore (__NR_Linux + 26) +#endif + +#if !defined(__NR_madvise) +#define __NR_madvise (__NR_Linux + 27) +#endif + +#if !defined(__NR_shmget) +#define __NR_shmget (__NR_Linux + 28) +#endif + +#if !defined(__NR_shmat) +#define __NR_shmat (__NR_Linux + 29) +#endif + +#if !defined(__NR_shmctl) +#define __NR_shmctl (__NR_Linux + 30) +#endif + +#if !defined(__NR_dup) +#define __NR_dup (__NR_Linux + 31) +#endif + +#if !defined(__NR_dup2) +#define __NR_dup2 (__NR_Linux + 32) +#endif + +#if !defined(__NR_pause) +#define __NR_pause (__NR_Linux + 33) +#endif + +#if !defined(__NR_nanosleep) +#define __NR_nanosleep (__NR_Linux + 34) +#endif + +#if !defined(__NR_getitimer) +#define __NR_getitimer (__NR_Linux + 35) +#endif + +#if !defined(__NR_setitimer) +#define __NR_setitimer (__NR_Linux + 36) +#endif + +#if !defined(__NR_alarm) +#define __NR_alarm (__NR_Linux + 37) +#endif + +#if !defined(__NR_getpid) +#define __NR_getpid (__NR_Linux + 38) +#endif + +#if !defined(__NR_sendfile) +#define __NR_sendfile (__NR_Linux + 39) +#endif + +#if !defined(__NR_socket) +#define __NR_socket (__NR_Linux + 40) +#endif + +#if !defined(__NR_connect) +#define __NR_connect (__NR_Linux + 41) +#endif + +#if !defined(__NR_accept) +#define __NR_accept (__NR_Linux + 42) +#endif + +#if !defined(__NR_sendto) +#define __NR_sendto (__NR_Linux + 43) +#endif + +#if !defined(__NR_recvfrom) +#define __NR_recvfrom (__NR_Linux + 44) +#endif + +#if !defined(__NR_sendmsg) +#define __NR_sendmsg (__NR_Linux + 45) +#endif + +#if !defined(__NR_recvmsg) +#define __NR_recvmsg (__NR_Linux + 46) +#endif + +#if !defined(__NR_shutdown) +#define __NR_shutdown (__NR_Linux + 47) +#endif + +#if !defined(__NR_bind) +#define __NR_bind (__NR_Linux + 48) +#endif + +#if !defined(__NR_listen) +#define __NR_listen (__NR_Linux + 49) +#endif + +#if !defined(__NR_getsockname) +#define __NR_getsockname (__NR_Linux + 50) +#endif + +#if !defined(__NR_getpeername) +#define __NR_getpeername (__NR_Linux + 51) +#endif + +#if !defined(__NR_socketpair) +#define __NR_socketpair (__NR_Linux + 52) +#endif + +#if !defined(__NR_setsockopt) +#define __NR_setsockopt (__NR_Linux + 53) +#endif + +#if !defined(__NR_getsockopt) +#define __NR_getsockopt (__NR_Linux + 54) +#endif + +#if !defined(__NR_clone) +#define __NR_clone (__NR_Linux + 55) +#endif + +#if !defined(__NR_fork) +#define __NR_fork (__NR_Linux + 56) +#endif + +#if !defined(__NR_execve) +#define __NR_execve (__NR_Linux + 57) +#endif + +#if !defined(__NR_exit) +#define __NR_exit (__NR_Linux + 58) +#endif + +#if !defined(__NR_wait4) +#define __NR_wait4 (__NR_Linux + 59) +#endif + +#if !defined(__NR_kill) +#define __NR_kill (__NR_Linux + 60) +#endif + +#if !defined(__NR_uname) +#define __NR_uname (__NR_Linux + 61) +#endif + +#if !defined(__NR_semget) +#define __NR_semget (__NR_Linux + 62) +#endif + +#if !defined(__NR_semop) +#define __NR_semop (__NR_Linux + 63) +#endif + +#if !defined(__NR_semctl) +#define __NR_semctl (__NR_Linux + 64) +#endif + +#if !defined(__NR_shmdt) +#define __NR_shmdt (__NR_Linux + 65) +#endif + +#if !defined(__NR_msgget) +#define __NR_msgget (__NR_Linux + 66) +#endif + +#if !defined(__NR_msgsnd) +#define __NR_msgsnd (__NR_Linux + 67) +#endif + +#if !defined(__NR_msgrcv) +#define __NR_msgrcv (__NR_Linux + 68) +#endif + +#if !defined(__NR_msgctl) +#define __NR_msgctl (__NR_Linux + 69) +#endif + +#if !defined(__NR_fcntl) +#define __NR_fcntl (__NR_Linux + 70) +#endif + +#if !defined(__NR_flock) +#define __NR_flock (__NR_Linux + 71) +#endif + +#if !defined(__NR_fsync) +#define __NR_fsync (__NR_Linux + 72) +#endif + +#if !defined(__NR_fdatasync) +#define __NR_fdatasync (__NR_Linux + 73) +#endif + +#if !defined(__NR_truncate) +#define __NR_truncate (__NR_Linux + 74) +#endif + +#if !defined(__NR_ftruncate) +#define __NR_ftruncate (__NR_Linux + 75) +#endif + +#if !defined(__NR_getdents) +#define __NR_getdents (__NR_Linux + 76) +#endif + +#if !defined(__NR_getcwd) +#define __NR_getcwd (__NR_Linux + 77) +#endif + +#if !defined(__NR_chdir) +#define __NR_chdir (__NR_Linux + 78) +#endif + +#if !defined(__NR_fchdir) +#define __NR_fchdir (__NR_Linux + 79) +#endif + +#if !defined(__NR_rename) +#define __NR_rename (__NR_Linux + 80) +#endif + +#if !defined(__NR_mkdir) +#define __NR_mkdir (__NR_Linux + 81) +#endif + +#if !defined(__NR_rmdir) +#define __NR_rmdir (__NR_Linux + 82) +#endif + +#if !defined(__NR_creat) +#define __NR_creat (__NR_Linux + 83) +#endif + +#if !defined(__NR_link) +#define __NR_link (__NR_Linux + 84) +#endif + +#if !defined(__NR_unlink) +#define __NR_unlink (__NR_Linux + 85) +#endif + +#if !defined(__NR_symlink) +#define __NR_symlink (__NR_Linux + 86) +#endif + +#if !defined(__NR_readlink) +#define __NR_readlink (__NR_Linux + 87) +#endif + +#if !defined(__NR_chmod) +#define __NR_chmod (__NR_Linux + 88) +#endif + +#if !defined(__NR_fchmod) +#define __NR_fchmod (__NR_Linux + 89) +#endif + +#if !defined(__NR_chown) +#define __NR_chown (__NR_Linux + 90) +#endif + +#if !defined(__NR_fchown) +#define __NR_fchown (__NR_Linux + 91) +#endif + +#if !defined(__NR_lchown) +#define __NR_lchown (__NR_Linux + 92) +#endif + +#if !defined(__NR_umask) +#define __NR_umask (__NR_Linux + 93) +#endif + +#if !defined(__NR_gettimeofday) +#define __NR_gettimeofday (__NR_Linux + 94) +#endif + +#if !defined(__NR_getrlimit) +#define __NR_getrlimit (__NR_Linux + 95) +#endif + +#if !defined(__NR_getrusage) +#define __NR_getrusage (__NR_Linux + 96) +#endif + +#if !defined(__NR_sysinfo) +#define __NR_sysinfo (__NR_Linux + 97) +#endif + +#if !defined(__NR_times) +#define __NR_times (__NR_Linux + 98) +#endif + +#if !defined(__NR_ptrace) +#define __NR_ptrace (__NR_Linux + 99) +#endif + +#if !defined(__NR_getuid) +#define __NR_getuid (__NR_Linux + 100) +#endif + +#if !defined(__NR_syslog) +#define __NR_syslog (__NR_Linux + 101) +#endif + +#if !defined(__NR_getgid) +#define __NR_getgid (__NR_Linux + 102) +#endif + +#if !defined(__NR_setuid) +#define __NR_setuid (__NR_Linux + 103) +#endif + +#if !defined(__NR_setgid) +#define __NR_setgid (__NR_Linux + 104) +#endif + +#if !defined(__NR_geteuid) +#define __NR_geteuid (__NR_Linux + 105) +#endif + +#if !defined(__NR_getegid) +#define __NR_getegid (__NR_Linux + 106) +#endif + +#if !defined(__NR_setpgid) +#define __NR_setpgid (__NR_Linux + 107) +#endif + +#if !defined(__NR_getppid) +#define __NR_getppid (__NR_Linux + 108) +#endif + +#if !defined(__NR_getpgrp) +#define __NR_getpgrp (__NR_Linux + 109) +#endif + +#if !defined(__NR_setsid) +#define __NR_setsid (__NR_Linux + 110) +#endif + +#if !defined(__NR_setreuid) +#define __NR_setreuid (__NR_Linux + 111) +#endif + +#if !defined(__NR_setregid) +#define __NR_setregid (__NR_Linux + 112) +#endif + +#if !defined(__NR_getgroups) +#define __NR_getgroups (__NR_Linux + 113) +#endif + +#if !defined(__NR_setgroups) +#define __NR_setgroups (__NR_Linux + 114) +#endif + +#if !defined(__NR_setresuid) +#define __NR_setresuid (__NR_Linux + 115) +#endif + +#if !defined(__NR_getresuid) +#define __NR_getresuid (__NR_Linux + 116) +#endif + +#if !defined(__NR_setresgid) +#define __NR_setresgid (__NR_Linux + 117) +#endif + +#if !defined(__NR_getresgid) +#define __NR_getresgid (__NR_Linux + 118) +#endif + +#if !defined(__NR_getpgid) +#define __NR_getpgid (__NR_Linux + 119) +#endif + +#if !defined(__NR_setfsuid) +#define __NR_setfsuid (__NR_Linux + 120) +#endif + +#if !defined(__NR_setfsgid) +#define __NR_setfsgid (__NR_Linux + 121) +#endif + +#if !defined(__NR_getsid) +#define __NR_getsid (__NR_Linux + 122) +#endif + +#if !defined(__NR_capget) +#define __NR_capget (__NR_Linux + 123) +#endif + +#if !defined(__NR_capset) +#define __NR_capset (__NR_Linux + 124) +#endif + +#if !defined(__NR_rt_sigpending) +#define __NR_rt_sigpending (__NR_Linux + 125) +#endif + +#if !defined(__NR_rt_sigtimedwait) +#define __NR_rt_sigtimedwait (__NR_Linux + 126) +#endif + +#if !defined(__NR_rt_sigqueueinfo) +#define __NR_rt_sigqueueinfo (__NR_Linux + 127) +#endif + +#if !defined(__NR_rt_sigsuspend) +#define __NR_rt_sigsuspend (__NR_Linux + 128) +#endif + +#if !defined(__NR_sigaltstack) +#define __NR_sigaltstack (__NR_Linux + 129) +#endif + +#if !defined(__NR_utime) +#define __NR_utime (__NR_Linux + 130) +#endif + +#if !defined(__NR_mknod) +#define __NR_mknod (__NR_Linux + 131) +#endif + +#if !defined(__NR_personality) +#define __NR_personality (__NR_Linux + 132) +#endif + +#if !defined(__NR_ustat) +#define __NR_ustat (__NR_Linux + 133) +#endif + +#if !defined(__NR_statfs) +#define __NR_statfs (__NR_Linux + 134) +#endif + +#if !defined(__NR_fstatfs) +#define __NR_fstatfs (__NR_Linux + 135) +#endif + +#if !defined(__NR_sysfs) +#define __NR_sysfs (__NR_Linux + 136) +#endif + +#if !defined(__NR_getpriority) +#define __NR_getpriority (__NR_Linux + 137) +#endif + +#if !defined(__NR_setpriority) +#define __NR_setpriority (__NR_Linux + 138) +#endif + +#if !defined(__NR_sched_setparam) +#define __NR_sched_setparam (__NR_Linux + 139) +#endif + +#if !defined(__NR_sched_getparam) +#define __NR_sched_getparam (__NR_Linux + 140) +#endif + +#if !defined(__NR_sched_setscheduler) +#define __NR_sched_setscheduler (__NR_Linux + 141) +#endif + +#if !defined(__NR_sched_getscheduler) +#define __NR_sched_getscheduler (__NR_Linux + 142) +#endif + +#if !defined(__NR_sched_get_priority_max) +#define __NR_sched_get_priority_max (__NR_Linux + 143) +#endif + +#if !defined(__NR_sched_get_priority_min) +#define __NR_sched_get_priority_min (__NR_Linux + 144) +#endif + +#if !defined(__NR_sched_rr_get_interval) +#define __NR_sched_rr_get_interval (__NR_Linux + 145) +#endif + +#if !defined(__NR_mlock) +#define __NR_mlock (__NR_Linux + 146) +#endif + +#if !defined(__NR_munlock) +#define __NR_munlock (__NR_Linux + 147) +#endif + +#if !defined(__NR_mlockall) +#define __NR_mlockall (__NR_Linux + 148) +#endif + +#if !defined(__NR_munlockall) +#define __NR_munlockall (__NR_Linux + 149) +#endif + +#if !defined(__NR_vhangup) +#define __NR_vhangup (__NR_Linux + 150) +#endif + +#if !defined(__NR_pivot_root) +#define __NR_pivot_root (__NR_Linux + 151) +#endif + +#if !defined(__NR__sysctl) +#define __NR__sysctl (__NR_Linux + 152) +#endif + +#if !defined(__NR_prctl) +#define __NR_prctl (__NR_Linux + 153) +#endif + +#if !defined(__NR_adjtimex) +#define __NR_adjtimex (__NR_Linux + 154) +#endif + +#if !defined(__NR_setrlimit) +#define __NR_setrlimit (__NR_Linux + 155) +#endif + +#if !defined(__NR_chroot) +#define __NR_chroot (__NR_Linux + 156) +#endif + +#if !defined(__NR_sync) +#define __NR_sync (__NR_Linux + 157) +#endif + +#if !defined(__NR_acct) +#define __NR_acct (__NR_Linux + 158) +#endif + +#if !defined(__NR_settimeofday) +#define __NR_settimeofday (__NR_Linux + 159) +#endif + +#if !defined(__NR_mount) +#define __NR_mount (__NR_Linux + 160) +#endif + +#if !defined(__NR_umount2) +#define __NR_umount2 (__NR_Linux + 161) +#endif + +#if !defined(__NR_swapon) +#define __NR_swapon (__NR_Linux + 162) +#endif + +#if !defined(__NR_swapoff) +#define __NR_swapoff (__NR_Linux + 163) +#endif + +#if !defined(__NR_reboot) +#define __NR_reboot (__NR_Linux + 164) +#endif + +#if !defined(__NR_sethostname) +#define __NR_sethostname (__NR_Linux + 165) +#endif + +#if !defined(__NR_setdomainname) +#define __NR_setdomainname (__NR_Linux + 166) +#endif + +#if !defined(__NR_create_module) +#define __NR_create_module (__NR_Linux + 167) +#endif + +#if !defined(__NR_init_module) +#define __NR_init_module (__NR_Linux + 168) +#endif + +#if !defined(__NR_delete_module) +#define __NR_delete_module (__NR_Linux + 169) +#endif + +#if !defined(__NR_get_kernel_syms) +#define __NR_get_kernel_syms (__NR_Linux + 170) +#endif + +#if !defined(__NR_query_module) +#define __NR_query_module (__NR_Linux + 171) +#endif + +#if !defined(__NR_quotactl) +#define __NR_quotactl (__NR_Linux + 172) +#endif + +#if !defined(__NR_nfsservctl) +#define __NR_nfsservctl (__NR_Linux + 173) +#endif + +#if !defined(__NR_getpmsg) +#define __NR_getpmsg (__NR_Linux + 174) +#endif + +#if !defined(__NR_putpmsg) +#define __NR_putpmsg (__NR_Linux + 175) +#endif + +#if !defined(__NR_afs_syscall) +#define __NR_afs_syscall (__NR_Linux + 176) +#endif + +#if !defined(__NR_reserved177) +#define __NR_reserved177 (__NR_Linux + 177) +#endif + +#if !defined(__NR_gettid) +#define __NR_gettid (__NR_Linux + 178) +#endif + +#if !defined(__NR_readahead) +#define __NR_readahead (__NR_Linux + 179) +#endif + +#if !defined(__NR_setxattr) +#define __NR_setxattr (__NR_Linux + 180) +#endif + +#if !defined(__NR_lsetxattr) +#define __NR_lsetxattr (__NR_Linux + 181) +#endif + +#if !defined(__NR_fsetxattr) +#define __NR_fsetxattr (__NR_Linux + 182) +#endif + +#if !defined(__NR_getxattr) +#define __NR_getxattr (__NR_Linux + 183) +#endif + +#if !defined(__NR_lgetxattr) +#define __NR_lgetxattr (__NR_Linux + 184) +#endif + +#if !defined(__NR_fgetxattr) +#define __NR_fgetxattr (__NR_Linux + 185) +#endif + +#if !defined(__NR_listxattr) +#define __NR_listxattr (__NR_Linux + 186) +#endif + +#if !defined(__NR_llistxattr) +#define __NR_llistxattr (__NR_Linux + 187) +#endif + +#if !defined(__NR_flistxattr) +#define __NR_flistxattr (__NR_Linux + 188) +#endif + +#if !defined(__NR_removexattr) +#define __NR_removexattr (__NR_Linux + 189) +#endif + +#if !defined(__NR_lremovexattr) +#define __NR_lremovexattr (__NR_Linux + 190) +#endif + +#if !defined(__NR_fremovexattr) +#define __NR_fremovexattr (__NR_Linux + 191) +#endif + +#if !defined(__NR_tkill) +#define __NR_tkill (__NR_Linux + 192) +#endif + +#if !defined(__NR_reserved193) +#define __NR_reserved193 (__NR_Linux + 193) +#endif + +#if !defined(__NR_futex) +#define __NR_futex (__NR_Linux + 194) +#endif + +#if !defined(__NR_sched_setaffinity) +#define __NR_sched_setaffinity (__NR_Linux + 195) +#endif + +#if !defined(__NR_sched_getaffinity) +#define __NR_sched_getaffinity (__NR_Linux + 196) +#endif + +#if !defined(__NR_cacheflush) +#define __NR_cacheflush (__NR_Linux + 197) +#endif + +#if !defined(__NR_cachectl) +#define __NR_cachectl (__NR_Linux + 198) +#endif + +#if !defined(__NR_sysmips) +#define __NR_sysmips (__NR_Linux + 199) +#endif + +#if !defined(__NR_io_setup) +#define __NR_io_setup (__NR_Linux + 200) +#endif + +#if !defined(__NR_io_destroy) +#define __NR_io_destroy (__NR_Linux + 201) +#endif + +#if !defined(__NR_io_getevents) +#define __NR_io_getevents (__NR_Linux + 202) +#endif + +#if !defined(__NR_io_submit) +#define __NR_io_submit (__NR_Linux + 203) +#endif + +#if !defined(__NR_io_cancel) +#define __NR_io_cancel (__NR_Linux + 204) +#endif + +#if !defined(__NR_exit_group) +#define __NR_exit_group (__NR_Linux + 205) +#endif + +#if !defined(__NR_lookup_dcookie) +#define __NR_lookup_dcookie (__NR_Linux + 206) +#endif + +#if !defined(__NR_epoll_create) +#define __NR_epoll_create (__NR_Linux + 207) +#endif + +#if !defined(__NR_epoll_ctl) +#define __NR_epoll_ctl (__NR_Linux + 208) +#endif + +#if !defined(__NR_epoll_wait) +#define __NR_epoll_wait (__NR_Linux + 209) +#endif + +#if !defined(__NR_remap_file_pages) +#define __NR_remap_file_pages (__NR_Linux + 210) +#endif + +#if !defined(__NR_rt_sigreturn) +#define __NR_rt_sigreturn (__NR_Linux + 211) +#endif + +#if !defined(__NR_set_tid_address) +#define __NR_set_tid_address (__NR_Linux + 212) +#endif + +#if !defined(__NR_restart_syscall) +#define __NR_restart_syscall (__NR_Linux + 213) +#endif + +#if !defined(__NR_semtimedop) +#define __NR_semtimedop (__NR_Linux + 214) +#endif + +#if !defined(__NR_fadvise64) +#define __NR_fadvise64 (__NR_Linux + 215) +#endif + +#if !defined(__NR_timer_create) +#define __NR_timer_create (__NR_Linux + 216) +#endif + +#if !defined(__NR_timer_settime) +#define __NR_timer_settime (__NR_Linux + 217) +#endif + +#if !defined(__NR_timer_gettime) +#define __NR_timer_gettime (__NR_Linux + 218) +#endif + +#if !defined(__NR_timer_getoverrun) +#define __NR_timer_getoverrun (__NR_Linux + 219) +#endif + +#if !defined(__NR_timer_delete) +#define __NR_timer_delete (__NR_Linux + 220) +#endif + +#if !defined(__NR_clock_settime) +#define __NR_clock_settime (__NR_Linux + 221) +#endif + +#if !defined(__NR_clock_gettime) +#define __NR_clock_gettime (__NR_Linux + 222) +#endif + +#if !defined(__NR_clock_getres) +#define __NR_clock_getres (__NR_Linux + 223) +#endif + +#if !defined(__NR_clock_nanosleep) +#define __NR_clock_nanosleep (__NR_Linux + 224) +#endif + +#if !defined(__NR_tgkill) +#define __NR_tgkill (__NR_Linux + 225) +#endif + +#if !defined(__NR_utimes) +#define __NR_utimes (__NR_Linux + 226) +#endif + +#if !defined(__NR_mbind) +#define __NR_mbind (__NR_Linux + 227) +#endif + +#if !defined(__NR_get_mempolicy) +#define __NR_get_mempolicy (__NR_Linux + 228) +#endif + +#if !defined(__NR_set_mempolicy) +#define __NR_set_mempolicy (__NR_Linux + 229) +#endif + +#if !defined(__NR_mq_open) +#define __NR_mq_open (__NR_Linux + 230) +#endif + +#if !defined(__NR_mq_unlink) +#define __NR_mq_unlink (__NR_Linux + 231) +#endif + +#if !defined(__NR_mq_timedsend) +#define __NR_mq_timedsend (__NR_Linux + 232) +#endif + +#if !defined(__NR_mq_timedreceive) +#define __NR_mq_timedreceive (__NR_Linux + 233) +#endif + +#if !defined(__NR_mq_notify) +#define __NR_mq_notify (__NR_Linux + 234) +#endif + +#if !defined(__NR_mq_getsetattr) +#define __NR_mq_getsetattr (__NR_Linux + 235) +#endif + +#if !defined(__NR_vserver) +#define __NR_vserver (__NR_Linux + 236) +#endif + +#if !defined(__NR_waitid) +#define __NR_waitid (__NR_Linux + 237) +#endif + +/* #define __NR_sys_setaltroot (__NR_Linux + 238) */ + +#if !defined(__NR_add_key) +#define __NR_add_key (__NR_Linux + 239) +#endif + +#if !defined(__NR_request_key) +#define __NR_request_key (__NR_Linux + 240) +#endif + +#if !defined(__NR_keyctl) +#define __NR_keyctl (__NR_Linux + 241) +#endif + +#if !defined(__NR_set_thread_area) +#define __NR_set_thread_area (__NR_Linux + 242) +#endif + +#if !defined(__NR_inotify_init) +#define __NR_inotify_init (__NR_Linux + 243) +#endif + +#if !defined(__NR_inotify_add_watch) +#define __NR_inotify_add_watch (__NR_Linux + 244) +#endif + +#if !defined(__NR_inotify_rm_watch) +#define __NR_inotify_rm_watch (__NR_Linux + 245) +#endif + +#if !defined(__NR_migrate_pages) +#define __NR_migrate_pages (__NR_Linux + 246) +#endif + +#if !defined(__NR_openat) +#define __NR_openat (__NR_Linux + 247) +#endif + +#if !defined(__NR_mkdirat) +#define __NR_mkdirat (__NR_Linux + 248) +#endif + +#if !defined(__NR_mknodat) +#define __NR_mknodat (__NR_Linux + 249) +#endif + +#if !defined(__NR_fchownat) +#define __NR_fchownat (__NR_Linux + 250) +#endif + +#if !defined(__NR_futimesat) +#define __NR_futimesat (__NR_Linux + 251) +#endif + +#if !defined(__NR_newfstatat) +#define __NR_newfstatat (__NR_Linux + 252) +#endif + +#if !defined(__NR_unlinkat) +#define __NR_unlinkat (__NR_Linux + 253) +#endif + +#if !defined(__NR_renameat) +#define __NR_renameat (__NR_Linux + 254) +#endif + +#if !defined(__NR_linkat) +#define __NR_linkat (__NR_Linux + 255) +#endif + +#if !defined(__NR_symlinkat) +#define __NR_symlinkat (__NR_Linux + 256) +#endif + +#if !defined(__NR_readlinkat) +#define __NR_readlinkat (__NR_Linux + 257) +#endif + +#if !defined(__NR_fchmodat) +#define __NR_fchmodat (__NR_Linux + 258) +#endif + +#if !defined(__NR_faccessat) +#define __NR_faccessat (__NR_Linux + 259) +#endif + +#if !defined(__NR_pselect6) +#define __NR_pselect6 (__NR_Linux + 260) +#endif + +#if !defined(__NR_ppoll) +#define __NR_ppoll (__NR_Linux + 261) +#endif + +#if !defined(__NR_unshare) +#define __NR_unshare (__NR_Linux + 262) +#endif + +#if !defined(__NR_splice) +#define __NR_splice (__NR_Linux + 263) +#endif + +#if !defined(__NR_sync_file_range) +#define __NR_sync_file_range (__NR_Linux + 264) +#endif + +#if !defined(__NR_tee) +#define __NR_tee (__NR_Linux + 265) +#endif + +#if !defined(__NR_vmsplice) +#define __NR_vmsplice (__NR_Linux + 266) +#endif + +#if !defined(__NR_move_pages) +#define __NR_move_pages (__NR_Linux + 267) +#endif + +#if !defined(__NR_set_robust_list) +#define __NR_set_robust_list (__NR_Linux + 268) +#endif + +#if !defined(__NR_get_robust_list) +#define __NR_get_robust_list (__NR_Linux + 269) +#endif + +#if !defined(__NR_kexec_load) +#define __NR_kexec_load (__NR_Linux + 270) +#endif + +#if !defined(__NR_getcpu) +#define __NR_getcpu (__NR_Linux + 271) +#endif + +#if !defined(__NR_epoll_pwait) +#define __NR_epoll_pwait (__NR_Linux + 272) +#endif + +#if !defined(__NR_ioprio_set) +#define __NR_ioprio_set (__NR_Linux + 273) +#endif + +#if !defined(__NR_ioprio_get) +#define __NR_ioprio_get (__NR_Linux + 274) +#endif + +#if !defined(__NR_utimensat) +#define __NR_utimensat (__NR_Linux + 275) +#endif + +#if !defined(__NR_signalfd) +#define __NR_signalfd (__NR_Linux + 276) +#endif + +#if !defined(__NR_timerfd) +#define __NR_timerfd (__NR_Linux + 277) +#endif + +#if !defined(__NR_eventfd) +#define __NR_eventfd (__NR_Linux + 278) +#endif + +#if !defined(__NR_fallocate) +#define __NR_fallocate (__NR_Linux + 279) +#endif + +#if !defined(__NR_timerfd_create) +#define __NR_timerfd_create (__NR_Linux + 280) +#endif + +#if !defined(__NR_timerfd_gettime) +#define __NR_timerfd_gettime (__NR_Linux + 281) +#endif + +#if !defined(__NR_timerfd_settime) +#define __NR_timerfd_settime (__NR_Linux + 282) +#endif + +#if !defined(__NR_signalfd4) +#define __NR_signalfd4 (__NR_Linux + 283) +#endif + +#if !defined(__NR_eventfd2) +#define __NR_eventfd2 (__NR_Linux + 284) +#endif + +#if !defined(__NR_epoll_create1) +#define __NR_epoll_create1 (__NR_Linux + 285) +#endif + +#if !defined(__NR_dup3) +#define __NR_dup3 (__NR_Linux + 286) +#endif + +#if !defined(__NR_pipe2) +#define __NR_pipe2 (__NR_Linux + 287) +#endif + +#if !defined(__NR_inotify_init1) +#define __NR_inotify_init1 (__NR_Linux + 288) +#endif + +#if !defined(__NR_preadv) +#define __NR_preadv (__NR_Linux + 289) +#endif + +#if !defined(__NR_pwritev) +#define __NR_pwritev (__NR_Linux + 290) +#endif + +#if !defined(__NR_rt_tgsigqueueinfo) +#define __NR_rt_tgsigqueueinfo (__NR_Linux + 291) +#endif + +#if !defined(__NR_perf_event_open) +#define __NR_perf_event_open (__NR_Linux + 292) +#endif + +#if !defined(__NR_accept4) +#define __NR_accept4 (__NR_Linux + 293) +#endif + +#if !defined(__NR_recvmmsg) +#define __NR_recvmmsg (__NR_Linux + 294) +#endif + +#if !defined(__NR_fanotify_init) +#define __NR_fanotify_init (__NR_Linux + 295) +#endif + +#if !defined(__NR_fanotify_mark) +#define __NR_fanotify_mark (__NR_Linux + 296) +#endif + +#if !defined(__NR_prlimit64) +#define __NR_prlimit64 (__NR_Linux + 297) +#endif + +#if !defined(__NR_name_to_handle_at) +#define __NR_name_to_handle_at (__NR_Linux + 298) +#endif + +#if !defined(__NR_open_by_handle_at) +#define __NR_open_by_handle_at (__NR_Linux + 299) +#endif + +#if !defined(__NR_clock_adjtime) +#define __NR_clock_adjtime (__NR_Linux + 300) +#endif + +#if !defined(__NR_syncfs) +#define __NR_syncfs (__NR_Linux + 301) +#endif + +#if !defined(__NR_sendmmsg) +#define __NR_sendmmsg (__NR_Linux + 302) +#endif + +#if !defined(__NR_setns) +#define __NR_setns (__NR_Linux + 303) +#endif + +#if !defined(__NR_process_vm_readv) +#define __NR_process_vm_readv (__NR_Linux + 304) +#endif + +#if !defined(__NR_process_vm_writev) +#define __NR_process_vm_writev (__NR_Linux + 305) +#endif + +#if !defined(__NR_kcmp) +#define __NR_kcmp (__NR_Linux + 306) +#endif + +#if !defined(__NR_finit_module) +#define __NR_finit_module (__NR_Linux + 307) +#endif + +#if !defined(__NR_getdents64) +#define __NR_getdents64 (__NR_Linux + 308) +#endif + +#if !defined(__NR_sched_setattr) +#define __NR_sched_setattr (__NR_Linux + 309) +#endif + +#if !defined(__NR_sched_getattr) +#define __NR_sched_getattr (__NR_Linux + 310) +#endif + +#if !defined(__NR_renameat2) +#define __NR_renameat2 (__NR_Linux + 311) +#endif + +#if !defined(__NR_seccomp) +#define __NR_seccomp (__NR_Linux + 312) +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_MIPS64_LINUX_SYSCALLS_H_ diff --git a/sandbox/linux/system_headers/mips_linux_syscalls.h b/sandbox/linux/system_headers/mips_linux_syscalls.h new file mode 100644 index 0000000000..eb1717aad9 --- /dev/null +++ b/sandbox/linux/system_headers/mips_linux_syscalls.h @@ -0,0 +1,1428 @@ +// 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. + +// Generated from the Linux kernel's calls.S. +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_MIPS_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_MIPS_LINUX_SYSCALLS_H_ + +#if !defined(__mips__) || (_MIPS_SIM != _ABIO32) +#error "Including header on wrong architecture" +#endif + +// __NR_Linux, is defined in <asm/unistd.h>. +#include <asm/unistd.h> + +#if !defined(__NR_syscall) +#define __NR_syscall (__NR_Linux + 0) +#endif + +#if !defined(__NR_exit) +#define __NR_exit (__NR_Linux + 1) +#endif + +#if !defined(__NR_fork) +#define __NR_fork (__NR_Linux + 2) +#endif + +#if !defined(__NR_read) +#define __NR_read (__NR_Linux + 3) +#endif + +#if !defined(__NR_write) +#define __NR_write (__NR_Linux + 4) +#endif + +#if !defined(__NR_open) +#define __NR_open (__NR_Linux + 5) +#endif + +#if !defined(__NR_close) +#define __NR_close (__NR_Linux + 6) +#endif + +#if !defined(__NR_waitpid) +#define __NR_waitpid (__NR_Linux + 7) +#endif + +#if !defined(__NR_creat) +#define __NR_creat (__NR_Linux + 8) +#endif + +#if !defined(__NR_link) +#define __NR_link (__NR_Linux + 9) +#endif + +#if !defined(__NR_unlink) +#define __NR_unlink (__NR_Linux + 10) +#endif + +#if !defined(__NR_execve) +#define __NR_execve (__NR_Linux + 11) +#endif + +#if !defined(__NR_chdir) +#define __NR_chdir (__NR_Linux + 12) +#endif + +#if !defined(__NR_time) +#define __NR_time (__NR_Linux + 13) +#endif + +#if !defined(__NR_mknod) +#define __NR_mknod (__NR_Linux + 14) +#endif + +#if !defined(__NR_chmod) +#define __NR_chmod (__NR_Linux + 15) +#endif + +#if !defined(__NR_lchown) +#define __NR_lchown (__NR_Linux + 16) +#endif + +#if !defined(__NR_break) +#define __NR_break (__NR_Linux + 17) +#endif + +#if !defined(__NR_unused18) +#define __NR_unused18 (__NR_Linux + 18) +#endif + +#if !defined(__NR_lseek) +#define __NR_lseek (__NR_Linux + 19) +#endif + +#if !defined(__NR_getpid) +#define __NR_getpid (__NR_Linux + 20) +#endif + +#if !defined(__NR_mount) +#define __NR_mount (__NR_Linux + 21) +#endif + +#if !defined(__NR_umount) +#define __NR_umount (__NR_Linux + 22) +#endif + +#if !defined(__NR_setuid) +#define __NR_setuid (__NR_Linux + 23) +#endif + +#if !defined(__NR_getuid) +#define __NR_getuid (__NR_Linux + 24) +#endif + +#if !defined(__NR_stime) +#define __NR_stime (__NR_Linux + 25) +#endif + +#if !defined(__NR_ptrace) +#define __NR_ptrace (__NR_Linux + 26) +#endif + +#if !defined(__NR_alarm) +#define __NR_alarm (__NR_Linux + 27) +#endif + +#if !defined(__NR_unused28) +#define __NR_unused28 (__NR_Linux + 28) +#endif + +#if !defined(__NR_pause) +#define __NR_pause (__NR_Linux + 29) +#endif + +#if !defined(__NR_utime) +#define __NR_utime (__NR_Linux + 30) +#endif + +#if !defined(__NR_stty) +#define __NR_stty (__NR_Linux + 31) +#endif + +#if !defined(__NR_gtty) +#define __NR_gtty (__NR_Linux + 32) +#endif + +#if !defined(__NR_access) +#define __NR_access (__NR_Linux + 33) +#endif + +#if !defined(__NR_nice) +#define __NR_nice (__NR_Linux + 34) +#endif + +#if !defined(__NR_ftime) +#define __NR_ftime (__NR_Linux + 35) +#endif + +#if !defined(__NR_sync) +#define __NR_sync (__NR_Linux + 36) +#endif + +#if !defined(__NR_kill) +#define __NR_kill (__NR_Linux + 37) +#endif + +#if !defined(__NR_rename) +#define __NR_rename (__NR_Linux + 38) +#endif + +#if !defined(__NR_mkdir) +#define __NR_mkdir (__NR_Linux + 39) +#endif + +#if !defined(__NR_rmdir) +#define __NR_rmdir (__NR_Linux + 40) +#endif + +#if !defined(__NR_dup) +#define __NR_dup (__NR_Linux + 41) +#endif + +#if !defined(__NR_pipe) +#define __NR_pipe (__NR_Linux + 42) +#endif + +#if !defined(__NR_times) +#define __NR_times (__NR_Linux + 43) +#endif + +#if !defined(__NR_prof) +#define __NR_prof (__NR_Linux + 44) +#endif + +#if !defined(__NR_brk) +#define __NR_brk (__NR_Linux + 45) +#endif + +#if !defined(__NR_setgid) +#define __NR_setgid (__NR_Linux + 46) +#endif + +#if !defined(__NR_getgid) +#define __NR_getgid (__NR_Linux + 47) +#endif + +#if !defined(__NR_signal) +#define __NR_signal (__NR_Linux + 48) +#endif + +#if !defined(__NR_geteuid) +#define __NR_geteuid (__NR_Linux + 49) +#endif + +#if !defined(__NR_getegid) +#define __NR_getegid (__NR_Linux + 50) +#endif + +#if !defined(__NR_acct) +#define __NR_acct (__NR_Linux + 51) +#endif + +#if !defined(__NR_umount2) +#define __NR_umount2 (__NR_Linux + 52) +#endif + +#if !defined(__NR_lock) +#define __NR_lock (__NR_Linux + 53) +#endif + +#if !defined(__NR_ioctl) +#define __NR_ioctl (__NR_Linux + 54) +#endif + +#if !defined(__NR_fcntl) +#define __NR_fcntl (__NR_Linux + 55) +#endif + +#if !defined(__NR_mpx) +#define __NR_mpx (__NR_Linux + 56) +#endif + +#if !defined(__NR_setpgid) +#define __NR_setpgid (__NR_Linux + 57) +#endif + +#if !defined(__NR_ulimit) +#define __NR_ulimit (__NR_Linux + 58) +#endif + +#if !defined(__NR_unused59) +#define __NR_unused59 (__NR_Linux + 59) +#endif + +#if !defined(__NR_umask) +#define __NR_umask (__NR_Linux + 60) +#endif + +#if !defined(__NR_chroot) +#define __NR_chroot (__NR_Linux + 61) +#endif + +#if !defined(__NR_ustat) +#define __NR_ustat (__NR_Linux + 62) +#endif + +#if !defined(__NR_dup2) +#define __NR_dup2 (__NR_Linux + 63) +#endif + +#if !defined(__NR_getppid) +#define __NR_getppid (__NR_Linux + 64) +#endif + +#if !defined(__NR_getpgrp) +#define __NR_getpgrp (__NR_Linux + 65) +#endif + +#if !defined(__NR_setsid) +#define __NR_setsid (__NR_Linux + 66) +#endif + +#if !defined(__NR_sigaction) +#define __NR_sigaction (__NR_Linux + 67) +#endif + +#if !defined(__NR_sgetmask) +#define __NR_sgetmask (__NR_Linux + 68) +#endif + +#if !defined(__NR_ssetmask) +#define __NR_ssetmask (__NR_Linux + 69) +#endif + +#if !defined(__NR_setreuid) +#define __NR_setreuid (__NR_Linux + 70) +#endif + +#if !defined(__NR_setregid) +#define __NR_setregid (__NR_Linux + 71) +#endif + +#if !defined(__NR_sigsuspend) +#define __NR_sigsuspend (__NR_Linux + 72) +#endif + +#if !defined(__NR_sigpending) +#define __NR_sigpending (__NR_Linux + 73) +#endif + +#if !defined(__NR_sethostname) +#define __NR_sethostname (__NR_Linux + 74) +#endif + +#if !defined(__NR_setrlimit) +#define __NR_setrlimit (__NR_Linux + 75) +#endif + +#if !defined(__NR_getrlimit) +#define __NR_getrlimit (__NR_Linux + 76) +#endif + +#if !defined(__NR_getrusage) +#define __NR_getrusage (__NR_Linux + 77) +#endif + +#if !defined(__NR_gettimeofday) +#define __NR_gettimeofday (__NR_Linux + 78) +#endif + +#if !defined(__NR_settimeofday) +#define __NR_settimeofday (__NR_Linux + 79) +#endif + +#if !defined(__NR_getgroups) +#define __NR_getgroups (__NR_Linux + 80) +#endif + +#if !defined(__NR_setgroups) +#define __NR_setgroups (__NR_Linux + 81) +#endif + +#if !defined(__NR_reserved82) +#define __NR_reserved82 (__NR_Linux + 82) +#endif + +#if !defined(__NR_symlink) +#define __NR_symlink (__NR_Linux + 83) +#endif + +#if !defined(__NR_unused84) +#define __NR_unused84 (__NR_Linux + 84) +#endif + +#if !defined(__NR_readlink) +#define __NR_readlink (__NR_Linux + 85) +#endif + +#if !defined(__NR_uselib) +#define __NR_uselib (__NR_Linux + 86) +#endif + +#if !defined(__NR_swapon) +#define __NR_swapon (__NR_Linux + 87) +#endif + +#if !defined(__NR_reboot) +#define __NR_reboot (__NR_Linux + 88) +#endif + +#if !defined(__NR_readdir) +#define __NR_readdir (__NR_Linux + 89) +#endif + +#if !defined(__NR_mmap) +#define __NR_mmap (__NR_Linux + 90) +#endif + +#if !defined(__NR_munmap) +#define __NR_munmap (__NR_Linux + 91) +#endif + +#if !defined(__NR_truncate) +#define __NR_truncate (__NR_Linux + 92) +#endif + +#if !defined(__NR_ftruncate) +#define __NR_ftruncate (__NR_Linux + 93) +#endif + +#if !defined(__NR_fchmod) +#define __NR_fchmod (__NR_Linux + 94) +#endif + +#if !defined(__NR_fchown) +#define __NR_fchown (__NR_Linux + 95) +#endif + +#if !defined(__NR_getpriority) +#define __NR_getpriority (__NR_Linux + 96) +#endif + +#if !defined(__NR_setpriority) +#define __NR_setpriority (__NR_Linux + 97) +#endif + +#if !defined(__NR_profil) +#define __NR_profil (__NR_Linux + 98) +#endif + +#if !defined(__NR_statfs) +#define __NR_statfs (__NR_Linux + 99) +#endif + +#if !defined(__NR_fstatfs) +#define __NR_fstatfs (__NR_Linux + 100) +#endif + +#if !defined(__NR_ioperm) +#define __NR_ioperm (__NR_Linux + 101) +#endif + +#if !defined(__NR_socketcall) +#define __NR_socketcall (__NR_Linux + 102) +#endif + +#if !defined(__NR_syslog) +#define __NR_syslog (__NR_Linux + 103) +#endif + +#if !defined(__NR_setitimer) +#define __NR_setitimer (__NR_Linux + 104) +#endif + +#if !defined(__NR_getitimer) +#define __NR_getitimer (__NR_Linux + 105) +#endif + +#if !defined(__NR_stat) +#define __NR_stat (__NR_Linux + 106) +#endif + +#if !defined(__NR_lstat) +#define __NR_lstat (__NR_Linux + 107) +#endif + +#if !defined(__NR_fstat) +#define __NR_fstat (__NR_Linux + 108) +#endif + +#if !defined(__NR_unused109) +#define __NR_unused109 (__NR_Linux + 109) +#endif + +#if !defined(__NR_iopl) +#define __NR_iopl (__NR_Linux + 110) +#endif + +#if !defined(__NR_vhangup) +#define __NR_vhangup (__NR_Linux + 111) +#endif + +#if !defined(__NR_idle) +#define __NR_idle (__NR_Linux + 112) +#endif + +#if !defined(__NR_vm86) +#define __NR_vm86 (__NR_Linux + 113) +#endif + +#if !defined(__NR_wait4) +#define __NR_wait4 (__NR_Linux + 114) +#endif + +#if !defined(__NR_swapoff) +#define __NR_swapoff (__NR_Linux + 115) +#endif + +#if !defined(__NR_sysinfo) +#define __NR_sysinfo (__NR_Linux + 116) +#endif + +#if !defined(__NR_ipc) +#define __NR_ipc (__NR_Linux + 117) +#endif + +#if !defined(__NR_fsync) +#define __NR_fsync (__NR_Linux + 118) +#endif + +#if !defined(__NR_sigreturn) +#define __NR_sigreturn (__NR_Linux + 119) +#endif + +#if !defined(__NR_clone) +#define __NR_clone (__NR_Linux + 120) +#endif + +#if !defined(__NR_setdomainname) +#define __NR_setdomainname (__NR_Linux + 121) +#endif + +#if !defined(__NR_uname) +#define __NR_uname (__NR_Linux + 122) +#endif + +#if !defined(__NR_modify_ldt) +#define __NR_modify_ldt (__NR_Linux + 123) +#endif + +#if !defined(__NR_adjtimex) +#define __NR_adjtimex (__NR_Linux + 124) +#endif + +#if !defined(__NR_mprotect) +#define __NR_mprotect (__NR_Linux + 125) +#endif + +#if !defined(__NR_sigprocmask) +#define __NR_sigprocmask (__NR_Linux + 126) +#endif + +#if !defined(__NR_create_module) +#define __NR_create_module (__NR_Linux + 127) +#endif + +#if !defined(__NR_init_module) +#define __NR_init_module (__NR_Linux + 128) +#endif + +#if !defined(__NR_delete_module) +#define __NR_delete_module (__NR_Linux + 129) +#endif + +#if !defined(__NR_get_kernel_syms) +#define __NR_get_kernel_syms (__NR_Linux + 130) +#endif + +#if !defined(__NR_quotactl) +#define __NR_quotactl (__NR_Linux + 131) +#endif + +#if !defined(__NR_getpgid) +#define __NR_getpgid (__NR_Linux + 132) +#endif + +#if !defined(__NR_fchdir) +#define __NR_fchdir (__NR_Linux + 133) +#endif + +#if !defined(__NR_bdflush) +#define __NR_bdflush (__NR_Linux + 134) +#endif + +#if !defined(__NR_sysfs) +#define __NR_sysfs (__NR_Linux + 135) +#endif + +#if !defined(__NR_personality) +#define __NR_personality (__NR_Linux + 136) +#endif + +#if !defined(__NR_afs_syscall) +#define __NR_afs_syscall \ + (__NR_Linux + 137) /* Syscall for Andrew File System \ + */ +#endif + +#if !defined(__NR_setfsuid) +#define __NR_setfsuid (__NR_Linux + 138) +#endif + +#if !defined(__NR_setfsgid) +#define __NR_setfsgid (__NR_Linux + 139) +#endif + +#if !defined(__NR__llseek) +#define __NR__llseek (__NR_Linux + 140) +#endif + +#if !defined(__NR_getdents) +#define __NR_getdents (__NR_Linux + 141) +#endif + +#if !defined(__NR__newselect) +#define __NR__newselect (__NR_Linux + 142) +#endif + +#if !defined(__NR_flock) +#define __NR_flock (__NR_Linux + 143) +#endif + +#if !defined(__NR_msync) +#define __NR_msync (__NR_Linux + 144) +#endif + +#if !defined(__NR_readv) +#define __NR_readv (__NR_Linux + 145) +#endif + +#if !defined(__NR_writev) +#define __NR_writev (__NR_Linux + 146) +#endif + +#if !defined(__NR_cacheflush) +#define __NR_cacheflush (__NR_Linux + 147) +#endif + +#if !defined(__NR_cachectl) +#define __NR_cachectl (__NR_Linux + 148) +#endif + +#if !defined(__NR_sysmips) +#define __NR_sysmips (__NR_Linux + 149) +#endif + +#if !defined(__NR_unused150) +#define __NR_unused150 (__NR_Linux + 150) +#endif + +#if !defined(__NR_getsid) +#define __NR_getsid (__NR_Linux + 151) +#endif + +#if !defined(__NR_fdatasync) +#define __NR_fdatasync (__NR_Linux + 152) +#endif + +#if !defined(__NR__sysctl) +#define __NR__sysctl (__NR_Linux + 153) +#endif + +#if !defined(__NR_mlock) +#define __NR_mlock (__NR_Linux + 154) +#endif + +#if !defined(__NR_munlock) +#define __NR_munlock (__NR_Linux + 155) +#endif + +#if !defined(__NR_mlockall) +#define __NR_mlockall (__NR_Linux + 156) +#endif + +#if !defined(__NR_munlockall) +#define __NR_munlockall (__NR_Linux + 157) +#endif + +#if !defined(__NR_sched_setparam) +#define __NR_sched_setparam (__NR_Linux + 158) +#endif + +#if !defined(__NR_sched_getparam) +#define __NR_sched_getparam (__NR_Linux + 159) +#endif + +#if !defined(__NR_sched_setscheduler) +#define __NR_sched_setscheduler (__NR_Linux + 160) +#endif + +#if !defined(__NR_sched_getscheduler) +#define __NR_sched_getscheduler (__NR_Linux + 161) +#endif + +#if !defined(__NR_sched_yield) +#define __NR_sched_yield (__NR_Linux + 162) +#endif + +#if !defined(__NR_sched_get_priority_max) +#define __NR_sched_get_priority_max (__NR_Linux + 163) +#endif + +#if !defined(__NR_sched_get_priority_min) +#define __NR_sched_get_priority_min (__NR_Linux + 164) +#endif + +#if !defined(__NR_sched_rr_get_interval) +#define __NR_sched_rr_get_interval (__NR_Linux + 165) +#endif + +#if !defined(__NR_nanosleep) +#define __NR_nanosleep (__NR_Linux + 166) +#endif + +#if !defined(__NR_mremap) +#define __NR_mremap (__NR_Linux + 167) +#endif + +#if !defined(__NR_accept) +#define __NR_accept (__NR_Linux + 168) +#endif + +#if !defined(__NR_bind) +#define __NR_bind (__NR_Linux + 169) +#endif + +#if !defined(__NR_connect) +#define __NR_connect (__NR_Linux + 170) +#endif + +#if !defined(__NR_getpeername) +#define __NR_getpeername (__NR_Linux + 171) +#endif + +#if !defined(__NR_getsockname) +#define __NR_getsockname (__NR_Linux + 172) +#endif + +#if !defined(__NR_getsockopt) +#define __NR_getsockopt (__NR_Linux + 173) +#endif + +#if !defined(__NR_listen) +#define __NR_listen (__NR_Linux + 174) +#endif + +#if !defined(__NR_recv) +#define __NR_recv (__NR_Linux + 175) +#endif + +#if !defined(__NR_recvfrom) +#define __NR_recvfrom (__NR_Linux + 176) +#endif + +#if !defined(__NR_recvmsg) +#define __NR_recvmsg (__NR_Linux + 177) +#endif + +#if !defined(__NR_send) +#define __NR_send (__NR_Linux + 178) +#endif + +#if !defined(__NR_sendmsg) +#define __NR_sendmsg (__NR_Linux + 179) +#endif + +#if !defined(__NR_sendto) +#define __NR_sendto (__NR_Linux + 180) +#endif + +#if !defined(__NR_setsockopt) +#define __NR_setsockopt (__NR_Linux + 181) +#endif + +#if !defined(__NR_shutdown) +#define __NR_shutdown (__NR_Linux + 182) +#endif + +#if !defined(__NR_socket) +#define __NR_socket (__NR_Linux + 183) +#endif + +#if !defined(__NR_socketpair) +#define __NR_socketpair (__NR_Linux + 184) +#endif + +#if !defined(__NR_setresuid) +#define __NR_setresuid (__NR_Linux + 185) +#endif + +#if !defined(__NR_getresuid) +#define __NR_getresuid (__NR_Linux + 186) +#endif + +#if !defined(__NR_query_module) +#define __NR_query_module (__NR_Linux + 187) +#endif + +#if !defined(__NR_poll) +#define __NR_poll (__NR_Linux + 188) +#endif + +#if !defined(__NR_nfsservctl) +#define __NR_nfsservctl (__NR_Linux + 189) +#endif + +#if !defined(__NR_setresgid) +#define __NR_setresgid (__NR_Linux + 190) +#endif + +#if !defined(__NR_getresgid) +#define __NR_getresgid (__NR_Linux + 191) +#endif + +#if !defined(__NR_prctl) +#define __NR_prctl (__NR_Linux + 192) +#endif + +#if !defined(__NR_rt_sigreturn) +#define __NR_rt_sigreturn (__NR_Linux + 193) +#endif + +#if !defined(__NR_rt_sigaction) +#define __NR_rt_sigaction (__NR_Linux + 194) +#endif + +#if !defined(__NR_rt_sigprocmask) +#define __NR_rt_sigprocmask (__NR_Linux + 195) +#endif + +#if !defined(__NR_rt_sigpending) +#define __NR_rt_sigpending (__NR_Linux + 196) +#endif + +#if !defined(__NR_rt_sigtimedwait) +#define __NR_rt_sigtimedwait (__NR_Linux + 197) +#endif + +#if !defined(__NR_rt_sigqueueinfo) +#define __NR_rt_sigqueueinfo (__NR_Linux + 198) +#endif + +#if !defined(__NR_rt_sigsuspend) +#define __NR_rt_sigsuspend (__NR_Linux + 199) +#endif + +#if !defined(__NR_pread64) +#define __NR_pread64 (__NR_Linux + 200) +#endif + +#if !defined(__NR_pwrite64) +#define __NR_pwrite64 (__NR_Linux + 201) +#endif + +#if !defined(__NR_chown) +#define __NR_chown (__NR_Linux + 202) +#endif + +#if !defined(__NR_getcwd) +#define __NR_getcwd (__NR_Linux + 203) +#endif + +#if !defined(__NR_capget) +#define __NR_capget (__NR_Linux + 204) +#endif + +#if !defined(__NR_capset) +#define __NR_capset (__NR_Linux + 205) +#endif + +#if !defined(__NR_sigaltstack) +#define __NR_sigaltstack (__NR_Linux + 206) +#endif + +#if !defined(__NR_sendfile) +#define __NR_sendfile (__NR_Linux + 207) +#endif + +#if !defined(__NR_getpmsg) +#define __NR_getpmsg (__NR_Linux + 208) +#endif + +#if !defined(__NR_putpmsg) +#define __NR_putpmsg (__NR_Linux + 209) +#endif + +#if !defined(__NR_mmap2) +#define __NR_mmap2 (__NR_Linux + 210) +#endif + +#if !defined(__NR_truncate64) +#define __NR_truncate64 (__NR_Linux + 211) +#endif + +#if !defined(__NR_ftruncate64) +#define __NR_ftruncate64 (__NR_Linux + 212) +#endif + +#if !defined(__NR_stat64) +#define __NR_stat64 (__NR_Linux + 213) +#endif + +#if !defined(__NR_lstat64) +#define __NR_lstat64 (__NR_Linux + 214) +#endif + +#if !defined(__NR_fstat64) +#define __NR_fstat64 (__NR_Linux + 215) +#endif + +#if !defined(__NR_pivot_root) +#define __NR_pivot_root (__NR_Linux + 216) +#endif + +#if !defined(__NR_mincore) +#define __NR_mincore (__NR_Linux + 217) +#endif + +#if !defined(__NR_madvise) +#define __NR_madvise (__NR_Linux + 218) +#endif + +#if !defined(__NR_getdents64) +#define __NR_getdents64 (__NR_Linux + 219) +#endif + +#if !defined(__NR_fcntl64) +#define __NR_fcntl64 (__NR_Linux + 220) +#endif + +#if !defined(__NR_reserved221) +#define __NR_reserved221 (__NR_Linux + 221) +#endif + +#if !defined(__NR_gettid) +#define __NR_gettid (__NR_Linux + 222) +#endif + +#if !defined(__NR_readahead) +#define __NR_readahead (__NR_Linux + 223) +#endif + +#if !defined(__NR_setxattr) +#define __NR_setxattr (__NR_Linux + 224) +#endif + +#if !defined(__NR_lsetxattr) +#define __NR_lsetxattr (__NR_Linux + 225) +#endif + +#if !defined(__NR_fsetxattr) +#define __NR_fsetxattr (__NR_Linux + 226) +#endif + +#if !defined(__NR_getxattr) +#define __NR_getxattr (__NR_Linux + 227) +#endif + +#if !defined(__NR_lgetxattr) +#define __NR_lgetxattr (__NR_Linux + 228) +#endif + +#if !defined(__NR_fgetxattr) +#define __NR_fgetxattr (__NR_Linux + 229) +#endif + +#if !defined(__NR_listxattr) +#define __NR_listxattr (__NR_Linux + 230) +#endif + +#if !defined(__NR_llistxattr) +#define __NR_llistxattr (__NR_Linux + 231) +#endif + +#if !defined(__NR_flistxattr) +#define __NR_flistxattr (__NR_Linux + 232) +#endif + +#if !defined(__NR_removexattr) +#define __NR_removexattr (__NR_Linux + 233) +#endif + +#if !defined(__NR_lremovexattr) +#define __NR_lremovexattr (__NR_Linux + 234) +#endif + +#if !defined(__NR_fremovexattr) +#define __NR_fremovexattr (__NR_Linux + 235) +#endif + +#if !defined(__NR_tkill) +#define __NR_tkill (__NR_Linux + 236) +#endif + +#if !defined(__NR_sendfile64) +#define __NR_sendfile64 (__NR_Linux + 237) +#endif + +#if !defined(__NR_futex) +#define __NR_futex (__NR_Linux + 238) +#endif + +#if !defined(__NR_sched_setaffinity) +#define __NR_sched_setaffinity (__NR_Linux + 239) +#endif + +#if !defined(__NR_sched_getaffinity) +#define __NR_sched_getaffinity (__NR_Linux + 240) +#endif + +#if !defined(__NR_io_setup) +#define __NR_io_setup (__NR_Linux + 241) +#endif + +#if !defined(__NR_io_destroy) +#define __NR_io_destroy (__NR_Linux + 242) +#endif + +#if !defined(__NR_io_getevents) +#define __NR_io_getevents (__NR_Linux + 243) +#endif + +#if !defined(__NR_io_submit) +#define __NR_io_submit (__NR_Linux + 244) +#endif + +#if !defined(__NR_io_cancel) +#define __NR_io_cancel (__NR_Linux + 245) +#endif + +#if !defined(__NR_exit_group) +#define __NR_exit_group (__NR_Linux + 246) +#endif + +#if !defined(__NR_lookup_dcookie) +#define __NR_lookup_dcookie (__NR_Linux + 247) +#endif + +#if !defined(__NR_epoll_create) +#define __NR_epoll_create (__NR_Linux + 248) +#endif + +#if !defined(__NR_epoll_ctl) +#define __NR_epoll_ctl (__NR_Linux + 249) +#endif + +#if !defined(__NR_epoll_wait) +#define __NR_epoll_wait (__NR_Linux + 250) +#endif + +#if !defined(__NR_remap_file_pages) +#define __NR_remap_file_pages (__NR_Linux + 251) +#endif + +#if !defined(__NR_set_tid_address) +#define __NR_set_tid_address (__NR_Linux + 252) +#endif + +#if !defined(__NR_restart_syscall) +#define __NR_restart_syscall (__NR_Linux + 253) +#endif + +#if !defined(__NR_fadvise64) +#define __NR_fadvise64 (__NR_Linux + 254) +#endif + +#if !defined(__NR_statfs64) +#define __NR_statfs64 (__NR_Linux + 255) +#endif + +#if !defined(__NR_fstatfs64) +#define __NR_fstatfs64 (__NR_Linux + 256) +#endif + +#if !defined(__NR_timer_create) +#define __NR_timer_create (__NR_Linux + 257) +#endif + +#if !defined(__NR_timer_settime) +#define __NR_timer_settime (__NR_Linux + 258) +#endif + +#if !defined(__NR_timer_gettime) +#define __NR_timer_gettime (__NR_Linux + 259) +#endif + +#if !defined(__NR_timer_getoverrun) +#define __NR_timer_getoverrun (__NR_Linux + 260) +#endif + +#if !defined(__NR_timer_delete) +#define __NR_timer_delete (__NR_Linux + 261) +#endif + +#if !defined(__NR_clock_settime) +#define __NR_clock_settime (__NR_Linux + 262) +#endif + +#if !defined(__NR_clock_gettime) +#define __NR_clock_gettime (__NR_Linux + 263) +#endif + +#if !defined(__NR_clock_getres) +#define __NR_clock_getres (__NR_Linux + 264) +#endif + +#if !defined(__NR_clock_nanosleep) +#define __NR_clock_nanosleep (__NR_Linux + 265) +#endif + +#if !defined(__NR_tgkill) +#define __NR_tgkill (__NR_Linux + 266) +#endif + +#if !defined(__NR_utimes) +#define __NR_utimes (__NR_Linux + 267) +#endif + +#if !defined(__NR_mbind) +#define __NR_mbind (__NR_Linux + 268) +#endif + +#if !defined(__NR_get_mempolicy) +#define __NR_get_mempolicy (__NR_Linux + 269) +#endif + +#if !defined(__NR_set_mempolicy) +#define __NR_set_mempolicy (__NR_Linux + 270) +#endif + +#if !defined(__NR_mq_open) +#define __NR_mq_open (__NR_Linux + 271) +#endif + +#if !defined(__NR_mq_unlink) +#define __NR_mq_unlink (__NR_Linux + 272) +#endif + +#if !defined(__NR_mq_timedsend) +#define __NR_mq_timedsend (__NR_Linux + 273) +#endif + +#if !defined(__NR_mq_timedreceive) +#define __NR_mq_timedreceive (__NR_Linux + 274) +#endif + +#if !defined(__NR_mq_notify) +#define __NR_mq_notify (__NR_Linux + 275) +#endif + +#if !defined(__NR_mq_getsetattr) +#define __NR_mq_getsetattr (__NR_Linux + 276) +#endif + +#if !defined(__NR_vserver) +#define __NR_vserver (__NR_Linux + 277) +#endif + +#if !defined(__NR_waitid) +#define __NR_waitid (__NR_Linux + 278) +#endif + +/* #define __NR_sys_setaltroot (__NR_Linux + 279) */ + +#if !defined(__NR_add_key) +#define __NR_add_key (__NR_Linux + 280) +#endif + +#if !defined(__NR_request_key) +#define __NR_request_key (__NR_Linux + 281) +#endif + +#if !defined(__NR_keyctl) +#define __NR_keyctl (__NR_Linux + 282) +#endif + +#if !defined(__NR_set_thread_area) +#define __NR_set_thread_area (__NR_Linux + 283) +#endif + +#if !defined(__NR_inotify_init) +#define __NR_inotify_init (__NR_Linux + 284) +#endif + +#if !defined(__NR_inotify_add_watch) +#define __NR_inotify_add_watch (__NR_Linux + 285) +#endif + +#if !defined(__NR_inotify_rm_watch) +#define __NR_inotify_rm_watch (__NR_Linux + 286) +#endif + +#if !defined(__NR_migrate_pages) +#define __NR_migrate_pages (__NR_Linux + 287) +#endif + +#if !defined(__NR_openat) +#define __NR_openat (__NR_Linux + 288) +#endif + +#if !defined(__NR_mkdirat) +#define __NR_mkdirat (__NR_Linux + 289) +#endif + +#if !defined(__NR_mknodat) +#define __NR_mknodat (__NR_Linux + 290) +#endif + +#if !defined(__NR_fchownat) +#define __NR_fchownat (__NR_Linux + 291) +#endif + +#if !defined(__NR_futimesat) +#define __NR_futimesat (__NR_Linux + 292) +#endif + +#if !defined(__NR_fstatat64) +#define __NR_fstatat64 (__NR_Linux + 293) +#endif + +#if !defined(__NR_unlinkat) +#define __NR_unlinkat (__NR_Linux + 294) +#endif + +#if !defined(__NR_renameat) +#define __NR_renameat (__NR_Linux + 295) +#endif + +#if !defined(__NR_linkat) +#define __NR_linkat (__NR_Linux + 296) +#endif + +#if !defined(__NR_symlinkat) +#define __NR_symlinkat (__NR_Linux + 297) +#endif + +#if !defined(__NR_readlinkat) +#define __NR_readlinkat (__NR_Linux + 298) +#endif + +#if !defined(__NR_fchmodat) +#define __NR_fchmodat (__NR_Linux + 299) +#endif + +#if !defined(__NR_faccessat) +#define __NR_faccessat (__NR_Linux + 300) +#endif + +#if !defined(__NR_pselect6) +#define __NR_pselect6 (__NR_Linux + 301) +#endif + +#if !defined(__NR_ppoll) +#define __NR_ppoll (__NR_Linux + 302) +#endif + +#if !defined(__NR_unshare) +#define __NR_unshare (__NR_Linux + 303) +#endif + +#if !defined(__NR_splice) +#define __NR_splice (__NR_Linux + 304) +#endif + +#if !defined(__NR_sync_file_range) +#define __NR_sync_file_range (__NR_Linux + 305) +#endif + +#if !defined(__NR_tee) +#define __NR_tee (__NR_Linux + 306) +#endif + +#if !defined(__NR_vmsplice) +#define __NR_vmsplice (__NR_Linux + 307) +#endif + +#if !defined(__NR_move_pages) +#define __NR_move_pages (__NR_Linux + 308) +#endif + +#if !defined(__NR_set_robust_list) +#define __NR_set_robust_list (__NR_Linux + 309) +#endif + +#if !defined(__NR_get_robust_list) +#define __NR_get_robust_list (__NR_Linux + 310) +#endif + +#if !defined(__NR_kexec_load) +#define __NR_kexec_load (__NR_Linux + 311) +#endif + +#if !defined(__NR_getcpu) +#define __NR_getcpu (__NR_Linux + 312) +#endif + +#if !defined(__NR_epoll_pwait) +#define __NR_epoll_pwait (__NR_Linux + 313) +#endif + +#if !defined(__NR_ioprio_set) +#define __NR_ioprio_set (__NR_Linux + 314) +#endif + +#if !defined(__NR_ioprio_get) +#define __NR_ioprio_get (__NR_Linux + 315) +#endif + +#if !defined(__NR_utimensat) +#define __NR_utimensat (__NR_Linux + 316) +#endif + +#if !defined(__NR_signalfd) +#define __NR_signalfd (__NR_Linux + 317) +#endif + +#if !defined(__NR_timerfd) +#define __NR_timerfd (__NR_Linux + 318) +#endif + +#if !defined(__NR_eventfd) +#define __NR_eventfd (__NR_Linux + 319) +#endif + +#if !defined(__NR_eventfd) +#define __NR_eventfd (__NR_Linux + 320) +#endif + +#if !defined(__NR_timerfd_create) +#define __NR_timerfd_create (__NR_Linux + 321) +#endif + +#if !defined(__NR_timerfd_gettime) +#define __NR_timerfd_gettime (__NR_Linux + 322) +#endif + +#if !defined(__NR_timerfd_settime) +#define __NR_timerfd_settime (__NR_Linux + 323) +#endif + +#if !defined(__NR_signalfd4) +#define __NR_signalfd4 (__NR_Linux + 324) +#endif + +#if !defined(__NR_eventfd2) +#define __NR_eventfd2 (__NR_Linux + 325) +#endif + +#if !defined(__NR_epoll_create1) +#define __NR_epoll_create1 (__NR_Linux + 326) +#endif + +#if !defined(__NR_dup3) +#define __NR_dup3 (__NR_Linux + 327) +#endif + +#if !defined(__NR_pipe2) +#define __NR_pipe2 (__NR_Linux + 328) +#endif + +#if !defined(__NR_inotify_init1) +#define __NR_inotify_init1 (__NR_Linux + 329) +#endif + +#if !defined(__NR_preadv) +#define __NR_preadv (__NR_Linux + 330) +#endif + +#if !defined(__NR_pwritev) +#define __NR_pwritev (__NR_Linux + 331) +#endif + +#if !defined(__NR_rt_tgsigqueueinfo) +#define __NR_rt_tgsigqueueinfo (__NR_Linux + 332) +#endif + +#if !defined(__NR_perf_event_open) +#define __NR_perf_event_open (__NR_Linux + 333) +#endif + +#if !defined(__NR_accept4) +#define __NR_accept4 (__NR_Linux + 334) +#endif + +#if !defined(__NR_recvmmsg) +#define __NR_recvmmsg (__NR_Linux + 335) +#endif + +#if !defined(__NR_fanotify_init) +#define __NR_fanotify_init (__NR_Linux + 336) +#endif + +#if !defined(__NR_fanotify_mark) +#define __NR_fanotify_mark (__NR_Linux + 337) +#endif + +#if !defined(__NR_prlimit64) +#define __NR_prlimit64 (__NR_Linux + 338) +#endif + +#if !defined(__NR_name_to_handle_at) +#define __NR_name_to_handle_at (__NR_Linux + 339) +#endif + +#if !defined(__NR_open_by_handle_at) +#define __NR_open_by_handle_at (__NR_Linux + 340) +#endif + +#if !defined(__NR_clock_adjtime) +#define __NR_clock_adjtime (__NR_Linux + 341) +#endif + +#if !defined(__NR_syncfs) +#define __NR_syncfs (__NR_Linux + 342) +#endif + +#if !defined(__NR_sendmmsg) +#define __NR_sendmmsg (__NR_Linux + 343) +#endif + +#if !defined(__NR_setns) +#define __NR_setns (__NR_Linux + 344) +#endif + +#if !defined(__NR_process_vm_readv) +#define __NR_process_vm_readv (__NR_Linux + 345) +#endif + +#if !defined(__NR_process_vm_writev) +#define __NR_process_vm_writev (__NR_Linux + 346) +#endif + +#if !defined(__NR_kcmp) +#define __NR_kcmp (__NR_Linux + 347) +#endif + +#if !defined(__NR_finit_module) +#define __NR_finit_module (__NR_Linux + 348) +#endif + +#if !defined(__NR_sched_setattr) +#define __NR_sched_setattr (__NR_Linux + 349) +#endif + +#if !defined(__NR_sched_getattr) +#define __NR_sched_getattr (__NR_Linux + 350) +#endif + +#if !defined(__NR_renameat2) +#define __NR_renameat2 (__NR_Linux + 351) +#endif + +#if !defined(__NR_seccomp) +#define __NR_seccomp (__NR_Linux + 352) +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_MIPS_LINUX_SYSCALLS_H_ diff --git a/sandbox/linux/system_headers/mips_linux_ucontext.h b/sandbox/linux/system_headers/mips_linux_ucontext.h new file mode 100644 index 0000000000..27b3763522 --- /dev/null +++ b/sandbox/linux/system_headers/mips_linux_ucontext.h @@ -0,0 +1,51 @@ +// 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_SYSTEM_HEADERS_MIPS_LINUX_UCONTEXT_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_MIPS_LINUX_UCONTEXT_H_ + +// This is mostly copied from breakpad (common/android/include/sys/ucontext.h), +// except we do use sigset_t for uc_sigmask instead of a custom type. +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +// Ensure that 'stack_t' is defined. +#include <asm/signal.h> + +// We also need greg_t for the sandbox, include it in this header as well. +typedef unsigned long greg_t; + +typedef struct { + uint32_t regmask; + uint32_t status; + uint64_t pc; + uint64_t gregs[32]; + uint64_t fpregs[32]; + uint32_t acx; + uint32_t fpc_csr; + uint32_t fpc_eir; + uint32_t used_math; + uint32_t dsp; + uint64_t mdhi; + uint64_t mdlo; + uint32_t hi1; + uint32_t lo1; + uint32_t hi2; + uint32_t lo2; + uint32_t hi3; + uint32_t lo3; +} mcontext_t; + +typedef struct ucontext { + uint32_t uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + mcontext_t uc_mcontext; + sigset_t uc_sigmask; + // Other fields are not used by Google Breakpad. Don't define them. +} ucontext_t; + +#else +#include <sys/ucontext.h> +#endif // __BIONIC_HAVE_UCONTEXT_T + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_MIPS_LINUX_UCONTEXT_H_ diff --git a/sandbox/linux/system_headers/x86_32_linux_syscalls.h b/sandbox/linux/system_headers/x86_32_linux_syscalls.h new file mode 100644 index 0000000000..a6afc62d99 --- /dev/null +++ b/sandbox/linux/system_headers/x86_32_linux_syscalls.h @@ -0,0 +1,1426 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Generated from the Linux kernel's syscall_32.tbl. +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_X86_32_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_X86_32_LINUX_SYSCALLS_H_ + +#if !defined(__i386__) +#error "Including header on wrong architecture" +#endif + +#if !defined(__NR_restart_syscall) +#define __NR_restart_syscall 0 +#endif + +#if !defined(__NR_exit) +#define __NR_exit 1 +#endif + +#if !defined(__NR_fork) +#define __NR_fork 2 +#endif + +#if !defined(__NR_read) +#define __NR_read 3 +#endif + +#if !defined(__NR_write) +#define __NR_write 4 +#endif + +#if !defined(__NR_open) +#define __NR_open 5 +#endif + +#if !defined(__NR_close) +#define __NR_close 6 +#endif + +#if !defined(__NR_waitpid) +#define __NR_waitpid 7 +#endif + +#if !defined(__NR_creat) +#define __NR_creat 8 +#endif + +#if !defined(__NR_link) +#define __NR_link 9 +#endif + +#if !defined(__NR_unlink) +#define __NR_unlink 10 +#endif + +#if !defined(__NR_execve) +#define __NR_execve 11 +#endif + +#if !defined(__NR_chdir) +#define __NR_chdir 12 +#endif + +#if !defined(__NR_time) +#define __NR_time 13 +#endif + +#if !defined(__NR_mknod) +#define __NR_mknod 14 +#endif + +#if !defined(__NR_chmod) +#define __NR_chmod 15 +#endif + +#if !defined(__NR_lchown) +#define __NR_lchown 16 +#endif + +#if !defined(__NR_break) +#define __NR_break 17 +#endif + +#if !defined(__NR_oldstat) +#define __NR_oldstat 18 +#endif + +#if !defined(__NR_lseek) +#define __NR_lseek 19 +#endif + +#if !defined(__NR_getpid) +#define __NR_getpid 20 +#endif + +#if !defined(__NR_mount) +#define __NR_mount 21 +#endif + +#if !defined(__NR_umount) +#define __NR_umount 22 +#endif + +#if !defined(__NR_setuid) +#define __NR_setuid 23 +#endif + +#if !defined(__NR_getuid) +#define __NR_getuid 24 +#endif + +#if !defined(__NR_stime) +#define __NR_stime 25 +#endif + +#if !defined(__NR_ptrace) +#define __NR_ptrace 26 +#endif + +#if !defined(__NR_alarm) +#define __NR_alarm 27 +#endif + +#if !defined(__NR_oldfstat) +#define __NR_oldfstat 28 +#endif + +#if !defined(__NR_pause) +#define __NR_pause 29 +#endif + +#if !defined(__NR_utime) +#define __NR_utime 30 +#endif + +#if !defined(__NR_stty) +#define __NR_stty 31 +#endif + +#if !defined(__NR_gtty) +#define __NR_gtty 32 +#endif + +#if !defined(__NR_access) +#define __NR_access 33 +#endif + +#if !defined(__NR_nice) +#define __NR_nice 34 +#endif + +#if !defined(__NR_ftime) +#define __NR_ftime 35 +#endif + +#if !defined(__NR_sync) +#define __NR_sync 36 +#endif + +#if !defined(__NR_kill) +#define __NR_kill 37 +#endif + +#if !defined(__NR_rename) +#define __NR_rename 38 +#endif + +#if !defined(__NR_mkdir) +#define __NR_mkdir 39 +#endif + +#if !defined(__NR_rmdir) +#define __NR_rmdir 40 +#endif + +#if !defined(__NR_dup) +#define __NR_dup 41 +#endif + +#if !defined(__NR_pipe) +#define __NR_pipe 42 +#endif + +#if !defined(__NR_times) +#define __NR_times 43 +#endif + +#if !defined(__NR_prof) +#define __NR_prof 44 +#endif + +#if !defined(__NR_brk) +#define __NR_brk 45 +#endif + +#if !defined(__NR_setgid) +#define __NR_setgid 46 +#endif + +#if !defined(__NR_getgid) +#define __NR_getgid 47 +#endif + +#if !defined(__NR_signal) +#define __NR_signal 48 +#endif + +#if !defined(__NR_geteuid) +#define __NR_geteuid 49 +#endif + +#if !defined(__NR_getegid) +#define __NR_getegid 50 +#endif + +#if !defined(__NR_acct) +#define __NR_acct 51 +#endif + +#if !defined(__NR_umount2) +#define __NR_umount2 52 +#endif + +#if !defined(__NR_lock) +#define __NR_lock 53 +#endif + +#if !defined(__NR_ioctl) +#define __NR_ioctl 54 +#endif + +#if !defined(__NR_fcntl) +#define __NR_fcntl 55 +#endif + +#if !defined(__NR_mpx) +#define __NR_mpx 56 +#endif + +#if !defined(__NR_setpgid) +#define __NR_setpgid 57 +#endif + +#if !defined(__NR_ulimit) +#define __NR_ulimit 58 +#endif + +#if !defined(__NR_oldolduname) +#define __NR_oldolduname 59 +#endif + +#if !defined(__NR_umask) +#define __NR_umask 60 +#endif + +#if !defined(__NR_chroot) +#define __NR_chroot 61 +#endif + +#if !defined(__NR_ustat) +#define __NR_ustat 62 +#endif + +#if !defined(__NR_dup2) +#define __NR_dup2 63 +#endif + +#if !defined(__NR_getppid) +#define __NR_getppid 64 +#endif + +#if !defined(__NR_getpgrp) +#define __NR_getpgrp 65 +#endif + +#if !defined(__NR_setsid) +#define __NR_setsid 66 +#endif + +#if !defined(__NR_sigaction) +#define __NR_sigaction 67 +#endif + +#if !defined(__NR_sgetmask) +#define __NR_sgetmask 68 +#endif + +#if !defined(__NR_ssetmask) +#define __NR_ssetmask 69 +#endif + +#if !defined(__NR_setreuid) +#define __NR_setreuid 70 +#endif + +#if !defined(__NR_setregid) +#define __NR_setregid 71 +#endif + +#if !defined(__NR_sigsuspend) +#define __NR_sigsuspend 72 +#endif + +#if !defined(__NR_sigpending) +#define __NR_sigpending 73 +#endif + +#if !defined(__NR_sethostname) +#define __NR_sethostname 74 +#endif + +#if !defined(__NR_setrlimit) +#define __NR_setrlimit 75 +#endif + +#if !defined(__NR_getrlimit) +#define __NR_getrlimit 76 +#endif + +#if !defined(__NR_getrusage) +#define __NR_getrusage 77 +#endif + +#if !defined(__NR_gettimeofday) +#define __NR_gettimeofday 78 +#endif + +#if !defined(__NR_settimeofday) +#define __NR_settimeofday 79 +#endif + +#if !defined(__NR_getgroups) +#define __NR_getgroups 80 +#endif + +#if !defined(__NR_setgroups) +#define __NR_setgroups 81 +#endif + +#if !defined(__NR_select) +#define __NR_select 82 +#endif + +#if !defined(__NR_symlink) +#define __NR_symlink 83 +#endif + +#if !defined(__NR_oldlstat) +#define __NR_oldlstat 84 +#endif + +#if !defined(__NR_readlink) +#define __NR_readlink 85 +#endif + +#if !defined(__NR_uselib) +#define __NR_uselib 86 +#endif + +#if !defined(__NR_swapon) +#define __NR_swapon 87 +#endif + +#if !defined(__NR_reboot) +#define __NR_reboot 88 +#endif + +#if !defined(__NR_readdir) +#define __NR_readdir 89 +#endif + +#if !defined(__NR_mmap) +#define __NR_mmap 90 +#endif + +#if !defined(__NR_munmap) +#define __NR_munmap 91 +#endif + +#if !defined(__NR_truncate) +#define __NR_truncate 92 +#endif + +#if !defined(__NR_ftruncate) +#define __NR_ftruncate 93 +#endif + +#if !defined(__NR_fchmod) +#define __NR_fchmod 94 +#endif + +#if !defined(__NR_fchown) +#define __NR_fchown 95 +#endif + +#if !defined(__NR_getpriority) +#define __NR_getpriority 96 +#endif + +#if !defined(__NR_setpriority) +#define __NR_setpriority 97 +#endif + +#if !defined(__NR_profil) +#define __NR_profil 98 +#endif + +#if !defined(__NR_statfs) +#define __NR_statfs 99 +#endif + +#if !defined(__NR_fstatfs) +#define __NR_fstatfs 100 +#endif + +#if !defined(__NR_ioperm) +#define __NR_ioperm 101 +#endif + +#if !defined(__NR_socketcall) +#define __NR_socketcall 102 +#endif + +#if !defined(__NR_syslog) +#define __NR_syslog 103 +#endif + +#if !defined(__NR_setitimer) +#define __NR_setitimer 104 +#endif + +#if !defined(__NR_getitimer) +#define __NR_getitimer 105 +#endif + +#if !defined(__NR_stat) +#define __NR_stat 106 +#endif + +#if !defined(__NR_lstat) +#define __NR_lstat 107 +#endif + +#if !defined(__NR_fstat) +#define __NR_fstat 108 +#endif + +#if !defined(__NR_olduname) +#define __NR_olduname 109 +#endif + +#if !defined(__NR_iopl) +#define __NR_iopl 110 +#endif + +#if !defined(__NR_vhangup) +#define __NR_vhangup 111 +#endif + +#if !defined(__NR_idle) +#define __NR_idle 112 +#endif + +#if !defined(__NR_vm86old) +#define __NR_vm86old 113 +#endif + +#if !defined(__NR_wait4) +#define __NR_wait4 114 +#endif + +#if !defined(__NR_swapoff) +#define __NR_swapoff 115 +#endif + +#if !defined(__NR_sysinfo) +#define __NR_sysinfo 116 +#endif + +#if !defined(__NR_ipc) +#define __NR_ipc 117 +#endif + +#if !defined(__NR_fsync) +#define __NR_fsync 118 +#endif + +#if !defined(__NR_sigreturn) +#define __NR_sigreturn 119 +#endif + +#if !defined(__NR_clone) +#define __NR_clone 120 +#endif + +#if !defined(__NR_setdomainname) +#define __NR_setdomainname 121 +#endif + +#if !defined(__NR_uname) +#define __NR_uname 122 +#endif + +#if !defined(__NR_modify_ldt) +#define __NR_modify_ldt 123 +#endif + +#if !defined(__NR_adjtimex) +#define __NR_adjtimex 124 +#endif + +#if !defined(__NR_mprotect) +#define __NR_mprotect 125 +#endif + +#if !defined(__NR_sigprocmask) +#define __NR_sigprocmask 126 +#endif + +#if !defined(__NR_create_module) +#define __NR_create_module 127 +#endif + +#if !defined(__NR_init_module) +#define __NR_init_module 128 +#endif + +#if !defined(__NR_delete_module) +#define __NR_delete_module 129 +#endif + +#if !defined(__NR_get_kernel_syms) +#define __NR_get_kernel_syms 130 +#endif + +#if !defined(__NR_quotactl) +#define __NR_quotactl 131 +#endif + +#if !defined(__NR_getpgid) +#define __NR_getpgid 132 +#endif + +#if !defined(__NR_fchdir) +#define __NR_fchdir 133 +#endif + +#if !defined(__NR_bdflush) +#define __NR_bdflush 134 +#endif + +#if !defined(__NR_sysfs) +#define __NR_sysfs 135 +#endif + +#if !defined(__NR_personality) +#define __NR_personality 136 +#endif + +#if !defined(__NR_afs_syscall) +#define __NR_afs_syscall 137 +#endif + +#if !defined(__NR_setfsuid) +#define __NR_setfsuid 138 +#endif + +#if !defined(__NR_setfsgid) +#define __NR_setfsgid 139 +#endif + +#if !defined(__NR__llseek) +#define __NR__llseek 140 +#endif + +#if !defined(__NR_getdents) +#define __NR_getdents 141 +#endif + +#if !defined(__NR__newselect) +#define __NR__newselect 142 +#endif + +#if !defined(__NR_flock) +#define __NR_flock 143 +#endif + +#if !defined(__NR_msync) +#define __NR_msync 144 +#endif + +#if !defined(__NR_readv) +#define __NR_readv 145 +#endif + +#if !defined(__NR_writev) +#define __NR_writev 146 +#endif + +#if !defined(__NR_getsid) +#define __NR_getsid 147 +#endif + +#if !defined(__NR_fdatasync) +#define __NR_fdatasync 148 +#endif + +#if !defined(__NR__sysctl) +#define __NR__sysctl 149 +#endif + +#if !defined(__NR_mlock) +#define __NR_mlock 150 +#endif + +#if !defined(__NR_munlock) +#define __NR_munlock 151 +#endif + +#if !defined(__NR_mlockall) +#define __NR_mlockall 152 +#endif + +#if !defined(__NR_munlockall) +#define __NR_munlockall 153 +#endif + +#if !defined(__NR_sched_setparam) +#define __NR_sched_setparam 154 +#endif + +#if !defined(__NR_sched_getparam) +#define __NR_sched_getparam 155 +#endif + +#if !defined(__NR_sched_setscheduler) +#define __NR_sched_setscheduler 156 +#endif + +#if !defined(__NR_sched_getscheduler) +#define __NR_sched_getscheduler 157 +#endif + +#if !defined(__NR_sched_yield) +#define __NR_sched_yield 158 +#endif + +#if !defined(__NR_sched_get_priority_max) +#define __NR_sched_get_priority_max 159 +#endif + +#if !defined(__NR_sched_get_priority_min) +#define __NR_sched_get_priority_min 160 +#endif + +#if !defined(__NR_sched_rr_get_interval) +#define __NR_sched_rr_get_interval 161 +#endif + +#if !defined(__NR_nanosleep) +#define __NR_nanosleep 162 +#endif + +#if !defined(__NR_mremap) +#define __NR_mremap 163 +#endif + +#if !defined(__NR_setresuid) +#define __NR_setresuid 164 +#endif + +#if !defined(__NR_getresuid) +#define __NR_getresuid 165 +#endif + +#if !defined(__NR_vm86) +#define __NR_vm86 166 +#endif + +#if !defined(__NR_query_module) +#define __NR_query_module 167 +#endif + +#if !defined(__NR_poll) +#define __NR_poll 168 +#endif + +#if !defined(__NR_nfsservctl) +#define __NR_nfsservctl 169 +#endif + +#if !defined(__NR_setresgid) +#define __NR_setresgid 170 +#endif + +#if !defined(__NR_getresgid) +#define __NR_getresgid 171 +#endif + +#if !defined(__NR_prctl) +#define __NR_prctl 172 +#endif + +#if !defined(__NR_rt_sigreturn) +#define __NR_rt_sigreturn 173 +#endif + +#if !defined(__NR_rt_sigaction) +#define __NR_rt_sigaction 174 +#endif + +#if !defined(__NR_rt_sigprocmask) +#define __NR_rt_sigprocmask 175 +#endif + +#if !defined(__NR_rt_sigpending) +#define __NR_rt_sigpending 176 +#endif + +#if !defined(__NR_rt_sigtimedwait) +#define __NR_rt_sigtimedwait 177 +#endif + +#if !defined(__NR_rt_sigqueueinfo) +#define __NR_rt_sigqueueinfo 178 +#endif + +#if !defined(__NR_rt_sigsuspend) +#define __NR_rt_sigsuspend 179 +#endif + +#if !defined(__NR_pread64) +#define __NR_pread64 180 +#endif + +#if !defined(__NR_pwrite64) +#define __NR_pwrite64 181 +#endif + +#if !defined(__NR_chown) +#define __NR_chown 182 +#endif + +#if !defined(__NR_getcwd) +#define __NR_getcwd 183 +#endif + +#if !defined(__NR_capget) +#define __NR_capget 184 +#endif + +#if !defined(__NR_capset) +#define __NR_capset 185 +#endif + +#if !defined(__NR_sigaltstack) +#define __NR_sigaltstack 186 +#endif + +#if !defined(__NR_sendfile) +#define __NR_sendfile 187 +#endif + +#if !defined(__NR_getpmsg) +#define __NR_getpmsg 188 +#endif + +#if !defined(__NR_putpmsg) +#define __NR_putpmsg 189 +#endif + +#if !defined(__NR_vfork) +#define __NR_vfork 190 +#endif + +#if !defined(__NR_ugetrlimit) +#define __NR_ugetrlimit 191 +#endif + +#if !defined(__NR_mmap2) +#define __NR_mmap2 192 +#endif + +#if !defined(__NR_truncate64) +#define __NR_truncate64 193 +#endif + +#if !defined(__NR_ftruncate64) +#define __NR_ftruncate64 194 +#endif + +#if !defined(__NR_stat64) +#define __NR_stat64 195 +#endif + +#if !defined(__NR_lstat64) +#define __NR_lstat64 196 +#endif + +#if !defined(__NR_fstat64) +#define __NR_fstat64 197 +#endif + +#if !defined(__NR_lchown32) +#define __NR_lchown32 198 +#endif + +#if !defined(__NR_getuid32) +#define __NR_getuid32 199 +#endif + +#if !defined(__NR_getgid32) +#define __NR_getgid32 200 +#endif + +#if !defined(__NR_geteuid32) +#define __NR_geteuid32 201 +#endif + +#if !defined(__NR_getegid32) +#define __NR_getegid32 202 +#endif + +#if !defined(__NR_setreuid32) +#define __NR_setreuid32 203 +#endif + +#if !defined(__NR_setregid32) +#define __NR_setregid32 204 +#endif + +#if !defined(__NR_getgroups32) +#define __NR_getgroups32 205 +#endif + +#if !defined(__NR_setgroups32) +#define __NR_setgroups32 206 +#endif + +#if !defined(__NR_fchown32) +#define __NR_fchown32 207 +#endif + +#if !defined(__NR_setresuid32) +#define __NR_setresuid32 208 +#endif + +#if !defined(__NR_getresuid32) +#define __NR_getresuid32 209 +#endif + +#if !defined(__NR_setresgid32) +#define __NR_setresgid32 210 +#endif + +#if !defined(__NR_getresgid32) +#define __NR_getresgid32 211 +#endif + +#if !defined(__NR_chown32) +#define __NR_chown32 212 +#endif + +#if !defined(__NR_setuid32) +#define __NR_setuid32 213 +#endif + +#if !defined(__NR_setgid32) +#define __NR_setgid32 214 +#endif + +#if !defined(__NR_setfsuid32) +#define __NR_setfsuid32 215 +#endif + +#if !defined(__NR_setfsgid32) +#define __NR_setfsgid32 216 +#endif + +#if !defined(__NR_pivot_root) +#define __NR_pivot_root 217 +#endif + +#if !defined(__NR_mincore) +#define __NR_mincore 218 +#endif + +#if !defined(__NR_madvise) +#define __NR_madvise 219 +#endif + +#if !defined(__NR_getdents64) +#define __NR_getdents64 220 +#endif + +#if !defined(__NR_fcntl64) +#define __NR_fcntl64 221 +#endif + +#if !defined(__NR_gettid) +#define __NR_gettid 224 +#endif + +#if !defined(__NR_readahead) +#define __NR_readahead 225 +#endif + +#if !defined(__NR_setxattr) +#define __NR_setxattr 226 +#endif + +#if !defined(__NR_lsetxattr) +#define __NR_lsetxattr 227 +#endif + +#if !defined(__NR_fsetxattr) +#define __NR_fsetxattr 228 +#endif + +#if !defined(__NR_getxattr) +#define __NR_getxattr 229 +#endif + +#if !defined(__NR_lgetxattr) +#define __NR_lgetxattr 230 +#endif + +#if !defined(__NR_fgetxattr) +#define __NR_fgetxattr 231 +#endif + +#if !defined(__NR_listxattr) +#define __NR_listxattr 232 +#endif + +#if !defined(__NR_llistxattr) +#define __NR_llistxattr 233 +#endif + +#if !defined(__NR_flistxattr) +#define __NR_flistxattr 234 +#endif + +#if !defined(__NR_removexattr) +#define __NR_removexattr 235 +#endif + +#if !defined(__NR_lremovexattr) +#define __NR_lremovexattr 236 +#endif + +#if !defined(__NR_fremovexattr) +#define __NR_fremovexattr 237 +#endif + +#if !defined(__NR_tkill) +#define __NR_tkill 238 +#endif + +#if !defined(__NR_sendfile64) +#define __NR_sendfile64 239 +#endif + +#if !defined(__NR_futex) +#define __NR_futex 240 +#endif + +#if !defined(__NR_sched_setaffinity) +#define __NR_sched_setaffinity 241 +#endif + +#if !defined(__NR_sched_getaffinity) +#define __NR_sched_getaffinity 242 +#endif + +#if !defined(__NR_set_thread_area) +#define __NR_set_thread_area 243 +#endif + +#if !defined(__NR_get_thread_area) +#define __NR_get_thread_area 244 +#endif + +#if !defined(__NR_io_setup) +#define __NR_io_setup 245 +#endif + +#if !defined(__NR_io_destroy) +#define __NR_io_destroy 246 +#endif + +#if !defined(__NR_io_getevents) +#define __NR_io_getevents 247 +#endif + +#if !defined(__NR_io_submit) +#define __NR_io_submit 248 +#endif + +#if !defined(__NR_io_cancel) +#define __NR_io_cancel 249 +#endif + +#if !defined(__NR_fadvise64) +#define __NR_fadvise64 250 +#endif + +#if !defined(__NR_exit_group) +#define __NR_exit_group 252 +#endif + +#if !defined(__NR_lookup_dcookie) +#define __NR_lookup_dcookie 253 +#endif + +#if !defined(__NR_epoll_create) +#define __NR_epoll_create 254 +#endif + +#if !defined(__NR_epoll_ctl) +#define __NR_epoll_ctl 255 +#endif + +#if !defined(__NR_epoll_wait) +#define __NR_epoll_wait 256 +#endif + +#if !defined(__NR_remap_file_pages) +#define __NR_remap_file_pages 257 +#endif + +#if !defined(__NR_set_tid_address) +#define __NR_set_tid_address 258 +#endif + +#if !defined(__NR_timer_create) +#define __NR_timer_create 259 +#endif + +#if !defined(__NR_timer_settime) +#define __NR_timer_settime 260 +#endif + +#if !defined(__NR_timer_gettime) +#define __NR_timer_gettime 261 +#endif + +#if !defined(__NR_timer_getoverrun) +#define __NR_timer_getoverrun 262 +#endif + +#if !defined(__NR_timer_delete) +#define __NR_timer_delete 263 +#endif + +#if !defined(__NR_clock_settime) +#define __NR_clock_settime 264 +#endif + +#if !defined(__NR_clock_gettime) +#define __NR_clock_gettime 265 +#endif + +#if !defined(__NR_clock_getres) +#define __NR_clock_getres 266 +#endif + +#if !defined(__NR_clock_nanosleep) +#define __NR_clock_nanosleep 267 +#endif + +#if !defined(__NR_statfs64) +#define __NR_statfs64 268 +#endif + +#if !defined(__NR_fstatfs64) +#define __NR_fstatfs64 269 +#endif + +#if !defined(__NR_tgkill) +#define __NR_tgkill 270 +#endif + +#if !defined(__NR_utimes) +#define __NR_utimes 271 +#endif + +#if !defined(__NR_fadvise64_64) +#define __NR_fadvise64_64 272 +#endif + +#if !defined(__NR_vserver) +#define __NR_vserver 273 +#endif + +#if !defined(__NR_mbind) +#define __NR_mbind 274 +#endif + +#if !defined(__NR_get_mempolicy) +#define __NR_get_mempolicy 275 +#endif + +#if !defined(__NR_set_mempolicy) +#define __NR_set_mempolicy 276 +#endif + +#if !defined(__NR_mq_open) +#define __NR_mq_open 277 +#endif + +#if !defined(__NR_mq_unlink) +#define __NR_mq_unlink 278 +#endif + +#if !defined(__NR_mq_timedsend) +#define __NR_mq_timedsend 279 +#endif + +#if !defined(__NR_mq_timedreceive) +#define __NR_mq_timedreceive 280 +#endif + +#if !defined(__NR_mq_notify) +#define __NR_mq_notify 281 +#endif + +#if !defined(__NR_mq_getsetattr) +#define __NR_mq_getsetattr 282 +#endif + +#if !defined(__NR_kexec_load) +#define __NR_kexec_load 283 +#endif + +#if !defined(__NR_waitid) +#define __NR_waitid 284 +#endif + +#if !defined(__NR_add_key) +#define __NR_add_key 286 +#endif + +#if !defined(__NR_request_key) +#define __NR_request_key 287 +#endif + +#if !defined(__NR_keyctl) +#define __NR_keyctl 288 +#endif + +#if !defined(__NR_ioprio_set) +#define __NR_ioprio_set 289 +#endif + +#if !defined(__NR_ioprio_get) +#define __NR_ioprio_get 290 +#endif + +#if !defined(__NR_inotify_init) +#define __NR_inotify_init 291 +#endif + +#if !defined(__NR_inotify_add_watch) +#define __NR_inotify_add_watch 292 +#endif + +#if !defined(__NR_inotify_rm_watch) +#define __NR_inotify_rm_watch 293 +#endif + +#if !defined(__NR_migrate_pages) +#define __NR_migrate_pages 294 +#endif + +#if !defined(__NR_openat) +#define __NR_openat 295 +#endif + +#if !defined(__NR_mkdirat) +#define __NR_mkdirat 296 +#endif + +#if !defined(__NR_mknodat) +#define __NR_mknodat 297 +#endif + +#if !defined(__NR_fchownat) +#define __NR_fchownat 298 +#endif + +#if !defined(__NR_futimesat) +#define __NR_futimesat 299 +#endif + +#if !defined(__NR_fstatat64) +#define __NR_fstatat64 300 +#endif + +#if !defined(__NR_unlinkat) +#define __NR_unlinkat 301 +#endif + +#if !defined(__NR_renameat) +#define __NR_renameat 302 +#endif + +#if !defined(__NR_linkat) +#define __NR_linkat 303 +#endif + +#if !defined(__NR_symlinkat) +#define __NR_symlinkat 304 +#endif + +#if !defined(__NR_readlinkat) +#define __NR_readlinkat 305 +#endif + +#if !defined(__NR_fchmodat) +#define __NR_fchmodat 306 +#endif + +#if !defined(__NR_faccessat) +#define __NR_faccessat 307 +#endif + +#if !defined(__NR_pselect6) +#define __NR_pselect6 308 +#endif + +#if !defined(__NR_ppoll) +#define __NR_ppoll 309 +#endif + +#if !defined(__NR_unshare) +#define __NR_unshare 310 +#endif + +#if !defined(__NR_set_robust_list) +#define __NR_set_robust_list 311 +#endif + +#if !defined(__NR_get_robust_list) +#define __NR_get_robust_list 312 +#endif + +#if !defined(__NR_splice) +#define __NR_splice 313 +#endif + +#if !defined(__NR_sync_file_range) +#define __NR_sync_file_range 314 +#endif + +#if !defined(__NR_tee) +#define __NR_tee 315 +#endif + +#if !defined(__NR_vmsplice) +#define __NR_vmsplice 316 +#endif + +#if !defined(__NR_move_pages) +#define __NR_move_pages 317 +#endif + +#if !defined(__NR_getcpu) +#define __NR_getcpu 318 +#endif + +#if !defined(__NR_epoll_pwait) +#define __NR_epoll_pwait 319 +#endif + +#if !defined(__NR_utimensat) +#define __NR_utimensat 320 +#endif + +#if !defined(__NR_signalfd) +#define __NR_signalfd 321 +#endif + +#if !defined(__NR_timerfd_create) +#define __NR_timerfd_create 322 +#endif + +#if !defined(__NR_eventfd) +#define __NR_eventfd 323 +#endif + +#if !defined(__NR_fallocate) +#define __NR_fallocate 324 +#endif + +#if !defined(__NR_timerfd_settime) +#define __NR_timerfd_settime 325 +#endif + +#if !defined(__NR_timerfd_gettime) +#define __NR_timerfd_gettime 326 +#endif + +#if !defined(__NR_signalfd4) +#define __NR_signalfd4 327 +#endif + +#if !defined(__NR_eventfd2) +#define __NR_eventfd2 328 +#endif + +#if !defined(__NR_epoll_create1) +#define __NR_epoll_create1 329 +#endif + +#if !defined(__NR_dup3) +#define __NR_dup3 330 +#endif + +#if !defined(__NR_pipe2) +#define __NR_pipe2 331 +#endif + +#if !defined(__NR_inotify_init1) +#define __NR_inotify_init1 332 +#endif + +#if !defined(__NR_preadv) +#define __NR_preadv 333 +#endif + +#if !defined(__NR_pwritev) +#define __NR_pwritev 334 +#endif + +#if !defined(__NR_rt_tgsigqueueinfo) +#define __NR_rt_tgsigqueueinfo 335 +#endif + +#if !defined(__NR_perf_event_open) +#define __NR_perf_event_open 336 +#endif + +#if !defined(__NR_recvmmsg) +#define __NR_recvmmsg 337 +#endif + +#if !defined(__NR_fanotify_init) +#define __NR_fanotify_init 338 +#endif + +#if !defined(__NR_fanotify_mark) +#define __NR_fanotify_mark 339 +#endif + +#if !defined(__NR_prlimit64) +#define __NR_prlimit64 340 +#endif + +#if !defined(__NR_name_to_handle_at) +#define __NR_name_to_handle_at 341 +#endif + +#if !defined(__NR_open_by_handle_at) +#define __NR_open_by_handle_at 342 +#endif + +#if !defined(__NR_clock_adjtime) +#define __NR_clock_adjtime 343 +#endif + +#if !defined(__NR_syncfs) +#define __NR_syncfs 344 +#endif + +#if !defined(__NR_sendmmsg) +#define __NR_sendmmsg 345 +#endif + +#if !defined(__NR_setns) +#define __NR_setns 346 +#endif + +#if !defined(__NR_process_vm_readv) +#define __NR_process_vm_readv 347 +#endif + +#if !defined(__NR_process_vm_writev) +#define __NR_process_vm_writev 348 +#endif + +#if !defined(__NR_kcmp) +#define __NR_kcmp 349 +#endif + +#if !defined(__NR_finit_module) +#define __NR_finit_module 350 +#endif + +#if !defined(__NR_sched_setattr) +#define __NR_sched_setattr 351 +#endif + +#if !defined(__NR_sched_getattr) +#define __NR_sched_getattr 352 +#endif + +#if !defined(__NR_renameat2) +#define __NR_renameat2 353 +#endif + +#if !defined(__NR_seccomp) +#define __NR_seccomp 354 +#endif + +#if !defined(__NR_getrandom) +#define __NR_getrandom 355 +#endif + +#if !defined(__NR_memfd_create) +#define __NR_memfd_create 356 +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_X86_32_LINUX_SYSCALLS_H_ + diff --git a/sandbox/linux/system_headers/x86_64_linux_syscalls.h b/sandbox/linux/system_headers/x86_64_linux_syscalls.h new file mode 100644 index 0000000000..349504aee4 --- /dev/null +++ b/sandbox/linux/system_headers/x86_64_linux_syscalls.h @@ -0,0 +1,1294 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Generated from the Linux kernel's syscall_64.tbl. +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_X86_64_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_X86_64_LINUX_SYSCALLS_H_ + +#if !defined(__x86_64__) +#error "Including header on wrong architecture" +#endif + +#if !defined(__NR_read) +#define __NR_read 0 +#endif + +#if !defined(__NR_write) +#define __NR_write 1 +#endif + +#if !defined(__NR_open) +#define __NR_open 2 +#endif + +#if !defined(__NR_close) +#define __NR_close 3 +#endif + +#if !defined(__NR_stat) +#define __NR_stat 4 +#endif + +#if !defined(__NR_fstat) +#define __NR_fstat 5 +#endif + +#if !defined(__NR_lstat) +#define __NR_lstat 6 +#endif + +#if !defined(__NR_poll) +#define __NR_poll 7 +#endif + +#if !defined(__NR_lseek) +#define __NR_lseek 8 +#endif + +#if !defined(__NR_mmap) +#define __NR_mmap 9 +#endif + +#if !defined(__NR_mprotect) +#define __NR_mprotect 10 +#endif + +#if !defined(__NR_munmap) +#define __NR_munmap 11 +#endif + +#if !defined(__NR_brk) +#define __NR_brk 12 +#endif + +#if !defined(__NR_rt_sigaction) +#define __NR_rt_sigaction 13 +#endif + +#if !defined(__NR_rt_sigprocmask) +#define __NR_rt_sigprocmask 14 +#endif + +#if !defined(__NR_rt_sigreturn) +#define __NR_rt_sigreturn 15 +#endif + +#if !defined(__NR_ioctl) +#define __NR_ioctl 16 +#endif + +#if !defined(__NR_pread64) +#define __NR_pread64 17 +#endif + +#if !defined(__NR_pwrite64) +#define __NR_pwrite64 18 +#endif + +#if !defined(__NR_readv) +#define __NR_readv 19 +#endif + +#if !defined(__NR_writev) +#define __NR_writev 20 +#endif + +#if !defined(__NR_access) +#define __NR_access 21 +#endif + +#if !defined(__NR_pipe) +#define __NR_pipe 22 +#endif + +#if !defined(__NR_select) +#define __NR_select 23 +#endif + +#if !defined(__NR_sched_yield) +#define __NR_sched_yield 24 +#endif + +#if !defined(__NR_mremap) +#define __NR_mremap 25 +#endif + +#if !defined(__NR_msync) +#define __NR_msync 26 +#endif + +#if !defined(__NR_mincore) +#define __NR_mincore 27 +#endif + +#if !defined(__NR_madvise) +#define __NR_madvise 28 +#endif + +#if !defined(__NR_shmget) +#define __NR_shmget 29 +#endif + +#if !defined(__NR_shmat) +#define __NR_shmat 30 +#endif + +#if !defined(__NR_shmctl) +#define __NR_shmctl 31 +#endif + +#if !defined(__NR_dup) +#define __NR_dup 32 +#endif + +#if !defined(__NR_dup2) +#define __NR_dup2 33 +#endif + +#if !defined(__NR_pause) +#define __NR_pause 34 +#endif + +#if !defined(__NR_nanosleep) +#define __NR_nanosleep 35 +#endif + +#if !defined(__NR_getitimer) +#define __NR_getitimer 36 +#endif + +#if !defined(__NR_alarm) +#define __NR_alarm 37 +#endif + +#if !defined(__NR_setitimer) +#define __NR_setitimer 38 +#endif + +#if !defined(__NR_getpid) +#define __NR_getpid 39 +#endif + +#if !defined(__NR_sendfile) +#define __NR_sendfile 40 +#endif + +#if !defined(__NR_socket) +#define __NR_socket 41 +#endif + +#if !defined(__NR_connect) +#define __NR_connect 42 +#endif + +#if !defined(__NR_accept) +#define __NR_accept 43 +#endif + +#if !defined(__NR_sendto) +#define __NR_sendto 44 +#endif + +#if !defined(__NR_recvfrom) +#define __NR_recvfrom 45 +#endif + +#if !defined(__NR_sendmsg) +#define __NR_sendmsg 46 +#endif + +#if !defined(__NR_recvmsg) +#define __NR_recvmsg 47 +#endif + +#if !defined(__NR_shutdown) +#define __NR_shutdown 48 +#endif + +#if !defined(__NR_bind) +#define __NR_bind 49 +#endif + +#if !defined(__NR_listen) +#define __NR_listen 50 +#endif + +#if !defined(__NR_getsockname) +#define __NR_getsockname 51 +#endif + +#if !defined(__NR_getpeername) +#define __NR_getpeername 52 +#endif + +#if !defined(__NR_socketpair) +#define __NR_socketpair 53 +#endif + +#if !defined(__NR_setsockopt) +#define __NR_setsockopt 54 +#endif + +#if !defined(__NR_getsockopt) +#define __NR_getsockopt 55 +#endif + +#if !defined(__NR_clone) +#define __NR_clone 56 +#endif + +#if !defined(__NR_fork) +#define __NR_fork 57 +#endif + +#if !defined(__NR_vfork) +#define __NR_vfork 58 +#endif + +#if !defined(__NR_execve) +#define __NR_execve 59 +#endif + +#if !defined(__NR_exit) +#define __NR_exit 60 +#endif + +#if !defined(__NR_wait4) +#define __NR_wait4 61 +#endif + +#if !defined(__NR_kill) +#define __NR_kill 62 +#endif + +#if !defined(__NR_uname) +#define __NR_uname 63 +#endif + +#if !defined(__NR_semget) +#define __NR_semget 64 +#endif + +#if !defined(__NR_semop) +#define __NR_semop 65 +#endif + +#if !defined(__NR_semctl) +#define __NR_semctl 66 +#endif + +#if !defined(__NR_shmdt) +#define __NR_shmdt 67 +#endif + +#if !defined(__NR_msgget) +#define __NR_msgget 68 +#endif + +#if !defined(__NR_msgsnd) +#define __NR_msgsnd 69 +#endif + +#if !defined(__NR_msgrcv) +#define __NR_msgrcv 70 +#endif + +#if !defined(__NR_msgctl) +#define __NR_msgctl 71 +#endif + +#if !defined(__NR_fcntl) +#define __NR_fcntl 72 +#endif + +#if !defined(__NR_flock) +#define __NR_flock 73 +#endif + +#if !defined(__NR_fsync) +#define __NR_fsync 74 +#endif + +#if !defined(__NR_fdatasync) +#define __NR_fdatasync 75 +#endif + +#if !defined(__NR_truncate) +#define __NR_truncate 76 +#endif + +#if !defined(__NR_ftruncate) +#define __NR_ftruncate 77 +#endif + +#if !defined(__NR_getdents) +#define __NR_getdents 78 +#endif + +#if !defined(__NR_getcwd) +#define __NR_getcwd 79 +#endif + +#if !defined(__NR_chdir) +#define __NR_chdir 80 +#endif + +#if !defined(__NR_fchdir) +#define __NR_fchdir 81 +#endif + +#if !defined(__NR_rename) +#define __NR_rename 82 +#endif + +#if !defined(__NR_mkdir) +#define __NR_mkdir 83 +#endif + +#if !defined(__NR_rmdir) +#define __NR_rmdir 84 +#endif + +#if !defined(__NR_creat) +#define __NR_creat 85 +#endif + +#if !defined(__NR_link) +#define __NR_link 86 +#endif + +#if !defined(__NR_unlink) +#define __NR_unlink 87 +#endif + +#if !defined(__NR_symlink) +#define __NR_symlink 88 +#endif + +#if !defined(__NR_readlink) +#define __NR_readlink 89 +#endif + +#if !defined(__NR_chmod) +#define __NR_chmod 90 +#endif + +#if !defined(__NR_fchmod) +#define __NR_fchmod 91 +#endif + +#if !defined(__NR_chown) +#define __NR_chown 92 +#endif + +#if !defined(__NR_fchown) +#define __NR_fchown 93 +#endif + +#if !defined(__NR_lchown) +#define __NR_lchown 94 +#endif + +#if !defined(__NR_umask) +#define __NR_umask 95 +#endif + +#if !defined(__NR_gettimeofday) +#define __NR_gettimeofday 96 +#endif + +#if !defined(__NR_getrlimit) +#define __NR_getrlimit 97 +#endif + +#if !defined(__NR_getrusage) +#define __NR_getrusage 98 +#endif + +#if !defined(__NR_sysinfo) +#define __NR_sysinfo 99 +#endif + +#if !defined(__NR_times) +#define __NR_times 100 +#endif + +#if !defined(__NR_ptrace) +#define __NR_ptrace 101 +#endif + +#if !defined(__NR_getuid) +#define __NR_getuid 102 +#endif + +#if !defined(__NR_syslog) +#define __NR_syslog 103 +#endif + +#if !defined(__NR_getgid) +#define __NR_getgid 104 +#endif + +#if !defined(__NR_setuid) +#define __NR_setuid 105 +#endif + +#if !defined(__NR_setgid) +#define __NR_setgid 106 +#endif + +#if !defined(__NR_geteuid) +#define __NR_geteuid 107 +#endif + +#if !defined(__NR_getegid) +#define __NR_getegid 108 +#endif + +#if !defined(__NR_setpgid) +#define __NR_setpgid 109 +#endif + +#if !defined(__NR_getppid) +#define __NR_getppid 110 +#endif + +#if !defined(__NR_getpgrp) +#define __NR_getpgrp 111 +#endif + +#if !defined(__NR_setsid) +#define __NR_setsid 112 +#endif + +#if !defined(__NR_setreuid) +#define __NR_setreuid 113 +#endif + +#if !defined(__NR_setregid) +#define __NR_setregid 114 +#endif + +#if !defined(__NR_getgroups) +#define __NR_getgroups 115 +#endif + +#if !defined(__NR_setgroups) +#define __NR_setgroups 116 +#endif + +#if !defined(__NR_setresuid) +#define __NR_setresuid 117 +#endif + +#if !defined(__NR_getresuid) +#define __NR_getresuid 118 +#endif + +#if !defined(__NR_setresgid) +#define __NR_setresgid 119 +#endif + +#if !defined(__NR_getresgid) +#define __NR_getresgid 120 +#endif + +#if !defined(__NR_getpgid) +#define __NR_getpgid 121 +#endif + +#if !defined(__NR_setfsuid) +#define __NR_setfsuid 122 +#endif + +#if !defined(__NR_setfsgid) +#define __NR_setfsgid 123 +#endif + +#if !defined(__NR_getsid) +#define __NR_getsid 124 +#endif + +#if !defined(__NR_capget) +#define __NR_capget 125 +#endif + +#if !defined(__NR_capset) +#define __NR_capset 126 +#endif + +#if !defined(__NR_rt_sigpending) +#define __NR_rt_sigpending 127 +#endif + +#if !defined(__NR_rt_sigtimedwait) +#define __NR_rt_sigtimedwait 128 +#endif + +#if !defined(__NR_rt_sigqueueinfo) +#define __NR_rt_sigqueueinfo 129 +#endif + +#if !defined(__NR_rt_sigsuspend) +#define __NR_rt_sigsuspend 130 +#endif + +#if !defined(__NR_sigaltstack) +#define __NR_sigaltstack 131 +#endif + +#if !defined(__NR_utime) +#define __NR_utime 132 +#endif + +#if !defined(__NR_mknod) +#define __NR_mknod 133 +#endif + +#if !defined(__NR_uselib) +#define __NR_uselib 134 +#endif + +#if !defined(__NR_personality) +#define __NR_personality 135 +#endif + +#if !defined(__NR_ustat) +#define __NR_ustat 136 +#endif + +#if !defined(__NR_statfs) +#define __NR_statfs 137 +#endif + +#if !defined(__NR_fstatfs) +#define __NR_fstatfs 138 +#endif + +#if !defined(__NR_sysfs) +#define __NR_sysfs 139 +#endif + +#if !defined(__NR_getpriority) +#define __NR_getpriority 140 +#endif + +#if !defined(__NR_setpriority) +#define __NR_setpriority 141 +#endif + +#if !defined(__NR_sched_setparam) +#define __NR_sched_setparam 142 +#endif + +#if !defined(__NR_sched_getparam) +#define __NR_sched_getparam 143 +#endif + +#if !defined(__NR_sched_setscheduler) +#define __NR_sched_setscheduler 144 +#endif + +#if !defined(__NR_sched_getscheduler) +#define __NR_sched_getscheduler 145 +#endif + +#if !defined(__NR_sched_get_priority_max) +#define __NR_sched_get_priority_max 146 +#endif + +#if !defined(__NR_sched_get_priority_min) +#define __NR_sched_get_priority_min 147 +#endif + +#if !defined(__NR_sched_rr_get_interval) +#define __NR_sched_rr_get_interval 148 +#endif + +#if !defined(__NR_mlock) +#define __NR_mlock 149 +#endif + +#if !defined(__NR_munlock) +#define __NR_munlock 150 +#endif + +#if !defined(__NR_mlockall) +#define __NR_mlockall 151 +#endif + +#if !defined(__NR_munlockall) +#define __NR_munlockall 152 +#endif + +#if !defined(__NR_vhangup) +#define __NR_vhangup 153 +#endif + +#if !defined(__NR_modify_ldt) +#define __NR_modify_ldt 154 +#endif + +#if !defined(__NR_pivot_root) +#define __NR_pivot_root 155 +#endif + +#if !defined(__NR__sysctl) +#define __NR__sysctl 156 +#endif + +#if !defined(__NR_prctl) +#define __NR_prctl 157 +#endif + +#if !defined(__NR_arch_prctl) +#define __NR_arch_prctl 158 +#endif + +#if !defined(__NR_adjtimex) +#define __NR_adjtimex 159 +#endif + +#if !defined(__NR_setrlimit) +#define __NR_setrlimit 160 +#endif + +#if !defined(__NR_chroot) +#define __NR_chroot 161 +#endif + +#if !defined(__NR_sync) +#define __NR_sync 162 +#endif + +#if !defined(__NR_acct) +#define __NR_acct 163 +#endif + +#if !defined(__NR_settimeofday) +#define __NR_settimeofday 164 +#endif + +#if !defined(__NR_mount) +#define __NR_mount 165 +#endif + +#if !defined(__NR_umount2) +#define __NR_umount2 166 +#endif + +#if !defined(__NR_swapon) +#define __NR_swapon 167 +#endif + +#if !defined(__NR_swapoff) +#define __NR_swapoff 168 +#endif + +#if !defined(__NR_reboot) +#define __NR_reboot 169 +#endif + +#if !defined(__NR_sethostname) +#define __NR_sethostname 170 +#endif + +#if !defined(__NR_setdomainname) +#define __NR_setdomainname 171 +#endif + +#if !defined(__NR_iopl) +#define __NR_iopl 172 +#endif + +#if !defined(__NR_ioperm) +#define __NR_ioperm 173 +#endif + +#if !defined(__NR_create_module) +#define __NR_create_module 174 +#endif + +#if !defined(__NR_init_module) +#define __NR_init_module 175 +#endif + +#if !defined(__NR_delete_module) +#define __NR_delete_module 176 +#endif + +#if !defined(__NR_get_kernel_syms) +#define __NR_get_kernel_syms 177 +#endif + +#if !defined(__NR_query_module) +#define __NR_query_module 178 +#endif + +#if !defined(__NR_quotactl) +#define __NR_quotactl 179 +#endif + +#if !defined(__NR_nfsservctl) +#define __NR_nfsservctl 180 +#endif + +#if !defined(__NR_getpmsg) +#define __NR_getpmsg 181 +#endif + +#if !defined(__NR_putpmsg) +#define __NR_putpmsg 182 +#endif + +#if !defined(__NR_afs_syscall) +#define __NR_afs_syscall 183 +#endif + +#if !defined(__NR_tuxcall) +#define __NR_tuxcall 184 +#endif + +#if !defined(__NR_security) +#define __NR_security 185 +#endif + +#if !defined(__NR_gettid) +#define __NR_gettid 186 +#endif + +#if !defined(__NR_readahead) +#define __NR_readahead 187 +#endif + +#if !defined(__NR_setxattr) +#define __NR_setxattr 188 +#endif + +#if !defined(__NR_lsetxattr) +#define __NR_lsetxattr 189 +#endif + +#if !defined(__NR_fsetxattr) +#define __NR_fsetxattr 190 +#endif + +#if !defined(__NR_getxattr) +#define __NR_getxattr 191 +#endif + +#if !defined(__NR_lgetxattr) +#define __NR_lgetxattr 192 +#endif + +#if !defined(__NR_fgetxattr) +#define __NR_fgetxattr 193 +#endif + +#if !defined(__NR_listxattr) +#define __NR_listxattr 194 +#endif + +#if !defined(__NR_llistxattr) +#define __NR_llistxattr 195 +#endif + +#if !defined(__NR_flistxattr) +#define __NR_flistxattr 196 +#endif + +#if !defined(__NR_removexattr) +#define __NR_removexattr 197 +#endif + +#if !defined(__NR_lremovexattr) +#define __NR_lremovexattr 198 +#endif + +#if !defined(__NR_fremovexattr) +#define __NR_fremovexattr 199 +#endif + +#if !defined(__NR_tkill) +#define __NR_tkill 200 +#endif + +#if !defined(__NR_time) +#define __NR_time 201 +#endif + +#if !defined(__NR_futex) +#define __NR_futex 202 +#endif + +#if !defined(__NR_sched_setaffinity) +#define __NR_sched_setaffinity 203 +#endif + +#if !defined(__NR_sched_getaffinity) +#define __NR_sched_getaffinity 204 +#endif + +#if !defined(__NR_set_thread_area) +#define __NR_set_thread_area 205 +#endif + +#if !defined(__NR_io_setup) +#define __NR_io_setup 206 +#endif + +#if !defined(__NR_io_destroy) +#define __NR_io_destroy 207 +#endif + +#if !defined(__NR_io_getevents) +#define __NR_io_getevents 208 +#endif + +#if !defined(__NR_io_submit) +#define __NR_io_submit 209 +#endif + +#if !defined(__NR_io_cancel) +#define __NR_io_cancel 210 +#endif + +#if !defined(__NR_get_thread_area) +#define __NR_get_thread_area 211 +#endif + +#if !defined(__NR_lookup_dcookie) +#define __NR_lookup_dcookie 212 +#endif + +#if !defined(__NR_epoll_create) +#define __NR_epoll_create 213 +#endif + +#if !defined(__NR_epoll_ctl_old) +#define __NR_epoll_ctl_old 214 +#endif + +#if !defined(__NR_epoll_wait_old) +#define __NR_epoll_wait_old 215 +#endif + +#if !defined(__NR_remap_file_pages) +#define __NR_remap_file_pages 216 +#endif + +#if !defined(__NR_getdents64) +#define __NR_getdents64 217 +#endif + +#if !defined(__NR_set_tid_address) +#define __NR_set_tid_address 218 +#endif + +#if !defined(__NR_restart_syscall) +#define __NR_restart_syscall 219 +#endif + +#if !defined(__NR_semtimedop) +#define __NR_semtimedop 220 +#endif + +#if !defined(__NR_fadvise64) +#define __NR_fadvise64 221 +#endif + +#if !defined(__NR_timer_create) +#define __NR_timer_create 222 +#endif + +#if !defined(__NR_timer_settime) +#define __NR_timer_settime 223 +#endif + +#if !defined(__NR_timer_gettime) +#define __NR_timer_gettime 224 +#endif + +#if !defined(__NR_timer_getoverrun) +#define __NR_timer_getoverrun 225 +#endif + +#if !defined(__NR_timer_delete) +#define __NR_timer_delete 226 +#endif + +#if !defined(__NR_clock_settime) +#define __NR_clock_settime 227 +#endif + +#if !defined(__NR_clock_gettime) +#define __NR_clock_gettime 228 +#endif + +#if !defined(__NR_clock_getres) +#define __NR_clock_getres 229 +#endif + +#if !defined(__NR_clock_nanosleep) +#define __NR_clock_nanosleep 230 +#endif + +#if !defined(__NR_exit_group) +#define __NR_exit_group 231 +#endif + +#if !defined(__NR_epoll_wait) +#define __NR_epoll_wait 232 +#endif + +#if !defined(__NR_epoll_ctl) +#define __NR_epoll_ctl 233 +#endif + +#if !defined(__NR_tgkill) +#define __NR_tgkill 234 +#endif + +#if !defined(__NR_utimes) +#define __NR_utimes 235 +#endif + +#if !defined(__NR_vserver) +#define __NR_vserver 236 +#endif + +#if !defined(__NR_mbind) +#define __NR_mbind 237 +#endif + +#if !defined(__NR_set_mempolicy) +#define __NR_set_mempolicy 238 +#endif + +#if !defined(__NR_get_mempolicy) +#define __NR_get_mempolicy 239 +#endif + +#if !defined(__NR_mq_open) +#define __NR_mq_open 240 +#endif + +#if !defined(__NR_mq_unlink) +#define __NR_mq_unlink 241 +#endif + +#if !defined(__NR_mq_timedsend) +#define __NR_mq_timedsend 242 +#endif + +#if !defined(__NR_mq_timedreceive) +#define __NR_mq_timedreceive 243 +#endif + +#if !defined(__NR_mq_notify) +#define __NR_mq_notify 244 +#endif + +#if !defined(__NR_mq_getsetattr) +#define __NR_mq_getsetattr 245 +#endif + +#if !defined(__NR_kexec_load) +#define __NR_kexec_load 246 +#endif + +#if !defined(__NR_waitid) +#define __NR_waitid 247 +#endif + +#if !defined(__NR_add_key) +#define __NR_add_key 248 +#endif + +#if !defined(__NR_request_key) +#define __NR_request_key 249 +#endif + +#if !defined(__NR_keyctl) +#define __NR_keyctl 250 +#endif + +#if !defined(__NR_ioprio_set) +#define __NR_ioprio_set 251 +#endif + +#if !defined(__NR_ioprio_get) +#define __NR_ioprio_get 252 +#endif + +#if !defined(__NR_inotify_init) +#define __NR_inotify_init 253 +#endif + +#if !defined(__NR_inotify_add_watch) +#define __NR_inotify_add_watch 254 +#endif + +#if !defined(__NR_inotify_rm_watch) +#define __NR_inotify_rm_watch 255 +#endif + +#if !defined(__NR_migrate_pages) +#define __NR_migrate_pages 256 +#endif + +#if !defined(__NR_openat) +#define __NR_openat 257 +#endif + +#if !defined(__NR_mkdirat) +#define __NR_mkdirat 258 +#endif + +#if !defined(__NR_mknodat) +#define __NR_mknodat 259 +#endif + +#if !defined(__NR_fchownat) +#define __NR_fchownat 260 +#endif + +#if !defined(__NR_futimesat) +#define __NR_futimesat 261 +#endif + +#if !defined(__NR_newfstatat) +#define __NR_newfstatat 262 +#endif + +#if !defined(__NR_unlinkat) +#define __NR_unlinkat 263 +#endif + +#if !defined(__NR_renameat) +#define __NR_renameat 264 +#endif + +#if !defined(__NR_linkat) +#define __NR_linkat 265 +#endif + +#if !defined(__NR_symlinkat) +#define __NR_symlinkat 266 +#endif + +#if !defined(__NR_readlinkat) +#define __NR_readlinkat 267 +#endif + +#if !defined(__NR_fchmodat) +#define __NR_fchmodat 268 +#endif + +#if !defined(__NR_faccessat) +#define __NR_faccessat 269 +#endif + +#if !defined(__NR_pselect6) +#define __NR_pselect6 270 +#endif + +#if !defined(__NR_ppoll) +#define __NR_ppoll 271 +#endif + +#if !defined(__NR_unshare) +#define __NR_unshare 272 +#endif + +#if !defined(__NR_set_robust_list) +#define __NR_set_robust_list 273 +#endif + +#if !defined(__NR_get_robust_list) +#define __NR_get_robust_list 274 +#endif + +#if !defined(__NR_splice) +#define __NR_splice 275 +#endif + +#if !defined(__NR_tee) +#define __NR_tee 276 +#endif + +#if !defined(__NR_sync_file_range) +#define __NR_sync_file_range 277 +#endif + +#if !defined(__NR_vmsplice) +#define __NR_vmsplice 278 +#endif + +#if !defined(__NR_move_pages) +#define __NR_move_pages 279 +#endif + +#if !defined(__NR_utimensat) +#define __NR_utimensat 280 +#endif + +#if !defined(__NR_epoll_pwait) +#define __NR_epoll_pwait 281 +#endif + +#if !defined(__NR_signalfd) +#define __NR_signalfd 282 +#endif + +#if !defined(__NR_timerfd_create) +#define __NR_timerfd_create 283 +#endif + +#if !defined(__NR_eventfd) +#define __NR_eventfd 284 +#endif + +#if !defined(__NR_fallocate) +#define __NR_fallocate 285 +#endif + +#if !defined(__NR_timerfd_settime) +#define __NR_timerfd_settime 286 +#endif + +#if !defined(__NR_timerfd_gettime) +#define __NR_timerfd_gettime 287 +#endif + +#if !defined(__NR_accept4) +#define __NR_accept4 288 +#endif + +#if !defined(__NR_signalfd4) +#define __NR_signalfd4 289 +#endif + +#if !defined(__NR_eventfd2) +#define __NR_eventfd2 290 +#endif + +#if !defined(__NR_epoll_create1) +#define __NR_epoll_create1 291 +#endif + +#if !defined(__NR_dup3) +#define __NR_dup3 292 +#endif + +#if !defined(__NR_pipe2) +#define __NR_pipe2 293 +#endif + +#if !defined(__NR_inotify_init1) +#define __NR_inotify_init1 294 +#endif + +#if !defined(__NR_preadv) +#define __NR_preadv 295 +#endif + +#if !defined(__NR_pwritev) +#define __NR_pwritev 296 +#endif + +#if !defined(__NR_rt_tgsigqueueinfo) +#define __NR_rt_tgsigqueueinfo 297 +#endif + +#if !defined(__NR_perf_event_open) +#define __NR_perf_event_open 298 +#endif + +#if !defined(__NR_recvmmsg) +#define __NR_recvmmsg 299 +#endif + +#if !defined(__NR_fanotify_init) +#define __NR_fanotify_init 300 +#endif + +#if !defined(__NR_fanotify_mark) +#define __NR_fanotify_mark 301 +#endif + +#if !defined(__NR_prlimit64) +#define __NR_prlimit64 302 +#endif + +#if !defined(__NR_name_to_handle_at) +#define __NR_name_to_handle_at 303 +#endif + +#if !defined(__NR_open_by_handle_at) +#define __NR_open_by_handle_at 304 +#endif + +#if !defined(__NR_clock_adjtime) +#define __NR_clock_adjtime 305 +#endif + +#if !defined(__NR_syncfs) +#define __NR_syncfs 306 +#endif + +#if !defined(__NR_sendmmsg) +#define __NR_sendmmsg 307 +#endif + +#if !defined(__NR_setns) +#define __NR_setns 308 +#endif + +#if !defined(__NR_getcpu) +#define __NR_getcpu 309 +#endif + +#if !defined(__NR_process_vm_readv) +#define __NR_process_vm_readv 310 +#endif + +#if !defined(__NR_process_vm_writev) +#define __NR_process_vm_writev 311 +#endif + +#if !defined(__NR_kcmp) +#define __NR_kcmp 312 +#endif + +#if !defined(__NR_finit_module) +#define __NR_finit_module 313 +#endif + +#if !defined(__NR_sched_setattr) +#define __NR_sched_setattr 314 +#endif + +#if !defined(__NR_sched_getattr) +#define __NR_sched_getattr 315 +#endif + +#if !defined(__NR_renameat2) +#define __NR_renameat2 316 +#endif + +#if !defined(__NR_seccomp) +#define __NR_seccomp 317 +#endif + +#if !defined(__NR_getrandom) +#define __NR_getrandom 318 +#endif + +#if !defined(__NR_memfd_create) +#define __NR_memfd_create 319 +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_X86_64_LINUX_SYSCALLS_H_ + diff --git a/sandbox/linux/system_headers/x86_64_linux_ucontext.h b/sandbox/linux/system_headers/x86_64_linux_ucontext.h new file mode 100644 index 0000000000..57b8919a9c --- /dev/null +++ b/sandbox/linux/system_headers/x86_64_linux_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_SYSTEM_HEADERS_X86_64_LINUX_UCONTEXT_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_X86_64_LINUX_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_SYSTEM_HEADERS_X86_64_LINUX_UCONTEXT_H_ diff --git a/sandbox/linux/tests/main.cc b/sandbox/linux/tests/main.cc new file mode 100644 index 0000000000..caeddee32c --- /dev/null +++ b/sandbox/linux/tests/main.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/at_exit.h" +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +namespace sandbox { +namespace { + +// Check for leaks in our tests. +void RunPostTestsChecks(const base::FilePath& orig_cwd) { + if (TestUtils::CurrentProcessHasChildren()) { + LOG(FATAL) << "One of the tests created a child that was not waited for. " + << "Please, clean up after your tests!"; + } + + base::FilePath cwd; + CHECK(GetCurrentDirectory(&cwd)); + if (orig_cwd != cwd) { + LOG(FATAL) << "One of the tests changed the current working directory. " + << "Please, clean up after your tests!"; + } +} + +} // namespace +} // namespace sandbox + +#if !defined(SANDBOX_USES_BASE_TEST_SUITE) +void UnitTestAssertHandler(const std::string& str) { + _exit(1); +} +#endif + +int main(int argc, char* argv[]) { + base::CommandLine::Init(argc, argv); + std::string client_func; +#if defined(SANDBOX_USES_BASE_TEST_SUITE) + client_func = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTestChildProcess); +#endif + if (!client_func.empty()) { + base::AtExitManager exit_manager; + return multi_process_function_list::InvokeChildProcessTest(client_func); + } + + base::FilePath orig_cwd; + CHECK(GetCurrentDirectory(&orig_cwd)); + +#if !defined(SANDBOX_USES_BASE_TEST_SUITE) + // The use of Callbacks requires an AtExitManager. + base::AtExitManager exit_manager; + testing::InitGoogleTest(&argc, argv); + // Death tests rely on LOG(FATAL) triggering an exit (the default behavior is + // SIGABRT). The normal test launcher does this at initialization, but since + // we still do not use this on Android, we must install the handler ourselves. + logging::SetLogAssertHandler(UnitTestAssertHandler); +#endif + // Always go through re-execution for death tests. + // This makes gtest only marginally slower for us and has the + // additional side effect of getting rid of gtest warnings about fork() + // safety. + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; +#if !defined(SANDBOX_USES_BASE_TEST_SUITE) + int tests_result = RUN_ALL_TESTS(); +#else + int tests_result = base::RunUnitTestsUsingBaseTestSuite(argc, argv); +#endif + + sandbox::RunPostTestsChecks(orig_cwd); + return tests_result; +} diff --git a/sandbox/linux/tests/sandbox_test_runner.cc b/sandbox/linux/tests/sandbox_test_runner.cc new file mode 100644 index 0000000000..b099b97289 --- /dev/null +++ b/sandbox/linux/tests/sandbox_test_runner.cc @@ -0,0 +1,19 @@ +// 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/tests/sandbox_test_runner.h" + +namespace sandbox { + +SandboxTestRunner::SandboxTestRunner() { +} + +SandboxTestRunner::~SandboxTestRunner() { +} + +bool SandboxTestRunner::ShouldCheckForLeaks() const { + return true; +} + +} // namespace sandbox diff --git a/sandbox/linux/tests/sandbox_test_runner.h b/sandbox/linux/tests/sandbox_test_runner.h new file mode 100644 index 0000000000..3155b74008 --- /dev/null +++ b/sandbox/linux/tests/sandbox_test_runner.h @@ -0,0 +1,30 @@ +// 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_TESTS_SANDBOX_TEST_RUNNER_H_ +#define SANDBOX_LINUX_TESTS_SANDBOX_TEST_RUNNER_H_ + +#include "base/macros.h" + +namespace sandbox { + +// A simple "runner" class to implement tests. +class SandboxTestRunner { + public: + SandboxTestRunner(); + virtual ~SandboxTestRunner(); + + virtual void Run() = 0; + + // Override to decide whether or not to check for leaks with LSAN + // (if built with LSAN and LSAN is enabled). + virtual bool ShouldCheckForLeaks() const; + + private: + DISALLOW_COPY_AND_ASSIGN(SandboxTestRunner); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_TESTS_SANDBOX_TEST_RUNNER_H_ diff --git a/sandbox/linux/tests/sandbox_test_runner_function_pointer.cc b/sandbox/linux/tests/sandbox_test_runner_function_pointer.cc new file mode 100644 index 0000000000..69e05ac4e0 --- /dev/null +++ b/sandbox/linux/tests/sandbox_test_runner_function_pointer.cc @@ -0,0 +1,25 @@ +// 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/tests/sandbox_test_runner_function_pointer.h" + +#include "base/logging.h" +#include "build/build_config.h" + +namespace sandbox { + +SandboxTestRunnerFunctionPointer::SandboxTestRunnerFunctionPointer( + void (*function_to_run)(void)) + : function_to_run_(function_to_run) { +} + +SandboxTestRunnerFunctionPointer::~SandboxTestRunnerFunctionPointer() { +} + +void SandboxTestRunnerFunctionPointer::Run() { + DCHECK(function_to_run_); + function_to_run_(); +} + +} // namespace sandbox diff --git a/sandbox/linux/tests/sandbox_test_runner_function_pointer.h b/sandbox/linux/tests/sandbox_test_runner_function_pointer.h new file mode 100644 index 0000000000..cadd07c248 --- /dev/null +++ b/sandbox/linux/tests/sandbox_test_runner_function_pointer.h @@ -0,0 +1,26 @@ +// 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_TESTS_SANDBOX_TEST_RUNNER_FUNCTION_POINTER_H_ +#define SANDBOX_LINUX_TESTS_SANDBOX_TEST_RUNNER_FUNCTION_POINTER_H_ + +#include "base/macros.h" +#include "sandbox/linux/tests/sandbox_test_runner.h" + +namespace sandbox { + +class SandboxTestRunnerFunctionPointer : public SandboxTestRunner { + public: + SandboxTestRunnerFunctionPointer(void (*function_to_run)(void)); + ~SandboxTestRunnerFunctionPointer() override; + void Run() override; + + private: + void (*function_to_run_)(void); + DISALLOW_COPY_AND_ASSIGN(SandboxTestRunnerFunctionPointer); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_TESTS_SANDBOX_TEST_RUNNER__FUNCTION_POINTER_H_ diff --git a/sandbox/linux/tests/scoped_temporary_file.cc b/sandbox/linux/tests/scoped_temporary_file.cc new file mode 100644 index 0000000000..1f2d66fd6b --- /dev/null +++ b/sandbox/linux/tests/scoped_temporary_file.cc @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/tests/scoped_temporary_file.h" + +#include <stdlib.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" + +namespace sandbox { + +ScopedTemporaryFile::ScopedTemporaryFile() : fd_(-1) { +#if defined(OS_ANDROID) + static const char file_template[] = "/data/local/tmp/ScopedTempFileXXXXXX"; +#else + static const char file_template[] = "/tmp/ScopedTempFileXXXXXX"; +#endif // defined(OS_ANDROID) + static_assert(sizeof(full_file_name_) >= sizeof(file_template), + "full_file_name is not large enough"); + memcpy(full_file_name_, file_template, sizeof(file_template)); + fd_ = mkstemp(full_file_name_); + CHECK_LE(0, fd_); +} + +ScopedTemporaryFile::~ScopedTemporaryFile() { + CHECK_EQ(0, unlink(full_file_name_)); + CHECK_EQ(0, IGNORE_EINTR(close(fd_))); +} + +} // namespace sandbox diff --git a/sandbox/linux/tests/scoped_temporary_file.h b/sandbox/linux/tests/scoped_temporary_file.h new file mode 100644 index 0000000000..0734130055 --- /dev/null +++ b/sandbox/linux/tests/scoped_temporary_file.h @@ -0,0 +1,30 @@ +// 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_TESTS_SCOPED_TEMPORARY_FILE_H_ +#define SANDBOX_LINUX_TESTS_SCOPED_TEMPORARY_FILE_H_ + +#include "base/macros.h" + +namespace sandbox { +// Creates and open a temporary file on creation and closes +// and removes it on destruction. +// Unlike base/ helpers, this does not require JNI on Android. +class ScopedTemporaryFile { + public: + ScopedTemporaryFile(); + ~ScopedTemporaryFile(); + + int fd() const { return fd_; } + const char* full_file_name() const { return full_file_name_; } + + private: + int fd_; + char full_file_name_[128]; + DISALLOW_COPY_AND_ASSIGN(ScopedTemporaryFile); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_TESTS_SCOPED_TEMPORARY_FILE_H_ diff --git a/sandbox/linux/tests/scoped_temporary_file_unittest.cc b/sandbox/linux/tests/scoped_temporary_file_unittest.cc new file mode 100644 index 0000000000..44a2ecb1ae --- /dev/null +++ b/sandbox/linux/tests/scoped_temporary_file_unittest.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/tests/scoped_temporary_file.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> + +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +bool FullWrite(int fd, const char* buffer, size_t count) { + while (count > 0) { + const ssize_t transfered = HANDLE_EINTR(write(fd, buffer, count)); + if (transfered <= 0 || static_cast<size_t>(transfered) > count) { + return false; + } + count -= transfered; + buffer += transfered; + } + return true; +} + +bool FullRead(int fd, char* buffer, size_t count) { + while (count > 0) { + const ssize_t transfered = HANDLE_EINTR(read(fd, buffer, count)); + if (transfered <= 0 || static_cast<size_t>(transfered) > count) { + return false; + } + count -= transfered; + buffer += transfered; + } + return true; +} + +TEST(ScopedTemporaryFile, Basics) { + std::string temp_file_name; + { + ScopedTemporaryFile temp_file_1; + const char kTestString[] = "This is a test"; + ASSERT_LE(0, temp_file_1.fd()); + + temp_file_name = temp_file_1.full_file_name(); + base::ScopedFD temp_file_2(open(temp_file_1.full_file_name(), O_RDONLY)); + ASSERT_TRUE(temp_file_2.is_valid()); + + ASSERT_TRUE(FullWrite(temp_file_1.fd(), kTestString, sizeof(kTestString))); + + char test_string_read[sizeof(kTestString)] = {0}; + ASSERT_TRUE(FullRead( + temp_file_2.get(), test_string_read, sizeof(test_string_read))); + ASSERT_EQ(0, memcmp(kTestString, test_string_read, sizeof(kTestString))); + } + + errno = 0; + struct stat buf; + ASSERT_EQ(-1, stat(temp_file_name.c_str(), &buf)); + ASSERT_EQ(ENOENT, errno); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/tests/test_utils.cc b/sandbox/linux/tests/test_utils.cc new file mode 100644 index 0000000000..747bad27a5 --- /dev/null +++ b/sandbox/linux/tests/test_utils.cc @@ -0,0 +1,42 @@ +// 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/tests/test_utils.h" + +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace sandbox { + +bool TestUtils::CurrentProcessHasChildren() { + siginfo_t process_info; + int ret = HANDLE_EINTR( + waitid(P_ALL, 0, &process_info, WEXITED | WNOHANG | WNOWAIT)); + if (-1 == ret) { + PCHECK(ECHILD == errno); + return false; + } else { + return true; + } +} + +void TestUtils::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); + } +} + +} // namespace sandbox diff --git a/sandbox/linux/tests/test_utils.h b/sandbox/linux/tests/test_utils.h new file mode 100644 index 0000000000..7cf9749fe4 --- /dev/null +++ b/sandbox/linux/tests/test_utils.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_TESTS_TEST_UTILS_H_ +#define SANDBOX_LINUX_TESTS_TEST_UTILS_H_ + +#include <sys/types.h> + +#include "base/macros.h" + +namespace sandbox { + +// This class provide small helpers to help writing tests. +class TestUtils { + public: + static bool CurrentProcessHasChildren(); + // |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. + static void HandlePostForkReturn(pid_t pid); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(TestUtils); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_TESTS_TEST_UTILS_H_ diff --git a/sandbox/linux/tests/test_utils_unittest.cc b/sandbox/linux/tests/test_utils_unittest.cc new file mode 100644 index 0000000000..0f86e616e9 --- /dev/null +++ b/sandbox/linux/tests/test_utils_unittest.cc @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/tests/test_utils.h" + +#include <sys/types.h> +#include <unistd.h> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +// Check that HandlePostForkReturn works. +TEST(TestUtils, HandlePostForkReturn) { + pid_t pid = fork(); + TestUtils::HandlePostForkReturn(pid); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/tests/unit_tests.cc b/sandbox/linux/tests/unit_tests.cc new file mode 100644 index 0000000000..4973c41fbd --- /dev/null +++ b/sandbox/linux/tests/unit_tests.cc @@ -0,0 +1,354 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <sys/resource.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +#include "base/debug/leak_annotations.h" +#include "base/files/file_util.h" +#include "base/posix/eintr_wrapper.h" +#include "base/third_party/valgrind/valgrind.h" +#include "build/build_config.h" +#include "sandbox/linux/tests/unit_tests.h" + +// Specifically, PNaCl toolchain does not have this flag. +#if !defined(POLLRDHUP) +#define POLLRDHUP 0x2000 +#endif + +namespace { +std::string TestFailedMessage(const std::string& msg) { + return msg.empty() ? std::string() : "Actual test failure: " + msg; +} + +int GetSubProcessTimeoutTimeInSeconds() { + // Previously 10s, but that timed out (just) on Chromecast. + return 12; +} + +// Returns the number of threads of the current process or -1. +int CountThreads() { + struct stat task_stat; + int task_d = stat("/proc/self/task", &task_stat); + // task_stat.st_nlink should be the number of tasks + 2 (accounting for + // "." and "..". + if (task_d != 0 || task_stat.st_nlink < 3) + return -1; + const int num_threads = task_stat.st_nlink - 2; + return num_threads; +} + +} // namespace + +namespace sandbox { + +bool IsAndroid() { +#if defined(OS_ANDROID) + return true; +#else + return false; +#endif +} + +bool IsArchitectureArm() { +#if defined(ARCH_CPU_ARM_FAMILY) + return true; +#else + return false; +#endif +} + +// TODO(jln): figure out why base/.../dynamic_annotations.h's +// RunningOnValgrind() cannot link. +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + +static const int kExpectedValue = 42; +static const int kIgnoreThisTest = 43; +static const int kExitWithAssertionFailure = 1; +#if !defined(OS_NACL_NONSFI) +static const int kExitForTimeout = 2; +#endif + +#if defined(SANDBOX_USES_BASE_TEST_SUITE) +// This is due to StackDumpSignalHandler() performing _exit(1). +// TODO(jln): get rid of the collision with kExitWithAssertionFailure. +const int kExitAfterSIGSEGV = 1; +#endif + +// PNaCl toolchain's signal ABIs are incompatible with Linux's. +// So, for simplicity, just drop the "timeout" feature from unittest framework +// with relying on the buildbot's timeout feature. +#if !defined(OS_NACL_NONSFI) +static void SigAlrmHandler(int) { + const char failure_message[] = "Timeout reached!\n"; + // Make sure that we never block here. + if (!fcntl(2, F_SETFL, O_NONBLOCK)) { + ignore_result(write(2, failure_message, sizeof(failure_message) - 1)); + } + _exit(kExitForTimeout); +} + +// Set a timeout with a handler that will automatically fail the +// test. +static void SetProcessTimeout(int time_in_seconds) { + struct sigaction act = {}; + act.sa_handler = SigAlrmHandler; + SANDBOX_ASSERT(sigemptyset(&act.sa_mask) == 0); + act.sa_flags = 0; + + struct sigaction old_act; + SANDBOX_ASSERT(sigaction(SIGALRM, &act, &old_act) == 0); + + // We don't implemenet signal chaining, so make sure that nothing else + // is expecting to handle SIGALRM. + SANDBOX_ASSERT((old_act.sa_flags & SA_SIGINFO) == 0); + SANDBOX_ASSERT(old_act.sa_handler == SIG_DFL); + sigset_t sigalrm_set; + SANDBOX_ASSERT(sigemptyset(&sigalrm_set) == 0); + SANDBOX_ASSERT(sigaddset(&sigalrm_set, SIGALRM) == 0); + SANDBOX_ASSERT(sigprocmask(SIG_UNBLOCK, &sigalrm_set, NULL) == 0); + SANDBOX_ASSERT(alarm(time_in_seconds) == 0); // There should be no previous + // alarm. +} +#endif // !defined(OS_NACL_NONSFI) + +// Runs a test in a sub-process. This is necessary for most of the code +// in the BPF sandbox, as it potentially makes global state changes and as +// it also tends to raise fatal errors, if the code has been used in an +// insecure manner. +void UnitTests::RunTestInProcess(SandboxTestRunner* test_runner, + DeathCheck death, + const void* death_aux) { + CHECK(test_runner); + // We need to fork(), so we can't be multi-threaded, as threads could hold + // locks. + int num_threads = CountThreads(); +#if !defined(THREAD_SANITIZER) + const int kNumExpectedThreads = 1; +#else + // Under TSAN, there is a special helper thread. It should be completely + // invisible to our testing, so we ignore it. It should be ok to fork() + // with this thread. It's currently buggy, but it's the best we can do until + // there is a way to delay the start of the thread + // (https://code.google.com/p/thread-sanitizer/issues/detail?id=19). + const int kNumExpectedThreads = 2; +#endif + + // The kernel is at liberty to wake a thread id futex before updating /proc. + // If another test running in the same process has stopped a thread, it may + // appear as still running in /proc. + // We poll /proc, with an exponential back-off. At most, we'll sleep around + // 2^iterations nanoseconds in nanosleep(). + for (unsigned int iteration = 0; iteration < 30; iteration++) { + struct timespec ts = {0, 1L << iteration /* nanoseconds */}; + PCHECK(0 == HANDLE_EINTR(nanosleep(&ts, &ts))); + num_threads = CountThreads(); + if (kNumExpectedThreads == num_threads) + break; + } + + ASSERT_EQ(kNumExpectedThreads, num_threads) + << "Running sandbox tests with multiple threads " + << "is not supported and will make the tests flaky."; + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + // Check that our pipe is not on one of the standard file descriptor. + SANDBOX_ASSERT(fds[0] > 2 && fds[1] > 2); + + pid_t pid; + ASSERT_LE(0, (pid = fork())); + if (!pid) { + // In child process + // Redirect stderr to our pipe. This way, we can capture all error + // messages, if we decide we want to do so in our tests. + SANDBOX_ASSERT(dup2(fds[1], 2) == 2); + SANDBOX_ASSERT(!close(fds[0])); + SANDBOX_ASSERT(!close(fds[1])); + + // Don't set a timeout if running on Valgrind, since it's generally much + // slower. + if (!IsRunningOnValgrind()) { +#if !defined(OS_NACL_NONSFI) + SetProcessTimeout(GetSubProcessTimeoutTimeInSeconds()); +#endif + } + + // Disable core files. They are not very useful for our individual test + // cases. + struct rlimit no_core = {0}; + setrlimit(RLIMIT_CORE, &no_core); + + test_runner->Run(); + if (test_runner->ShouldCheckForLeaks()) { +#if defined(LEAK_SANITIZER) + __lsan_do_leak_check(); +#endif + } + _exit(kExpectedValue); + } + + close(fds[1]); + std::vector<char> msg_buf; + ssize_t rc; + + // Make sure read() will never block as we'll use poll() to + // block with a timeout instead. + const int fcntl_ret = fcntl(fds[0], F_SETFL, O_NONBLOCK); + ASSERT_EQ(0, fcntl_ret); + struct pollfd poll_fd = {fds[0], POLLIN | POLLRDHUP, 0}; + + int poll_ret; + // We prefer the SIGALRM timeout to trigger in the child than this timeout + // so we double the common value here. + int poll_timeout = GetSubProcessTimeoutTimeInSeconds() * 2 * 1000; + while ((poll_ret = poll(&poll_fd, 1, poll_timeout) > 0)) { + const size_t kCapacity = 256; + const size_t len = msg_buf.size(); + msg_buf.resize(len + kCapacity); + rc = HANDLE_EINTR(read(fds[0], &msg_buf[len], kCapacity)); + msg_buf.resize(len + std::max(rc, static_cast<ssize_t>(0))); + if (rc <= 0) + break; + } + ASSERT_NE(poll_ret, -1) << "poll() failed"; + ASSERT_NE(poll_ret, 0) << "Timeout while reading child state"; + close(fds[0]); + std::string msg(msg_buf.begin(), msg_buf.end()); + + int status = 0; + int waitpid_returned = HANDLE_EINTR(waitpid(pid, &status, 0)); + ASSERT_EQ(pid, waitpid_returned) << TestFailedMessage(msg); + + // At run-time, we sometimes decide that a test shouldn't actually + // run (e.g. when testing sandbox features on a kernel that doesn't + // have sandboxing support). When that happens, don't attempt to + // call the "death" function, as it might be looking for a + // death-test condition that would never have triggered. + if (!WIFEXITED(status) || WEXITSTATUS(status) != kIgnoreThisTest || + !msg.empty()) { + // We use gtest's ASSERT_XXX() macros instead of the DeathCheck + // functions. This means, on failure, "return" is called. This + // only works correctly, if the call of the "death" callback is + // the very last thing in our function. + death(status, msg, death_aux); + } +} + +void UnitTests::DeathSuccess(int status, const std::string& msg, const void*) { + std::string details(TestFailedMessage(msg)); + + bool subprocess_terminated_normally = WIFEXITED(status); + ASSERT_TRUE(subprocess_terminated_normally) << details; + int subprocess_exit_status = WEXITSTATUS(status); + ASSERT_EQ(kExpectedValue, subprocess_exit_status) << details; + bool subprocess_exited_but_printed_messages = !msg.empty(); + EXPECT_FALSE(subprocess_exited_but_printed_messages) << details; +} + +void UnitTests::DeathSuccessAllowNoise(int status, + const std::string& msg, + const void*) { + std::string details(TestFailedMessage(msg)); + + bool subprocess_terminated_normally = WIFEXITED(status); + ASSERT_TRUE(subprocess_terminated_normally) << details; + int subprocess_exit_status = WEXITSTATUS(status); + ASSERT_EQ(kExpectedValue, subprocess_exit_status) << details; +} + +void UnitTests::DeathMessage(int status, + const std::string& msg, + const void* aux) { + std::string details(TestFailedMessage(msg)); + const char* expected_msg = static_cast<const char*>(aux); + + bool subprocess_terminated_normally = WIFEXITED(status); + ASSERT_TRUE(subprocess_terminated_normally) << "Exit status: " << status + << " " << details; + int subprocess_exit_status = WEXITSTATUS(status); + ASSERT_EQ(1, subprocess_exit_status) << details; + + bool subprocess_exited_without_matching_message = + msg.find(expected_msg) == std::string::npos; + +// In official builds CHECK messages are dropped, so look for SIGABRT. +// See https://code.google.com/p/chromium/issues/detail?id=437312 +#if defined(OFFICIAL_BUILD) && defined(NDEBUG) && !defined(OS_ANDROID) + if (subprocess_exited_without_matching_message) { + static const char kSigAbortMessage[] = "Received signal 6"; + subprocess_exited_without_matching_message = + msg.find(kSigAbortMessage) == std::string::npos; + } +#endif + EXPECT_FALSE(subprocess_exited_without_matching_message) << details; +} + +void UnitTests::DeathSEGVMessage(int status, + const std::string& msg, + const void* aux) { + std::string details(TestFailedMessage(msg)); + const char* expected_msg = static_cast<const char*>(aux); + +#if !defined(SANDBOX_USES_BASE_TEST_SUITE) + const bool subprocess_got_sigsegv = + WIFSIGNALED(status) && (SIGSEGV == WTERMSIG(status)); +#else + // This hack is required when a signal handler is installed + // for SEGV that will _exit(1). + const bool subprocess_got_sigsegv = + WIFEXITED(status) && (kExitAfterSIGSEGV == WEXITSTATUS(status)); +#endif + + ASSERT_TRUE(subprocess_got_sigsegv) << "Exit status: " << status + << " " << details; + + bool subprocess_exited_without_matching_message = + msg.find(expected_msg) == std::string::npos; + EXPECT_FALSE(subprocess_exited_without_matching_message) << details; +} + +void UnitTests::DeathExitCode(int status, + const std::string& msg, + const void* aux) { + int expected_exit_code = static_cast<int>(reinterpret_cast<intptr_t>(aux)); + std::string details(TestFailedMessage(msg)); + + bool subprocess_terminated_normally = WIFEXITED(status); + ASSERT_TRUE(subprocess_terminated_normally) << details; + int subprocess_exit_status = WEXITSTATUS(status); + ASSERT_EQ(expected_exit_code, subprocess_exit_status) << details; +} + +void UnitTests::DeathBySignal(int status, + const std::string& msg, + const void* aux) { + int expected_signo = static_cast<int>(reinterpret_cast<intptr_t>(aux)); + std::string details(TestFailedMessage(msg)); + + bool subprocess_terminated_by_signal = WIFSIGNALED(status); + ASSERT_TRUE(subprocess_terminated_by_signal) << details; + int subprocess_signal_number = WTERMSIG(status); + ASSERT_EQ(expected_signo, subprocess_signal_number) << details; +} + +void UnitTests::AssertionFailure(const char* expr, const char* file, int line) { + fprintf(stderr, "%s:%d:%s", file, line, expr); + fflush(stderr); + _exit(kExitWithAssertionFailure); +} + +void UnitTests::IgnoreThisTest() { + fflush(stderr); + _exit(kIgnoreThisTest); +} + +} // namespace diff --git a/sandbox/linux/tests/unit_tests.h b/sandbox/linux/tests/unit_tests.h new file mode 100644 index 0000000000..5a7116e932 --- /dev/null +++ b/sandbox/linux/tests/unit_tests.h @@ -0,0 +1,201 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_TESTS_UNIT_TESTS_H_ +#define SANDBOX_LINUX_TESTS_UNIT_TESTS_H_ + +#include "base/macros.h" +#include "build/build_config.h" +#include "sandbox/linux/tests/sandbox_test_runner_function_pointer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Has this been compiled to run on Android? +bool IsAndroid(); + +bool IsArchitectureArm(); + +// Is Valgrind currently being used? +bool IsRunningOnValgrind(); + +#if defined(ADDRESS_SANITIZER) +#define DISABLE_ON_ASAN(test_name) DISABLED_##test_name +#else +#define DISABLE_ON_ASAN(test_name) test_name +#endif // defined(ADDRESS_SANITIZER) + +#if defined(LEAK_SANITIZER) +#define DISABLE_ON_LSAN(test_name) DISABLED_##test_name +#else +#define DISABLE_ON_LSAN(test_name) test_name +#endif + +#if defined(THREAD_SANITIZER) +#define DISABLE_ON_TSAN(test_name) DISABLED_##test_name +#else +#define DISABLE_ON_TSAN(test_name) test_name +#endif // defined(THREAD_SANITIZER) + +#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ + defined(THREAD_SANITIZER) || defined(LEAK_SANITIZER) || \ + defined(UNDEFINED_SANITIZER) || defined(SANITIZER_COVERAGE) +#define DISABLE_ON_SANITIZERS(test_name) DISABLED_##test_name +#else +#define DISABLE_ON_SANITIZERS(test_name) test_name +#endif + +#if defined(OS_ANDROID) +#define DISABLE_ON_ANDROID(test_name) DISABLED_##test_name +#else +#define DISABLE_ON_ANDROID(test_name) test_name +#endif + +// While it is perfectly OK for a complex test to provide its own DeathCheck +// function. Most death tests have very simple requirements. These tests should +// use one of the predefined DEATH_XXX macros as an argument to +// SANDBOX_DEATH_TEST(). You can check for a (sub-)string in the output of the +// test, for a particular exit code, or for a particular death signal. +// NOTE: If you do decide to write your own DeathCheck, make sure to use +// gtests's ASSERT_XXX() macros instead of SANDBOX_ASSERT(). See +// unit_tests.cc for examples. +#define DEATH_SUCCESS() sandbox::UnitTests::DeathSuccess, NULL +#define DEATH_SUCCESS_ALLOW_NOISE() \ + sandbox::UnitTests::DeathSuccessAllowNoise, NULL +#define DEATH_MESSAGE(msg) \ + sandbox::UnitTests::DeathMessage, \ + static_cast<const void*>(static_cast<const char*>(msg)) +#define DEATH_SEGV_MESSAGE(msg) \ + sandbox::UnitTests::DeathSEGVMessage, \ + static_cast<const void*>(static_cast<const char*>(msg)) +#define DEATH_EXIT_CODE(rc) \ + sandbox::UnitTests::DeathExitCode, \ + reinterpret_cast<void*>(static_cast<intptr_t>(rc)) +#define DEATH_BY_SIGNAL(s) \ + sandbox::UnitTests::DeathBySignal, \ + reinterpret_cast<void*>(static_cast<intptr_t>(s)) + +// A SANDBOX_DEATH_TEST is just like a SANDBOX_TEST (see below), but it assumes +// that the test actually dies. The death test only passes if the death occurs +// in the expected fashion, as specified by "death" and "death_aux". These two +// parameters are typically set to one of the DEATH_XXX() macros. +#define SANDBOX_DEATH_TEST(test_case_name, test_name, death) \ + void TEST_##test_name(void); \ + TEST(test_case_name, test_name) { \ + SandboxTestRunnerFunctionPointer sandbox_test_runner(TEST_##test_name); \ + sandbox::UnitTests::RunTestInProcess(&sandbox_test_runner, death); \ + } \ + void TEST_##test_name(void) + +// Define a new test case that runs inside of a GTest death test. This is +// necessary, as most of our tests by definition make global and irreversible +// changes to the system (i.e. they install a sandbox). GTest provides death +// tests as a tool to isolate global changes from the rest of the tests. +#define SANDBOX_TEST(test_case_name, test_name) \ + SANDBOX_DEATH_TEST(test_case_name, test_name, DEATH_SUCCESS()) + +// SANDBOX_TEST_ALLOW_NOISE is just like SANDBOX_TEST, except it does not +// consider log error messages printed by the test to be test failures. +#define SANDBOX_TEST_ALLOW_NOISE(test_case_name, test_name) \ + SANDBOX_DEATH_TEST(test_case_name, test_name, DEATH_SUCCESS_ALLOW_NOISE()) + +// Simple assertion macro that is compatible with running inside of a death +// test. We unfortunately cannot use any of the GTest macros. +#define SANDBOX_STR(x) #x +#define SANDBOX_ASSERT(expr) \ + ((expr) ? static_cast<void>(0) : sandbox::UnitTests::AssertionFailure( \ + SANDBOX_STR(expr), __FILE__, __LINE__)) + +#define SANDBOX_ASSERT_EQ(x, y) SANDBOX_ASSERT((x) == (y)) +#define SANDBOX_ASSERT_NE(x, y) SANDBOX_ASSERT((x) != (y)) +#define SANDBOX_ASSERT_LT(x, y) SANDBOX_ASSERT((x) < (y)) +#define SANDBOX_ASSERT_GT(x, y) SANDBOX_ASSERT((x) > (y)) +#define SANDBOX_ASSERT_LE(x, y) SANDBOX_ASSERT((x) <= (y)) +#define SANDBOX_ASSERT_GE(x, y) SANDBOX_ASSERT((x) >= (y)) + +// This class allows to run unittests in their own process. The main method is +// RunTestInProcess(). +class UnitTests { + public: + typedef void (*DeathCheck)(int status, + const std::string& msg, + const void* aux); + + // Runs a test inside a short-lived process. Do not call this function + // directly. It is automatically invoked by SANDBOX_TEST(). Most sandboxing + // functions make global irreversible changes to the execution environment + // and must therefore execute in their own isolated process. + // |test_runner| must implement the SandboxTestRunner interface and will run + // in a subprocess. + // Note: since the child process (created with fork()) will never return from + // RunTestInProcess(), |test_runner| is guaranteed to exist for the lifetime + // of the child process. + static void RunTestInProcess(SandboxTestRunner* test_runner, + DeathCheck death, + const void* death_aux); + + // Report a useful error message and terminate the current SANDBOX_TEST(). + // Calling this function from outside a SANDBOX_TEST() is unlikely to do + // anything useful. + static void AssertionFailure(const char* expr, const char* file, int line); + + // Sometimes we determine at run-time that a test should be disabled. + // Call this method if we want to return from a test and completely + // ignore its results. + // You should not call this method, if the test already ran any test-relevant + // code. Most notably, you should not call it, you already wrote any messages + // to stderr. + static void IgnoreThisTest(); + + // A DeathCheck method that verifies that the test completed succcessfully. + // This is the default test mode for SANDBOX_TEST(). The "aux" parameter + // of this DeathCheck is unused (and thus unnamed) + static void DeathSuccess(int status, const std::string& msg, const void*); + + // A DeathCheck method that verifies that the test completed succcessfully + // allowing for log error messages. + static void DeathSuccessAllowNoise(int status, + const std::string& msg, + const void*); + + // A DeathCheck method that verifies that the test completed with error + // code "1" and printed a message containing a particular substring. The + // "aux" pointer should point to a C-string containing the expected error + // message. This method is useful for checking assertion failures such as + // in SANDBOX_ASSERT() and/or SANDBOX_DIE(). + static void DeathMessage(int status, const std::string& msg, const void* aux); + + // Like DeathMessage() but the process must be terminated with a segmentation + // fault. + // Implementation detail: On Linux (but not on Android), this does check for + // the return value of our default signal handler rather than for the actual + // reception of a SIGSEGV. + // TODO(jln): make this more robust. + static void DeathSEGVMessage(int status, + const std::string& msg, + const void* aux); + + // A DeathCheck method that verifies that the test completed with a + // particular exit code. If the test output any messages to stderr, they are + // silently ignored. The expected exit code should be passed in by + // casting the its "int" value to a "void *", which is then used for "aux". + static void DeathExitCode(int status, + const std::string& msg, + const void* aux); + + // A DeathCheck method that verifies that the test was terminated by a + // particular signal. If the test output any messages to stderr, they are + // silently ignore. The expected signal number should be passed in by + // casting the its "int" value to a "void *", which is then used for "aux". + static void DeathBySignal(int status, + const std::string& msg, + const void* aux); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(UnitTests); +}; + +} // namespace + +#endif // SANDBOX_LINUX_TESTS_UNIT_TESTS_H_ diff --git a/sandbox/linux/tests/unit_tests_unittest.cc b/sandbox/linux/tests/unit_tests_unittest.cc new file mode 100644 index 0000000000..57799b14c0 --- /dev/null +++ b/sandbox/linux/tests/unit_tests_unittest.cc @@ -0,0 +1,62 @@ +// 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 <signal.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +namespace { + +// Let's not use any of the "magic" values used internally in unit_tests.cc, +// such as kExpectedValue. +const int kExpectedExitCode = 100; + +SANDBOX_DEATH_TEST(UnitTests, + DeathExitCode, + DEATH_EXIT_CODE(kExpectedExitCode)) { + _exit(kExpectedExitCode); +} + +const int kExpectedSignalNumber = SIGKILL; + +SANDBOX_DEATH_TEST(UnitTests, + DeathBySignal, + DEATH_BY_SIGNAL(kExpectedSignalNumber)) { + raise(kExpectedSignalNumber); +} + +SANDBOX_DEATH_TEST(UnitTests, + DeathWithMessage, + DEATH_MESSAGE("Hello")) { + LOG(ERROR) << "Hello"; + _exit(1); +} + +SANDBOX_DEATH_TEST(UnitTests, + SEGVDeathWithMessage, + DEATH_SEGV_MESSAGE("Hello")) { + LOG(ERROR) << "Hello"; + while (1) { + volatile char* addr = reinterpret_cast<volatile char*>(NULL); + *addr = '\0'; + } + + _exit(2); +} + +SANDBOX_TEST_ALLOW_NOISE(UnitTests, NoisyTest) { + LOG(ERROR) << "The cow says moo!"; +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/mac/BUILD.gn b/sandbox/mac/BUILD.gn new file mode 100644 index 0000000000..13960bfaef --- /dev/null +++ b/sandbox/mac/BUILD.gn @@ -0,0 +1,101 @@ +# 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/mac/mac_sdk.gni") +import("//testing/test.gni") + +generate_stubs_script = "//tools/generate_stubs/generate_stubs.py" +generate_stubs_header = "xpc_stubs_header.fragment" +generate_stubs_sig_public = "xpc_stubs.sig" +generate_stubs_sig_private = "xpc_private_stubs.sig" +generate_stubs_project = "sandbox/mac" +generate_stubs_output_stem = "xpc_stubs" + +action("generate_stubs") { + script = generate_stubs_script + sources = [ + generate_stubs_sig_private, + generate_stubs_sig_public, + ] + inputs = [ + generate_stubs_header, + ] + outputs = [ + "$target_gen_dir/$generate_stubs_output_stem.cc", + "$target_gen_dir/$generate_stubs_output_stem.h", + ] + args = [ + "-i", + rebase_path(target_gen_dir, root_build_dir), + "-o", + rebase_path(target_gen_dir, root_build_dir), + "-t", + "posix_stubs", + "-e", + rebase_path(generate_stubs_header, root_build_dir), + "-s", + generate_stubs_output_stem, + "-p", + generate_stubs_project, + "-x", + "SANDBOX_EXPORT", + ] + args += rebase_path(sources, root_build_dir) +} + +component("sandbox") { + sources = [ + "bootstrap_sandbox.cc", + "bootstrap_sandbox.h", + "launchd_interception_server.cc", + "launchd_interception_server.h", + "mach_message_server.cc", + "mach_message_server.h", + "message_server.h", + "os_compatibility.cc", + "os_compatibility.h", + "policy.cc", + "policy.h", + "xpc.cc", + "xpc.h", + "xpc_message_server.cc", + "xpc_message_server.h", + ] + + defines = [ "SANDBOX_IMPLEMENTATION" ] + libs = [ "bsm" ] + + deps = [ + "//base", + ] + + # When the build SDK is 10.6, generate a dynamic stub loader. When the + # SDK is higher, then libxpc.dylib will be loaded automatically as part + # of libSystem, and only forward declarations of private symbols are + # necessary. + if (mac_sdk_version == "10.6") { + deps += [ ":generate_stubs" ] + sources += get_target_outputs(":generate_stubs") + } +} + +test("sandbox_mac_unittests") { + sources = [ + "bootstrap_sandbox_unittest.mm", + "policy_unittest.cc", + "xpc_message_server_unittest.cc", + ] + + libs = [ + "CoreFoundation.framework", + "Foundation.framework", + ] + + deps = [ + ":sandbox", + "//base", + "//base/test:run_all_unittests", + "//testing/gtest", + ] +} diff --git a/sandbox/mac/OWNERS b/sandbox/mac/OWNERS new file mode 100644 index 0000000000..163563f967 --- /dev/null +++ b/sandbox/mac/OWNERS @@ -0,0 +1,2 @@ +mark@chromium.org +rsesek@chromium.org diff --git a/sandbox/mac/bootstrap_sandbox.cc b/sandbox/mac/bootstrap_sandbox.cc new file mode 100644 index 0000000000..a90f570eb4 --- /dev/null +++ b/sandbox/mac/bootstrap_sandbox.cc @@ -0,0 +1,133 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/mac/bootstrap_sandbox.h" + +#include <servers/bootstrap.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/mac/mach_logging.h" +#include "base/strings/stringprintf.h" +#include "sandbox/mac/launchd_interception_server.h" + +namespace sandbox { + +const int kNotAPolicy = -1; + +// static +scoped_ptr<BootstrapSandbox> BootstrapSandbox::Create() { + scoped_ptr<BootstrapSandbox> null; // Used for early returns. + scoped_ptr<BootstrapSandbox> sandbox(new BootstrapSandbox()); + sandbox->server_.reset(new LaunchdInterceptionServer(sandbox.get())); + + // Check in with launchd to get the receive right for the server that is + // published in the bootstrap namespace. + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = bootstrap_check_in(bootstrap_port, + sandbox->server_bootstrap_name().c_str(), &port); + if (kr != KERN_SUCCESS) { + BOOTSTRAP_LOG(ERROR, kr) + << "Failed to bootstrap_check_in the sandbox server."; + return null.Pass(); + } + base::mac::ScopedMachReceiveRight scoped_port(port); + + // Start the sandbox server. + if (sandbox->server_->Initialize(scoped_port.get())) + ignore_result(scoped_port.release()); // Transferred to server_. + else + return null.Pass(); + + return sandbox.Pass(); +} + +BootstrapSandbox::~BootstrapSandbox() { +} + +void BootstrapSandbox::RegisterSandboxPolicy( + int sandbox_policy_id, + const BootstrapSandboxPolicy& policy) { + CHECK(IsPolicyValid(policy)); + CHECK_GT(sandbox_policy_id, kNotAPolicy); + base::AutoLock lock(lock_); + DCHECK(policies_.find(sandbox_policy_id) == policies_.end()); + policies_.insert(std::make_pair(sandbox_policy_id, policy)); +} + +void BootstrapSandbox::PrepareToForkWithPolicy(int sandbox_policy_id) { + base::AutoLock lock(lock_); + + // Verify that this is a real policy. + CHECK(policies_.find(sandbox_policy_id) != policies_.end()); + CHECK_EQ(kNotAPolicy, effective_policy_id_) + << "Cannot nest calls to PrepareToForkWithPolicy()"; + + // Store the policy for the process we're about to create. + effective_policy_id_ = sandbox_policy_id; +} + +// TODO(rsesek): The |lock_| needs to be taken twice because +// base::LaunchProcess handles both fork+exec, and holding the lock for the +// duration would block servicing of other bootstrap messages. If a better +// LaunchProcess existed (do arbitrary work without layering violations), this +// could be avoided. + +void BootstrapSandbox::FinishedFork(base::ProcessHandle handle) { + base::AutoLock lock(lock_); + + CHECK_NE(kNotAPolicy, effective_policy_id_) + << "Must PrepareToForkWithPolicy() before FinishedFork()"; + + // Apply the policy to the new process. + if (handle != base::kNullProcessHandle) { + const auto& existing_process = sandboxed_processes_.find(handle); + CHECK(existing_process == sandboxed_processes_.end()); + sandboxed_processes_.insert(std::make_pair(handle, effective_policy_id_)); + VLOG(3) << "Bootstrap sandbox enforced for pid " << handle; + } + + effective_policy_id_ = kNotAPolicy; +} + +void BootstrapSandbox::ChildDied(base::ProcessHandle handle) { + base::AutoLock lock(lock_); + const auto& it = sandboxed_processes_.find(handle); + if (it != sandboxed_processes_.end()) + sandboxed_processes_.erase(it); +} + +const BootstrapSandboxPolicy* BootstrapSandbox::PolicyForProcess( + pid_t pid) const { + base::AutoLock lock(lock_); + const auto& process = sandboxed_processes_.find(pid); + + // The new child could send bootstrap requests before the parent calls + // FinishedFork(). + int policy_id = effective_policy_id_; + if (process != sandboxed_processes_.end()) { + policy_id = process->second; + } + + if (policy_id == kNotAPolicy) + return NULL; + + return &policies_.find(policy_id)->second; +} + +BootstrapSandbox::BootstrapSandbox() + : server_bootstrap_name_( + base::StringPrintf("%s.sandbox.%d", base::mac::BaseBundleID(), + getpid())), + real_bootstrap_port_(MACH_PORT_NULL), + effective_policy_id_(kNotAPolicy) { + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = task_get_special_port( + mach_task_self(), TASK_BOOTSTRAP_PORT, &port); + MACH_CHECK(kr == KERN_SUCCESS, kr); + real_bootstrap_port_.reset(port); +} + +} // namespace sandbox diff --git a/sandbox/mac/bootstrap_sandbox.h b/sandbox/mac/bootstrap_sandbox.h new file mode 100644 index 0000000000..fd808cdf61 --- /dev/null +++ b/sandbox/mac/bootstrap_sandbox.h @@ -0,0 +1,114 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_MAC_BOOTSTRAP_SANDBOX_H_ +#define SANDBOX_MAC_BOOTSTRAP_SANDBOX_H_ + +#include <mach/mach.h> + +#include <map> +#include <string> + +#include "base/mac/scoped_mach_port.h" +#include "base/memory/scoped_ptr.h" +#include "base/process/process_handle.h" +#include "base/synchronization/lock.h" +#include "sandbox/mac/policy.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +class LaunchdInterceptionServer; + +// The BootstrapSandbox is a second-layer sandbox for Mac. It is used to limit +// the bootstrap namespace attack surface of child processes. The parent +// process creates an instance of this class and registers policies that it +// can enforce on its children. +// +// With this sandbox, the parent process must replace the bootstrap port prior +// to the sandboxed target's execution. This should be done by setting the +// base::LaunchOptions.replacement_bootstrap_name to the +// server_bootstrap_name() of this class. Requests from the child that would +// normally go to launchd are filtered based on the specified per-process +// policies. If a request is permitted by the policy, it is forwarded on to +// launchd for servicing. If it is not, then the sandbox will reply with a +// primitive that does not grant additional capabilities to the receiver. +// +// Clients that which to use the sandbox must inform it of the creation and +// death of child processes for which the sandbox should be enforced. The +// client of the sandbox is intended to be an unsandboxed parent process that +// fork()s sandboxed (and other unsandboxed) child processes. +// +// When the parent is ready to fork a new child process with this sandbox +// being enforced, it should use the pair of methods PrepareToForkWithPolicy() +// and FinishedFork(), and call fork() between them. The first method will +// set the policy for the new process, and the second will finialize the +// association between the process ID and sandbox policy ID. +// +// All methods of this class may be called from any thread, but +// PrepareToForkWithPolicy() and FinishedFork() must be non-nested and balanced. +class SANDBOX_EXPORT BootstrapSandbox { + public: + // Creates a new sandbox manager. Returns NULL on failure. + static scoped_ptr<BootstrapSandbox> Create(); + + ~BootstrapSandbox(); + + // Registers a bootstrap policy associated it with an identifier. The + // |sandbox_policy_id| must be greater than 0. + void RegisterSandboxPolicy(int sandbox_policy_id, + const BootstrapSandboxPolicy& policy); + + // Called in the parent prior to fork()ing a child. The policy registered + // to |sandbox_policy_id| will be enforced on the new child. This must be + // followed by a call to FinishedFork(). + void PrepareToForkWithPolicy(int sandbox_policy_id); + + // Called in the parent after fork()ing a child. It records the |handle| + // and associates it with the specified-above |sandbox_policy_id|. + // If fork() failed and a new child was not created, pass kNullProcessHandle. + void FinishedFork(base::ProcessHandle handle); + + // Called in the parent when a process has died. It cleans up the references + // to the process. + void ChildDied(base::ProcessHandle handle); + + // Looks up the policy for a given process ID. If no policy is associated + // with the |pid|, this returns NULL. + const BootstrapSandboxPolicy* PolicyForProcess(pid_t pid) const; + + std::string server_bootstrap_name() const { return server_bootstrap_name_; } + mach_port_t real_bootstrap_port() const { return real_bootstrap_port_; } + + private: + BootstrapSandbox(); + + // The name in the system bootstrap server by which the |server_|'s port + // is known. + const std::string server_bootstrap_name_; + + // The original bootstrap port of the process, which is connected to the + // real launchd server. + base::mac::ScopedMachSendRight real_bootstrap_port_; + + // The |lock_| protects all the following variables. + mutable base::Lock lock_; + + // The sandbox_policy_id that will be enforced for the new child. + int effective_policy_id_; + + // All the policies that have been registered with this sandbox manager. + std::map<int, const BootstrapSandboxPolicy> policies_; + + // The association between process ID and sandbox policy ID. + std::map<base::ProcessHandle, int> sandboxed_processes_; + + // A Mach IPC message server that is used to intercept and filter bootstrap + // requests. + scoped_ptr<LaunchdInterceptionServer> server_; +}; + +} // namespace sandbox + +#endif // SANDBOX_MAC_BOOTSTRAP_SANDBOX_H_ diff --git a/sandbox/mac/bootstrap_sandbox_unittest.mm b/sandbox/mac/bootstrap_sandbox_unittest.mm new file mode 100644 index 0000000000..717f3f99f3 --- /dev/null +++ b/sandbox/mac/bootstrap_sandbox_unittest.mm @@ -0,0 +1,518 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/mac/bootstrap_sandbox.h" + +#include <CoreFoundation/CoreFoundation.h> +#import <Foundation/Foundation.h> +#include <mach/mach.h> +#include <servers/bootstrap.h> + +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/mach_logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/mac/scoped_nsobject.h" +#include "base/process/kill.h" +#include "base/strings/stringprintf.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#import "testing/gtest_mac.h" +#include "testing/multiprocess_func_list.h" + +NSString* const kTestNotification = @"org.chromium.bootstrap_sandbox_test"; + +@interface DistributedNotificationObserver : NSObject { + @private + int receivedCount_; + base::scoped_nsobject<NSString> object_; +} +- (int)receivedCount; +- (NSString*)object; +- (void)waitForNotification; +@end + +@implementation DistributedNotificationObserver +- (id)init { + if ((self = [super init])) { + [[NSDistributedNotificationCenter defaultCenter] + addObserver:self + selector:@selector(observeNotification:) + name:kTestNotification + object:nil]; + } + return self; +} + +- (void)dealloc { + [[NSDistributedNotificationCenter defaultCenter] + removeObserver:self + name:kTestNotification + object:nil]; + [super dealloc]; +} + +- (int)receivedCount { + return receivedCount_; +} + +- (NSString*)object { + return object_.get(); +} + +- (void)waitForNotification { + object_.reset(); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, + TestTimeouts::action_timeout().InSeconds(), false); +} + +- (void)observeNotification:(NSNotification*)notification { + ++receivedCount_; + object_.reset([[notification object] copy]); + CFRunLoopStop(CFRunLoopGetCurrent()); +} +@end + +//////////////////////////////////////////////////////////////////////////////// + +namespace sandbox { + +class BootstrapSandboxTest : public base::MultiProcessTest { + public: + void SetUp() override { + base::MultiProcessTest::SetUp(); + + sandbox_ = BootstrapSandbox::Create(); + ASSERT_TRUE(sandbox_.get()); + } + + BootstrapSandboxPolicy BaselinePolicy() { + BootstrapSandboxPolicy policy; + if (base::mac::IsOSSnowLeopard()) + policy.rules["com.apple.SecurityServer"] = Rule(POLICY_ALLOW); + return policy; + } + + void RunChildWithPolicy(int policy_id, + const char* child_name, + base::ProcessHandle* out_pid) { + sandbox_->PrepareToForkWithPolicy(policy_id); + base::LaunchOptions options; + options.replacement_bootstrap_name = sandbox_->server_bootstrap_name(); + base::Process process = SpawnChildWithOptions(child_name, options); + ASSERT_TRUE(process.IsValid()); + sandbox_->FinishedFork(process.Handle()); + int code = 0; + EXPECT_TRUE(process.WaitForExit(&code)); + EXPECT_EQ(0, code); + if (out_pid) + *out_pid = process.Pid(); + } + + protected: + scoped_ptr<BootstrapSandbox> sandbox_; +}; + +const char kNotificationTestMain[] = "PostNotification"; + +// Run the test without the sandbox. +TEST_F(BootstrapSandboxTest, DistributedNotifications_Unsandboxed) { + base::scoped_nsobject<DistributedNotificationObserver> observer( + [[DistributedNotificationObserver alloc] init]); + + base::Process process = SpawnChild(kNotificationTestMain); + ASSERT_TRUE(process.IsValid()); + int code = 0; + EXPECT_TRUE(process.WaitForExit(&code)); + EXPECT_EQ(0, code); + + [observer waitForNotification]; + EXPECT_EQ(1, [observer receivedCount]); + EXPECT_EQ(process.Pid(), [[observer object] intValue]); +} + +// Run the test with the sandbox enabled without notifications on the policy +// whitelist. +TEST_F(BootstrapSandboxTest, DistributedNotifications_SandboxDeny) { + base::scoped_nsobject<DistributedNotificationObserver> observer( + [[DistributedNotificationObserver alloc] init]); + + sandbox_->RegisterSandboxPolicy(1, BaselinePolicy()); + RunChildWithPolicy(1, kNotificationTestMain, NULL); + + [observer waitForNotification]; + EXPECT_EQ(0, [observer receivedCount]); + EXPECT_EQ(nil, [observer object]); +} + +// Run the test with notifications permitted. +TEST_F(BootstrapSandboxTest, DistributedNotifications_SandboxAllow) { + base::scoped_nsobject<DistributedNotificationObserver> observer( + [[DistributedNotificationObserver alloc] init]); + + BootstrapSandboxPolicy policy(BaselinePolicy()); + // 10.9: + policy.rules["com.apple.distributed_notifications@Uv3"] = Rule(POLICY_ALLOW); + policy.rules["com.apple.distributed_notifications@1v3"] = Rule(POLICY_ALLOW); + // 10.6: + policy.rules["com.apple.system.notification_center"] = Rule(POLICY_ALLOW); + policy.rules["com.apple.distributed_notifications.2"] = Rule(POLICY_ALLOW); + sandbox_->RegisterSandboxPolicy(2, policy); + + base::ProcessHandle pid; + RunChildWithPolicy(2, kNotificationTestMain, &pid); + + [observer waitForNotification]; + EXPECT_EQ(1, [observer receivedCount]); + EXPECT_EQ(pid, [[observer object] intValue]); +} + +MULTIPROCESS_TEST_MAIN(PostNotification) { + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:kTestNotification + object:[NSString stringWithFormat:@"%d", getpid()]]; + return 0; +} + +const char kTestServer[] = "org.chromium.test_bootstrap_server"; + +TEST_F(BootstrapSandboxTest, PolicyDenyError) { + BootstrapSandboxPolicy policy(BaselinePolicy()); + policy.rules[kTestServer] = Rule(POLICY_DENY_ERROR); + sandbox_->RegisterSandboxPolicy(1, policy); + + RunChildWithPolicy(1, "PolicyDenyError", NULL); +} + +MULTIPROCESS_TEST_MAIN(PolicyDenyError) { + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = bootstrap_look_up(bootstrap_port, kTestServer, + &port); + CHECK_EQ(BOOTSTRAP_UNKNOWN_SERVICE, kr); + CHECK(port == MACH_PORT_NULL); + + kr = bootstrap_look_up(bootstrap_port, "org.chromium.some_other_server", + &port); + CHECK_EQ(BOOTSTRAP_UNKNOWN_SERVICE, kr); + CHECK(port == MACH_PORT_NULL); + + return 0; +} + +TEST_F(BootstrapSandboxTest, PolicyDenyDummyPort) { + BootstrapSandboxPolicy policy(BaselinePolicy()); + policy.rules[kTestServer] = Rule(POLICY_DENY_DUMMY_PORT); + sandbox_->RegisterSandboxPolicy(1, policy); + + RunChildWithPolicy(1, "PolicyDenyDummyPort", NULL); +} + +MULTIPROCESS_TEST_MAIN(PolicyDenyDummyPort) { + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = bootstrap_look_up(bootstrap_port, kTestServer, + &port); + CHECK_EQ(KERN_SUCCESS, kr); + CHECK(port != MACH_PORT_NULL); + return 0; +} + +struct SubstitutePortAckSend { + mach_msg_header_t header; + char buf[32]; +}; + +struct SubstitutePortAckRecv : public SubstitutePortAckSend { + mach_msg_trailer_t trailer; +}; + +const char kSubstituteAck[] = "Hello, this is doge!"; + +TEST_F(BootstrapSandboxTest, PolicySubstitutePort) { + mach_port_t task = mach_task_self(); + + mach_port_t port; + ASSERT_EQ(KERN_SUCCESS, mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, + &port)); + base::mac::ScopedMachReceiveRight scoped_port(port); + + mach_port_urefs_t send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(0u, send_rights); + + ASSERT_EQ(KERN_SUCCESS, mach_port_insert_right(task, port, port, + MACH_MSG_TYPE_MAKE_SEND)); + base::mac::ScopedMachSendRight scoped_port_send(port); + + send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(1u, send_rights); + + BootstrapSandboxPolicy policy(BaselinePolicy()); + policy.rules[kTestServer] = Rule(port); + sandbox_->RegisterSandboxPolicy(1, policy); + + RunChildWithPolicy(1, "PolicySubstitutePort", NULL); + + struct SubstitutePortAckRecv msg; + bzero(&msg, sizeof(msg)); + msg.header.msgh_size = sizeof(msg); + msg.header.msgh_local_port = port; + kern_return_t kr = mach_msg(&msg.header, MACH_RCV_MSG, 0, + msg.header.msgh_size, port, + TestTimeouts::tiny_timeout().InMilliseconds(), MACH_PORT_NULL); + EXPECT_EQ(KERN_SUCCESS, kr); + + send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(1u, send_rights); + + EXPECT_EQ(0, strncmp(kSubstituteAck, msg.buf, sizeof(msg.buf))); +} + +MULTIPROCESS_TEST_MAIN(PolicySubstitutePort) { + mach_port_t port = MACH_PORT_NULL; + kern_return_t kr = bootstrap_look_up(bootstrap_port, kTestServer, &port); + CHECK_EQ(KERN_SUCCESS, kr); + CHECK(port != MACH_PORT_NULL); + + struct SubstitutePortAckSend msg; + bzero(&msg, sizeof(msg)); + msg.header.msgh_size = sizeof(msg); + msg.header.msgh_remote_port = port; + msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND); + strncpy(msg.buf, kSubstituteAck, sizeof(msg.buf)); + + CHECK_EQ(KERN_SUCCESS, mach_msg_send(&msg.header)); + + return 0; +} + +TEST_F(BootstrapSandboxTest, ForwardMessageInProcess) { + mach_port_t task = mach_task_self(); + + mach_port_t port; + ASSERT_EQ(KERN_SUCCESS, mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, + &port)); + base::mac::ScopedMachReceiveRight scoped_port_recv(port); + + mach_port_urefs_t send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(0u, send_rights); + + ASSERT_EQ(KERN_SUCCESS, mach_port_insert_right(task, port, port, + MACH_MSG_TYPE_MAKE_SEND)); + base::mac::ScopedMachSendRight scoped_port_send(port); + + send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(1u, send_rights); + + mach_port_t bp; + ASSERT_EQ(KERN_SUCCESS, task_get_bootstrap_port(task, &bp)); + base::mac::ScopedMachSendRight scoped_bp(bp); + + char service_name[] = "org.chromium.sandbox.test.ForwardMessageInProcess"; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + kern_return_t kr = bootstrap_register(bp, service_name, port); +#pragma GCC diagnostic pop + EXPECT_EQ(KERN_SUCCESS, kr); + + send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + EXPECT_EQ(1u, send_rights); + + mach_port_t service_port; + EXPECT_EQ(KERN_SUCCESS, bootstrap_look_up(bp, service_name, &service_port)); + base::mac::ScopedMachSendRight scoped_service_port(service_port); + + send_rights = 0; + ASSERT_EQ(KERN_SUCCESS, mach_port_get_refs(task, port, MACH_PORT_RIGHT_SEND, + &send_rights)); + // On 10.6, bootstrap_lookup2 may add an extra right to place it in a per- + // process cache. + if (base::mac::IsOSSnowLeopard()) + EXPECT_TRUE(send_rights == 3u || send_rights == 2u) << send_rights; + else + EXPECT_EQ(2u, send_rights); +} + +const char kDefaultRuleTestAllow[] = + "org.chromium.sandbox.test.DefaultRuleAllow"; +const char kDefaultRuleTestDeny[] = + "org.chromium.sandbox.test.DefaultRuleAllow.Deny"; + +TEST_F(BootstrapSandboxTest, DefaultRuleAllow) { + mach_port_t task = mach_task_self(); + + mach_port_t port; + ASSERT_EQ(KERN_SUCCESS, mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, + &port)); + base::mac::ScopedMachReceiveRight scoped_port_recv(port); + + ASSERT_EQ(KERN_SUCCESS, mach_port_insert_right(task, port, port, + MACH_MSG_TYPE_MAKE_SEND)); + base::mac::ScopedMachSendRight scoped_port_send(port); + + BootstrapSandboxPolicy policy; + policy.default_rule = Rule(POLICY_ALLOW); + policy.rules[kDefaultRuleTestAllow] = Rule(port); + policy.rules[kDefaultRuleTestDeny] = Rule(POLICY_DENY_ERROR); + sandbox_->RegisterSandboxPolicy(3, policy); + + base::scoped_nsobject<DistributedNotificationObserver> observer( + [[DistributedNotificationObserver alloc] init]); + + int pid = 0; + RunChildWithPolicy(3, "DefaultRuleAllow", &pid); + EXPECT_GT(pid, 0); + + [observer waitForNotification]; + EXPECT_EQ(1, [observer receivedCount]); + EXPECT_EQ(pid, [[observer object] intValue]); + + struct SubstitutePortAckRecv msg; + bzero(&msg, sizeof(msg)); + msg.header.msgh_size = sizeof(msg); + msg.header.msgh_local_port = port; + kern_return_t kr = mach_msg(&msg.header, MACH_RCV_MSG, 0, + msg.header.msgh_size, port, + TestTimeouts::tiny_timeout().InMilliseconds(), MACH_PORT_NULL); + EXPECT_EQ(KERN_SUCCESS, kr); + + EXPECT_EQ(0, strncmp(kSubstituteAck, msg.buf, sizeof(msg.buf))); +} + +MULTIPROCESS_TEST_MAIN(DefaultRuleAllow) { + [[NSDistributedNotificationCenter defaultCenter] + postNotificationName:kTestNotification + object:[NSString stringWithFormat:@"%d", getpid()]]; + + mach_port_t port = MACH_PORT_NULL; + CHECK_EQ(BOOTSTRAP_UNKNOWN_SERVICE, bootstrap_look_up(bootstrap_port, + const_cast<char*>(kDefaultRuleTestDeny), &port)); + CHECK(port == MACH_PORT_NULL); + + CHECK_EQ(KERN_SUCCESS, bootstrap_look_up(bootstrap_port, + const_cast<char*>(kDefaultRuleTestAllow), &port)); + CHECK(port != MACH_PORT_NULL); + + struct SubstitutePortAckSend msg; + bzero(&msg, sizeof(msg)); + msg.header.msgh_size = sizeof(msg); + msg.header.msgh_remote_port = port; + msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND); + strncpy(msg.buf, kSubstituteAck, sizeof(msg.buf)); + + CHECK_EQ(KERN_SUCCESS, mach_msg_send(&msg.header)); + + return 0; +} + +TEST_F(BootstrapSandboxTest, ChildOutliveSandbox) { + const int kTestPolicyId = 1; + mach_port_t task = mach_task_self(); + + // Create a server port. + mach_port_t port; + ASSERT_EQ(KERN_SUCCESS, mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, + &port)); + base::mac::ScopedMachReceiveRight scoped_port_recv(port); + + ASSERT_EQ(KERN_SUCCESS, mach_port_insert_right(task, port, port, + MACH_MSG_TYPE_MAKE_SEND)); + base::mac::ScopedMachSendRight scoped_port_send(port); + + // Set up the policy and register the port. + BootstrapSandboxPolicy policy(BaselinePolicy()); + policy.rules["sync"] = Rule(port); + sandbox_->RegisterSandboxPolicy(kTestPolicyId, policy); + + // Launch the child. + sandbox_->PrepareToForkWithPolicy(kTestPolicyId); + base::LaunchOptions options; + options.replacement_bootstrap_name = sandbox_->server_bootstrap_name(); + base::Process process = SpawnChildWithOptions("ChildOutliveSandbox", options); + ASSERT_TRUE(process.IsValid()); + sandbox_->FinishedFork(process.Handle()); + + // Synchronize with the child. + mach_msg_empty_rcv_t rcv_msg; + bzero(&rcv_msg, sizeof(rcv_msg)); + kern_return_t kr = mach_msg(&rcv_msg.header, MACH_RCV_MSG, 0, + sizeof(rcv_msg), port, + TestTimeouts::tiny_timeout().InMilliseconds(), MACH_PORT_NULL); + ASSERT_EQ(KERN_SUCCESS, kr) << mach_error_string(kr); + + // Destroy the sandbox. + sandbox_.reset(); + + // Synchronize again with the child. + mach_msg_empty_send_t send_msg; + bzero(&send_msg, sizeof(send_msg)); + send_msg.header.msgh_size = sizeof(send_msg); + send_msg.header.msgh_remote_port = rcv_msg.header.msgh_remote_port; + send_msg.header.msgh_bits = + MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE); + kr = mach_msg(&send_msg.header, MACH_SEND_MSG, send_msg.header.msgh_size, 0, + MACH_PORT_NULL, TestTimeouts::tiny_timeout().InMilliseconds(), + MACH_PORT_NULL); + EXPECT_EQ(KERN_SUCCESS, kr) << mach_error_string(kr); + + int code = 0; + EXPECT_TRUE(process.WaitForExit(&code)); + EXPECT_EQ(0, code); +} + +MULTIPROCESS_TEST_MAIN(ChildOutliveSandbox) { + // Get the synchronization channel. + mach_port_t port = MACH_PORT_NULL; + CHECK_EQ(KERN_SUCCESS, bootstrap_look_up(bootstrap_port, "sync", &port)); + + // Create a reply port. + mach_port_t reply_port; + CHECK_EQ(KERN_SUCCESS, mach_port_allocate(mach_task_self(), + MACH_PORT_RIGHT_RECEIVE, &reply_port)); + base::mac::ScopedMachReceiveRight scoped_reply_port(reply_port); + + // Send a message to shutdown the sandbox. + mach_msg_empty_send_t send_msg; + bzero(&send_msg, sizeof(send_msg)); + send_msg.header.msgh_size = sizeof(send_msg); + send_msg.header.msgh_local_port = reply_port; + send_msg.header.msgh_remote_port = port; + send_msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, + MACH_MSG_TYPE_MAKE_SEND_ONCE); + kern_return_t kr = mach_msg_send(&send_msg.header); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_msg_send"; + + // Flood the server's message queue with messages. There should be some + // pending when the sandbox is destroyed. + for (int i = 0; i < 20; ++i) { + mach_port_t tmp = MACH_PORT_NULL; + std::string name = base::StringPrintf("test.%d", i); + bootstrap_look_up(bootstrap_port, const_cast<char*>(name.c_str()), &tmp); + } + + // Ack that the sandbox has been shutdown. + mach_msg_empty_rcv_t rcv_msg; + bzero(&rcv_msg, sizeof(rcv_msg)); + rcv_msg.header.msgh_size = sizeof(rcv_msg); + rcv_msg.header.msgh_local_port = reply_port; + kr = mach_msg_receive(&rcv_msg.header); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_msg_receive"; + + // Try to message the sandbox. + bootstrap_look_up(bootstrap_port, "test", &port); + + return 0; +} + +} // namespace sandbox diff --git a/sandbox/mac/launchd_interception_server.cc b/sandbox/mac/launchd_interception_server.cc new file mode 100644 index 0000000000..06f10812e4 --- /dev/null +++ b/sandbox/mac/launchd_interception_server.cc @@ -0,0 +1,153 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/mac/launchd_interception_server.h" + +#include <servers/bootstrap.h> + +#include "base/logging.h" +#include "base/mac/mach_logging.h" +#include "sandbox/mac/bootstrap_sandbox.h" +#include "sandbox/mac/mach_message_server.h" + +namespace sandbox { + +// The buffer size for all launchd messages. This comes from +// sizeof(union __RequestUnion__vproc_mig_job_subsystem) in launchd, and it +// is larger than the __ReplyUnion. +const mach_msg_size_t kBufferSize = 2096; + +LaunchdInterceptionServer::LaunchdInterceptionServer( + const BootstrapSandbox* sandbox) + : sandbox_(sandbox), + sandbox_port_(MACH_PORT_NULL), + compat_shim_(GetLaunchdCompatibilityShim()) { +} + +LaunchdInterceptionServer::~LaunchdInterceptionServer() { +} + +bool LaunchdInterceptionServer::Initialize(mach_port_t server_receive_right) { + mach_port_t task = mach_task_self(); + kern_return_t kr; + + // Allocate the dummy sandbox port. + mach_port_t port; + if ((kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port)) != + KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Failed to allocate dummy sandbox port."; + return false; + } + sandbox_port_.reset(port); + if ((kr = mach_port_insert_right(task, sandbox_port_, sandbox_port_, + MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS)) { + MACH_LOG(ERROR, kr) << "Failed to allocate dummy sandbox port send right."; + return false; + } + sandbox_send_port_.reset(sandbox_port_); + + message_server_.reset( + new MachMessageServer(this, server_receive_right, kBufferSize)); + return message_server_->Initialize(); +} + +void LaunchdInterceptionServer::DemuxMessage(IPCMessage request) { + const uint64_t message_id = compat_shim_.ipc_message_get_id(request); + VLOG(3) << "Incoming message #" << message_id; + + pid_t sender_pid = message_server_->GetMessageSenderPID(request); + const BootstrapSandboxPolicy* policy = + sandbox_->PolicyForProcess(sender_pid); + if (policy == NULL) { + // No sandbox policy is in place for the sender of this message, which + // means it came from the unknown. Reject it. + VLOG(3) << "Message from unknown pid " << sender_pid << " rejected."; + message_server_->RejectMessage(request, MIG_REMOTE_ERROR); + return; + } + + if (message_id == compat_shim_.msg_id_look_up2) { + // Filter messages sent via bootstrap_look_up to enforce the sandbox policy + // over the bootstrap namespace. + HandleLookUp(request, policy); + } else if (message_id == compat_shim_.msg_id_swap_integer) { + // Ensure that any vproc_swap_integer requests are safe. + HandleSwapInteger(request); + } else { + // All other messages are not permitted. + VLOG(1) << "Rejecting unhandled message #" << message_id; + message_server_->RejectMessage(request, MIG_REMOTE_ERROR); + } +} + +void LaunchdInterceptionServer::HandleLookUp( + IPCMessage request, + const BootstrapSandboxPolicy* policy) { + const std::string request_service_name( + compat_shim_.look_up2_get_request_name(request)); + VLOG(2) << "Incoming look_up2 request for " << request_service_name; + + // Find the Rule for this service. If a named rule is not found, use the + // default specified by the policy. + const BootstrapSandboxPolicy::NamedRules::const_iterator it = + policy->rules.find(request_service_name); + Rule rule(policy->default_rule); + if (it != policy->rules.end()) + rule = it->second; + + if (rule.result == POLICY_ALLOW) { + // This service is explicitly allowed, so this message will not be + // intercepted by the sandbox. + VLOG(1) << "Permitting and forwarding look_up2: " << request_service_name; + ForwardMessage(request); + } else if (rule.result == POLICY_DENY_ERROR) { + // The child is not permitted to look up this service. Send a MIG error + // reply to the client. Returning a NULL or unserviced port for a look up + // can cause clients to crash or hang. + VLOG(1) << "Denying look_up2 with MIG error: " << request_service_name; + message_server_->RejectMessage(request, BOOTSTRAP_UNKNOWN_SERVICE); + } else if (rule.result == POLICY_DENY_DUMMY_PORT || + rule.result == POLICY_SUBSTITUTE_PORT) { + // The policy result is to deny access to the real service port, replying + // with a sandboxed port in its stead. Use either the dummy sandbox_port_ + // or the one specified in the policy. + VLOG(1) << "Intercepting look_up2 with a sandboxed service port: " + << request_service_name; + + mach_port_t result_port; + if (rule.result == POLICY_DENY_DUMMY_PORT) + result_port = sandbox_port_.get(); + else + result_port = rule.substitute_port; + + IPCMessage reply = message_server_->CreateReply(request); + compat_shim_.look_up2_fill_reply(reply, result_port); + // If the message was sent successfully, clear the result_port out of the + // message so that it is not destroyed at the end of ReceiveMessage. The + // above-inserted right has been moved out of the process, and destroying + // the message will unref yet another right. + if (message_server_->SendReply(reply)) + compat_shim_.look_up2_fill_reply(reply, MACH_PORT_NULL); + } else { + NOTREACHED(); + } +} + +void LaunchdInterceptionServer::HandleSwapInteger(IPCMessage request) { + // Only allow getting information out of launchd. Do not allow setting + // values. Two commonly observed values that are retrieved are + // VPROC_GSK_MGR_PID and VPROC_GSK_TRANSACTIONS_ENABLED. + if (compat_shim_.swap_integer_is_get_only(request)) { + VLOG(2) << "Forwarding vproc swap_integer message."; + ForwardMessage(request); + } else { + VLOG(2) << "Rejecting non-read-only swap_integer message."; + message_server_->RejectMessage(request, BOOTSTRAP_NOT_PRIVILEGED); + } +} +void LaunchdInterceptionServer::ForwardMessage(IPCMessage request) { + message_server_->ForwardMessage(request, sandbox_->real_bootstrap_port()); +} + +} // namespace sandbox diff --git a/sandbox/mac/launchd_interception_server.h b/sandbox/mac/launchd_interception_server.h new file mode 100644 index 0000000000..144d67d947 --- /dev/null +++ b/sandbox/mac/launchd_interception_server.h @@ -0,0 +1,73 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_MAC_LAUNCHD_INTERCEPTION_SERVER_H_ +#define SANDBOX_MAC_LAUNCHD_INTERCEPTION_SERVER_H_ + +#include <dispatch/dispatch.h> + +#include "base/mac/scoped_mach_port.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/mac/message_server.h" +#include "sandbox/mac/os_compatibility.h" + +namespace sandbox { + +class BootstrapSandbox; +struct BootstrapSandboxPolicy; + +// This class is used to run a Mach IPC message server. This server can +// hold the receive right for a bootstrap_port of a process, and it filters +// a subset of the launchd/bootstrap IPC call set for sandboxing. It permits +// or rejects requests based on the per-process policy specified in the +// BootstrapSandbox. +class LaunchdInterceptionServer : public MessageDemuxer { + public: + explicit LaunchdInterceptionServer(const BootstrapSandbox* sandbox); + ~LaunchdInterceptionServer() override; + + // Initializes the class and starts running the message server. If the + // |server_receive_right| is non-NULL, this class will take ownership of + // the receive right and intercept messages sent to that port. + bool Initialize(mach_port_t server_receive_right); + + // MessageDemuxer: + void DemuxMessage(IPCMessage request) override; + + mach_port_t server_port() const { return message_server_->GetServerPort(); } + + private: + // Given a look_up2 request message, this looks up the appropriate sandbox + // policy for the service name then formulates and sends the reply message. + void HandleLookUp(IPCMessage request, const BootstrapSandboxPolicy* policy); + + // Given a swap_integer request message, this verifies that it is safe, and + // if so, forwards it on to launchd for servicing. If the request is unsafe, + // it replies with an error. + void HandleSwapInteger(IPCMessage request); + + // Forwards the original |request| on to real bootstrap server for handling. + void ForwardMessage(IPCMessage request); + + // The sandbox for which this message server is running. + const BootstrapSandbox* sandbox_; + + // The Mach IPC server. + scoped_ptr<MessageServer> message_server_; + + // The Mach port handed out in reply to denied look up requests. All denied + // requests share the same port, though nothing reads messages from it. + base::mac::ScopedMachReceiveRight sandbox_port_; + // The send right for the above |sandbox_port_|, used with + // MACH_MSG_TYPE_COPY_SEND when handing out references to the dummy port. + base::mac::ScopedMachSendRight sandbox_send_port_; + + // The compatibility shim that handles differences in message header IDs and + // request/reply structures between different OS X versions. + const LaunchdCompatibilityShim compat_shim_; +}; + +} // namespace sandbox + +#endif // SANDBOX_MAC_LAUNCHD_INTERCEPTION_SERVER_H_ diff --git a/sandbox/mac/mach_message_server.cc b/sandbox/mac/mach_message_server.cc new file mode 100644 index 0000000000..7cfcecc6cc --- /dev/null +++ b/sandbox/mac/mach_message_server.cc @@ -0,0 +1,192 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/mac/mach_message_server.h" + +#include <bsm/libbsm.h> +#include <servers/bootstrap.h> + +#include <string> + +#include "base/logging.h" +#include "base/mac/mach_logging.h" +#include "base/strings/stringprintf.h" + +namespace sandbox { + +MachMessageServer::MachMessageServer( + MessageDemuxer* demuxer, + mach_port_t server_receive_right, + mach_msg_size_t buffer_size) + : demuxer_(demuxer), + server_port_(server_receive_right), + buffer_size_( + mach_vm_round_page(buffer_size + sizeof(mach_msg_audit_trailer_t))), + did_forward_message_(false) { + DCHECK(demuxer_); +} + +MachMessageServer::~MachMessageServer() { +} + +bool MachMessageServer::Initialize() { + mach_port_t task = mach_task_self(); + kern_return_t kr; + + // Allocate a port for use as a new server port if one was not passed to the + // constructor. + if (!server_port_.is_valid()) { + mach_port_t port; + if ((kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port)) != + KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Failed to allocate new server port."; + return false; + } + server_port_.reset(port); + } + + // Allocate the message request and reply buffers. + const int kMachMsgMemoryFlags = VM_MAKE_TAG(VM_MEMORY_MACH_MSG) | + VM_FLAGS_ANYWHERE; + vm_address_t buffer = 0; + + kr = vm_allocate(task, &buffer, buffer_size_, kMachMsgMemoryFlags); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Failed to allocate request buffer."; + return false; + } + request_buffer_.reset(buffer, buffer_size_); + + kr = vm_allocate(task, &buffer, buffer_size_, kMachMsgMemoryFlags); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Failed to allocate reply buffer."; + return false; + } + reply_buffer_.reset(buffer, buffer_size_); + + // Set up the dispatch queue to service the bootstrap port. + std::string label = base::StringPrintf( + "org.chromium.sandbox.MachMessageServer.%p", demuxer_); + dispatch_source_.reset(new base::DispatchSourceMach( + label.c_str(), server_port_.get(), ^{ ReceiveMessage(); })); + dispatch_source_->Resume(); + + return true; +} + +pid_t MachMessageServer::GetMessageSenderPID(IPCMessage request) { + // Get the PID of the task that sent this request. This requires getting at + // the trailer of the message, from the header. + mach_msg_audit_trailer_t* trailer = + reinterpret_cast<mach_msg_audit_trailer_t*>( + reinterpret_cast<vm_address_t>(request.mach) + + round_msg(request.mach->msgh_size)); + // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid(). + pid_t sender_pid; + audit_token_to_au32(trailer->msgh_audit, + NULL, NULL, NULL, NULL, NULL, &sender_pid, NULL, NULL); + return sender_pid; +} + +IPCMessage MachMessageServer::CreateReply(IPCMessage request_message) { + mach_msg_header_t* request = request_message.mach; + + IPCMessage reply_message; + mach_msg_header_t* reply = reply_message.mach = + reinterpret_cast<mach_msg_header_t*>(reply_buffer_.address()); + bzero(reply, buffer_size_); + + reply->msgh_bits = MACH_MSGH_BITS_REMOTE(reply->msgh_bits); + // Since mach_msg will automatically swap the request and reply ports, + // undo that. + reply->msgh_remote_port = request->msgh_remote_port; + reply->msgh_local_port = MACH_PORT_NULL; + // MIG servers simply add 100 to the request ID to generate the reply ID. + reply->msgh_id = request->msgh_id + 100; + + return reply_message; +} + +bool MachMessageServer::SendReply(IPCMessage reply) { + kern_return_t kr = mach_msg(reply.mach, MACH_SEND_MSG, + reply.mach->msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) + << "Unable to send intercepted reply message."; + return kr == KERN_SUCCESS; +} + +void MachMessageServer::ForwardMessage(IPCMessage message, + mach_port_t destination) { + mach_msg_header_t* request = message.mach; + request->msgh_local_port = request->msgh_remote_port; + request->msgh_remote_port = destination; + // Preserve the msgh_bits that do not deal with the local and remote ports. + request->msgh_bits = (request->msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) | + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND_ONCE); + kern_return_t kr = mach_msg_send(request); + if (kr == KERN_SUCCESS) { + did_forward_message_ = true; + } else { + MACH_LOG(ERROR, kr) << "Unable to forward message to the real launchd."; + } +} + +void MachMessageServer::RejectMessage(IPCMessage request, int error_code) { + IPCMessage reply = CreateReply(request); + mig_reply_error_t* error_reply = + reinterpret_cast<mig_reply_error_t*>(reply.mach); + error_reply->Head.msgh_size = sizeof(mig_reply_error_t); + error_reply->Head.msgh_bits = + MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE); + error_reply->NDR = NDR_record; + error_reply->RetCode = error_code; + SendReply(reply); +} + +mach_port_t MachMessageServer::GetServerPort() const { + return server_port_.get(); +} + +void MachMessageServer::ReceiveMessage() { + const mach_msg_options_t kRcvOptions = MACH_RCV_MSG | + MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | + MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); + + mach_msg_header_t* request = + reinterpret_cast<mach_msg_header_t*>(request_buffer_.address()); + mach_msg_header_t* reply = + reinterpret_cast<mach_msg_header_t*>(reply_buffer_.address()); + + // Zero out the buffers from handling any previous message. + bzero(request, buffer_size_); + bzero(reply, buffer_size_); + did_forward_message_ = false; + + // A Mach message server-once. The system library to run a message server + // cannot be used here, because some requests are conditionally forwarded + // to another server. + kern_return_t kr = mach_msg(request, kRcvOptions, 0, buffer_size_, + server_port_.get(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Unable to receive message."; + return; + } + + // Process the message. + IPCMessage request_message = { request }; + demuxer_->DemuxMessage(request_message); + + // Free any descriptors in the message body. If the message was forwarded, + // any descriptors would have been moved out of the process on send. If the + // forwarded message was sent from the process hosting this sandbox server, + // destroying the message could also destroy rights held outside the scope of + // this message server. + if (!did_forward_message_) { + mach_msg_destroy(request); + mach_msg_destroy(reply); + } +} + +} // namespace sandbox diff --git a/sandbox/mac/mach_message_server.h b/sandbox/mac/mach_message_server.h new file mode 100644 index 0000000000..20a543b3c7 --- /dev/null +++ b/sandbox/mac/mach_message_server.h @@ -0,0 +1,72 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_MAC_MACH_MESSAGE_SERVER_H_ +#define SANDBOX_MAC_MACH_MESSAGE_SERVER_H_ + +#include <mach/mach.h> + +#include "base/mac/dispatch_source_mach.h" +#include "base/mac/scoped_mach_port.h" +#include "base/mac/scoped_mach_vm.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/mac/message_server.h" + +namespace sandbox { + +// A Mach message server that operates a receive port. Messages are received +// and then passed to the MessageDemuxer for handling. The Demuxer +// can use the server class to send a reply, forward the message to a +// different port, or reply to the message with a MIG error. +class MachMessageServer : public MessageServer { + public: + // Creates a new Mach message server that will send messages to |demuxer| + // for handling. If the |server_receive_right| is non-NULL, this class will + // take ownership of the port and it will be used to receive messages. + // Otherwise the server will create a new receive right. + // The maximum size of messages is specified by |buffer_size|. + MachMessageServer(MessageDemuxer* demuxer, + mach_port_t server_receive_right, + mach_msg_size_t buffer_size); + ~MachMessageServer() override; + + // MessageServer: + bool Initialize() override; + pid_t GetMessageSenderPID(IPCMessage request) override; + IPCMessage CreateReply(IPCMessage request) override; + bool SendReply(IPCMessage reply) override; + void ForwardMessage(IPCMessage request, mach_port_t destination) override; + // Replies to the message with the specified |error_code| as a MIG + // error_reply RetCode. + void RejectMessage(IPCMessage request, int error_code) override; + mach_port_t GetServerPort() const override; + + private: + // Event handler for the |server_source_| that reads a message from the queue + // and processes it. + void ReceiveMessage(); + + // The demuxer delegate. Weak. + MessageDemuxer* demuxer_; + + // The Mach port on which the server is receiving requests. + base::mac::ScopedMachReceiveRight server_port_; + + // The size of the two message buffers below. + const mach_msg_size_t buffer_size_; + + // Request and reply buffers used in ReceiveMessage. + base::mac::ScopedMachVM request_buffer_; + base::mac::ScopedMachVM reply_buffer_; + + // MACH_RECV dispatch source that handles the |server_port_|. + scoped_ptr<base::DispatchSourceMach> dispatch_source_; + + // Whether or not ForwardMessage() was called during ReceiveMessage(). + bool did_forward_message_; +}; + +} // namespace sandbox + +#endif // SANDBOX_MAC_MACH_MESSAGE_SERVER_H_ diff --git a/sandbox/mac/message_server.h b/sandbox/mac/message_server.h new file mode 100644 index 0000000000..1cd40b0a3d --- /dev/null +++ b/sandbox/mac/message_server.h @@ -0,0 +1,71 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_MAC_MESSAGE_SERVER_H_ +#define SANDBOX_MAC_MESSAGE_SERVER_H_ + +#include <mach/mach.h> +#include <unistd.h> + +#include "sandbox/mac/xpc.h" + +namespace sandbox { + +// A message received by a MessageServer. Each concrete implementation of +// that interface will handle the fields of this union appropriately. +// Consumers should treat this as an opaque handle. +union IPCMessage { + mach_msg_header_t* mach; + xpc_object_t xpc; +}; + +// A delegate interface for MessageServer that handles processing of +// incoming intercepted IPC messages. +class MessageDemuxer { + public: + // Handle a |request| message. The message is owned by the server. Use the + // server's methods to create and send a reply message. + virtual void DemuxMessage(IPCMessage request) = 0; + + protected: + virtual ~MessageDemuxer() {} +}; + +// An interaface for an IPC server that implements Mach messaging semantics. +// The concrete implementation may be powered by raw Mach messages, XPC, or +// some other technology. This interface is the abstraction on top of those +// that enables message interception. +class MessageServer { + public: + virtual ~MessageServer() {} + + // Initializes the class and starts running the message server. If this + // returns false, no other methods may be called on this class. + virtual bool Initialize() = 0; + + // Given a received request message, returns the PID of the sending process. + virtual pid_t GetMessageSenderPID(IPCMessage request) = 0; + + // Creates a reply message from a request message. The result is owned by + // the server. + virtual IPCMessage CreateReply(IPCMessage request) = 0; + + // Sends a reply message. Returns true if the message was sent successfully. + virtual bool SendReply(IPCMessage reply) = 0; + + // Forwards the original |request| to the |destination| for handling. + virtual void ForwardMessage(IPCMessage request, mach_port_t destination) = 0; + + // Replies to the received |request| message by creating a reply and setting + // the specified |error_code| in a field that is interpreted by the + // underlying IPC system. + virtual void RejectMessage(IPCMessage request, int error_code) = 0; + + // Returns the Mach port on which the MessageServer is listening. + virtual mach_port_t GetServerPort() const = 0; +}; + +} // namespace sandbox + +#endif // SANDBOX_MAC_MESSAGE_SERVER_H_ diff --git a/sandbox/mac/os_compatibility.cc b/sandbox/mac/os_compatibility.cc new file mode 100644 index 0000000000..6624f3a19b --- /dev/null +++ b/sandbox/mac/os_compatibility.cc @@ -0,0 +1,135 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/mac/os_compatibility.h" + +#include <servers/bootstrap.h> +#include <unistd.h> + +#include "base/mac/mac_util.h" + +namespace sandbox { + +namespace { + +#pragma pack(push, 4) +// Verified from launchd-329.3.3 (10.6.8). +struct look_up2_request_10_6 { + mach_msg_header_t Head; + NDR_record_t NDR; + name_t servicename; + pid_t targetpid; + uint64_t flags; +}; + +struct look_up2_reply_10_6 { + mach_msg_header_t Head; + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t service_port; +}; + +// Verified from: +// launchd-392.39 (10.7.5) +// launchd-442.26.2 (10.8.5) +// launchd-842.1.4 (10.9.0) +struct look_up2_request_10_7 { + mach_msg_header_t Head; + NDR_record_t NDR; + name_t servicename; + pid_t targetpid; + uuid_t instanceid; + uint64_t flags; +}; + +// look_up2_reply_10_7 is the same as the 10_6 version. + +// Verified from: +// launchd-329.3.3 (10.6.8) +// launchd-392.39 (10.7.5) +// launchd-442.26.2 (10.8.5) +// launchd-842.1.4 (10.9.0) +typedef int vproc_gsk_t; // Defined as an enum in liblaunch/vproc_priv.h. +struct swap_integer_request_10_6 { + mach_msg_header_t Head; + NDR_record_t NDR; + vproc_gsk_t inkey; + vproc_gsk_t outkey; + int64_t inval; +}; +#pragma pack(pop) + +// TODO(rsesek): Libc provides strnlen() starting in 10.7. +size_t strnlen(const char* str, size_t maxlen) { + size_t len = 0; + for (; len < maxlen; ++len, ++str) { + if (*str == '\0') + break; + } + return len; +} + +uint64_t MachGetMessageID(const IPCMessage message) { + return message.mach->msgh_id; +} + +template <typename R> +std::string LaunchdLookUp2GetRequestName(const IPCMessage message) { + mach_msg_header_t* header = message.mach; + DCHECK_EQ(sizeof(R), header->msgh_size); + const R* request = reinterpret_cast<const R*>(header); + // Make sure the name is properly NUL-terminated. + const size_t name_length = + strnlen(request->servicename, BOOTSTRAP_MAX_NAME_LEN); + std::string name = std::string(request->servicename, name_length); + return name; +} + +template <typename R> +void LaunchdLookUp2FillReply(IPCMessage message, mach_port_t port) { + R* reply = reinterpret_cast<R*>(message.mach); + reply->Head.msgh_size = sizeof(R); + reply->Head.msgh_bits = + MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE) | + MACH_MSGH_BITS_COMPLEX; + reply->msgh_body.msgh_descriptor_count = 1; + reply->service_port.name = port; + reply->service_port.disposition = MACH_MSG_TYPE_COPY_SEND; + reply->service_port.type = MACH_MSG_PORT_DESCRIPTOR; +} + +template <typename R> +bool LaunchdSwapIntegerIsGetOnly(const IPCMessage message) { + const R* request = reinterpret_cast<const R*>(message.mach); + return request->inkey == 0 && request->inval == 0 && request->outkey != 0; +} + +} // namespace + +const LaunchdCompatibilityShim GetLaunchdCompatibilityShim() { + LaunchdCompatibilityShim shim = { + .ipc_message_get_id = &MachGetMessageID, + .msg_id_look_up2 = 404, + .msg_id_swap_integer = 416, + .look_up2_fill_reply = &LaunchdLookUp2FillReply<look_up2_reply_10_6>, + .swap_integer_is_get_only = + &LaunchdSwapIntegerIsGetOnly<swap_integer_request_10_6>, + }; + + if (base::mac::IsOSSnowLeopard()) { + shim.look_up2_get_request_name = + &LaunchdLookUp2GetRequestName<look_up2_request_10_6>; + } else if (base::mac::IsOSLionOrLater() && + !base::mac::IsOSYosemiteOrLater()) { + shim.look_up2_get_request_name = + &LaunchdLookUp2GetRequestName<look_up2_request_10_7>; + } else { + DLOG(ERROR) << "Unknown OS, using launchd compatibility shim from 10.7."; + shim.look_up2_get_request_name = + &LaunchdLookUp2GetRequestName<look_up2_request_10_7>; + } + + return shim; +} + +} // namespace sandbox diff --git a/sandbox/mac/os_compatibility.h b/sandbox/mac/os_compatibility.h new file mode 100644 index 0000000000..1205cd6868 --- /dev/null +++ b/sandbox/mac/os_compatibility.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. + +// This file is used to handle differences in Mach message IDs and structures +// that occur between different OS versions. The Mach messages that the sandbox +// is interested in are decoded using information derived from the open-source +// libraries, i.e. <http://www.opensource.apple.com/source/launchd/>. While +// these messages definitions are open source, they are not considered part of +// the stable OS API, and so differences do exist between OS versions. + +#ifndef SANDBOX_MAC_OS_COMPATIBILITY_H_ +#define SANDBOX_MAC_OS_COMPATIBILITY_H_ + +#include <mach/mach.h> + +#include <string> + +#include "sandbox/mac/message_server.h" + +namespace sandbox { + +typedef uint64_t (*IPCMessageGetID)(const IPCMessage); + +typedef std::string (*LookUp2GetRequestName)(const IPCMessage); +typedef void (*LookUp2FillReply)(IPCMessage, mach_port_t service_port); + +typedef bool (*SwapIntegerIsGetOnly)(const IPCMessage); + +struct LaunchdCompatibilityShim { + // Gets the message ID of an IPC message. + IPCMessageGetID ipc_message_get_id; + + // The msgh_id for look_up2. + uint64_t msg_id_look_up2; + + // The msgh_id for swap_integer. + uint64_t msg_id_swap_integer; + + // A function to take a look_up2 message and return the string service name + // that was requested via the message. + LookUp2GetRequestName look_up2_get_request_name; + + // A function to formulate a reply to a look_up2 message, given the reply + // message and the port to return as the service. + LookUp2FillReply look_up2_fill_reply; + + // A function to take a swap_integer message and return true if the message + // is only getting the value of a key, neither setting it directly, nor + // swapping two keys. + SwapIntegerIsGetOnly swap_integer_is_get_only; +}; + +// Gets the compatibility shim for the launchd job subsystem. +const LaunchdCompatibilityShim GetLaunchdCompatibilityShim(); + +} // namespace sandbox + +#endif // SANDBOX_MAC_OS_COMPATIBILITY_H_ diff --git a/sandbox/mac/policy.cc b/sandbox/mac/policy.cc new file mode 100644 index 0000000000..293255adef --- /dev/null +++ b/sandbox/mac/policy.cc @@ -0,0 +1,56 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/mac/policy.h" + +namespace sandbox { + +Rule::Rule() + : result(POLICY_DECISION_INVALID), + substitute_port(MACH_PORT_NULL) { +} + +Rule::Rule(PolicyDecision result) + : result(result), + substitute_port(MACH_PORT_NULL) { +} + +Rule::Rule(mach_port_t override_port) + : result(POLICY_SUBSTITUTE_PORT), + substitute_port(override_port) { +} + +BootstrapSandboxPolicy::BootstrapSandboxPolicy() + : default_rule(POLICY_DENY_ERROR) { +} + +BootstrapSandboxPolicy::~BootstrapSandboxPolicy() {} + +static bool IsRuleValid(const Rule& rule) { + if (!(rule.result > POLICY_DECISION_INVALID && + rule.result < POLICY_DECISION_LAST)) { + return false; + } + if (rule.result == POLICY_SUBSTITUTE_PORT) { + if (rule.substitute_port == MACH_PORT_NULL) + return false; + } else { + if (rule.substitute_port != MACH_PORT_NULL) + return false; + } + return true; +} + +bool IsPolicyValid(const BootstrapSandboxPolicy& policy) { + if (!IsRuleValid(policy.default_rule)) + return false; + + for (const auto& pair : policy.rules) { + if (!IsRuleValid(pair.second)) + return false; + } + return true; +} + +} // namespace sandbox diff --git a/sandbox/mac/policy.h b/sandbox/mac/policy.h new file mode 100644 index 0000000000..e500468237 --- /dev/null +++ b/sandbox/mac/policy.h @@ -0,0 +1,70 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_MAC_POLICY_H_ +#define SANDBOX_MAC_POLICY_H_ + +#include <mach/mach.h> + +#include <map> +#include <string> + +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +enum PolicyDecision { + POLICY_DECISION_INVALID, + // Explicitly allows the real service to be looked up from launchd. + POLICY_ALLOW, + // Deny the look up request by replying with a MIG error. This is the + // default behavior for servers not given an explicit rule. + POLICY_DENY_ERROR, + // Deny the look up request with a well-formed reply containing a + // Mach port with a send right, messages to which will be ignored. + POLICY_DENY_DUMMY_PORT, + // Reply to the look up request with a send right to the substitute_port + // specified in the Rule. + POLICY_SUBSTITUTE_PORT, + POLICY_DECISION_LAST, +}; + +// A Rule expresses the action to take when a service port is requested via +// bootstrap_look_up. If |result| is not POLICY_SUBSTITUTE_PORT, then +// |substitute_port| must be NULL. If result is POLICY_SUBSTITUTE_PORT, then +// |substitute_port| must not be NULL. +struct SANDBOX_EXPORT Rule { + Rule(); + explicit Rule(PolicyDecision result); + explicit Rule(mach_port_t override_port); + + PolicyDecision result; + + // The Rule does not take ownership of this port, but additional send rights + // will be allocated to it before it is sent to a client. This name must + // denote a send right that can duplicated with MACH_MSG_TYPE_COPY_SEND. + mach_port_t substitute_port; +}; + +// A policy object manages the rules enforced on a target sandboxed process. +struct SANDBOX_EXPORT BootstrapSandboxPolicy { + typedef std::map<std::string, Rule> NamedRules; + + BootstrapSandboxPolicy(); + ~BootstrapSandboxPolicy(); + + // The default action to take if the server name being looked up is not + // present in |rules|. + Rule default_rule; + + // A map of bootstrap server names to policy Rules. + NamedRules rules; +}; + +// Checks that a policy is well-formed. +SANDBOX_EXPORT bool IsPolicyValid(const BootstrapSandboxPolicy& policy); + +} // namespace sandbox + +#endif // SANDBOX_MAC_POLICY_H_ diff --git a/sandbox/mac/policy_unittest.cc b/sandbox/mac/policy_unittest.cc new file mode 100644 index 0000000000..54e0e74895 --- /dev/null +++ b/sandbox/mac/policy_unittest.cc @@ -0,0 +1,98 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/mac/policy.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +TEST(PolicyTest, ValidEmptyPolicy) { + EXPECT_TRUE(IsPolicyValid(BootstrapSandboxPolicy())); +} + +TEST(PolicyTest, ValidPolicy) { + BootstrapSandboxPolicy policy; + policy.rules["allow"] = Rule(POLICY_ALLOW); + policy.rules["deny_error"] = Rule(POLICY_DENY_ERROR); + policy.rules["deny_dummy"] = Rule(POLICY_DENY_DUMMY_PORT); + policy.rules["substitue"] = Rule(mach_task_self()); + EXPECT_TRUE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyEmptyRule) { + Rule rule; + BootstrapSandboxPolicy policy; + policy.rules["test"] = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicySubstitue) { + Rule rule(POLICY_SUBSTITUTE_PORT); + BootstrapSandboxPolicy policy; + policy.rules["test"] = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyWithPortAllow) { + Rule rule(POLICY_ALLOW); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.rules["allow"] = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyWithPortDenyError) { + Rule rule(POLICY_DENY_ERROR); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.rules["deny_error"] = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyWithPortDummy) { + Rule rule(POLICY_DENY_DUMMY_PORT); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.rules["deny_dummy"] = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyDefaultRule) { + BootstrapSandboxPolicy policy; + policy.default_rule = Rule(); + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyDefaultRuleSubstitue) { + BootstrapSandboxPolicy policy; + policy.default_rule = Rule(POLICY_SUBSTITUTE_PORT); + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyDefaultRuleWithPortAllow) { + Rule rule(POLICY_ALLOW); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.default_rule = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyDefaultRuleWithPortDenyError) { + Rule rule(POLICY_DENY_ERROR); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.default_rule = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +TEST(PolicyTest, InvalidPolicyDefaultRuleWithPortDummy) { + Rule rule(POLICY_DENY_DUMMY_PORT); + rule.substitute_port = mach_task_self(); + BootstrapSandboxPolicy policy; + policy.default_rule = rule; + EXPECT_FALSE(IsPolicyValid(policy)); +} + +} // namespace sandbox diff --git a/sandbox/mac/sandbox_mac.gypi b/sandbox/mac/sandbox_mac.gypi new file mode 100644 index 0000000000..d5013f832e --- /dev/null +++ b/sandbox/mac/sandbox_mac.gypi @@ -0,0 +1,114 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'sandbox', + 'type': '<(component)', + 'sources': [ + 'bootstrap_sandbox.cc', + 'bootstrap_sandbox.h', + 'launchd_interception_server.cc', + 'launchd_interception_server.h', + 'mach_message_server.cc', + 'mach_message_server.h', + 'message_server.h', + 'os_compatibility.cc', + 'os_compatibility.h', + 'policy.cc', + 'policy.h', + 'xpc.cc', + 'xpc.h', + 'xpc_message_server.cc', + 'xpc_message_server.h', + ], + 'dependencies': [ + '../base/base.gyp:base', + ], + 'include_dirs': [ + '..', + '<(SHARED_INTERMEDIATE_DIR)', + ], + 'defines': [ + 'SANDBOX_IMPLEMENTATION', + ], + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/usr/lib/libbsm.dylib', + ], + }, + 'conditions': [ + # When the build SDK is 10.6, generate a dynamic stub loader. When the + # SDK is higher, then libxpc.dylib will be loaded automatically as part + # of libSystem, and only forward declarations of private symbols are + # necessary. + ['mac_sdk == "10.6"', { + 'actions': [ + { + 'variables': { + 'generate_stubs_script': '../tools/generate_stubs/generate_stubs.py', + 'generate_stubs_header_path': 'xpc_stubs_header.fragment', + 'generate_stubs_sig_public_path': 'xpc_stubs.sig', + 'generate_stubs_sig_private_path': 'xpc_private_stubs.sig', + 'generate_stubs_project': 'sandbox/mac', + 'generate_stubs_output_stem': 'xpc_stubs', + }, + 'action_name': 'generate_stubs', + 'inputs': [ + '<(generate_stubs_script)', + '<(generate_stubs_header_path)', + '<(generate_stubs_sig_public_path)', + '<(generate_stubs_sig_private_path)', + ], + 'outputs': [ + '<(INTERMEDIATE_DIR)/<(generate_stubs_output_stem).cc', + '<(SHARED_INTERMEDIATE_DIR)/<(generate_stubs_project)/<(generate_stubs_output_stem).h', + ], + 'action': [ + 'python', + '<(generate_stubs_script)', + '-i', '<(INTERMEDIATE_DIR)', + '-o', '<(SHARED_INTERMEDIATE_DIR)/<(generate_stubs_project)', + '-t', 'posix_stubs', + '-e', '<(generate_stubs_header_path)', + '-s', '<(generate_stubs_output_stem)', + '-p', '<(generate_stubs_project)', + '-x', 'SANDBOX_EXPORT', + '<(generate_stubs_sig_public_path)', + '<(generate_stubs_sig_private_path)', + ], + 'process_outputs_as_sources': 1, + 'message': 'Generating XPC stubs for 10.6 compatability.', + }, + ], + }], + ], + }, + { + 'target_name': 'sandbox_mac_unittests', + 'type': 'executable', + 'sources': [ + 'bootstrap_sandbox_unittest.mm', + 'policy_unittest.cc', + 'xpc_message_server_unittest.cc', + ], + 'dependencies': [ + 'sandbox', + '../base/base.gyp:base', + '../base/base.gyp:run_all_unittests', + '../testing/gtest.gyp:gtest', + ], + 'include_dirs': [ + '..', + ], + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/CoreFoundation.framework', + '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', + ], + }, + }, + ], +} diff --git a/sandbox/mac/xpc.cc b/sandbox/mac/xpc.cc new file mode 100644 index 0000000000..b8d526bdad --- /dev/null +++ b/sandbox/mac/xpc.cc @@ -0,0 +1,25 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/mac/xpc.h" + +namespace sandbox { + +bool InitializeXPC() { +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 + std::vector<std::string> path_list; + path_list.push_back("/usr/lib/system/libxpc.dylib"); + + sandbox_mac::StubPathMap path_map; + path_map[sandbox_mac::kModuleXpc_stubs] = path_list; + path_map[sandbox_mac::kModuleXpc_private_stubs] = path_list; + + return sandbox_mac::InitializeStubs(path_map); +#else + return true; +#endif +} + +} // namespace sandbox diff --git a/sandbox/mac/xpc.h b/sandbox/mac/xpc.h new file mode 100644 index 0000000000..33d3945e38 --- /dev/null +++ b/sandbox/mac/xpc.h @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides forward declarations for XPC symbols that are not +// present in the 10.6 SDK. It uses generate_stubs to produce code to +// dynamically load the libxpc.dylib library and set up a stub table, with +// the same names as the real XPC functions. + +#ifndef SANDBOX_MAC_XPC_H_ +#define SANDBOX_MAC_XPC_H_ + +#include <AvailabilityMacros.h> +#include <mach/mach.h> + +#include "sandbox/sandbox_export.h" + +// Declares XPC object types. This includes <xpc/xpc.h> if available. +#include "sandbox/mac/xpc_stubs_header.fragment" + +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 + +// C++ library loader. +#include "sandbox/mac/xpc_stubs.h" + +extern "C" { +// Signatures for XPC public functions that are loaded by xpc_stubs.h. +#include "sandbox/mac/xpc_stubs.sig" +// Signatures for private XPC functions. +#include "sandbox/mac/xpc_private_stubs.sig" +} // extern "C" + +#else + +// Signatures for private XPC functions. +extern "C" { +#include "sandbox/mac/xpc_private_stubs.sig" +} // extern "C" + +#endif + +namespace sandbox { + +// Dynamically loads the XPC library. +bool SANDBOX_EXPORT InitializeXPC(); + +} // namespace sandbox + +#endif // SANDBOX_MAC_XPC_H_ diff --git a/sandbox/mac/xpc_message_server.cc b/sandbox/mac/xpc_message_server.cc new file mode 100644 index 0000000000..1cd5c63f38 --- /dev/null +++ b/sandbox/mac/xpc_message_server.cc @@ -0,0 +1,126 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/mac/xpc_message_server.h" + +#include <bsm/libbsm.h> + +#include <string> + +#include "base/mac/mach_logging.h" +#include "base/strings/stringprintf.h" +#include "sandbox/mac/xpc.h" + +#if defined(MAC_OS_X_VERSION_10_7) && \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 +// Redeclare methods that only exist on 10.7+ to suppress +// -Wpartial-availability warnings. +extern "C" { +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT XPC_NONNULL_ALL + xpc_object_t + xpc_dictionary_create_reply(xpc_object_t original); +} // extern "C" +#endif + +namespace sandbox { + +XPCMessageServer::XPCMessageServer(MessageDemuxer* demuxer, + mach_port_t server_receive_right) + : demuxer_(demuxer), + server_port_(server_receive_right), + reply_message_(NULL) { +} + +XPCMessageServer::~XPCMessageServer() { +} + +bool XPCMessageServer::Initialize() { + // Allocate a port for use as a new server port if one was not passed to the + // constructor. + if (!server_port_.is_valid()) { + mach_port_t port; + kern_return_t kr; + if ((kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, + &port)) != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "Failed to allocate new server port."; + return false; + } + server_port_.reset(port); + } + + std::string label = base::StringPrintf( + "org.chromium.sandbox.XPCMessageServer.%p", demuxer_); + dispatch_source_.reset(new base::DispatchSourceMach( + label.c_str(), server_port_.get(), ^{ ReceiveMessage(); })); + dispatch_source_->Resume(); + + return true; +} + +pid_t XPCMessageServer::GetMessageSenderPID(IPCMessage request) { + audit_token_t token; + xpc_dictionary_get_audit_token(request.xpc, &token); + // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid(). + pid_t sender_pid; + audit_token_to_au32(token, + NULL, NULL, NULL, NULL, NULL, &sender_pid, NULL, NULL); + return sender_pid; +} + +IPCMessage XPCMessageServer::CreateReply(IPCMessage request) { + if (!reply_message_) + reply_message_ = xpc_dictionary_create_reply(request.xpc); + + IPCMessage reply; + reply.xpc = reply_message_; + return reply; +} + +bool XPCMessageServer::SendReply(IPCMessage reply) { + int rv = xpc_pipe_routine_reply(reply.xpc); + if (rv) { + LOG(ERROR) << "Failed to xpc_pipe_routine_reply(): " << rv; + return false; + } + return true; +} + +void XPCMessageServer::ForwardMessage(IPCMessage request, + mach_port_t destination) { + xpc_pipe_t pipe = xpc_pipe_create_from_port(destination, 0); + int rv = xpc_pipe_routine_forward(pipe, request.xpc); + if (rv) { + LOG(ERROR) << "Failed to xpc_pipe_routine_forward(): " << rv; + } + xpc_release(pipe); +} + +void XPCMessageServer::RejectMessage(IPCMessage request, int error_code) { + IPCMessage reply = CreateReply(request); + xpc_dictionary_set_int64(reply.xpc, "error", error_code); + SendReply(reply); +} + +mach_port_t XPCMessageServer::GetServerPort() const { + return server_port_.get(); +} + +void XPCMessageServer::ReceiveMessage() { + IPCMessage request; + int rv = xpc_pipe_receive(server_port_, &request.xpc); + if (rv) { + LOG(ERROR) << "Failed to xpc_pipe_receive(): " << rv; + return; + } + + demuxer_->DemuxMessage(request); + + xpc_release(request.xpc); + if (reply_message_) { + xpc_release(reply_message_); + reply_message_ = NULL; + } +} + +} // namespace sandbox diff --git a/sandbox/mac/xpc_message_server.h b/sandbox/mac/xpc_message_server.h new file mode 100644 index 0000000000..5f5a9fa24a --- /dev/null +++ b/sandbox/mac/xpc_message_server.h @@ -0,0 +1,74 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_MAC_XPC_MESSAGE_SERVER_H_ +#define SANDBOX_MAC_XPC_MESSAGE_SERVER_H_ + +#include <AvailabilityMacros.h> + +#include "base/mac/dispatch_source_mach.h" +#include "base/mac/scoped_mach_port.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/mac/message_server.h" +#include "sandbox/mac/xpc.h" +#include "sandbox/sandbox_export.h" + +#if defined(MAC_OS_X_VERSION_10_7) && \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 +// Redeclare methods that only exist on 10.7+ to suppress +// -Wpartial-availability warnings. +extern "C" { +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 void +xpc_dictionary_set_int64(xpc_object_t xdict, const char* key, int64_t value); + +XPC_EXPORT XPC_NONNULL1 void xpc_release(xpc_object_t object); +} // extern "C" +#endif + +namespace sandbox { + +// An implementation of MessageServer that uses XPC pipes to read and write XPC +// messages from a Mach port. +class SANDBOX_EXPORT XPCMessageServer : public MessageServer { + public: + // Creates a new XPC message server that will send messages to |demuxer| + // for handling. If the |server_receive_right| is non-NULL, this class will + // take ownership of the port and it will be used to receive messages. + // Otherwise the server will create a new receive right on which to listen. + XPCMessageServer(MessageDemuxer* demuxer, + mach_port_t server_receive_right); + ~XPCMessageServer() override; + + // MessageServer: + bool Initialize() override; + pid_t GetMessageSenderPID(IPCMessage request) override; + IPCMessage CreateReply(IPCMessage request) override; + bool SendReply(IPCMessage reply) override; + void ForwardMessage(IPCMessage request, mach_port_t destination) override; + // Creates an error reply message with a field "error" set to |error_code|. + void RejectMessage(IPCMessage request, int error_code) override; + mach_port_t GetServerPort() const override; + + private: + // Reads a message from the XPC pipe. + void ReceiveMessage(); + + // The demuxer delegate. Weak. + MessageDemuxer* demuxer_; + + // The Mach port on which the server is receiving requests. + base::mac::ScopedMachReceiveRight server_port_; + + // MACH_RECV dispatch source that handles the |server_port_|. + scoped_ptr<base::DispatchSourceMach> dispatch_source_; + + // The reply message, if one has been created. + xpc_object_t reply_message_; + + DISALLOW_COPY_AND_ASSIGN(XPCMessageServer); +}; + +} // namespace sandbox + +#endif // SANDBOX_MAC_XPC_MESSAGE_SERVER_H_ diff --git a/sandbox/mac/xpc_message_server_unittest.cc b/sandbox/mac/xpc_message_server_unittest.cc new file mode 100644 index 0000000000..4c4fcf9c94 --- /dev/null +++ b/sandbox/mac/xpc_message_server_unittest.cc @@ -0,0 +1,238 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/mac/xpc_message_server.h" + +#include <Block.h> +#include <mach/mach.h> +#include <servers/bootstrap.h> + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_mach_port.h" +#include "base/process/kill.h" +#include "base/test/multiprocess_test.h" +#include "sandbox/mac/xpc.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +#if defined(MAC_OS_X_VERSION_10_7) && \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_7 +// Redeclare methods that only exist on 10.7+ to suppress +// -Wpartial-availability warnings. +extern "C" { +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL int64_t +xpc_dictionary_get_int64(xpc_object_t xdict, const char* key); + +XPC_EXPORT XPC_WARN_RESULT XPC_NONNULL_ALL uint64_t +xpc_dictionary_get_uint64(xpc_object_t xdict, const char* key); + +XPC_EXPORT XPC_NONNULL1 XPC_NONNULL2 void +xpc_dictionary_set_uint64(xpc_object_t xdict, const char* key, uint64_t value); + +XPC_EXPORT XPC_MALLOC XPC_RETURNS_RETAINED XPC_WARN_RESULT xpc_object_t +xpc_dictionary_create(const char* const* keys, + const xpc_object_t* values, + size_t count); +} // extern "C" +#endif + +namespace sandbox { + +class XPCMessageServerTest : public testing::Test { + public: + void SetUp() override { + if (!RunXPCTest()) + return; + ASSERT_TRUE(InitializeXPC()); + } + + bool RunXPCTest() { + return base::mac::IsOSMountainLionOrLater(); + } +}; + +// A MessageDemuxer that manages a test server and executes a block for every +// message. +class BlockDemuxer : public MessageDemuxer { + public: + BlockDemuxer() + : demux_block_(NULL), + server_(this, MACH_PORT_NULL), + pipe_(NULL) { + } + + ~BlockDemuxer() override { + if (pipe_) + xpc_release(pipe_); + if (demux_block_) + Block_release(demux_block_); + } + + // Starts running the server, given a block to handle incoming IPC messages. + bool Initialize(void (^demux_block)(IPCMessage request)) { + if (!server_.Initialize()) + return false; + + // Create a send right on the port so that the XPC pipe can be created. + if (mach_port_insert_right(mach_task_self(), server_.GetServerPort(), + server_.GetServerPort(), MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) { + return false; + } + scoped_send_right_.reset(server_.GetServerPort()); + + demux_block_ = Block_copy(demux_block); + pipe_ = xpc_pipe_create_from_port(server_.GetServerPort(), 0); + + return true; + } + + void DemuxMessage(IPCMessage request) override { + demux_block_(request); + } + + xpc_pipe_t pipe() { return pipe_; } + + XPCMessageServer* server() { return &server_; } + + private: + void (^demux_block_)(IPCMessage request); + + XPCMessageServer server_; + + base::mac::ScopedMachSendRight scoped_send_right_; + + xpc_pipe_t pipe_; +}; + +#define XPC_TEST_F(name) TEST_F(XPCMessageServerTest, name) { \ + if (!RunXPCTest()) \ + return; \ + +XPC_TEST_F(ReceiveMessage) // { + BlockDemuxer fixture; + XPCMessageServer* server = fixture.server(); + + uint64_t __block value = 0; + ASSERT_TRUE(fixture.Initialize(^(IPCMessage request) { + value = xpc_dictionary_get_uint64(request.xpc, "test_value"); + server->SendReply(server->CreateReply(request)); + })); + + xpc_object_t request = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_uint64(request, "test_value", 42); + + xpc_object_t reply; + EXPECT_EQ(0, xpc_pipe_routine(fixture.pipe(), request, &reply)); + + EXPECT_EQ(42u, value); + + xpc_release(request); + xpc_release(reply); +} + +XPC_TEST_F(RejectMessage) // { + BlockDemuxer fixture; + XPCMessageServer* server = fixture.server(); + ASSERT_TRUE(fixture.Initialize(^(IPCMessage request) { + server->RejectMessage(request, EPERM); + })); + + xpc_object_t request = xpc_dictionary_create(NULL, NULL, 0); + xpc_object_t reply; + EXPECT_EQ(0, xpc_pipe_routine(fixture.pipe(), request, &reply)); + + EXPECT_EQ(EPERM, xpc_dictionary_get_int64(reply, "error")); + + xpc_release(request); + xpc_release(reply); +} + +char kGetSenderPID[] = "org.chromium.sandbox.test.GetSenderPID"; + +XPC_TEST_F(GetSenderPID) // { + BlockDemuxer fixture; + XPCMessageServer* server = fixture.server(); + + pid_t __block sender_pid = 0; + int64_t __block child_pid = 0; + ASSERT_TRUE(fixture.Initialize(^(IPCMessage request) { + sender_pid = server->GetMessageSenderPID(request); + child_pid = xpc_dictionary_get_int64(request.xpc, "child_pid"); + })); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + kern_return_t kr = bootstrap_register(bootstrap_port, kGetSenderPID, + server->GetServerPort()); +#pragma GCC diagnostic pop + ASSERT_EQ(KERN_SUCCESS, kr); + + base::Process child = base::SpawnMultiProcessTestChild( + "GetSenderPID", + base::GetMultiProcessTestChildBaseCommandLine(), + base::LaunchOptions()); + ASSERT_TRUE(child.IsValid()); + + int exit_code = -1; + ASSERT_TRUE(child.WaitForExit(&exit_code)); + EXPECT_EQ(0, exit_code); + + EXPECT_EQ(child.Pid(), sender_pid); + EXPECT_EQ(child.Pid(), child_pid); + EXPECT_EQ(sender_pid, child_pid); +} + +MULTIPROCESS_TEST_MAIN(GetSenderPID) { + CHECK(sandbox::InitializeXPC()); + + mach_port_t port = MACH_PORT_NULL; + CHECK_EQ(KERN_SUCCESS, bootstrap_look_up(bootstrap_port, kGetSenderPID, + &port)); + base::mac::ScopedMachSendRight scoped_port(port); + + xpc_pipe_t pipe = xpc_pipe_create_from_port(port, 0); + + xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_int64(message, "child_pid", getpid()); + CHECK_EQ(0, xpc_pipe_simpleroutine(pipe, message)); + + xpc_release(message); + xpc_release(pipe); + + return 0; +} + +XPC_TEST_F(ForwardMessage) // { + BlockDemuxer first; + XPCMessageServer* first_server = first.server(); + + BlockDemuxer second; + XPCMessageServer* second_server = second.server(); + + ASSERT_TRUE(first.Initialize(^(IPCMessage request) { + xpc_dictionary_set_int64(request.xpc, "seen_by_first", 1); + first_server->ForwardMessage(request, second_server->GetServerPort()); + })); + ASSERT_TRUE(second.Initialize(^(IPCMessage request) { + IPCMessage reply = second_server->CreateReply(request); + xpc_dictionary_set_int64(reply.xpc, "seen_by_first", + xpc_dictionary_get_int64(request.xpc, "seen_by_first")); + xpc_dictionary_set_int64(reply.xpc, "seen_by_second", 2); + second_server->SendReply(reply); + })); + + xpc_object_t request = xpc_dictionary_create(NULL, NULL, 0); + xpc_object_t reply; + ASSERT_EQ(0, xpc_pipe_routine(first.pipe(), request, &reply)); + + EXPECT_EQ(1, xpc_dictionary_get_int64(reply, "seen_by_first")); + EXPECT_EQ(2, xpc_dictionary_get_int64(reply, "seen_by_second")); + + xpc_release(request); + xpc_release(reply); +} + +} // namespace sandbox diff --git a/sandbox/mac/xpc_private_stubs.sig b/sandbox/mac/xpc_private_stubs.sig new file mode 100644 index 0000000000..7ab2934c52 --- /dev/null +++ b/sandbox/mac/xpc_private_stubs.sig @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains declarations of private XPC functions. This file is +// used for both forward declarations of private symbols and to use with +// tools/generate_stubs for creating a dynamic library loader. + +// Dictionary manipulation. +void xpc_dictionary_set_mach_send(xpc_object_t dictionary, const char* name, mach_port_t port); +void xpc_dictionary_get_audit_token(xpc_object_t dictionary, audit_token_t* token); + +// Pipe methods. +xpc_pipe_t xpc_pipe_create_from_port(mach_port_t port, int flags); +int xpc_pipe_receive(mach_port_t port, xpc_object_t* message); +int xpc_pipe_routine(xpc_pipe_t pipe, xpc_object_t request, xpc_object_t* reply); +int xpc_pipe_routine_reply(xpc_object_t reply); +int xpc_pipe_simpleroutine(xpc_pipe_t pipe, xpc_object_t message); +int xpc_pipe_routine_forward(xpc_pipe_t forward_to, xpc_object_t request); diff --git a/sandbox/mac/xpc_stubs.sig b/sandbox/mac/xpc_stubs.sig new file mode 100644 index 0000000000..d20af58a6e --- /dev/null +++ b/sandbox/mac/xpc_stubs.sig @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains declarations of public XPC functions used in the sandbox. +// This file is used with tools/generate_stubs for creating a dynamic library +// loader. + +// XPC object management. +void xpc_release(xpc_object_t object); + +// Dictionary manipulation. +xpc_object_t xpc_dictionary_create(const char* const *keys, const xpc_object_t* values, size_t count); +const char* xpc_dictionary_get_string(xpc_object_t dictionary, const char* key); +uint64_t xpc_dictionary_get_uint64(xpc_object_t dictionary, const char* key); +void xpc_dictionary_set_uint64(xpc_object_t dictionary, const char* key, uint64_t value); +int64_t xpc_dictionary_get_int64(xpc_object_t dictionary, const char* key); +void xpc_dictionary_set_int64(xpc_object_t dictionary, const char* key, int64_t value); +xpc_object_t xpc_dictionary_create_reply(xpc_object_t request); diff --git a/sandbox/mac/xpc_stubs_header.fragment b/sandbox/mac/xpc_stubs_header.fragment new file mode 100644 index 0000000000..8197587fc7 --- /dev/null +++ b/sandbox/mac/xpc_stubs_header.fragment @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_MAC_XPC_STUBS_HEADER_FRAGMENT_ +#define SANDBOX_MAC_XPC_STUBS_HEADER_FRAGMENT_ + +#include <bsm/libbsm.h> + +#include "sandbox/sandbox_export.h" + +// Declare or include public types. +#if !defined(MAC_OS_X_VERSION_10_7) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 + +extern "C" { +typedef void* xpc_object_t; +} // extern "C" + +#else + +#include <xpc/xpc.h> + +#endif + +// Declare private types. +extern "C" { +typedef struct _xpc_pipe_s* xpc_pipe_t; +} // extern "C" + +#endif // SANDBOX_MAC_XPC_STUBS_HEADER_FRAGMENT_ diff --git a/sandbox/sandbox.gyp b/sandbox/sandbox.gyp new file mode 100644 index 0000000000..f93fa1862a --- /dev/null +++ b/sandbox/sandbox.gyp @@ -0,0 +1,35 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'conditions': [ + [ 'OS=="win"', { + 'includes': [ + 'win/sandbox_win.gypi', + ], + }], + [ 'OS=="linux" or OS=="android"', { + 'includes': [ + 'linux/sandbox_linux.gypi', + ], + }], + [ 'OS=="mac" and OS!="ios"', { + 'includes': [ + 'mac/sandbox_mac.gypi', + ], + }], + [ 'OS!="win" and OS!="mac" and OS!="linux" and OS!="android"', { + # A 'default' to accomodate the "sandbox" target. + 'targets': [ + { + 'target_name': 'sandbox', + 'type': 'none', + } + ] + }], + ], +} diff --git a/sandbox/sandbox_export.h b/sandbox/sandbox_export.h new file mode 100644 index 0000000000..40a4036640 --- /dev/null +++ b/sandbox/sandbox_export.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SANDBOX_EXPORT_H_ +#define SANDBOX_SANDBOX_EXPORT_H_ + +#if defined(WIN32) +#error "sandbox_export.h does not support WIN32." +#endif + +#if defined(COMPONENT_BUILD) + +#if defined(SANDBOX_IMPLEMENTATION) +#define SANDBOX_EXPORT __attribute__((visibility("default"))) +#define SANDBOX_EXPORT_PRIVATE __attribute__((visibility("default"))) +#else +#define SANDBOX_EXPORT +#define SANDBOX_EXPORT_PRIVATE +#endif // defined(SANDBOX_IMPLEMENTATION) + +#else // defined(COMPONENT_BUILD) + +#define SANDBOX_EXPORT +#define SANDBOX_EXPORT_PRIVATE + +#endif // defined(COMPONENT_BUILD) + +#endif // SANDBOX_SANDBOX_EXPORT_H_ diff --git a/sandbox/sandbox_linux_unittests.isolate b/sandbox/sandbox_linux_unittests.isolate new file mode 100644 index 0000000000..2dadddd098 --- /dev/null +++ b/sandbox/sandbox_linux_unittests.isolate @@ -0,0 +1,27 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Because of a limitation in isolate_driver.py, this file needs to be in +# the same directory as the main .gyp file. + +{ + 'conditions': [ + ['OS=="android" or OS=="linux"', { + 'variables': { + 'command': [ + '<(PRODUCT_DIR)/sandbox_linux_unittests', + ], + 'files': [ + '<(PRODUCT_DIR)/sandbox_linux_unittests', + ], + 'read_only': 1, + }, + }], + ], + 'includes': [ + # This is needed because of base/ dependencies on + # icudtl.dat. + '../base/base.isolate', + ], +} diff --git a/sandbox/win/BUILD.gn b/sandbox/win/BUILD.gn new file mode 100644 index 0000000000..3063f7f17d --- /dev/null +++ b/sandbox/win/BUILD.gn @@ -0,0 +1,302 @@ +# 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("//testing/test.gni") + +source_set("sandbox") { + sources = [ + "src/acl.cc", + "src/acl.h", + "src/app_container.cc", + "src/app_container.h", + "src/broker_services.cc", + "src/broker_services.h", + "src/crosscall_client.h", + "src/crosscall_params.h", + "src/crosscall_server.cc", + "src/crosscall_server.h", + "src/eat_resolver.cc", + "src/eat_resolver.h", + "src/filesystem_dispatcher.cc", + "src/filesystem_dispatcher.h", + "src/filesystem_interception.cc", + "src/filesystem_interception.h", + "src/filesystem_policy.cc", + "src/filesystem_policy.h", + "src/handle_closer.cc", + "src/handle_closer.h", + "src/handle_closer_agent.cc", + "src/handle_closer_agent.h", + "src/handle_dispatcher.cc", + "src/handle_dispatcher.h", + "src/handle_interception.cc", + "src/handle_interception.h", + "src/handle_policy.cc", + "src/handle_policy.h", + "src/handle_table.cc", + "src/handle_table.h", + "src/interception.cc", + "src/interception.h", + "src/interception_agent.cc", + "src/interception_agent.h", + "src/interception_internal.h", + "src/interceptors.h", + "src/internal_types.h", + "src/ipc_tags.h", + "src/job.cc", + "src/job.h", + "src/named_pipe_dispatcher.cc", + "src/named_pipe_dispatcher.h", + "src/named_pipe_interception.cc", + "src/named_pipe_interception.h", + "src/named_pipe_policy.cc", + "src/named_pipe_policy.h", + "src/nt_internals.h", + "src/policy_broker.cc", + "src/policy_broker.h", + "src/policy_engine_opcodes.cc", + "src/policy_engine_opcodes.h", + "src/policy_engine_params.h", + "src/policy_engine_processor.cc", + "src/policy_engine_processor.h", + "src/policy_low_level.cc", + "src/policy_low_level.h", + "src/policy_params.h", + "src/policy_target.cc", + "src/policy_target.h", + "src/process_mitigations.cc", + "src/process_mitigations.h", + "src/process_mitigations_win32k_dispatcher.cc", + "src/process_mitigations_win32k_dispatcher.h", + "src/process_mitigations_win32k_interception.cc", + "src/process_mitigations_win32k_interception.h", + "src/process_mitigations_win32k_policy.cc", + "src/process_mitigations_win32k_policy.h", + "src/process_thread_dispatcher.cc", + "src/process_thread_dispatcher.h", + "src/process_thread_interception.cc", + "src/process_thread_interception.h", + "src/process_thread_policy.cc", + "src/process_thread_policy.h", + "src/registry_dispatcher.cc", + "src/registry_dispatcher.h", + "src/registry_interception.cc", + "src/registry_interception.h", + "src/registry_policy.cc", + "src/registry_policy.h", + "src/resolver.cc", + "src/resolver.h", + "src/restricted_token.cc", + "src/restricted_token.h", + "src/restricted_token_utils.cc", + "src/restricted_token_utils.h", + "src/sandbox.cc", + "src/sandbox.h", + "src/sandbox_factory.h", + "src/sandbox_globals.cc", + "src/sandbox_nt_types.h", + "src/sandbox_nt_util.cc", + "src/sandbox_nt_util.h", + "src/sandbox_policy.h", + "src/sandbox_policy_base.cc", + "src/sandbox_policy_base.h", + "src/sandbox_types.h", + "src/sandbox_utils.cc", + "src/sandbox_utils.h", + "src/security_level.h", + "src/service_resolver.cc", + "src/service_resolver.h", + "src/shared_handles.cc", + "src/shared_handles.h", + "src/sharedmem_ipc_client.cc", + "src/sharedmem_ipc_client.h", + "src/sharedmem_ipc_server.cc", + "src/sharedmem_ipc_server.h", + "src/sid.cc", + "src/sid.h", + "src/sync_dispatcher.cc", + "src/sync_dispatcher.h", + "src/sync_interception.cc", + "src/sync_interception.h", + "src/sync_policy.cc", + "src/sync_policy.h", + "src/target_interceptions.cc", + "src/target_interceptions.h", + "src/target_process.cc", + "src/target_process.h", + "src/target_services.cc", + "src/target_services.h", + "src/win2k_threadpool.cc", + "src/win2k_threadpool.h", + "src/win_utils.cc", + "src/win_utils.h", + "src/window.cc", + "src/window.h", + ] + + if (current_cpu == "x64") { + sources += [ + "src/Wow64_64.cc", + "src/interceptors_64.cc", + "src/interceptors_64.h", + "src/resolver_64.cc", + "src/service_resolver_64.cc", + ] + } else if (current_cpu == "x86") { + sources += [ + "src/Wow64.cc", + "src/Wow64.h", + "src/resolver_32.cc", + "src/service_resolver_32.cc", + "src/sidestep/ia32_modrm_map.cpp", + "src/sidestep/ia32_opcode_map.cpp", + "src/sidestep/mini_disassembler.cpp", + "src/sidestep/mini_disassembler.h", + "src/sidestep/mini_disassembler_types.h", + "src/sidestep/preamble_patcher.h", + "src/sidestep/preamble_patcher_with_stub.cpp", + "src/sidestep_resolver.cc", + "src/sidestep_resolver.h", + ] + } + + deps = [ + "//base", + "//base:base_static", + ] + if (current_cpu == "x86") { + deps += [ ":copy_wow_helper" ] + } +} + +if (current_cpu == "x86") { + # Make a target that copies the wow_helper files to the out dir. + # + # TODO(brettw) we can probably just build this now that we have proper + # toolchain support. + copy("copy_wow_helper") { + sources = [ + "wow_helper/wow_helper.exe", + "wow_helper/wow_helper.pdb", + ] + outputs = [ + "$root_out_dir/{{source_file_part}}", + ] + } +} + +test("sbox_integration_tests") { + sources = [ + "src/address_sanitizer_test.cc", + "src/app_container_test.cc", + "src/file_policy_test.cc", + "src/handle_closer_test.cc", + "src/handle_inheritance_test.cc", + "src/handle_policy_test.cc", + "src/integrity_level_test.cc", + "src/ipc_ping_test.cc", + "src/named_pipe_policy_test.cc", + "src/policy_target_test.cc", + "src/process_mitigations_test.cc", + "src/process_policy_test.cc", + "src/registry_policy_test.cc", + "src/sync_policy_test.cc", + "src/sync_policy_test.h", + "src/unload_dll_test.cc", + "tests/common/controller.cc", + "tests/common/controller.h", + "tests/common/test_utils.cc", + "tests/common/test_utils.h", + "tests/integration_tests/integration_tests.cc", + "tests/integration_tests/integration_tests_test.cc", + ] + + deps = [ + ":sandbox", + "//base/test:test_support", + "//testing/gtest", + ] +} + +test("sbox_validation_tests") { + sources = [ + "tests/common/controller.cc", + "tests/common/controller.h", + "tests/validation_tests/commands.cc", + "tests/validation_tests/commands.h", + "tests/validation_tests/suite.cc", + "tests/validation_tests/unit_tests.cc", + ] + + deps = [ + ":sandbox", + "//base/test:test_support", + "//testing/gtest", + ] +} + +test("sbox_unittests") { + sources = [ + "src/app_container_unittest.cc", + "src/interception_unittest.cc", + "src/ipc_unittest.cc", + "src/job_unittest.cc", + "src/policy_engine_unittest.cc", + "src/policy_low_level_unittest.cc", + "src/policy_opcodes_unittest.cc", + "src/restricted_token_unittest.cc", + "src/service_resolver_unittest.cc", + "src/sid_unittest.cc", + "src/threadpool_unittest.cc", + "src/win_utils_unittest.cc", + "tests/common/test_utils.cc", + "tests/common/test_utils.h", + "tests/unit_tests/unit_tests.cc", + ] + + deps = [ + ":sandbox", + "//base/test:test_support", + "//testing/gtest", + ] +} + +test("sandbox_poc") { + sources = [ + "sandbox_poc/main_ui_window.cc", + "sandbox_poc/main_ui_window.h", + "sandbox_poc/resource.h", + "sandbox_poc/sandbox.cc", + "sandbox_poc/sandbox.h", + "sandbox_poc/sandbox.ico", + "sandbox_poc/sandbox.rc", + ] + + configs -= [ "//build/config/win:console" ] + configs += [ "//build/config/win:windowed" ] + + libs = [ "comctl32.lib" ] + + deps = [ + ":sandbox", + ":pocdll", + ] +} + +shared_library("pocdll") { + sources = [ + "sandbox_poc/pocdll/exports.h", + "sandbox_poc/pocdll/fs.cc", + "sandbox_poc/pocdll/handles.cc", + "sandbox_poc/pocdll/invasive.cc", + "sandbox_poc/pocdll/network.cc", + "sandbox_poc/pocdll/pocdll.cc", + "sandbox_poc/pocdll/processes_and_threads.cc", + "sandbox_poc/pocdll/registry.cc", + "sandbox_poc/pocdll/spyware.cc", + "sandbox_poc/pocdll/utils.h", + ] + + defines = [ "POCDLL_EXPORTS" ] +} diff --git a/sandbox/win/OWNERS b/sandbox/win/OWNERS new file mode 100644 index 0000000000..fd5dcfda2c --- /dev/null +++ b/sandbox/win/OWNERS @@ -0,0 +1,4 @@ +cpu@chromium.org +jschuh@chromium.org +rvargas@chromium.org +wfh@chromium.org diff --git a/sandbox/win/sandbox_poc/main_ui_window.cc b/sandbox/win/sandbox_poc/main_ui_window.cc new file mode 100644 index 0000000000..6c5d17727a --- /dev/null +++ b/sandbox/win/sandbox_poc/main_ui_window.cc @@ -0,0 +1,670 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> +#include <CommCtrl.h> +#include <commdlg.h> +#include <stdarg.h> +#include <time.h> +#include <windowsx.h> +#include <atlbase.h> +#include <atlsecurity.h> +#include <algorithm> +#include <sstream> + +#include "sandbox/win/sandbox_poc/main_ui_window.h" +#include "base/logging.h" +#include "sandbox/win/sandbox_poc/resource.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/win_utils.h" + +HWND MainUIWindow::list_view_ = NULL; + +const wchar_t MainUIWindow::kDefaultDll_[] = L"\\POCDLL.dll"; +const wchar_t MainUIWindow::kDefaultEntryPoint_[] = L"Run"; +const wchar_t MainUIWindow::kDefaultLogFile_[] = L""; + +MainUIWindow::MainUIWindow() + : instance_handle_(NULL), + spawn_target_(L""), + dll_path_(L""), + entry_point_(L""), + broker_(NULL) { +} + +MainUIWindow::~MainUIWindow() { +} + +unsigned int MainUIWindow::CreateMainWindowAndLoop( + HINSTANCE instance, + wchar_t* command_line, + int show_command, + sandbox::BrokerServices* broker) { + DCHECK(instance); + DCHECK(command_line); + DCHECK(broker); + + instance_handle_ = instance; + spawn_target_ = command_line; + broker_ = broker; + + // We'll use spawn_target_ later for creating a child process, but + // CreateProcess doesn't like double quotes, so we remove them along with + // tabs and spaces from the start and end of the string + const wchar_t *trim_removal = L" \r\t\""; + spawn_target_.erase(0, spawn_target_.find_first_not_of(trim_removal)); + spawn_target_.erase(spawn_target_.find_last_not_of(trim_removal) + 1); + + WNDCLASSEX window_class = {0}; + window_class.cbSize = sizeof(WNDCLASSEX); + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.lpfnWndProc = MainUIWindow::WndProc; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = instance; + window_class.hIcon = + ::LoadIcon(instance, MAKEINTRESOURCE(IDI_SANDBOX)); + window_class.hCursor = ::LoadCursor(NULL, IDC_ARROW); + window_class.hbrBackground = GetStockBrush(WHITE_BRUSH); + window_class.lpszMenuName = MAKEINTRESOURCE(IDR_MENU_MAIN_UI); + window_class.lpszClassName = L"sandbox_ui_1"; + window_class.hIconSm = NULL; + + INITCOMMONCONTROLSEX controls = { + sizeof(INITCOMMONCONTROLSEX), + ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES + }; + ::InitCommonControlsEx(&controls); + + if (!::RegisterClassEx(&window_class)) + return ::GetLastError(); + + // Create a main window of size 600x400 + HWND window = ::CreateWindowW(window_class.lpszClassName, + L"", // window name + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, // x + CW_USEDEFAULT, // y + 600, // width + 400, // height + NULL, // parent + NULL, // NULL = use class menu + instance, + 0); // lpParam + + if (NULL == window) + return ::GetLastError(); + + ::SetWindowLongPtr(window, + GWLP_USERDATA, + reinterpret_cast<LONG_PTR>(this)); + + ::SetWindowText(window, L"Sandbox Proof of Concept"); + + ::ShowWindow(window, show_command); + + MSG message; + // Now lets start the message pump retrieving messages for any window that + // belongs to the current thread + while (::GetMessage(&message, NULL, 0, 0)) { + ::TranslateMessage(&message); + ::DispatchMessage(&message); + } + + return 0; +} + +LRESULT CALLBACK MainUIWindow::WndProc(HWND window, + UINT message_id, + WPARAM wparam, + LPARAM lparam) { + MainUIWindow* host = FromWindow(window); + + #define HANDLE_MSG(hwnd, message, fn) \ + case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn)) + + switch (message_id) { + case WM_CREATE: + // 'host' is not yet available when we get the WM_CREATE message + return HANDLE_WM_CREATE(window, wparam, lparam, OnCreate); + case WM_DESTROY: + return HANDLE_WM_DESTROY(window, wparam, lparam, host->OnDestroy); + case WM_SIZE: + return HANDLE_WM_SIZE(window, wparam, lparam, host->OnSize); + case WM_COMMAND: { + // Look at which menu item was clicked on (or which accelerator) + int id = LOWORD(wparam); + switch (id) { + case ID_FILE_EXIT: + host->OnFileExit(); + break; + case ID_COMMANDS_SPAWNTARGET: + host->OnCommandsLaunch(window); + break; + default: + // Some other menu item or accelerator + break; + } + + return ERROR_SUCCESS; + } + + default: + // Some other WM_message, let it pass to DefWndProc + break; + } + + return DefWindowProc(window, message_id, wparam, lparam); +} + +INT_PTR CALLBACK MainUIWindow::SpawnTargetWndProc(HWND dialog, + UINT message_id, + WPARAM wparam, + LPARAM lparam) { + UNREFERENCED_PARAMETER(lparam); + + // Grab a reference to the main UI window (from the window handle) + MainUIWindow* host = FromWindow(GetParent(dialog)); + DCHECK(host); + + switch (message_id) { + case WM_INITDIALOG: { + // Initialize the window text for DLL name edit box + HWND edit_box_dll_name = ::GetDlgItem(dialog, IDC_DLL_NAME); + wchar_t current_dir[MAX_PATH]; + if (GetCurrentDirectory(MAX_PATH, current_dir)) { + base::string16 dll_path = base::string16(current_dir) + + base::string16(kDefaultDll_); + ::SetWindowText(edit_box_dll_name, dll_path.c_str()); + } + + // Initialize the window text for Entry Point edit box + HWND edit_box_entry_point = ::GetDlgItem(dialog, IDC_ENTRY_POINT); + ::SetWindowText(edit_box_entry_point, kDefaultEntryPoint_); + + // Initialize the window text for Log File edit box + HWND edit_box_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE); + ::SetWindowText(edit_box_log_file, kDefaultLogFile_); + + return static_cast<INT_PTR>(TRUE); + } + case WM_COMMAND: + // If the user presses the OK button (Launch) + if (LOWORD(wparam) == IDOK) { + if (host->OnLaunchDll(dialog)) { + if (host->SpawnTarget()) { + ::EndDialog(dialog, LOWORD(wparam)); + } + } + return static_cast<INT_PTR>(TRUE); + } else if (LOWORD(wparam) == IDCANCEL) { + // If the user presses the Cancel button + ::EndDialog(dialog, LOWORD(wparam)); + return static_cast<INT_PTR>(TRUE); + } else if (LOWORD(wparam) == IDC_BROWSE_DLL) { + // If the user presses the Browse button to look for a DLL + base::string16 dll_path = host->OnShowBrowseForDllDlg(dialog); + if (dll_path.length() > 0) { + // Initialize the window text for Log File edit box + HWND edit_box_dll_path = ::GetDlgItem(dialog, IDC_DLL_NAME); + ::SetWindowText(edit_box_dll_path, dll_path.c_str()); + } + return static_cast<INT_PTR>(TRUE); + } else if (LOWORD(wparam) == IDC_BROWSE_LOG) { + // If the user presses the Browse button to look for a log file + base::string16 log_path = host->OnShowBrowseForLogFileDlg(dialog); + if (log_path.length() > 0) { + // Initialize the window text for Log File edit box + HWND edit_box_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE); + ::SetWindowText(edit_box_log_file, log_path.c_str()); + } + return static_cast<INT_PTR>(TRUE); + } + + break; + } + + return static_cast<INT_PTR>(FALSE); +} + +MainUIWindow* MainUIWindow::FromWindow(HWND main_window) { + // We store a 'this' pointer using SetWindowLong in CreateMainWindowAndLoop + // so that we can retrieve it with this function later. This prevents us + // from having to define all the message handling functions (that we refer to + // in the window proc) as static + ::GetWindowLongPtr(main_window, GWLP_USERDATA); + return reinterpret_cast<MainUIWindow*>( + ::GetWindowLongPtr(main_window, GWLP_USERDATA)); +} + +BOOL MainUIWindow::OnCreate(HWND parent_window, LPCREATESTRUCT) { + // Create the listview that will the main app UI + list_view_ = ::CreateWindow(WC_LISTVIEW, // Class name + L"", // Window name + WS_CHILD | WS_VISIBLE | LVS_REPORT | + LVS_NOCOLUMNHEADER | WS_BORDER, + 0, // x + 0, // y + 0, // width + 0, // height + parent_window, // parent + NULL, // menu + ::GetModuleHandle(NULL), + 0); // lpParam + + DCHECK(list_view_); + if (!list_view_) + return FALSE; + + LVCOLUMN list_view_column = {0}; + list_view_column.mask = LVCF_FMT | LVCF_WIDTH ; + list_view_column.fmt = LVCFMT_LEFT; + list_view_column.cx = 10000; // Maximum size of an entry in the list view. + ListView_InsertColumn(list_view_, 0, &list_view_column); + + // Set list view to show green font on black background + ListView_SetBkColor(list_view_, CLR_NONE); + ListView_SetTextColor(list_view_, RGB(0x0, 0x0, 0x0)); + ListView_SetTextBkColor(list_view_, CLR_NONE); + + return TRUE; +} + +void MainUIWindow::OnDestroy(HWND window) { + UNREFERENCED_PARAMETER(window); + + // Post a quit message because our application is over when the + // user closes this window. + ::PostQuitMessage(0); +} + +void MainUIWindow::OnSize(HWND window, UINT state, int cx, int cy) { + UNREFERENCED_PARAMETER(window); + UNREFERENCED_PARAMETER(state); + + // If we have a valid inner child, resize it to cover the entire + // client area of the main UI window. + if (list_view_) { + ::MoveWindow(list_view_, + 0, // x + 0, // y + cx, // width + cy, // height + TRUE); // repaint + } +} + +void MainUIWindow::OnPaint(HWND window) { + PAINTSTRUCT paintstruct; + ::BeginPaint(window, &paintstruct); + // add painting code here if required + ::EndPaint(window, &paintstruct); +} + +void MainUIWindow::OnFileExit() { + ::PostQuitMessage(0); +} + +void MainUIWindow::OnCommandsLaunch(HWND window) { + // User wants to see the Select DLL dialog box + ::DialogBox(instance_handle_, + MAKEINTRESOURCE(IDD_LAUNCH_DLL), + window, + SpawnTargetWndProc); +} + +bool MainUIWindow::OnLaunchDll(HWND dialog) { + HWND edit_box_dll_name = ::GetDlgItem(dialog, IDC_DLL_NAME); + HWND edit_box_entry_point = ::GetDlgItem(dialog, IDC_ENTRY_POINT); + HWND edit_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE); + + wchar_t dll_path[MAX_PATH]; + wchar_t entry_point[MAX_PATH]; + wchar_t log_file[MAX_PATH]; + + int dll_name_len = ::GetWindowText(edit_box_dll_name, dll_path, MAX_PATH); + int entry_point_len = ::GetWindowText(edit_box_entry_point, + entry_point, MAX_PATH); + // Log file is optional (can be blank) + ::GetWindowText(edit_log_file, log_file, MAX_PATH); + + if (0 >= dll_name_len) { + ::MessageBox(dialog, + L"Please specify a DLL for the target to load", + L"No DLL specified", + MB_ICONERROR); + return false; + } + + if (GetFileAttributes(dll_path) == INVALID_FILE_ATTRIBUTES) { + ::MessageBox(dialog, + L"DLL specified was not found", + L"DLL not found", + MB_ICONERROR); + return false; + } + + if (0 >= entry_point_len) { + ::MessageBox(dialog, + L"Please specify an entry point for the DLL", + L"No entry point specified", + MB_ICONERROR); + return false; + } + + // store these values in the member variables for use in SpawnTarget + log_file_ = base::string16(L"\"") + log_file + base::string16(L"\""); + dll_path_ = dll_path; + entry_point_ = entry_point; + + return true; +} + +DWORD WINAPI MainUIWindow::ListenPipeThunk(void *param) { + return reinterpret_cast<MainUIWindow*>(param)->ListenPipe(); +} + +DWORD WINAPI MainUIWindow::WaitForTargetThunk(void *param) { + return reinterpret_cast<MainUIWindow*>(param)->WaitForTarget(); +} + +// Thread waiting for the target application to die. It displays +// a message in the list view when it happens. +DWORD MainUIWindow::WaitForTarget() { + WaitForSingleObject(target_.hProcess, INFINITE); + + DWORD exit_code = 0; + if (!GetExitCodeProcess(target_.hProcess, &exit_code)) { + exit_code = 0xFFFF; // Default exit code + } + + ::CloseHandle(target_.hProcess); + ::CloseHandle(target_.hThread); + + AddDebugMessage(L"Targed exited with return code %d", exit_code); + return 0; +} + +// Thread waiting for messages on the log pipe. It displays the messages +// in the listview. +DWORD MainUIWindow::ListenPipe() { + HANDLE logfile_handle = NULL; + ATL::CString file_to_open = log_file_.c_str(); + file_to_open.Remove(L'\"'); + if (file_to_open.GetLength()) { + logfile_handle = ::CreateFile(file_to_open.GetBuffer(), + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, // Default security attributes + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); // No template + if (INVALID_HANDLE_VALUE == logfile_handle) { + AddDebugMessage(L"Failed to open \"%ls\" for logging. Error %d", + file_to_open.GetBuffer(), ::GetLastError()); + logfile_handle = NULL; + } + } + + const int kSizeBuffer = 1024; + BYTE read_buffer[kSizeBuffer] = {0}; + ATL::CStringA read_buffer_global; + ATL::CStringA string_to_print; + + DWORD last_error = 0; + while(last_error == ERROR_SUCCESS || last_error == ERROR_PIPE_LISTENING || + last_error == ERROR_NO_DATA) + { + DWORD read_data_length; + if (::ReadFile(pipe_handle_, + read_buffer, + kSizeBuffer - 1, // Max read size + &read_data_length, + NULL)) { // Not overlapped + if (logfile_handle) { + DWORD write_data_length; + ::WriteFile(logfile_handle, + read_buffer, + read_data_length, + &write_data_length, + FALSE); // Not overlapped + } + + // Append the new buffer to the current buffer + read_buffer[read_data_length] = NULL; + read_buffer_global += reinterpret_cast<char *>(read_buffer); + read_buffer_global.Remove(10); // Remove the CRs + + // If we completed a new line, output it + int endline = read_buffer_global.Find(13); // search for LF + while (-1 != endline) { + string_to_print = read_buffer_global; + string_to_print.Delete(endline, string_to_print.GetLength()); + read_buffer_global.Delete(0, endline); + + // print the line (with the ending LF) + OutputDebugStringA(string_to_print.GetBuffer()); + + // Remove the ending LF + read_buffer_global.Delete(0, 1); + + // Add the line to the log + AddDebugMessage(L"%S", string_to_print.GetBuffer()); + + endline = read_buffer_global.Find(13); + } + last_error = ERROR_SUCCESS; + } else { + last_error = GetLastError(); + Sleep(100); + } + } + + if (read_buffer_global.GetLength()) { + AddDebugMessage(L"%S", read_buffer_global.GetBuffer()); + } + + CloseHandle(pipe_handle_); + + if (logfile_handle) { + CloseHandle(logfile_handle); + } + + return 0; +} + +bool MainUIWindow::SpawnTarget() { + // Generate the pipe name + GUID random_id; + CoCreateGuid(&random_id); + + wchar_t log_pipe[MAX_PATH] = {0}; + wnsprintf(log_pipe, MAX_PATH - 1, + L"\\\\.\\pipe\\sbox_pipe_log_%lu_%lu_%lu_%lu", + random_id.Data1, + random_id.Data2, + random_id.Data3, + random_id.Data4); + + // We concatenate the four strings, add three spaces and a zero termination + // We use the resulting string as a param to CreateProcess (in SpawnTarget) + // Documented maximum for command line in CreateProcess is 32K (msdn) + size_t size_call = spawn_target_.length() + entry_point_.length() + + dll_path_.length() + wcslen(log_pipe) + 6; + if (32 * 1024 < (size_call * sizeof(wchar_t))) { + AddDebugMessage(L"The length of the arguments exceeded 32K. " + L"Aborting operation."); + return false; + } + + wchar_t * arguments = new wchar_t[size_call]; + wnsprintf(arguments, static_cast<int>(size_call), L"%ls %ls \"%ls\" %ls", + spawn_target_.c_str(), entry_point_.c_str(), + dll_path_.c_str(), log_pipe); + + arguments[size_call - 1] = L'\0'; + + sandbox::TargetPolicy* policy = broker_->CreatePolicy(); + policy->SetJobLevel(sandbox::JOB_LOCKDOWN, 0); + policy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS, + sandbox::USER_LOCKDOWN); + policy->SetAlternateDesktop(true); + policy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW); + + // Set the rule to allow the POC dll to be loaded by the target. Note that + // the rule allows 'all access' to the DLL, which could mean that the target + // could modify the DLL on disk. + policy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, dll_path_.c_str()); + + sandbox::ResultCode result = broker_->SpawnTarget(spawn_target_.c_str(), + arguments, policy, + &target_); + + policy->Release(); + policy = NULL; + + bool return_value = false; + if (sandbox::SBOX_ALL_OK != result) { + AddDebugMessage( + L"Failed to spawn target %ls w/args (%ls), sandbox error code: %d", + spawn_target_.c_str(), arguments, result); + return_value = false; + } else { + + DWORD thread_id; + ::CreateThread(NULL, // Default security attributes + NULL, // Default stack size + &MainUIWindow::WaitForTargetThunk, + this, + 0, // No flags + &thread_id); + + pipe_handle_ = ::CreateNamedPipe(log_pipe, + PIPE_ACCESS_INBOUND | WRITE_DAC, + PIPE_TYPE_MESSAGE | PIPE_NOWAIT, + 1, // Number of instances. + 512, // Out buffer size. + 512, // In buffer size. + NMPWAIT_USE_DEFAULT_WAIT, + NULL); // Default security descriptor + + if (INVALID_HANDLE_VALUE == pipe_handle_) + AddDebugMessage(L"Failed to create pipe. Error %d", ::GetLastError()); + + if (!sandbox::AddKnownSidToObject(pipe_handle_, SE_KERNEL_OBJECT, + WinWorldSid, GRANT_ACCESS, + FILE_ALL_ACCESS)) + AddDebugMessage(L"Failed to set security on pipe. Error %d", + ::GetLastError()); + + ::CreateThread(NULL, // Default security attributes + NULL, // Default stack size + &MainUIWindow::ListenPipeThunk, + this, + 0, // No flags + &thread_id); + + ::ResumeThread(target_.hThread); + + AddDebugMessage(L"Successfully spawned target w/args (%ls)", arguments); + return_value = true; + } + + delete[] arguments; + return return_value; +} + +base::string16 MainUIWindow::OnShowBrowseForDllDlg(HWND owner) { + wchar_t filename[MAX_PATH]; + wcscpy_s(filename, MAX_PATH, L""); + + OPENFILENAMEW file_info = {0}; + file_info.lStructSize = sizeof(file_info); + file_info.hwndOwner = owner; + file_info.lpstrFile = filename; + file_info.nMaxFile = MAX_PATH; + file_info.lpstrFilter = L"DLL files (*.dll)\0*.dll\0All files\0*.*\0\0\0"; + + file_info.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; + + if (GetOpenFileName(&file_info)) { + return file_info.lpstrFile; + } + + return L""; +} + +base::string16 MainUIWindow::OnShowBrowseForLogFileDlg(HWND owner) { + wchar_t filename[MAX_PATH]; + wcscpy_s(filename, MAX_PATH, L""); + + OPENFILENAMEW file_info = {0}; + file_info.lStructSize = sizeof(file_info); + file_info.hwndOwner = owner; + file_info.lpstrFile = filename; + file_info.nMaxFile = MAX_PATH; + file_info.lpstrFilter = L"Log file (*.txt)\0*.txt\0All files\0*.*\0\0\0"; + + file_info.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; + + if (GetSaveFileName(&file_info)) { + return file_info.lpstrFile; + } + + return L""; +} + +void MainUIWindow::AddDebugMessage(const wchar_t* format, ...) { + DCHECK(format); + if (!format) + return; + + const int kMaxDebugBuffSize = 1024; + + va_list arg_list; + va_start(arg_list, format); + + wchar_t text[kMaxDebugBuffSize + 1]; + vswprintf_s(text, kMaxDebugBuffSize, format, arg_list); + text[kMaxDebugBuffSize] = L'\0'; + + InsertLineInListView(text); +} + + +void MainUIWindow::InsertLineInListView(wchar_t* debug_message) { + DCHECK(debug_message); + if (!debug_message) + return; + + // Prepend the time to the message + const int kSizeTime = 100; + size_t size_message_with_time = wcslen(debug_message) + kSizeTime; + wchar_t * message_time = new wchar_t[size_message_with_time]; + + time_t time_temp; + time_temp = time(NULL); + + struct tm time = {0}; + localtime_s(&time, &time_temp); + + size_t return_code; + return_code = wcsftime(message_time, kSizeTime, L"[%H:%M:%S] ", &time); + + wcscat_s(message_time, size_message_with_time, debug_message); + + // We add the debug message to the top of the listview + LVITEM item; + item.iItem = ListView_GetItemCount(list_view_); + item.iSubItem = 0; + item.mask = LVIF_TEXT | LVIF_PARAM; + item.pszText = message_time; + item.lParam = 0; + + ListView_InsertItem(list_view_, &item); + + delete[] message_time; +} diff --git a/sandbox/win/sandbox_poc/main_ui_window.h b/sandbox/win/sandbox_poc/main_ui_window.h new file mode 100644 index 0000000000..84fb9861c9 --- /dev/null +++ b/sandbox/win/sandbox_poc/main_ui_window.h @@ -0,0 +1,194 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SANDBOX_POC_MAIN_UI_WINDOW_H__ +#define SANDBOX_SANDBOX_POC_MAIN_UI_WINDOW_H__ + +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string16.h" + +namespace sandbox { +class BrokerServices; +enum ResultCode; +} + +// Header file for the MainUIWindow, a simple window with a menu bar that +// can pop up a dialog (accessible through the menu bar), to specify a path and +// filename of any DLL to load and choose an entry point of his/her choice +// (note: only entry points with no parameters are expected to work). +// +// The purpose of this is to be able to spawn an EXE inside a SandBox, have it +// load a DLL and call the entry point on it to test how it behaves inside the +// sandbox. This is useful for developer debugging and for security testing. +// +// The MainUIWindow also has a listview that displays debugging information to +// the user. +// +// Sample usage: +// +// MainUIWindow window; +// unsigned int ret = window.CreateMainWindowAndLoop( +// handle_to_current_instance, +// ::GetCommandLineW(), +// show_command, +// broker); +// +// The CreateMainWindowAndLoop() contains a message loop that ends when the +// user closes the MainUIWindow. + +// This class encapsulates the Main UI window for the broker application. +// It simply shows a menu that gives the user the ability (through a dialog) to +// specify a DLL and what entry point to call in the DLL. +class MainUIWindow { + public: + MainUIWindow(); + ~MainUIWindow(); + + // Creates the main window, displays it and starts the message pump. This + // call will not return until user closes the main UI window that appears + // as a result. Arguments 'instance', 'command_line' and 'show_cmd' can be + // passed in directly from winmain. The 'broker' argument is a pointer to a + // BrokerService that will launch a new EXE inside the sandbox and load the + // DLL of the user's choice. + unsigned int CreateMainWindowAndLoop(HINSTANCE instance, + wchar_t* command_line, + int show_command, + sandbox::BrokerServices* broker); + + private: + // The default value DLL name to add to the edit box. + static const wchar_t kDefaultDll_[]; + + // The default value to show in the entry point. + static const wchar_t kDefaultEntryPoint_[]; + + // The default value to show in the log file. + static const wchar_t kDefaultLogFile_[]; + + // Handles the messages sent to the main UI window. The return value is the + // result of the message processing and depends on the message. + static LRESULT CALLBACK WndProc(HWND window, + UINT message_id, + WPARAM wparam, + LPARAM lparam); + + // Handles the messages sent to the SpawnTarget dialog. The return value is + // the result of the message processing and depends on the message. + static INT_PTR CALLBACK SpawnTargetWndProc(HWND dialog, + UINT message_id, + WPARAM wparam, + LPARAM lparam); + + // Retrieves a pointer to the MainWindow from a value stored along with the + // window handle (passed in as hwnd). Return value is a pointer to the + // MainUIWindow previously stored with SetWindowLong() during WM_CREATE. + static MainUIWindow* FromWindow(HWND main_window); + + // Handles the WM_CREATE message for the main UI window. Returns TRUE on + // success. + static BOOL OnCreate(HWND parent_window, LPCREATESTRUCT); + + // Handles the WM_DESTROY message for the main UI window. + void OnDestroy(HWND window); + + // Handles the WM_SIZE message for the main UI window. + void OnSize(HWND window, UINT state, int cx, int cy); + + // Handles the WM_PAINT message for the main UI window. + void OnPaint(HWND window); + + // Handles the menu command File \ Exit for the main UI window. + void OnFileExit(); + + // Handles the menu command Commands \ Launch for the main UI window. + void OnCommandsLaunch(HWND window); + + // Handles the Launch button in the SpawnTarget dialog (normally clicked + // after selecting DLL and entry point). OnLaunchDll will retrieve the + // values entered by the user and store it in the members of the class. + // Returns true if user selected a non-zero values for DLL filename + // (possibly including path also) and entry point. + bool OnLaunchDll(HWND dialog); + + // Spawns a target EXE inside the sandbox (with the help of the + // BrokerServices passed in to CreateMainWindowAndLoop), and passes to it + // (as command line arguments) the DLL path and the entry point function + // name. The EXE is expected to parse the command line and load the DLL. + // NOTE: The broker does not know if the target EXE successfully loaded the + // DLL, for that you have to rely on the EXE providing a log. + // Returns true if the broker reports that it was successful in creating + // the target and false if not. + bool SpawnTarget(); + + // Shows a standard File Open dialog and returns the DLL filename selected or + // blank string if the user cancelled (or an error occurred). + base::string16 OnShowBrowseForDllDlg(HWND owner); + + // Shows a standard Save As dialog and returns the log filename selected or + // blank string if the user cancelled (or an error occurred). + base::string16 OnShowBrowseForLogFileDlg(HWND owner); + + // Formats a message using the supplied format string and prints it in the + // listview in the main UI window. Passing a NULL param in 'fmt' results in + // no action being performed. Maximum message length is 1K. + void AddDebugMessage(const wchar_t* format, ...); + + // Assists AddDebugMessage in displaying a message in the ListView. It + // simply wraps ListView_InsertItem to insert a debugging message to the + // top of the list view. Passing a NULL param in 'fmt' results in no action + // being performed. + void InsertLineInListView(wchar_t* debug_message); + + // Calls ListenPipe using the class instance received in parameter. This is + // used to create new threads executing ListenPipe + static DWORD WINAPI ListenPipeThunk(void *param); + + // Calls WaitForTargetThunk using the class instance received in parameter + // This is used to create new threads executing WaitForTarget. + static DWORD WINAPI WaitForTargetThunk(void *param); + + // Listens on a pipe and output the data received to a file and to the UI. + DWORD ListenPipe(); + + // Waits for the target to dies and display a message in the UI. + DWORD WaitForTarget(); + + // The BrokerServices will be used to spawn an EXE in a sandbox and ask + // it to load a DLL. + sandbox::BrokerServices* broker_; + + // Contains the information about the running target. + PROCESS_INFORMATION target_; + + // This is essentially a command line to a target executable that the + // broker will spawn and ask to load the DLL. + base::string16 spawn_target_; + + // A handle to the current instance of the app. Passed in to this class + // through CreateMainWindowAndLoop. + HINSTANCE instance_handle_; + + // A path to the DLL that the target should load once it executes. + base::string16 dll_path_; + + // The name of the entry point the target should call after it loads the DLL. + base::string16 entry_point_; + + // The name of the log file to use. + base::string16 log_file_; + + // This is a static handle to the list view that fills up the entire main + // UI window. The list view is used to display debugging information to the + // user. + static HWND list_view_; + + // Pipe used to communicate the logs between the target and the broker. + HANDLE pipe_handle_; + + DISALLOW_COPY_AND_ASSIGN(MainUIWindow); +}; + +#endif // SANDBOX_SANDBOX_POC_MAIN_UI_WINDOW_H__ diff --git a/sandbox/win/sandbox_poc/pocdll/exports.h b/sandbox/win/sandbox_poc/pocdll/exports.h new file mode 100644 index 0000000000..66a07d6b78 --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/exports.h @@ -0,0 +1,89 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SANDBOX_POC_POCDLL_EXPORTS_H__ +#define SANDBOX_SANDBOX_POC_POCDLL_EXPORTS_H__ + +#include <windows.h> + +#ifdef POCDLL_EXPORTS +#define POCDLL_API __declspec(dllexport) __cdecl +#else +#define POCDLL_API __declspec(dllimport) __cdecl +#endif + +extern "C" { +// Tries to open several known system path and outputs +// the result. +// "log" is the handle of the log file. +void POCDLL_API TestFileSystem(HANDLE log); + +// Tries to find all handles open in the process and prints the name of the +// resource references by the handle along with the access right. +// "log" is the handle of the log file. +void POCDLL_API TestGetHandle(HANDLE log); + +// Creates a lot of threads until it cannot create more. The goal of this +// function is to determine if it's possible to crash the machine when we +// flood the machine with new threads +// "log" is the handle of the log file. +void POCDLL_API TestThreadBombing(HANDLE log); + +// Takes all cpu of the machine. For each processor on the machine we assign +// a thread. This thread will compute a mathematical expression over and over +// to take all cpu. +// "log" is the handle of the log file. +// Note: here we are using the affinity to find out how many processors are on +// the machine and to force a thread to run only on a given processor. +void POCDLL_API TestTakeAllCpu(HANDLE log); + +// Creates memory in the heap until it fails 5 times in a row and prints the +// amount of memory created. This function is used to find out if it's possible +// to take all memory on the machine and crash the system. +// "log" is the handle of the log file. +void POCDLL_API TestUseAllMemory(HANDLE log); + +// Creates millions of kernel objects. This function is used to find out if it's +// possible to crash the system if we create too many kernel objects and if we +// hold too many handles. All those kernel objects are unnamed. +// "log" is the handle of the log file. +void POCDLL_API TestCreateObjects(HANDLE log); + +// Receives a hwnd and tries to close it. This is the callback for EnumWindows. +// It will be called for each window(hwnd) on the system. +// "log" is the handle of the log file. +// Always returns TRUE to tell the system that we want to continue the +// enumeration. +void POCDLL_API TestCloseHWND(HANDLE log); + +// Tries to listen on the port 88. +// "log" is the handle of the log file. +void POCDLL_API TestNetworkListen(HANDLE log); + +// Lists all processes on the system and tries to open them +// "log" is the handle of the log file. +void POCDLL_API TestProcesses(HANDLE log); + +// Lists all threads on the system and tries to open them +// "log" is the handle of the log file. +void POCDLL_API TestThreads(HANDLE log); + +// Tries to open some known system registry key and outputs the result. +// "log" is the handle of the log file. +void POCDLL_API TestRegistry(HANDLE log); + +// Records all keystrokes typed for 15 seconds and then display them. +// "log" is the handle of the log file. +void POCDLL_API TestSpyKeys(HANDLE log); + +// Tries to read pixels on the monitor and output if the operation +// failes or succeeded. +// "log" is the handle of the log file. +void POCDLL_API TestSpyScreen(HANDLE log); + +// Runs all tests except those who are invasive +void POCDLL_API Run(HANDLE log); +} + +#endif // SANDBOX_SANDBOX_POC_POCDLL_EXPORTS_H__ diff --git a/sandbox/win/sandbox_poc/pocdll/fs.cc b/sandbox/win/sandbox_poc/pocdll/fs.cc new file mode 100644 index 0000000000..40596af6dd --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/fs.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/sandbox_poc/pocdll/exports.h" +#include "sandbox/win/sandbox_poc/pocdll/utils.h" + +// This file contains the tests used to verify the security of the file system. + +// Tries to open a file and outputs the result. +// "path" can contain environment variables. +// "output" is the stream for the logging. +void TryOpenFile(const wchar_t *path, FILE *output) { + wchar_t path_expanded[MAX_PATH] = {0}; + DWORD size = ::ExpandEnvironmentStrings(path, path_expanded, MAX_PATH - 1); + if (!size) { + fprintf(output, "[ERROR] Cannot expand \"%S\". Error %ld.\r\n", path, + ::GetLastError()); + } + + HANDLE file; + file = ::CreateFile(path_expanded, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, // No security attributes + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); // No template + + if (file && INVALID_HANDLE_VALUE != file) { + fprintf(output, "[GRANTED] Opening file \"%S\". Handle 0x%p\r\n", path, + file); + ::CloseHandle(file); + } else { + fprintf(output, "[BLOCKED] Opening file \"%S\". Error %ld.\r\n", path, + ::GetLastError()); + } +} + +void POCDLL_API TestFileSystem(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + TryOpenFile(L"%SystemDrive%", output); + TryOpenFile(L"%SystemRoot%", output); + TryOpenFile(L"%ProgramFiles%", output); + TryOpenFile(L"%SystemRoot%\\System32", output); + TryOpenFile(L"%SystemRoot%\\explorer.exe", output); + TryOpenFile(L"%SystemRoot%\\Cursors\\arrow_i.cur", output); + TryOpenFile(L"%AllUsersProfile%", output); + TryOpenFile(L"%UserProfile%", output); + TryOpenFile(L"%Temp%", output); + TryOpenFile(L"%AppData%", output); +} diff --git a/sandbox/win/sandbox_poc/pocdll/handles.cc b/sandbox/win/sandbox_poc/pocdll/handles.cc new file mode 100644 index 0000000000..a12d433411 --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/handles.cc @@ -0,0 +1,186 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/sandbox_poc/pocdll/exports.h" +#include "sandbox/win/sandbox_poc/pocdll/utils.h" +#include "sandbox/win/tools/finder/ntundoc.h" + +// This file contains the tests used to verify the security of handles in +// the process + +NTQUERYOBJECT NtQueryObject; +NTQUERYINFORMATIONFILE NtQueryInformationFile; +NTQUERYSYSTEMINFORMATION NtQuerySystemInformation; + +void POCDLL_API TestGetHandle(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + // Initialize the NTAPI functions we need + HMODULE ntdll_handle = ::GetModuleHandle(L"ntdll.dll"); + if (!ntdll_handle) { + fprintf(output, "[ERROR] Cannot load ntdll.dll. Error %ld\r\n", + ::GetLastError()); + return; + } + + NtQueryObject = reinterpret_cast<NTQUERYOBJECT>( + GetProcAddress(ntdll_handle, "NtQueryObject")); + NtQueryInformationFile = reinterpret_cast<NTQUERYINFORMATIONFILE>( + GetProcAddress(ntdll_handle, "NtQueryInformationFile")); + NtQuerySystemInformation = reinterpret_cast<NTQUERYSYSTEMINFORMATION>( + GetProcAddress(ntdll_handle, "NtQuerySystemInformation")); + + if (!NtQueryObject || !NtQueryInformationFile || !NtQuerySystemInformation) { + fprintf(output, "[ERROR] Cannot load all NT functions. Error %ld\r\n", + ::GetLastError()); + return; + } + + // Get the number of handles on the system + DWORD buffer_size = 0; + SYSTEM_HANDLE_INFORMATION_EX temp_info; + NTSTATUS status = NtQuerySystemInformation( + SystemHandleInformation, &temp_info, sizeof(temp_info), + &buffer_size); + if (!buffer_size) { + fprintf(output, "[ERROR] Get the number of handles. Error 0x%lX\r\n", + status); + return; + } + + SYSTEM_HANDLE_INFORMATION_EX *system_handles = + reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(new BYTE[buffer_size]); + + status = NtQuerySystemInformation(SystemHandleInformation, system_handles, + buffer_size, &buffer_size); + if (STATUS_SUCCESS != status) { + fprintf(output, "[ERROR] Failed to get the handle list. Error 0x%lX\r\n", + status); + delete [] system_handles; + return; + } + + for (ULONG i = 0; i < system_handles->NumberOfHandles; ++i) { + USHORT h = system_handles->Information[i].Handle; + if (system_handles->Information[i].ProcessId != ::GetCurrentProcessId()) + continue; + + OBJECT_NAME_INFORMATION *name = NULL; + ULONG name_size = 0; + // Query the name information a first time to get the size of the name. + status = NtQueryObject(reinterpret_cast<HANDLE>(h), + ObjectNameInformation, + name, + name_size, + &name_size); + + if (name_size) { + name = reinterpret_cast<OBJECT_NAME_INFORMATION *>(new BYTE[name_size]); + + // Query the name information a second time to get the name of the + // object referenced by the handle. + status = NtQueryObject(reinterpret_cast<HANDLE>(h), + ObjectNameInformation, + name, + name_size, + &name_size); + } + + PUBLIC_OBJECT_TYPE_INFORMATION *type = NULL; + ULONG type_size = 0; + + // Query the object to get the size of the object type name. + status = NtQueryObject(reinterpret_cast<HANDLE>(h), + ObjectTypeInformation, + type, + type_size, + &type_size); + if (type_size) { + type = reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION *>( + new BYTE[type_size]); + + // Query the type information a second time to get the object type + // name. + status = NtQueryObject(reinterpret_cast<HANDLE>(h), + ObjectTypeInformation, + type, + type_size, + &type_size); + } + + // NtQueryObject cannot return the name for a file. In this case we + // need to ask NtQueryInformationFile + FILE_NAME_INFORMATION *file_name = NULL; + if (type && wcsncmp(L"File", type->TypeName.Buffer, + (type->TypeName.Length / + sizeof(type->TypeName.Buffer[0]))) == 0) { + // This function does not return the size of the buffer. We need to + // iterate and always increase the buffer size until the function + // succeeds. (Or at least does not fail with STATUS_BUFFER_OVERFLOW) + ULONG size_file = MAX_PATH; + IO_STATUS_BLOCK status_block = {0}; + do { + // Delete the previous buffer create. The buffer was too small + if (file_name) { + delete[] reinterpret_cast<BYTE*>(file_name); + file_name = NULL; + } + + // Increase the buffer and do the call agan + size_file += MAX_PATH; + file_name = reinterpret_cast<FILE_NAME_INFORMATION *>( + new BYTE[size_file]); + status = NtQueryInformationFile(reinterpret_cast<HANDLE>(h), + &status_block, + file_name, + size_file, + FileNameInformation); + } while (status == STATUS_BUFFER_OVERFLOW); + + if (STATUS_SUCCESS != status) { + if (file_name) { + delete[] file_name; + file_name = NULL; + } + } + } + + if (file_name) { + UNICODE_STRING file_name_string; + file_name_string.Buffer = file_name->FileName; + file_name_string.Length = (USHORT)file_name->FileNameLength; + file_name_string.MaximumLength = (USHORT)file_name->FileNameLength; + fprintf(output, "[GRANTED] Handle 0x%4.4X Access: 0x%8.8lX " + "Type: %-13wZ Path: %wZ\r\n", + h, + system_handles->Information[i].GrantedAccess, + type ? &type->TypeName : NULL, + &file_name_string); + } else { + fprintf(output, "[GRANTED] Handle 0x%4.4X Access: 0x%8.8lX " + "Type: %-13wZ Path: %wZ\r\n", + h, + system_handles->Information[i].GrantedAccess, + type ? &type->TypeName : NULL, + name ? &name->ObjectName : NULL); + } + + if (type) { + delete[] type; + } + + if (file_name) { + delete[] file_name; + } + + if (name) { + delete [] name; + } + } + + if (system_handles) { + delete [] system_handles; + } +} diff --git a/sandbox/win/sandbox_poc/pocdll/invasive.cc b/sandbox/win/sandbox_poc/pocdll/invasive.cc new file mode 100644 index 0000000000..9ee13b0cb9 --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/invasive.cc @@ -0,0 +1,196 @@ +// Copyright (c) 2006-2008 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 <malloc.h> +#include "sandbox/win/sandbox_poc/pocdll/exports.h" +#include "sandbox/win/sandbox_poc/pocdll/utils.h" + +// This file contains the tests used to verify if it's possible to DOS or crash +// the machine. All tests that can impact the stability of the machine should +// be in this file. + +// Sleeps forever. this function is used to be the +// entry point for the threads created by the thread bombing function. +// This function never returns. +DWORD WINAPI MyThreadBombimgFunction(void *param) { + UNREFERENCED_PARAMETER(param); + Sleep(INFINITE); + return 0; +} + +void POCDLL_API TestThreadBombing(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + // we stop after 5 errors in a row + int number_errors = 0; + for (int i = 0; i < 100000; ++i) { + DWORD tid; + // Create the thread and leak the handle. + HANDLE thread = ::CreateThread(NULL, // Default security attributes + NULL, // Stack size + MyThreadBombimgFunction, + NULL, // Parameter + 0, // No creation flags + &tid); + if (thread) { + fprintf(output, "[GRANTED] Creating thread with tid 0x%lX\r\n", tid); + ::CloseHandle(thread); + number_errors = 0; + } else { + fprintf(output, "[BLOCKED] Creating thread. Error %ld\r\n", + ::GetLastError()); + number_errors++; + } + + if (number_errors >= 5) { + break; + } + } +} + + +// Executes a complex mathematical operation forever in a loop. This function +// is used as entry point for the threads created by TestTakeAllCpu. It it +// designed to take all CPU on the processor where the thread is running. +// The return value is always 0. +DWORD WINAPI TakeAllCpu(void *param) { + UNREFERENCED_PARAMETER(param); + int cpt = 0; + for (;;) { + cpt += 2; + cpt /= 2; + cpt *= cpt; + cpt = cpt % 100; + cpt = cpt | (cpt * cpt); + } +} + +void POCDLL_API TestTakeAllCpu(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + DWORD_PTR process_mask = 0; + DWORD_PTR system_mask = 0; + if (::GetProcessAffinityMask(::GetCurrentProcess(), + &process_mask, + &system_mask)) { + DWORD_PTR affinity_mask = 1; + + while (system_mask) { + DWORD tid = 0; + + HANDLE thread = ::CreateThread(NULL, // Default security attributes. + NULL, // Stack size. + TakeAllCpu, + NULL, // Parameter. + 0, // No creation flags. + &tid); + ::SetThreadAffinityMask(thread, affinity_mask); + + if (::SetThreadPriority(thread, REALTIME_PRIORITY_CLASS)) { + fprintf(output, "[GRANTED] Set thread(%ld) priority to Realtime\r\n", + tid); + } else { + fprintf(output, "[BLOCKED] Set thread(%ld) priority to Realtime\r\n", + tid); + } + + ::CloseHandle(thread); + + affinity_mask = affinity_mask << 1; + system_mask = system_mask >> 1; + } + } else { + fprintf(output, "[ERROR] Cannot get affinity mask. Error %ld\r\n", + ::GetLastError()); + } +} + +void POCDLL_API TestUseAllMemory(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + int number_errors = 0; + unsigned long memory_size = 0; + for (;;) { + DWORD *ptr_to_leak = reinterpret_cast<DWORD *>(malloc(1024*256)); + if (ptr_to_leak) { + memory_size += (256); + number_errors = 0; + } else { + number_errors++; + } + + // check if we have more than 5 errors in a row. If so, quit. + if (number_errors >= 5) { + fprintf(output, "[INFO] Created %lu kb of memory\r\n", memory_size); + return; + } + + Sleep(5); // 5ms to be able to see the progression easily with taskmgr. + } +} + +void POCDLL_API TestCreateObjects(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + int mutexes = 0; + int jobs = 0; + int events = 0; + for (int i = 0; i < 1000000; ++i) { + if (::CreateMutex(NULL, // Default security attributes. + TRUE, // We are the initial owner. + NULL)) { // No name. + mutexes++; + } + + if (::CreateJobObject(NULL, // Default security attributes. + NULL)) { // No name. + jobs++; + } + + if (::CreateEvent(NULL, // Default security attributes. + TRUE, // Manual Reset. + TRUE, // Object is signaled. + NULL)) { // No name. + events++; + } + } + + fprintf(output, "[GRANTED] Created %d mutexes, %d jobs and %d events for " + "a total of %d objects out of 3 000 000\r\n", mutexes, jobs, + events, mutexes + jobs + events); +} + +BOOL CALLBACK EnumWindowCallback(HWND hwnd, LPARAM output) { + DWORD pid; + ::GetWindowThreadProcessId(hwnd, &pid); + if (pid != ::GetCurrentProcessId()) { + wchar_t window_title[100 + 1] = {0}; + ::GetWindowText(hwnd, window_title, 100); + fprintf(reinterpret_cast<FILE*>(output), + "[GRANTED] Found window 0x%p with title %S\r\n", + hwnd, + window_title); + ::CloseWindow(hwnd); + } + + return TRUE; +} + +// Enumerates all the windows on the system and call the function to try to +// close them. The goal of this function is to try to kill the system by +// closing all windows. +// "output" is the stream used for logging. +void POCDLL_API TestCloseHWND(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + ::EnumWindows(EnumWindowCallback, PtrToLong(output)); + // TODO(nsylvain): find a way to know when the enum is finished + // before returning. + ::Sleep(3000); +} diff --git a/sandbox/win/sandbox_poc/pocdll/network.cc b/sandbox/win/sandbox_poc/pocdll/network.cc new file mode 100644 index 0000000000..56ab5aefb0 --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/network.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/sandbox_poc/pocdll/exports.h" +#include "sandbox/win/sandbox_poc/pocdll/utils.h" + +// This file contains the tests used to verify the security of the network. + +void POCDLL_API TestNetworkListen(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); +#if DONT_WANT_INTERCEPTIONS_JUST_WANT_NETWORK + // Initialize Winsock + WSADATA wsa_data; + int result = ::WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (result != NO_ERROR) { + fprintf(output, "[ERROR] Cannot initialize winsock. Error%d\r\n", result); + return; + } + + // Create a SOCKET for listening for + // incoming connection requests. + SOCKET listen_socket; + listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listen_socket == INVALID_SOCKET) { + fprintf(output, "[ERROR] Failed to create socket. Error %ld\r\n", + ::WSAGetLastError()); + ::WSACleanup(); + return; + } + + // The sockaddr_in structure specifies the address family, + // IP address, and port for the socket that is being bound. + sockaddr_in service; + service.sin_family = AF_INET; + service.sin_addr.s_addr = inet_addr("127.0.0.1"); + service.sin_port = htons(88); + + if (bind(listen_socket, reinterpret_cast<SOCKADDR*>(&service), + sizeof(service)) == SOCKET_ERROR) { + fprintf(output, "[BLOCKED] Bind socket on port 88. Error %ld\r\n", + ::WSAGetLastError()); + closesocket(listen_socket); + ::WSACleanup(); + return; + } + + // Listen for incoming connection requests + // on the created socket + if (listen(listen_socket, SOMAXCONN) == SOCKET_ERROR) { + fprintf(output, "[BLOCKED] Listen socket on port 88. Error %ld\r\n", + ::WSAGetLastError()); + + } else { + fprintf(output, "[GRANTED] Listen socket on port 88.\r\n", + ::WSAGetLastError()); + } + + ::WSACleanup(); + return; +#else // DONT_WANT_INTERCEPTIONS_JUST_WANT_NETWORK + // Just print out that this test is not running. + fprintf(output, "[ERROR] No network tests.\r\n"); +#endif // DONT_WANT_INTERCEPTIONS_JUST_WANT_NETWORK +} diff --git a/sandbox/win/sandbox_poc/pocdll/pocdll.cc b/sandbox/win/sandbox_poc/pocdll/pocdll.cc new file mode 100644 index 0000000000..e058f58b82 --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/pocdll.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +#include "sandbox/win/sandbox_poc/pocdll/exports.h" +#include "sandbox/win/sandbox_poc/pocdll/utils.h" + +BOOL APIENTRY DllMain(HMODULE module, + DWORD reason_for_call, + LPVOID reserved) { + UNREFERENCED_PARAMETER(module); + UNREFERENCED_PARAMETER(reason_for_call); + UNREFERENCED_PARAMETER(reserved); + return TRUE; +} + +void POCDLL_API Run(HANDLE log) { + TestFileSystem(log); + TestRegistry(log); + TestNetworkListen(log); + TestSpyScreen(log); + TestSpyKeys(log); + TestThreads(log); + TestProcesses(log); + TestGetHandle(log); +} diff --git a/sandbox/win/sandbox_poc/pocdll/pocdll.vcproj b/sandbox/win/sandbox_poc/pocdll/pocdll.vcproj new file mode 100644 index 0000000000..8e4e31fc6a --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/pocdll.vcproj @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="pocdll" + ProjectGUID="{AE5BFB87-850E-4454-B01D-58E7D8BAC224}" + RootNamespace="pocdll" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="2" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\build\common.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="POCDLL_EXPORTS" + UsePrecompiledHeader="2" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + ModuleDefinitionFile="" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + EmbedManifest="false" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="2" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\build\common.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="POCDLL_EXPORTS" + UsePrecompiledHeader="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + ModuleDefinitionFile="" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + EmbedManifest="false" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath=".\exports.h" + > + </File> + <File + RelativePath=".\fs.cc" + > + </File> + <File + RelativePath=".\handles.cc" + > + </File> + <File + RelativePath=".\invasive.cc" + > + </File> + <File + RelativePath=".\network.cc" + > + </File> + <File + RelativePath=".\pocdll.cc" + > + </File> + <File + RelativePath=".\processes_and_threads.cc" + > + </File> + <File + RelativePath=".\registry.cc" + > + </File> + <File + RelativePath=".\spyware.cc" + > + </File> + <File + RelativePath=".\stdafx.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + <File + RelativePath=".\utils.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/sandbox/win/sandbox_poc/pocdll/processes_and_threads.cc b/sandbox/win/sandbox_poc/pocdll/processes_and_threads.cc new file mode 100644 index 0000000000..03e12ba522 --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/processes_and_threads.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2006-2008 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 <windows.h> +#include <Tlhelp32.h> +#include "sandbox/win/sandbox_poc/pocdll/exports.h" +#include "sandbox/win/sandbox_poc/pocdll/utils.h" + +// This file contains the tests used to verify the security of threads and +// processes. + +void POCDLL_API TestProcesses(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + HANDLE snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + if (INVALID_HANDLE_VALUE == snapshot) { + fprintf(output, "[BLOCKED] Cannot list all processes on the system. " + "Error %ld\r\n", ::GetLastError()); + return; + } + + PROCESSENTRY32 process_entry = {0}; + process_entry.dwSize = sizeof(PROCESSENTRY32); + + BOOL result = ::Process32First(snapshot, &process_entry); + + while (result) { + HANDLE process = ::OpenProcess(PROCESS_VM_READ, + FALSE, // Do not inherit handle. + process_entry.th32ProcessID); + if (NULL == process) { + fprintf(output, "[BLOCKED] Found process %S:%ld but cannot open it. " + "Error %ld\r\n", + process_entry.szExeFile, + process_entry.th32ProcessID, + ::GetLastError()); + } else { + fprintf(output, "[GRANTED] Found process %S:%ld and open succeeded.\r\n", + process_entry.szExeFile, process_entry.th32ProcessID); + ::CloseHandle(process); + } + + result = ::Process32Next(snapshot, &process_entry); + } + + DWORD err_code = ::GetLastError(); + if (ERROR_NO_MORE_FILES != err_code) { + fprintf(output, "[ERROR] Error %ld while looking at the processes on " + "the system\r\n", err_code); + } + + ::CloseHandle(snapshot); +} + +void POCDLL_API TestThreads(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + HANDLE snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL); + if (INVALID_HANDLE_VALUE == snapshot) { + fprintf(output, "[BLOCKED] Cannot list all threads on the system. " + "Error %ld\r\n", ::GetLastError()); + return; + } + + THREADENTRY32 thread_entry = {0}; + thread_entry.dwSize = sizeof(THREADENTRY32); + + BOOL result = ::Thread32First(snapshot, &thread_entry); + int nb_success = 0; + int nb_failure = 0; + + while (result) { + HANDLE thread = ::OpenThread(THREAD_QUERY_INFORMATION, + FALSE, // Do not inherit handles. + thread_entry.th32ThreadID); + if (NULL == thread) { + nb_failure++; + } else { + nb_success++; + fprintf(output, "[GRANTED] Found thread %ld:%ld and able to open it.\r\n", + thread_entry.th32OwnerProcessID, + thread_entry.th32ThreadID); + ::CloseHandle(thread); + } + + result = Thread32Next(snapshot, &thread_entry); + } + + DWORD err_code = ::GetLastError(); + if (ERROR_NO_MORE_FILES != err_code) { + fprintf(output, "[ERROR] Error %ld while looking at the processes on " + "the system\r\n", err_code); + } + + fprintf(output, "[INFO] Found %d threads. Able to open %d of them\r\n", + nb_success + nb_failure, nb_success); + + ::CloseHandle(snapshot); +} diff --git a/sandbox/win/sandbox_poc/pocdll/registry.cc b/sandbox/win/sandbox_poc/pocdll/registry.cc new file mode 100644 index 0000000000..5784db65d0 --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/registry.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/sandbox_poc/pocdll/exports.h" +#include "sandbox/win/sandbox_poc/pocdll/utils.h" + +// This file contains the tests used to verify the security of the registry. + +// Tries to open the key hive\path and outputs the result. +// "output" is the stream used for logging. +void TryOpenKey(const HKEY hive, + const wchar_t* hive_name, + const wchar_t* path, + FILE* output) { + HKEY key; + LONG err_code = ::RegOpenKeyEx(hive, + path, + 0, // Reserved, must be 0. + MAXIMUM_ALLOWED, + &key); + if (ERROR_SUCCESS == err_code) { + fprintf(output, + "[GRANTED] Opening key \"%S\\%S\". Handle 0x%p\r\n", + hive_name, + path, + key); + ::RegCloseKey(key); + } else { + fprintf(output, + "[BLOCKED] Opening key \"%S\\%S\". Error %ld\r\n", + hive_name, + path, + err_code); + } +} + +void POCDLL_API TestRegistry(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + TryOpenKey(HKEY_LOCAL_MACHINE, L"HKEY_LOCAL_MACHINE", NULL, output); + TryOpenKey(HKEY_CURRENT_USER, L"HKEY_CURRENT_USER", NULL, output); + TryOpenKey(HKEY_USERS, L"HKEY_USERS", NULL, output); + TryOpenKey(HKEY_LOCAL_MACHINE, + L"HKEY_LOCAL_MACHINE", + L"Software\\Microsoft\\Windows NT\\CurrentVersion\\WinLogon", + output); +} diff --git a/sandbox/win/sandbox_poc/pocdll/spyware.cc b/sandbox/win/sandbox_poc/pocdll/spyware.cc new file mode 100644 index 0000000000..cf0bd4177f --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/spyware.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2006-2009 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 <string> + +#include "base/strings/string16.h" +#include "sandbox/win/sandbox_poc/pocdll/exports.h" +#include "sandbox/win/sandbox_poc/pocdll/utils.h" + +// This file contains the tests used to verify the security of the system by +// using some spying techniques. + +void POCDLL_API TestSpyKeys(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + if (RegisterHotKey(NULL, 1, 0, 0x42)) { + fprintf(output, "[GRANTED] successfully registered hotkey\r\n"); + UnregisterHotKey(NULL, 1); + } else { + fprintf(output, "[BLOCKED] Failed to register hotkey. Error = %ld\r\n", + ::GetLastError()); + } + + fprintf(output, "[INFO] Logging keystrokes for 15 seconds\r\n"); + fflush(output); + base::string16 logged; + DWORD tick = ::GetTickCount() + 15000; + while (tick > ::GetTickCount()) { + for (int i = 0; i < 256; ++i) { + if (::GetAsyncKeyState(i) & 1) { + if (i >= VK_SPACE && i <= 0x5A /*VK_Z*/) { + logged.append(1, static_cast<wchar_t>(i)); + } else { + logged.append(1, '?'); + } + } + } + } + + if (logged.size()) { + fprintf(output, "[GRANTED] Spyed keystrokes \"%S\"\r\n", + logged.c_str()); + } else { + fprintf(output, "[BLOCKED] Spyed keystrokes \"(null)\"\r\n"); + } +} + +void POCDLL_API TestSpyScreen(HANDLE log) { + HandleToFile handle2file; + FILE *output = handle2file.Translate(log, "w"); + + HDC screen_dc = ::GetDC(NULL); + COLORREF pixel_color = ::GetPixel(screen_dc, 0, 0); + + for (int x = 0; x < 10; ++x) { + for (int y = 0; y < 10; ++y) { + if (::GetPixel(screen_dc, x, y) != pixel_color) { + fprintf(output, "[GRANTED] Read pixel on screen\r\n"); + return; + } + } + } + + fprintf(output, "[BLOCKED] Read pixel on screen. Error = %ld\r\n", + ::GetLastError()); +} diff --git a/sandbox/win/sandbox_poc/pocdll/utils.h b/sandbox/win/sandbox_poc/pocdll/utils.h new file mode 100644 index 0000000000..ae4286147a --- /dev/null +++ b/sandbox/win/sandbox_poc/pocdll/utils.h @@ -0,0 +1,65 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SANDBOX_POC_POCDLL_UTILS_H__ +#define SANDBOX_SANDBOX_POC_POCDLL_UTILS_H__ + +#include <stdio.h> +#include <io.h> +#include "base/basictypes.h" + +// Class to convert a HANDLE to a FILE *. The FILE * is closed when the +// object goes out of scope +class HandleToFile { + public: + HandleToFile() { + file_ = NULL; + }; + + // Note: c_file_handle_ does not need to be closed because fclose does it. + ~HandleToFile() { + if (file_) { + fflush(file_); + fclose(file_); + } + }; + + // Translates a HANDLE (handle) to a FILE * opened with the mode "mode". + // The return value is the FILE * or NULL if there is an error. + FILE* Translate(HANDLE handle, const char *mode) { + if (file_) { + return NULL; + } + + HANDLE new_handle; + BOOL result = ::DuplicateHandle(::GetCurrentProcess(), + handle, + ::GetCurrentProcess(), + &new_handle, + 0, // Don't ask for a specific + // desired access. + FALSE, // Not inheritable. + DUPLICATE_SAME_ACCESS); + + if (!result) { + return NULL; + } + + int c_file_handle = _open_osfhandle(reinterpret_cast<LONG_PTR>(new_handle), + 0); // No flags + if (-1 == c_file_handle) { + return NULL; + } + + file_ = _fdopen(c_file_handle, mode); + return file_; + }; + private: + // the FILE* returned. We need to closed it at the end. + FILE* file_; + + DISALLOW_COPY_AND_ASSIGN(HandleToFile); +}; + +#endif // SANDBOX_SANDBOX_POC_POCDLL_UTILS_H__ diff --git a/sandbox/win/sandbox_poc/resource.h b/sandbox/win/sandbox_poc/resource.h new file mode 100644 index 0000000000..87ff920ca4 --- /dev/null +++ b/sandbox/win/sandbox_poc/resource.h @@ -0,0 +1,30 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by sandbox.rc +// +#define IDI_SANDBOX 107 +#define IDR_MENU_MAIN_UI 129 +#define IDD_LAUNCH_DLL 130 +#define IDC_RADIO_POCDLL 1000 +#define IDC_RADIO_CUSTOM_DLL 1001 +#define IDC_DLL_NAME 1002 +#define IDC_ENTRY_POINT 1003 +#define IDC_LOG_FILE 1004 +#define IDC_BROWSE_DLL 1005 +#define IDC_BROWSE_LOG 1006 +#define ID_FILE_EXIT 32771 +#define ID_COMMANDS_LAUNCHDLL 32772 +#define ID_COMMANDS_SPAWNTARGET 32773 +#define IDC_STATIC -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 131 +#define _APS_NEXT_COMMAND_VALUE 32774 +#define _APS_NEXT_CONTROL_VALUE 1007 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif diff --git a/sandbox/win/sandbox_poc/sandbox.cc b/sandbox/win/sandbox_poc/sandbox.cc new file mode 100644 index 0000000000..e282075321 --- /dev/null +++ b/sandbox/win/sandbox_poc/sandbox.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2006-2010 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 <windows.h> +#include <tchar.h> +#include <shellapi.h> +#include "sandbox/win/sandbox_poc/sandbox.h" +#include "base/logging.h" +#include "sandbox/win/sandbox_poc/main_ui_window.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" + +// Prototype allowed for functions to be called in the POC +typedef void(__cdecl *lpfnInit)(HANDLE); + +bool ParseCommandLine(wchar_t * command_line, + std::string * dll_name, + std::string * entry_point, + base::string16 * log_file) { + DCHECK(dll_name); + DCHECK(entry_point); + DCHECK(log_file); + if (!dll_name || !entry_point || !log_file) + return false; + + LPWSTR *arg_list; + int arg_count; + + // We expect the command line to contain: EntryPointName "DLLPath" "LogPath" + // NOTE: Double quotes are required, even if long path name not used + // NOTE: LogPath can be blank, but still requires the double quotes + arg_list = CommandLineToArgvW(command_line, &arg_count); + if (NULL == arg_list || arg_count < 4) { + return false; + } + + base::string16 entry_point_wide = arg_list[1]; + base::string16 dll_name_wide = arg_list[2]; + *entry_point = std::string(entry_point_wide.begin(), entry_point_wide.end()); + *dll_name = std::string(dll_name_wide.begin(), dll_name_wide.end()); + *log_file = arg_list[3]; + + // Free memory allocated for CommandLineToArgvW arguments. + LocalFree(arg_list); + + return true; +} + +int APIENTRY _tWinMain(HINSTANCE instance, HINSTANCE, wchar_t* command_line, + int show_command) { + UNREFERENCED_PARAMETER(command_line); + + sandbox::BrokerServices* broker_service = + sandbox::SandboxFactory::GetBrokerServices(); + sandbox::ResultCode result; + + // This application starts as the broker; an application with a UI that + // spawns an instance of itself (called a 'target') inside the sandbox. + // Before spawning a hidden instance of itself, the application will have + // asked the user which DLL the spawned instance should load and passes + // that as command line argument to the spawned instance. + // + // We check here to see if we can retrieve a pointer to the BrokerServices, + // which is not possible if we are running inside the sandbox under a + // restricted token so it also tells us which mode we are in. If we can + // retrieve the pointer, then we are the broker, otherwise we are the target + // that the broker launched. + if (NULL != broker_service) { + // Yes, we are the broker so we need to initialize and show the UI + if (0 != (result = broker_service->Init())) { + ::MessageBox(NULL, L"Failed to initialize the BrokerServices object", + L"Error during initialization", MB_ICONERROR); + return 1; + } + + wchar_t exe_name[MAX_PATH]; + if (0 == GetModuleFileName(NULL, exe_name, MAX_PATH - 1)) { + ::MessageBox(NULL, L"Failed to get name of current EXE", + L"Error during initialization", MB_ICONERROR); + return 1; + } + + // The CreateMainWindowAndLoop() call will not return until the user closes + // the application window (or selects File\Exit). + MainUIWindow window; + window.CreateMainWindowAndLoop(instance, + exe_name, + show_command, + broker_service); + + + // Cannot exit until we have cleaned up after all the targets we have + // created + broker_service->WaitForAllTargets(); + } else { + // This is an instance that has been spawned inside the sandbox by the + // broker, so we need to parse the command line to figure out which DLL to + // load and what entry point to call + sandbox::TargetServices* target_service + = sandbox::SandboxFactory::GetTargetServices(); + + if (NULL == target_service) { + // TODO(finnur): write the failure to the log file + // We cannot display messageboxes inside the sandbox unless access to + // the desktop handle has been granted to us, and we don't have a + // console window to write to. Therefore we need to have the broker + // grant us access to a handle to a logfile and write the error that + // occurred into the log before continuing + return -1; + } + + // Debugging the spawned application can be tricky, because DebugBreak() + // and _asm int 3 cause the app to terminate (due to a flag in the job + // object), MessageBoxes() will not be displayed unless we have been granted + // that privilege and the target finishes its business so quickly we cannot + // attach to it quickly enough. Therefore, you can uncomment the + // following line and attach (w. msdev or windbg) as the target is sleeping + + // Sleep(10000); + + if (sandbox::SBOX_ALL_OK != (result = target_service->Init())) { + // TODO(finnur): write the initialization error to the log file + return -2; + } + + // Parse the command line to find out what we need to call + std::string dll_name, entry_point; + base::string16 log_file; + if (!ParseCommandLine(GetCommandLineW(), + &dll_name, + &entry_point, + &log_file)) { + // TODO(finnur): write the failure to the log file + return -3; + } + + // Open the pipe to transfert the log output + HANDLE pipe = ::CreateFile(log_file.c_str(), + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, // Default security attributes. + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); // No template + + if (INVALID_HANDLE_VALUE == pipe) { + return -4; + } + + // We now know what we should load, so load it + HMODULE dll_module = ::LoadLibraryA(dll_name.c_str()); + if (dll_module == NULL) { + // TODO(finnur): write the failure to the log file + return -5; + } + + // Initialization is finished, so we can enter lock-down mode + target_service->LowerToken(); + + lpfnInit init_function = + (lpfnInit) ::GetProcAddress(dll_module, entry_point.c_str()); + + if (!init_function) { + // TODO(finnur): write the failure to the log file + ::FreeLibrary(dll_module); + CloseHandle(pipe); + return -6; + } + + // Transfer control to the entry point in the DLL requested + init_function(pipe); + + CloseHandle(pipe); + Sleep(1000); // Give a change to the debug output to arrive before the + // end of the process + + ::FreeLibrary(dll_module); + } + + return 0; +} diff --git a/sandbox/win/sandbox_poc/sandbox.h b/sandbox/win/sandbox_poc/sandbox.h new file mode 100644 index 0000000000..65c09a119f --- /dev/null +++ b/sandbox/win/sandbox_poc/sandbox.h @@ -0,0 +1,10 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SANDBOX_POC_SANDBOX_H__ +#define SANDBOX_SANDBOX_POC_SANDBOX_H__ + +#include "sandbox/win/sandbox_poc/resource.h" + +#endif // SANDBOX_SANDBOX_POC_SANDBOX_H__ diff --git a/sandbox/win/sandbox_poc/sandbox.ico b/sandbox/win/sandbox_poc/sandbox.ico Binary files differnew file mode 100644 index 0000000000..916fa12fc2 --- /dev/null +++ b/sandbox/win/sandbox_poc/sandbox.ico diff --git a/sandbox/win/sandbox_poc/sandbox.rc b/sandbox/win/sandbox_poc/sandbox.rc new file mode 100644 index 0000000000..978c96f6f2 --- /dev/null +++ b/sandbox/win/sandbox_poc/sandbox.rc @@ -0,0 +1,136 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDR_MENU_MAIN_UI MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "E&xit", ID_FILE_EXIT + END + POPUP "&Commands" + BEGIN + MENUITEM "&Spawn target", ID_COMMANDS_SPAWNTARGET + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_LAUNCH_DLL DIALOGEX 0, 0, 269, 118 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "BrokerUI: Load an Attack DLL" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Call now",IDOK,212,70,50,14 + PUSHBUTTON "Cancel",IDCANCEL,212,95,50,14 + EDITTEXT IDC_DLL_NAME,7,43,200,13,ES_AUTOHSCROLL + LTEXT "DLL to load in target:",IDC_STATIC,7,33,168,8 + LTEXT "Function to call:",IDC_STATIC,7,61,139,8 + EDITTEXT IDC_ENTRY_POINT,7,71,200,13,ES_AUTOHSCROLL + EDITTEXT IDC_LOG_FILE,7,17,200,13,ES_AUTOHSCROLL + LTEXT "File for Target logging (optional):",IDC_STATIC,7,7,139,8 + PUSHBUTTON "Browse...",IDC_BROWSE_DLL,212,42,50,14 + PUSHBUTTON "Browse...",IDC_BROWSE_LOG,212,16,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_LAUNCH_DLL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 262 + TOPMARGIN, 7 + BOTTOMMARGIN, 111 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_SANDBOX ICON "sandbox.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""windows.h""\r\n" + "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/sandbox/win/sandbox_poc/sandbox_poc.vcproj b/sandbox/win/sandbox_poc/sandbox_poc.vcproj new file mode 100644 index 0000000000..5fde1cd407 --- /dev/null +++ b/sandbox/win/sandbox_poc/sandbox_poc.vcproj @@ -0,0 +1,202 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="sandbox_poc" + ProjectGUID="{CF757839-F2A1-417C-8F25-DCAE480020F1}" + RootNamespace="sandbox_poc" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\build\common.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="comctl32.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\build\common.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="comctl32.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath=".\main_ui_window.cc" + > + </File> + <File + RelativePath=".\main_ui_window.h" + > + </File> + <File + RelativePath=".\resource.h" + > + </File> + <File + RelativePath=".\sandbox.cc" + > + </File> + <File + RelativePath=".\sandbox.h" + > + </File> + <File + RelativePath=".\sandbox.ico" + > + </File> + <File + RelativePath=".\sandbox.rc" + > + </File> + <File + RelativePath=".\stdafx.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/sandbox/win/sandbox_standalone.sln b/sandbox/win/sandbox_standalone.sln new file mode 100644 index 0000000000..529d20e09e --- /dev/null +++ b/sandbox/win/sandbox_standalone.sln @@ -0,0 +1,127 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sandbox", "src\sandbox.vcproj", "{881F6A97-D539-4C48-B401-DF04385B2343}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sbox_unittests", "tests\unit_tests\sbox_unittests.vcproj", "{883553BE-2A9D-418C-A121-61FE1DFBC562}" + ProjectSection(ProjectDependencies) = postProject + {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165} + {881F6A97-D539-4C48-B401-DF04385B2343} = {881F6A97-D539-4C48-B401-DF04385B2343} + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F7A3B82E-B8B4-4FDF-BC8E-FEC9398F57ED}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sbox_validation_tests", "tests\validation_tests\sbox_validation_tests.vcproj", "{B9CC7B0D-145A-49C2-B887-84E43CFA0F27}" + ProjectSection(ProjectDependencies) = postProject + {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165} + {881F6A97-D539-4C48-B401-DF04385B2343} = {881F6A97-D539-4C48-B401-DF04385B2343} + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependencies", "dependencies", "{BCE54389-D18D-48B9-977E-9D1998200F63}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "debug_message", "..\base\debug_message.vcproj", "{F0F92189-193A-6607-C2BB-0F98BBD19ADF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{7F36EE20-5016-4051-B0D7-42824CDA0291}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "proof_of_concept", "proof_of_concept", "{B607BE7B-3555-422C-A40B-28E73C0B5E24}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sandbox_poc", "sandbox_poc\sandbox_poc.vcproj", "{CF757839-F2A1-417C-8F25-DCAE480020F1}" + ProjectSection(ProjectDependencies) = postProject + {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165} + {881F6A97-D539-4C48-B401-DF04385B2343} = {881F6A97-D539-4C48-B401-DF04385B2343} + {AE5BFB87-850E-4454-B01D-58E7D8BAC224} = {AE5BFB87-850E-4454-B01D-58E7D8BAC224} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pocdll", "sandbox_poc\pocdll\pocdll.vcproj", "{AE5BFB87-850E-4454-B01D-58E7D8BAC224}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "finder", "tools\finder\finder.vcproj", "{ACDC2E06-0366-41A4-A646-C37E130A605D}" + ProjectSection(ProjectDependencies) = postProject + {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165} + {881F6A97-D539-4C48-B401-DF04385B2343} = {881F6A97-D539-4C48-B401-DF04385B2343} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "launcher", "tools\launcher\launcher.vcproj", "{386FA217-FBC2-4461-882D-CDAD221ED800}" + ProjectSection(ProjectDependencies) = postProject + {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165} + {881F6A97-D539-4C48-B401-DF04385B2343} = {881F6A97-D539-4C48-B401-DF04385B2343} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sbox_integration_tests", "tests\integration_tests\sbox_integration_tests.vcproj", "{542D4B3B-98D4-4233-B68D-0103891508C6}" + ProjectSection(ProjectDependencies) = postProject + {1832A374-8A74-4F9E-B536-69A699B3E165} = {1832A374-8A74-4F9E-B536-69A699B3E165} + {881F6A97-D539-4C48-B401-DF04385B2343} = {881F6A97-D539-4C48-B401-DF04385B2343} + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "base", "..\base\base.vcproj", "{1832A374-8A74-4F9E-B536-69A699B3E165}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gtest", "..\testing\gtest.vcproj", "{BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {881F6A97-D539-4C48-B401-DF04385B2343}.Debug|Win32.ActiveCfg = Debug|Win32 + {881F6A97-D539-4C48-B401-DF04385B2343}.Debug|Win32.Build.0 = Debug|Win32 + {881F6A97-D539-4C48-B401-DF04385B2343}.Release|Win32.ActiveCfg = Release|Win32 + {881F6A97-D539-4C48-B401-DF04385B2343}.Release|Win32.Build.0 = Release|Win32 + {883553BE-2A9D-418C-A121-61FE1DFBC562}.Debug|Win32.ActiveCfg = Debug|Win32 + {883553BE-2A9D-418C-A121-61FE1DFBC562}.Debug|Win32.Build.0 = Debug|Win32 + {883553BE-2A9D-418C-A121-61FE1DFBC562}.Release|Win32.ActiveCfg = Release|Win32 + {883553BE-2A9D-418C-A121-61FE1DFBC562}.Release|Win32.Build.0 = Release|Win32 + {B9CC7B0D-145A-49C2-B887-84E43CFA0F27}.Debug|Win32.ActiveCfg = Debug|Win32 + {B9CC7B0D-145A-49C2-B887-84E43CFA0F27}.Debug|Win32.Build.0 = Debug|Win32 + {B9CC7B0D-145A-49C2-B887-84E43CFA0F27}.Release|Win32.ActiveCfg = Release|Win32 + {B9CC7B0D-145A-49C2-B887-84E43CFA0F27}.Release|Win32.Build.0 = Release|Win32 + {F0F92189-193A-6607-C2BB-0F98BBD19ADF}.Debug|Win32.ActiveCfg = Debug|Win32 + {F0F92189-193A-6607-C2BB-0F98BBD19ADF}.Debug|Win32.Build.0 = Debug|Win32 + {F0F92189-193A-6607-C2BB-0F98BBD19ADF}.Release|Win32.ActiveCfg = Release|Win32 + {F0F92189-193A-6607-C2BB-0F98BBD19ADF}.Release|Win32.Build.0 = Release|Win32 + {CF757839-F2A1-417C-8F25-DCAE480020F1}.Debug|Win32.ActiveCfg = Debug|Win32 + {CF757839-F2A1-417C-8F25-DCAE480020F1}.Debug|Win32.Build.0 = Debug|Win32 + {CF757839-F2A1-417C-8F25-DCAE480020F1}.Release|Win32.ActiveCfg = Release|Win32 + {CF757839-F2A1-417C-8F25-DCAE480020F1}.Release|Win32.Build.0 = Release|Win32 + {AE5BFB87-850E-4454-B01D-58E7D8BAC224}.Debug|Win32.ActiveCfg = Debug|Win32 + {AE5BFB87-850E-4454-B01D-58E7D8BAC224}.Debug|Win32.Build.0 = Debug|Win32 + {AE5BFB87-850E-4454-B01D-58E7D8BAC224}.Release|Win32.ActiveCfg = Release|Win32 + {AE5BFB87-850E-4454-B01D-58E7D8BAC224}.Release|Win32.Build.0 = Release|Win32 + {ACDC2E06-0366-41A4-A646-C37E130A605D}.Debug|Win32.ActiveCfg = Debug|Win32 + {ACDC2E06-0366-41A4-A646-C37E130A605D}.Debug|Win32.Build.0 = Debug|Win32 + {ACDC2E06-0366-41A4-A646-C37E130A605D}.Release|Win32.ActiveCfg = Release|Win32 + {ACDC2E06-0366-41A4-A646-C37E130A605D}.Release|Win32.Build.0 = Release|Win32 + {386FA217-FBC2-4461-882D-CDAD221ED800}.Debug|Win32.ActiveCfg = Debug|Win32 + {386FA217-FBC2-4461-882D-CDAD221ED800}.Debug|Win32.Build.0 = Debug|Win32 + {386FA217-FBC2-4461-882D-CDAD221ED800}.Release|Win32.ActiveCfg = Release|Win32 + {386FA217-FBC2-4461-882D-CDAD221ED800}.Release|Win32.Build.0 = Release|Win32 + {542D4B3B-98D4-4233-B68D-0103891508C6}.Debug|Win32.ActiveCfg = Debug|Win32 + {542D4B3B-98D4-4233-B68D-0103891508C6}.Debug|Win32.Build.0 = Debug|Win32 + {542D4B3B-98D4-4233-B68D-0103891508C6}.Release|Win32.ActiveCfg = Release|Win32 + {542D4B3B-98D4-4233-B68D-0103891508C6}.Release|Win32.Build.0 = Release|Win32 + {1832A374-8A74-4F9E-B536-69A699B3E165}.Debug|Win32.ActiveCfg = Debug|Win32 + {1832A374-8A74-4F9E-B536-69A699B3E165}.Debug|Win32.Build.0 = Debug|Win32 + {1832A374-8A74-4F9E-B536-69A699B3E165}.Release|Win32.ActiveCfg = Release|Win32 + {1832A374-8A74-4F9E-B536-69A699B3E165}.Release|Win32.Build.0 = Release|Win32 + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Debug|Win32.ActiveCfg = Debug|Win32 + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Debug|Win32.Build.0 = Debug|Win32 + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Release|Win32.ActiveCfg = Release|Win32 + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {883553BE-2A9D-418C-A121-61FE1DFBC562} = {F7A3B82E-B8B4-4FDF-BC8E-FEC9398F57ED} + {B9CC7B0D-145A-49C2-B887-84E43CFA0F27} = {F7A3B82E-B8B4-4FDF-BC8E-FEC9398F57ED} + {542D4B3B-98D4-4233-B68D-0103891508C6} = {F7A3B82E-B8B4-4FDF-BC8E-FEC9398F57ED} + {F0F92189-193A-6607-C2BB-0F98BBD19ADF} = {BCE54389-D18D-48B9-977E-9D1998200F63} + {1832A374-8A74-4F9E-B536-69A699B3E165} = {BCE54389-D18D-48B9-977E-9D1998200F63} + {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {BCE54389-D18D-48B9-977E-9D1998200F63} + {ACDC2E06-0366-41A4-A646-C37E130A605D} = {7F36EE20-5016-4051-B0D7-42824CDA0291} + {386FA217-FBC2-4461-882D-CDAD221ED800} = {7F36EE20-5016-4051-B0D7-42824CDA0291} + {CF757839-F2A1-417C-8F25-DCAE480020F1} = {B607BE7B-3555-422C-A40B-28E73C0B5E24} + {AE5BFB87-850E-4454-B01D-58E7D8BAC224} = {B607BE7B-3555-422C-A40B-28E73C0B5E24} + EndGlobalSection +EndGlobal diff --git a/sandbox/win/sandbox_win.gypi b/sandbox/win/sandbox_win.gypi new file mode 100644 index 0000000000..e085cfb5ef --- /dev/null +++ b/sandbox/win/sandbox_win.gypi @@ -0,0 +1,373 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'target_defaults': { + 'variables': { + 'sandbox_windows_target': 0, + 'target_arch%': 'ia32', + }, + 'target_conditions': [ + ['sandbox_windows_target==1', { + # Files that are shared between the 32-bit and the 64-bit versions + # of the Windows sandbox library. + 'sources': [ + 'src/acl.cc', + 'src/acl.h', + 'src/app_container.cc', + 'src/app_container.h', + 'src/broker_services.cc', + 'src/broker_services.h', + 'src/crosscall_client.h', + 'src/crosscall_params.h', + 'src/crosscall_server.cc', + 'src/crosscall_server.h', + 'src/eat_resolver.cc', + 'src/eat_resolver.h', + 'src/filesystem_dispatcher.cc', + 'src/filesystem_dispatcher.h', + 'src/filesystem_interception.cc', + 'src/filesystem_interception.h', + 'src/filesystem_policy.cc', + 'src/filesystem_policy.h', + 'src/handle_closer.cc', + 'src/handle_closer.h', + 'src/handle_closer_agent.cc', + 'src/handle_closer_agent.h', + 'src/handle_dispatcher.cc', + 'src/handle_dispatcher.h', + 'src/handle_interception.cc', + 'src/handle_interception.h', + 'src/handle_policy.cc', + 'src/handle_policy.h', + 'src/handle_table.cc', + 'src/handle_table.h', + 'src/interception.cc', + 'src/interception.h', + 'src/interception_agent.cc', + 'src/interception_agent.h', + 'src/interception_internal.h', + 'src/interceptors.h', + 'src/internal_types.h', + 'src/ipc_tags.h', + 'src/job.cc', + 'src/job.h', + 'src/named_pipe_dispatcher.cc', + 'src/named_pipe_dispatcher.h', + 'src/named_pipe_interception.cc', + 'src/named_pipe_interception.h', + 'src/named_pipe_policy.cc', + 'src/named_pipe_policy.h', + 'src/nt_internals.h', + 'src/policy_broker.cc', + 'src/policy_broker.h', + 'src/policy_engine_opcodes.cc', + 'src/policy_engine_opcodes.h', + 'src/policy_engine_params.h', + 'src/policy_engine_processor.cc', + 'src/policy_engine_processor.h', + 'src/policy_low_level.cc', + 'src/policy_low_level.h', + 'src/policy_params.h', + 'src/policy_target.cc', + 'src/policy_target.h', + 'src/process_mitigations.cc', + 'src/process_mitigations.h', + 'src/process_mitigations_win32k_dispatcher.cc', + 'src/process_mitigations_win32k_dispatcher.h', + 'src/process_mitigations_win32k_interception.cc', + 'src/process_mitigations_win32k_interception.h', + 'src/process_mitigations_win32k_policy.cc', + 'src/process_mitigations_win32k_policy.h', + 'src/process_thread_dispatcher.cc', + 'src/process_thread_dispatcher.h', + 'src/process_thread_interception.cc', + 'src/process_thread_interception.h', + 'src/process_thread_policy.cc', + 'src/process_thread_policy.h', + 'src/registry_dispatcher.cc', + 'src/registry_dispatcher.h', + 'src/registry_interception.cc', + 'src/registry_interception.h', + 'src/registry_policy.cc', + 'src/registry_policy.h', + 'src/resolver.cc', + 'src/resolver.h', + 'src/restricted_token_utils.cc', + 'src/restricted_token_utils.h', + 'src/restricted_token.cc', + 'src/restricted_token.h', + 'src/sandbox_factory.h', + 'src/sandbox_globals.cc', + 'src/sandbox_nt_types.h', + 'src/sandbox_nt_util.cc', + 'src/sandbox_nt_util.h', + 'src/sandbox_policy_base.cc', + 'src/sandbox_policy_base.h', + 'src/sandbox_policy.h', + 'src/sandbox_types.h', + 'src/sandbox_utils.cc', + 'src/sandbox_utils.h', + 'src/sandbox.cc', + 'src/sandbox.h', + 'src/security_level.h', + 'src/service_resolver.cc', + 'src/service_resolver.h', + 'src/shared_handles.cc', + 'src/shared_handles.h', + 'src/sharedmem_ipc_client.cc', + 'src/sharedmem_ipc_client.h', + 'src/sharedmem_ipc_server.cc', + 'src/sharedmem_ipc_server.h', + 'src/sid.cc', + 'src/sid.h', + 'src/sync_dispatcher.cc', + 'src/sync_dispatcher.h', + 'src/sync_interception.cc', + 'src/sync_interception.h', + 'src/sync_policy.cc', + 'src/sync_policy.h', + 'src/target_interceptions.cc', + 'src/target_interceptions.h', + 'src/target_process.cc', + 'src/target_process.h', + 'src/target_services.cc', + 'src/target_services.h', + 'src/win_utils.cc', + 'src/win_utils.h', + 'src/win2k_threadpool.cc', + 'src/win2k_threadpool.h', + 'src/window.cc', + 'src/window.h', + ], + 'target_conditions': [ + ['target_arch=="x64"', { + 'sources': [ + 'src/interceptors_64.cc', + 'src/interceptors_64.h', + 'src/resolver_64.cc', + 'src/service_resolver_64.cc', + 'src/Wow64_64.cc', + ], + }], + ['target_arch=="ia32"', { + 'sources': [ + 'src/resolver_32.cc', + 'src/service_resolver_32.cc', + 'src/sidestep_resolver.cc', + 'src/sidestep_resolver.h', + 'src/sidestep\ia32_modrm_map.cpp', + 'src/sidestep\ia32_opcode_map.cpp', + 'src/sidestep\mini_disassembler_types.h', + 'src/sidestep\mini_disassembler.cpp', + 'src/sidestep\mini_disassembler.h', + 'src/sidestep\preamble_patcher_with_stub.cpp', + 'src/sidestep\preamble_patcher.h', + 'src/Wow64.cc', + 'src/Wow64.h', + ], + }], + ], + }], + ], + }, + 'targets': [ + { + 'target_name': 'sandbox', + 'type': 'static_library', + 'variables': { + 'sandbox_windows_target': 1, + }, + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:base_static', + ], + 'export_dependent_settings': [ + '../base/base.gyp:base', + ], + 'include_dirs': [ + '../..', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'src', + '../..', + ], + }, + 'target_conditions': [ + ['target_arch=="ia32"', { + 'copies': [ + { + 'destination': '<(PRODUCT_DIR)', + 'files': [ + 'wow_helper/wow_helper.exe', + 'wow_helper/wow_helper.pdb', + ], + }, + ], + }], + ], + }, + { + 'target_name': 'sbox_integration_tests', + 'type': 'executable', + 'dependencies': [ + 'sandbox', + '../base/base.gyp:test_support_base', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'src/address_sanitizer_test.cc', + 'src/app_container_test.cc', + 'src/file_policy_test.cc', + 'src/handle_inheritance_test.cc', + 'src/handle_policy_test.cc', + 'tests/integration_tests/integration_tests_test.cc', + 'src/handle_closer_test.cc', + 'src/integrity_level_test.cc', + 'src/ipc_ping_test.cc', + 'src/named_pipe_policy_test.cc', + 'src/policy_target_test.cc', + 'src/process_mitigations_test.cc', + 'src/process_policy_test.cc', + 'src/registry_policy_test.cc', + 'src/sync_policy_test.cc', + 'src/sync_policy_test.h', + 'src/unload_dll_test.cc', + 'tests/common/controller.cc', + 'tests/common/controller.h', + 'tests/common/test_utils.cc', + 'tests/common/test_utils.h', + 'tests/integration_tests/integration_tests.cc', + ], + }, + { + 'target_name': 'sbox_validation_tests', + 'type': 'executable', + 'dependencies': [ + 'sandbox', + '../base/base.gyp:test_support_base', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'tests/common/controller.cc', + 'tests/common/controller.h', + 'tests/validation_tests/unit_tests.cc', + 'tests/validation_tests/commands.cc', + 'tests/validation_tests/commands.h', + 'tests/validation_tests/suite.cc', + ], + }, + { + 'target_name': 'sbox_unittests', + 'type': 'executable', + 'dependencies': [ + 'sandbox', + '../base/base.gyp:test_support_base', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'src/app_container_unittest.cc', + 'src/interception_unittest.cc', + 'src/service_resolver_unittest.cc', + 'src/restricted_token_unittest.cc', + 'src/job_unittest.cc', + 'src/sid_unittest.cc', + 'src/policy_engine_unittest.cc', + 'src/policy_low_level_unittest.cc', + 'src/policy_opcodes_unittest.cc', + 'src/ipc_unittest.cc', + 'src/threadpool_unittest.cc', + 'src/win_utils_unittest.cc', + 'tests/common/test_utils.cc', + 'tests/common/test_utils.h', + 'tests/unit_tests/unit_tests.cc', + ], + }, + { + 'target_name': 'sandbox_poc', + 'type': 'executable', + 'dependencies': [ + 'sandbox', + 'pocdll', + ], + 'sources': [ + 'sandbox_poc/main_ui_window.cc', + 'sandbox_poc/main_ui_window.h', + 'sandbox_poc/resource.h', + 'sandbox_poc/sandbox.cc', + 'sandbox_poc/sandbox.h', + 'sandbox_poc/sandbox.ico', + 'sandbox_poc/sandbox.rc', + ], + 'link_settings': { + 'libraries': [ + '-lcomctl32.lib', + ], + }, + 'msvs_settings': { + 'VCLinkerTool': { + 'SubSystem': '2', # Set /SUBSYSTEM:WINDOWS + }, + }, + }, + { + 'target_name': 'pocdll', + 'type': 'shared_library', + 'sources': [ + 'sandbox_poc/pocdll/exports.h', + 'sandbox_poc/pocdll/fs.cc', + 'sandbox_poc/pocdll/handles.cc', + 'sandbox_poc/pocdll/invasive.cc', + 'sandbox_poc/pocdll/network.cc', + 'sandbox_poc/pocdll/pocdll.cc', + 'sandbox_poc/pocdll/processes_and_threads.cc', + 'sandbox_poc/pocdll/registry.cc', + 'sandbox_poc/pocdll/spyware.cc', + 'sandbox_poc/pocdll/utils.h', + ], + 'defines': [ + 'POCDLL_EXPORTS', + ], + 'include_dirs': [ + '../..', + ], + }, + ], + 'conditions': [ + ['OS=="win" and target_arch=="ia32"', { + 'targets': [ + { + 'target_name': 'sandbox_win64', + 'type': 'static_library', + 'variables': { + 'sandbox_windows_target': 1, + 'target_arch': 'x64', + }, + 'dependencies': [ + '../base/base.gyp:base_win64', + '../base/base.gyp:base_static_win64', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + 'include_dirs': [ + '../..', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'src', + '../..', + ], + }, + 'defines': [ + '<@(nacl_win64_defines)', + ] + }, + ], + }], + ], +} diff --git a/sandbox/win/src/Wow64.cc b/sandbox/win/src/Wow64.cc new file mode 100644 index 0000000000..60ee13d258 --- /dev/null +++ b/sandbox/win/src/Wow64.cc @@ -0,0 +1,221 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/wow64.h" + +#include <sstream> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/target_process.h" + +namespace { + +// Holds the information needed for the interception of NtMapViewOfSection on +// 64 bits. +// Warning: do not modify this definition without changing also the code on the +// 64 bit helper process. +struct PatchInfo32 { + HANDLE dll_load; // Event to signal the broker. + ULONG pad1; + HANDLE continue_load; // Event to wait for the broker. + ULONG pad2; + HANDLE section; // First argument of the call. + ULONG pad3; + void* orig_MapViewOfSection; + ULONG original_high; + void* signal_and_wait; + ULONG pad4; + void* patch_location; + ULONG patch_high; +}; + +// Size of the 64 bit service entry. +const SIZE_T kServiceEntry64Size = 0x10; + +// Removes the interception of ntdll64. +bool Restore64Code(HANDLE child, PatchInfo32* patch_info) { + PatchInfo32 local_patch_info; + SIZE_T actual; + if (!::ReadProcessMemory(child, patch_info, &local_patch_info, + sizeof(local_patch_info), &actual)) + return false; + if (sizeof(local_patch_info) != actual) + return false; + + if (local_patch_info.original_high) + return false; + if (local_patch_info.patch_high) + return false; + + char buffer[kServiceEntry64Size]; + + if (!::ReadProcessMemory(child, local_patch_info.orig_MapViewOfSection, + &buffer, kServiceEntry64Size, &actual)) + return false; + if (kServiceEntry64Size != actual) + return false; + + if (!::WriteProcessMemory(child, local_patch_info.patch_location, &buffer, + kServiceEntry64Size, &actual)) + return false; + if (kServiceEntry64Size != actual) + return false; + return true; +} + +typedef BOOL (WINAPI* IsWow64ProcessFunction)(HANDLE process, BOOL* wow64); + +} // namespace + +namespace sandbox { + +Wow64::~Wow64() { + if (dll_load_) + ::CloseHandle(dll_load_); + + if (continue_load_) + ::CloseHandle(continue_load_); +} + +// The basic idea is to allocate one page of memory on the child, and initialize +// the first part of it with our version of PatchInfo32. Then launch the helper +// process passing it that address on the child. The helper process will patch +// the 64 bit version of NtMapViewOfFile, and the interception will signal the +// first event on the buffer. We'll be waiting on that event and after the 32 +// bit version of ntdll is loaded, we'll remove the interception and return to +// our caller. +bool Wow64::WaitForNtdll() { + if (base::win::OSInfo::GetInstance()->wow64_status() != + base::win::OSInfo::WOW64_ENABLED) + return true; + + const size_t page_size = 4096; + + // Create some default manual reset un-named events, not signaled. + dll_load_ = ::CreateEvent(NULL, TRUE, FALSE, NULL); + continue_load_ = ::CreateEvent(NULL, TRUE, FALSE, NULL); + HANDLE current_process = ::GetCurrentProcess(); + HANDLE remote_load, remote_continue; + DWORD access = EVENT_MODIFY_STATE | SYNCHRONIZE; + if (!::DuplicateHandle(current_process, dll_load_, child_->Process(), + &remote_load, access, FALSE, 0)) + return false; + if (!::DuplicateHandle(current_process, continue_load_, child_->Process(), + &remote_continue, access, FALSE, 0)) + return false; + + void* buffer = ::VirtualAllocEx(child_->Process(), NULL, page_size, + MEM_COMMIT, PAGE_EXECUTE_READWRITE); + DCHECK(buffer); + if (!buffer) + return false; + + PatchInfo32* patch_info = reinterpret_cast<PatchInfo32*>(buffer); + PatchInfo32 local_patch_info = {0}; + local_patch_info.dll_load = remote_load; + local_patch_info.continue_load = remote_continue; + SIZE_T written; + if (!::WriteProcessMemory(child_->Process(), patch_info, &local_patch_info, + offsetof(PatchInfo32, section), &written)) + return false; + if (offsetof(PatchInfo32, section) != written) + return false; + + if (!RunWowHelper(buffer)) + return false; + + // The child is intercepted on 64 bit, go on and wait for our event. + if (!DllMapped()) + return false; + + // The 32 bit version is available, cleanup the child. + return Restore64Code(child_->Process(), patch_info); +} + +bool Wow64::RunWowHelper(void* buffer) { + static_assert(sizeof(buffer) <= sizeof(DWORD), "unsupported 64 bits"); + + // Get the path to the helper (beside the exe). + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(NULL, prog_name, MAX_PATH); + base::string16 path(prog_name); + size_t name_pos = path.find_last_of(L"\\"); + if (base::string16::npos == name_pos) + return false; + path.resize(name_pos + 1); + + std::basic_stringstream<base::char16> command; + command << std::hex << std::showbase << L"\"" << path << + L"wow_helper.exe\" " << child_->ProcessId() << " " << + bit_cast<ULONG>(buffer); + + scoped_ptr<wchar_t, base::FreeDeleter> + writable_command(_wcsdup(command.str().c_str())); + + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + PROCESS_INFORMATION temp_process_info = {}; + if (!::CreateProcess(NULL, writable_command.get(), NULL, NULL, FALSE, 0, NULL, + NULL, &startup_info, &temp_process_info)) + return false; + base::win::ScopedProcessInformation process_info(temp_process_info); + + DWORD reason = ::WaitForSingleObject(process_info.process_handle(), INFINITE); + + DWORD code; + bool ok = + ::GetExitCodeProcess(process_info.process_handle(), &code) ? true : false; + + if (WAIT_TIMEOUT == reason) + return false; + + return ok && (0 == code); +} + +// First we must wake up the child, then wait for dll loads on the child until +// the one we care is loaded; at that point we must suspend the child again. +bool Wow64::DllMapped() { + if (1 != ::ResumeThread(child_->MainThread())) { + NOTREACHED(); + return false; + } + + for (;;) { + DWORD reason = ::WaitForSingleObject(dll_load_, INFINITE); + if (WAIT_TIMEOUT == reason || WAIT_ABANDONED == reason) + return false; + + if (!::ResetEvent(dll_load_)) + return false; + + bool found = NtdllPresent(); + if (found) { + if (::SuspendThread(child_->MainThread())) + return false; + } + + if (!::SetEvent(continue_load_)) + return false; + + if (found) + return true; + } +} + +bool Wow64::NtdllPresent() { + const size_t kBufferSize = 512; + char buffer[kBufferSize]; + SIZE_T read; + if (!::ReadProcessMemory(child_->Process(), ntdll_, &buffer, kBufferSize, + &read)) + return false; + if (kBufferSize != read) + return false; + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/Wow64.h b/sandbox/win/src/Wow64.h new file mode 100644 index 0000000000..e9bbd53d9d --- /dev/null +++ b/sandbox/win/src/Wow64.h @@ -0,0 +1,50 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_WOW64_H__ +#define SANDBOX_SRC_WOW64_H__ + +#include <windows.h> + +#include "base/basictypes.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +class TargetProcess; + +// This class wraps the code needed to interact with the Windows On Windows +// subsystem on 64 bit OSes, from the point of view of interceptions. +class Wow64 { + public: + Wow64(TargetProcess* child, HMODULE ntdll) + : child_(child), ntdll_(ntdll), dll_load_(NULL), continue_load_(NULL) {} + ~Wow64(); + + // Waits for the 32 bit DLL to get loaded on the child process. This function + // will return immediately if not running under WOW, or launch the helper + // process and wait until ntdll is ready. + bool WaitForNtdll(); + + private: + // Runs the WOW helper process, passing the address of a buffer allocated on + // the child (one page). + bool RunWowHelper(void* buffer); + + // This method receives "notifications" whenever a DLL is mapped on the child. + bool DllMapped(); + + // Returns true if ntdll.dll is mapped on the child. + bool NtdllPresent(); + + TargetProcess* child_; // Child process. + HMODULE ntdll_; // ntdll on the parent. + HANDLE dll_load_; // Event that is signaled on dll load. + HANDLE continue_load_; // Event to signal to continue execution on the child. + DISALLOW_IMPLICIT_CONSTRUCTORS(Wow64); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_WOW64_H__ diff --git a/sandbox/win/src/Wow64_64.cc b/sandbox/win/src/Wow64_64.cc new file mode 100644 index 0000000000..f03831bd11 --- /dev/null +++ b/sandbox/win/src/Wow64_64.cc @@ -0,0 +1,18 @@ +// Copyright (c) 2011 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. + +// Wow64 implementation for native 64-bit Windows (in other words, never WOW). + +#include "sandbox/win/src/wow64.h" + +namespace sandbox { + +Wow64::~Wow64() { +} + +bool Wow64::WaitForNtdll() { + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/acl.cc b/sandbox/win/src/acl.cc new file mode 100644 index 0000000000..f140c7e6c4 --- /dev/null +++ b/sandbox/win/src/acl.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/acl.h" + +#include <aclapi.h> +#include <sddl.h> + +#include "base/logging.h" + +namespace sandbox { + +bool GetDefaultDacl( + HANDLE token, + scoped_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter>* default_dacl) { + if (token == NULL) + return false; + + DCHECK(default_dacl != NULL); + + unsigned long length = 0; + ::GetTokenInformation(token, TokenDefaultDacl, NULL, 0, &length); + if (length == 0) { + NOTREACHED(); + return false; + } + + TOKEN_DEFAULT_DACL* acl = + reinterpret_cast<TOKEN_DEFAULT_DACL*>(malloc(length)); + default_dacl->reset(acl); + + if (!::GetTokenInformation(token, TokenDefaultDacl, default_dacl->get(), + length, &length)) + return false; + + return true; +} + +bool AddSidToDacl(const Sid& sid, ACL* old_dacl, ACCESS_MODE access_mode, + ACCESS_MASK access, ACL** new_dacl) { + EXPLICIT_ACCESS new_access = {0}; + new_access.grfAccessMode = access_mode; + new_access.grfAccessPermissions = access; + new_access.grfInheritance = NO_INHERITANCE; + + new_access.Trustee.pMultipleTrustee = NULL; + new_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + new_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; + new_access.Trustee.ptstrName = reinterpret_cast<LPWSTR>( + const_cast<SID*>(sid.GetPSID())); + + if (ERROR_SUCCESS != ::SetEntriesInAcl(1, &new_access, old_dacl, new_dacl)) + return false; + + return true; +} + +bool AddSidToDefaultDacl(HANDLE token, const Sid& sid, ACCESS_MASK access) { + if (token == NULL) + return false; + + scoped_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter> default_dacl; + if (!GetDefaultDacl(token, &default_dacl)) + return false; + + ACL* new_dacl = NULL; + if (!AddSidToDacl(sid, default_dacl->DefaultDacl, GRANT_ACCESS, access, + &new_dacl)) + return false; + + TOKEN_DEFAULT_DACL new_token_dacl = {0}; + new_token_dacl.DefaultDacl = new_dacl; + + BOOL ret = ::SetTokenInformation(token, TokenDefaultDacl, &new_token_dacl, + sizeof(new_token_dacl)); + ::LocalFree(new_dacl); + return (TRUE == ret); +} + +bool AddUserSidToDefaultDacl(HANDLE token, ACCESS_MASK access) { + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(malloc(size)); + + scoped_ptr<TOKEN_USER, base::FreeDeleter> token_user_ptr(token_user); + + if (!::GetTokenInformation(token, TokenUser, token_user, size, &size)) + return false; + + return AddSidToDefaultDacl(token, + reinterpret_cast<SID*>(token_user->User.Sid), + access); +} + +bool AddKnownSidToObject(HANDLE object, SE_OBJECT_TYPE object_type, + const Sid& sid, ACCESS_MODE access_mode, + ACCESS_MASK access) { + PSECURITY_DESCRIPTOR descriptor = NULL; + PACL old_dacl = NULL; + PACL new_dacl = NULL; + + if (ERROR_SUCCESS != ::GetSecurityInfo(object, object_type, + DACL_SECURITY_INFORMATION, NULL, NULL, + &old_dacl, NULL, &descriptor)) + return false; + + if (!AddSidToDacl(sid.GetPSID(), old_dacl, access_mode, access, &new_dacl)) { + ::LocalFree(descriptor); + return false; + } + + DWORD result = ::SetSecurityInfo(object, object_type, + DACL_SECURITY_INFORMATION, NULL, NULL, + new_dacl, NULL); + + ::LocalFree(new_dacl); + ::LocalFree(descriptor); + + if (ERROR_SUCCESS != result) + return false; + + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/acl.h b/sandbox/win/src/acl.h new file mode 100644 index 0000000000..b5021e7be8 --- /dev/null +++ b/sandbox/win/src/acl.h @@ -0,0 +1,44 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_ACL_H_ +#define SANDBOX_SRC_ACL_H_ + +#include <AccCtrl.h> +#include <windows.h> + +#include "base/memory/scoped_ptr.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +// Returns the default dacl from the token passed in. +bool GetDefaultDacl( + HANDLE token, + scoped_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter>* default_dacl); + +// Appends an ACE represented by |sid|, |access_mode|, and |access| to +// |old_dacl|. If the function succeeds, new_dacl contains the new dacl and +// must be freed using LocalFree. +bool AddSidToDacl(const Sid& sid, ACL* old_dacl, ACCESS_MODE access_mode, + ACCESS_MASK access, ACL** new_dacl); + +// Adds and ACE represented by |sid| and |access| to the default dacl present +// in the token. +bool AddSidToDefaultDacl(HANDLE token, const Sid& sid, ACCESS_MASK access); + +// Adds an ACE represented by the user sid and |access| to the default dacl +// present in the token. +bool AddUserSidToDefaultDacl(HANDLE token, ACCESS_MASK access); + +// Adds an ACE represented by |known_sid|, |access_mode|, and |access| to +// the dacl of the kernel object referenced by |object| and of |object_type|. +bool AddKnownSidToObject(HANDLE object, SE_OBJECT_TYPE object_type, + const Sid& sid, ACCESS_MODE access_mode, + ACCESS_MASK access); + +} // namespace sandbox + + +#endif // SANDBOX_SRC_ACL_H_ diff --git a/sandbox/win/src/address_sanitizer_test.cc b/sandbox/win/src/address_sanitizer_test.cc new file mode 100644 index 0000000000..b88aa81cd4 --- /dev/null +++ b/sandbox/win/src/address_sanitizer_test.cc @@ -0,0 +1,107 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdio.h> + +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +class AddressSanitizerTests : public ::testing::Test { + public: + void SetUp() override { + env_.reset(base::Environment::Create()); + had_asan_options_ = env_->GetVar("ASAN_OPTIONS", &old_asan_options_); + } + + void TearDown() override { + if (had_asan_options_) + ASSERT_TRUE(env_->SetVar("ASAN_OPTIONS", old_asan_options_)); + else + env_->UnSetVar("ASAN_OPTIONS"); + } + + protected: + scoped_ptr<base::Environment> env_; + bool had_asan_options_; + std::string old_asan_options_; +}; + +SBOX_TESTS_COMMAND int AddressSanitizerTests_Report(int argc, wchar_t** argv) { + // AddressSanitizer should detect an out of bounds write (heap buffer + // overflow) in this code. + volatile int idx = 42; + int *blah = new int[42]; + blah[idx] = 42; + delete [] blah; + return SBOX_TEST_FAILED; +} + +TEST_F(AddressSanitizerTests, TestAddressSanitizer) { + // This test is only supposed to work when using AddressSanitizer. + // However, ASan/Win is not on the CQ yet, so compiler breakages may get into + // the code unnoticed. To avoid that, we compile this test in all Windows + // builds, but only run the AddressSanitizer-specific part of the test when + // compiled with AddressSanitizer. +#if defined(ADDRESS_SANITIZER) + bool asan_build = true; +#else + bool asan_build = false; +#endif + base::ScopedTempDir temp_directory; + base::FilePath temp_file_name; + ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); + ASSERT_TRUE(CreateTemporaryFileInDir(temp_directory.path(), &temp_file_name)); + + SECURITY_ATTRIBUTES attrs = {}; + attrs.nLength = sizeof(attrs); + attrs.bInheritHandle = TRUE; + base::win::ScopedHandle tmp_handle( + CreateFile(temp_file_name.value().c_str(), GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, + &attrs, OPEN_EXISTING, 0, NULL)); + EXPECT_TRUE(tmp_handle.IsValid()); + + TestRunner runner; + ASSERT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetStderrHandle(tmp_handle.Get())); + + base::FilePath exe; + ASSERT_TRUE(PathService::Get(base::FILE_EXE, &exe)); + base::FilePath pdb_path = exe.DirName().Append(L"*.pdb"); + ASSERT_TRUE(runner.AddFsRule(sandbox::TargetPolicy::FILES_ALLOW_READONLY, + pdb_path.value().c_str())); + + env_->SetVar("ASAN_OPTIONS", "exitcode=123"); + if (asan_build) { + int result = runner.RunTest(L"AddressSanitizerTests_Report"); + EXPECT_EQ(123, result); + + std::string data; + ASSERT_TRUE(base::ReadFileToString(base::FilePath(temp_file_name), &data)); + // Redirection uses a feature that was added in Windows Vista. + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + ASSERT_TRUE( + strstr(data.c_str(), "ERROR: AddressSanitizer: heap-buffer-overflow")) + << "There doesn't seem to be an ASan report:\n" << data; + ASSERT_TRUE(strstr(data.c_str(), "AddressSanitizerTests_Report")) + << "The ASan report doesn't appear to be symbolized:\n" << data; + ASSERT_TRUE(strstr(data.c_str(), strrchr(__FILE__, '\\'))) + << "The stack trace doesn't have a correct filename:\n" << data; + } else { + LOG(WARNING) << "Pre-Vista versions are not supported."; + } + } else { + LOG(WARNING) << "Not an AddressSanitizer build, skipping the run."; + } +} + +} diff --git a/sandbox/win/src/app_container.cc b/sandbox/win/src/app_container.cc new file mode 100644 index 0000000000..f8d7541915 --- /dev/null +++ b/sandbox/win/src/app_container.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/app_container.h" + +#include <Sddl.h> +#include <vector> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/startup_information.h" +#include "sandbox/win/src/internal_types.h" + +namespace { + +// Converts the passed in sid string to a PSID that must be relased with +// LocalFree. +PSID ConvertSid(const base::string16& sid) { + PSID local_sid; + if (!ConvertStringSidToSid(sid.c_str(), &local_sid)) + return NULL; + return local_sid; +} + +template <typename T> +T BindFunction(const char* name) { + HMODULE module = GetModuleHandle(sandbox::kKerneldllName); + void* function = GetProcAddress(module, name); + if (!function) { + module = GetModuleHandle(sandbox::kKernelBasedllName); + function = GetProcAddress(module, name); + } + return reinterpret_cast<T>(function); +} + +} // namespace + +namespace sandbox { + +AppContainerAttributes::AppContainerAttributes() { + memset(&capabilities_, 0, sizeof(capabilities_)); +} + +AppContainerAttributes::~AppContainerAttributes() { + for (size_t i = 0; i < attributes_.size(); i++) + LocalFree(attributes_[i].Sid); + LocalFree(capabilities_.AppContainerSid); +} + +ResultCode AppContainerAttributes::SetAppContainer( + const base::string16& app_container_sid, + const std::vector<base::string16>& capabilities) { + DCHECK(!capabilities_.AppContainerSid); + DCHECK(attributes_.empty()); + capabilities_.AppContainerSid = ConvertSid(app_container_sid); + if (!capabilities_.AppContainerSid) + return SBOX_ERROR_INVALID_APP_CONTAINER; + + for (size_t i = 0; i < capabilities.size(); i++) { + SID_AND_ATTRIBUTES sid_and_attributes; + sid_and_attributes.Sid = ConvertSid(capabilities[i]); + if (!sid_and_attributes.Sid) + return SBOX_ERROR_INVALID_CAPABILITY; + + sid_and_attributes.Attributes = SE_GROUP_ENABLED; + attributes_.push_back(sid_and_attributes); + } + + if (capabilities.size()) { + capabilities_.CapabilityCount = static_cast<DWORD>(capabilities.size()); + capabilities_.Capabilities = &attributes_[0]; + } + return SBOX_ALL_OK; +} + +ResultCode AppContainerAttributes::ShareForStartup( + base::win::StartupInformation* startup_information) const { + // The only thing we support so far is an AppContainer. + if (!capabilities_.AppContainerSid) + return SBOX_ERROR_INVALID_APP_CONTAINER; + + if (!startup_information->UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, + const_cast<SECURITY_CAPABILITIES*>(&capabilities_), + sizeof(capabilities_))) { + DPLOG(ERROR) << "Failed UpdateProcThreadAttribute"; + return SBOX_ERROR_CANNOT_INIT_APPCONTAINER; + } + return SBOX_ALL_OK; +} + +bool AppContainerAttributes::HasAppContainer() const { + return (capabilities_.AppContainerSid != NULL); +} + +ResultCode CreateAppContainer(const base::string16& sid, + const base::string16& name) { + PSID local_sid; + if (!ConvertStringSidToSid(sid.c_str(), &local_sid)) + return SBOX_ERROR_INVALID_APP_CONTAINER; + + typedef HRESULT (WINAPI* AppContainerRegisterSidPtr)(PSID sid, + LPCWSTR moniker, + LPCWSTR display_name); + static AppContainerRegisterSidPtr AppContainerRegisterSid = NULL; + + if (!AppContainerRegisterSid) { + AppContainerRegisterSid = + BindFunction<AppContainerRegisterSidPtr>("AppContainerRegisterSid"); + } + + ResultCode operation_result = SBOX_ERROR_GENERIC; + if (AppContainerRegisterSid) { + HRESULT rv = AppContainerRegisterSid(local_sid, name.c_str(), name.c_str()); + if (SUCCEEDED(rv)) + operation_result = SBOX_ALL_OK; + else + DLOG(ERROR) << "AppContainerRegisterSid error:" << std::hex << rv; + } + LocalFree(local_sid); + return operation_result; +} + +ResultCode DeleteAppContainer(const base::string16& sid) { + PSID local_sid; + if (!ConvertStringSidToSid(sid.c_str(), &local_sid)) + return SBOX_ERROR_INVALID_APP_CONTAINER; + + typedef HRESULT (WINAPI* AppContainerUnregisterSidPtr)(PSID sid); + static AppContainerUnregisterSidPtr AppContainerUnregisterSid = NULL; + + if (!AppContainerUnregisterSid) { + AppContainerUnregisterSid = + BindFunction<AppContainerUnregisterSidPtr>("AppContainerUnregisterSid"); + } + + ResultCode operation_result = SBOX_ERROR_GENERIC; + if (AppContainerUnregisterSid) { + HRESULT rv = AppContainerUnregisterSid(local_sid); + if (SUCCEEDED(rv)) + operation_result = SBOX_ALL_OK; + else + DLOG(ERROR) << "AppContainerUnregisterSid error:" << std::hex << rv; + } + LocalFree(local_sid); + return operation_result; +} + +base::string16 LookupAppContainer(const base::string16& sid) { + PSID local_sid; + if (!ConvertStringSidToSid(sid.c_str(), &local_sid)) + return base::string16(); + + typedef HRESULT (WINAPI* AppContainerLookupMonikerPtr)(PSID sid, + LPWSTR* moniker); + typedef BOOLEAN (WINAPI* AppContainerFreeMemoryPtr)(void* ptr); + + static AppContainerLookupMonikerPtr AppContainerLookupMoniker = NULL; + static AppContainerFreeMemoryPtr AppContainerFreeMemory = NULL; + + if (!AppContainerLookupMoniker || !AppContainerFreeMemory) { + AppContainerLookupMoniker = + BindFunction<AppContainerLookupMonikerPtr>("AppContainerLookupMoniker"); + AppContainerFreeMemory = + BindFunction<AppContainerFreeMemoryPtr>("AppContainerFreeMemory"); + } + + if (!AppContainerLookupMoniker || !AppContainerFreeMemory) + return base::string16(); + + wchar_t* buffer = NULL; + HRESULT rv = AppContainerLookupMoniker(local_sid, &buffer); + if (FAILED(rv)) + return base::string16(); + + base::string16 name(buffer); + if (!AppContainerFreeMemory(buffer)) + NOTREACHED(); + return name; +} + +} // namespace sandbox diff --git a/sandbox/win/src/app_container.h b/sandbox/win/src/app_container.h new file mode 100644 index 0000000000..8125d706fb --- /dev/null +++ b/sandbox/win/src/app_container.h @@ -0,0 +1,69 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_APP_CONTAINER_H_ +#define SANDBOX_WIN_SRC_APP_CONTAINER_H_ + +#include <windows.h> + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace base { +namespace win { +class StartupInformation; +} +} + +namespace sandbox { + +// Maintains an attribute list to be used during creation of a new sandboxed +// process. +class AppContainerAttributes { + public: + AppContainerAttributes(); + ~AppContainerAttributes(); + + // Sets the AppContainer and capabilities to be used with the new process. + ResultCode SetAppContainer(const base::string16& app_container_sid, + const std::vector<base::string16>& capabilities); + + // Updates the proc_thred attribute list of the provided startup_information + // with the app container related data. + // WARNING: startup_information just points back to our internal memory, so + // the lifetime of this object has to be greater than the lifetime of the + // provided startup_information. + ResultCode ShareForStartup( + base::win::StartupInformation* startup_information) const; + + bool HasAppContainer() const; + + private: + SECURITY_CAPABILITIES capabilities_; + std::vector<SID_AND_ATTRIBUTES> attributes_; + + DISALLOW_COPY_AND_ASSIGN(AppContainerAttributes); +}; + +// Creates a new AppContainer on the system. |sid| is the identifier of the new +// AppContainer, and |name| will be used as both the display name and moniker. +// This function fails if the OS doesn't support AppContainers, or if there is +// an AppContainer registered with the same id. +ResultCode CreateAppContainer(const base::string16& sid, + const base::string16& name); + +// Deletes an AppContainer previously created with a successfull call to +// CreateAppContainer. +ResultCode DeleteAppContainer(const base::string16& sid); + +// Retrieves the name associated with the provided AppContainer sid. Returns an +// empty string if the AppContainer is not registered with the system. +base::string16 LookupAppContainer(const base::string16& sid); + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_APP_CONTAINER_H_ diff --git a/sandbox/win/src/app_container_test.cc b/sandbox/win/src/app_container_test.cc new file mode 100644 index 0000000000..ced5cbde7c --- /dev/null +++ b/sandbox/win/src/app_container_test.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> + +#define _ATL_NO_EXCEPTIONS +#include <atlbase.h> +#include <atlsecurity.h> + +#include "base/strings/string16.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/sync_policy_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const wchar_t kAppContainerName[] = L"sbox_test"; +const wchar_t kAppContainerSid[] = + L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-" + L"924012148-2839372144"; + +const ULONG kSharing = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; + +HANDLE CreateTaggedEvent(const base::string16& name, + const base::string16& sid) { + base::win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, name.c_str())); + if (!event.IsValid()) + return NULL; + + wchar_t file_name[MAX_PATH] = {}; + wchar_t temp_directory[MAX_PATH] = {}; + GetTempPath(MAX_PATH, temp_directory); + GetTempFileName(temp_directory, L"test", 0, file_name); + + base::win::ScopedHandle file; + file.Set(CreateFile(file_name, GENERIC_READ | STANDARD_RIGHTS_READ, kSharing, + NULL, OPEN_EXISTING, 0, NULL)); + DeleteFile(file_name); + if (!file.IsValid()) + return NULL; + + CSecurityDesc sd; + if (!AtlGetSecurityDescriptor(file.Get(), SE_FILE_OBJECT, &sd, + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION)) { + return NULL; + } + + PSID local_sid; + if (!ConvertStringSidToSid(sid.c_str(), &local_sid)) + return NULL; + + CDacl new_dacl; + sd.GetDacl(&new_dacl); + CSid csid(reinterpret_cast<SID*>(local_sid)); + new_dacl.AddAllowedAce(csid, EVENT_ALL_ACCESS); + if (!AtlSetDacl(event.Get(), SE_KERNEL_OBJECT, new_dacl)) + event.Close(); + + LocalFree(local_sid); + return event.IsValid() ? event.Take() : NULL; +} + +} // namespace + +namespace sandbox { + +TEST(AppContainerTest, AllowOpenEvent) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return; + + TestRunner runner(JOB_UNPROTECTED, USER_UNPROTECTED, USER_UNPROTECTED); + + const wchar_t capability[] = L"S-1-15-3-12345678-87654321"; + base::win::ScopedHandle handle(CreateTaggedEvent(L"test", capability)); + ASSERT_TRUE(handle.IsValid()); + + EXPECT_EQ(SBOX_ALL_OK, + runner.broker()->InstallAppContainer(kAppContainerSid, + kAppContainerName)); + EXPECT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetCapability(capability)); + EXPECT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetAppContainer(kAppContainerSid)); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_Open f test")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_Open f test")); + EXPECT_EQ(SBOX_ALL_OK, + runner.broker()->UninstallAppContainer(kAppContainerSid)); +} + +TEST(AppContainerTest, DenyOpenEvent) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return; + + TestRunner runner(JOB_UNPROTECTED, USER_UNPROTECTED, USER_UNPROTECTED); + + const wchar_t capability[] = L"S-1-15-3-12345678-87654321"; + base::win::ScopedHandle handle(CreateTaggedEvent(L"test", capability)); + ASSERT_TRUE(handle.IsValid()); + + EXPECT_EQ(SBOX_ALL_OK, + runner.broker()->InstallAppContainer(kAppContainerSid, + kAppContainerName)); + EXPECT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetAppContainer(kAppContainerSid)); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test")); + EXPECT_EQ(SBOX_ALL_OK, + runner.broker()->UninstallAppContainer(kAppContainerSid)); +} + +TEST(AppContainerTest, NoImpersonation) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return; + + TestRunner runner(JOB_UNPROTECTED, USER_LIMITED, USER_LIMITED); + EXPECT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetAppContainer(kAppContainerSid)); +} + +TEST(AppContainerTest, WantsImpersonation) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return; + + TestRunner runner(JOB_UNPROTECTED, USER_UNPROTECTED, USER_NON_ADMIN); + EXPECT_EQ(SBOX_ERROR_CANNOT_INIT_APPCONTAINER, + runner.GetPolicy()->SetAppContainer(kAppContainerSid)); +} + +TEST(AppContainerTest, RequiresImpersonation) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return; + + TestRunner runner(JOB_UNPROTECTED, USER_RESTRICTED, USER_RESTRICTED); + EXPECT_EQ(SBOX_ERROR_CANNOT_INIT_APPCONTAINER, + runner.GetPolicy()->SetAppContainer(kAppContainerSid)); +} + +TEST(AppContainerTest, DenyOpenEventForLowBox) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return; + + TestRunner runner(JOB_UNPROTECTED, USER_UNPROTECTED, USER_UNPROTECTED); + + base::win::ScopedHandle event(CreateEvent(NULL, FALSE, FALSE, L"test")); + ASSERT_TRUE(event.IsValid()); + + EXPECT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetLowBox(kAppContainerSid)); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test")); +} + +// TODO(shrikant): Please add some tests to prove usage of lowbox token like +// socket connection to local server in lock down mode. + +} // namespace sandbox diff --git a/sandbox/win/src/app_container_unittest.cc b/sandbox/win/src/app_container_unittest.cc new file mode 100644 index 0000000000..4bce16a42b --- /dev/null +++ b/sandbox/win/src/app_container_unittest.cc @@ -0,0 +1,58 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/windows_version.h" +#include "sandbox/win/src/app_container.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Tests the low level AppContainer interface. +TEST(AppContainerTest, CreateAppContainer) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return; + + const wchar_t kName[] = L"Test"; + const wchar_t kValidSid[] = L"S-1-15-2-12345-234-567-890-123-456-789"; + + EXPECT_TRUE(LookupAppContainer(kValidSid).empty()); + EXPECT_EQ(SBOX_ERROR_GENERIC, DeleteAppContainer(kValidSid)); + + EXPECT_EQ(SBOX_ALL_OK, CreateAppContainer(kValidSid, kName)); + EXPECT_EQ(SBOX_ERROR_GENERIC, CreateAppContainer(kValidSid, kName)); + EXPECT_EQ(kName, LookupAppContainer(kValidSid)); + EXPECT_EQ(SBOX_ALL_OK, DeleteAppContainer(kValidSid)); + + EXPECT_TRUE(LookupAppContainer(kValidSid).empty()); + EXPECT_EQ(SBOX_ERROR_GENERIC, DeleteAppContainer(kValidSid)); + + EXPECT_EQ(SBOX_ERROR_INVALID_APP_CONTAINER, + CreateAppContainer(L"Foo", kName)); +} + +// Tests handling of security capabilities on the attribute list. +TEST(AppContainerTest, SecurityCapabilities) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return; + + scoped_ptr<AppContainerAttributes> attributes(new AppContainerAttributes); + std::vector<base::string16> capabilities; + EXPECT_EQ(SBOX_ERROR_INVALID_APP_CONTAINER, + attributes->SetAppContainer(L"S-1-foo", capabilities)); + + EXPECT_EQ(SBOX_ALL_OK, + attributes->SetAppContainer(L"S-1-15-2-12345-234", capabilities)); + EXPECT_TRUE(attributes->HasAppContainer()); + + attributes.reset(new AppContainerAttributes); + capabilities.push_back(L"S-1-15-3-12345678-87654321"); + capabilities.push_back(L"S-1-15-3-1"); + capabilities.push_back(L"S-1-15-3-2"); + capabilities.push_back(L"S-1-15-3-3"); + EXPECT_EQ(SBOX_ALL_OK, + attributes->SetAppContainer(L"S-1-15-2-1-2", capabilities)); + EXPECT_TRUE(attributes->HasAppContainer()); +} + +} // namespace sandbox diff --git a/sandbox/win/src/broker_services.cc b/sandbox/win/src/broker_services.cc new file mode 100644 index 0000000000..57aa51a52d --- /dev/null +++ b/sandbox/win/src/broker_services.cc @@ -0,0 +1,623 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/broker_services.h" + +#include <AclAPI.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/platform_thread.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/startup_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/app_container.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/sandbox_policy_base.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/target_process.h" +#include "sandbox/win/src/win2k_threadpool.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// Utility function to associate a completion port to a job object. +bool AssociateCompletionPort(HANDLE job, HANDLE port, void* key) { + JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_acp = { key, port }; + return ::SetInformationJobObject(job, + JobObjectAssociateCompletionPortInformation, + &job_acp, sizeof(job_acp))? true : false; +} + +// Utility function to do the cleanup necessary when something goes wrong +// while in SpawnTarget and we must terminate the target process. +sandbox::ResultCode SpawnCleanup(sandbox::TargetProcess* target, DWORD error) { + if (0 == error) + error = ::GetLastError(); + + target->Terminate(); + delete target; + ::SetLastError(error); + return sandbox::SBOX_ERROR_GENERIC; +} + +// the different commands that you can send to the worker thread that +// executes TargetEventsThread(). +enum { + THREAD_CTRL_NONE, + THREAD_CTRL_REMOVE_PEER, + THREAD_CTRL_QUIT, + THREAD_CTRL_LAST, +}; + +// Helper structure that allows the Broker to associate a job notification +// with a job object and with a policy. +struct JobTracker { + HANDLE job; + sandbox::PolicyBase* policy; + JobTracker(HANDLE cjob, sandbox::PolicyBase* cpolicy) + : job(cjob), policy(cpolicy) { + } +}; + +// Helper structure that allows the broker to track peer processes +struct PeerTracker { + HANDLE wait_object; + base::win::ScopedHandle process; + DWORD id; + HANDLE job_port; + PeerTracker(DWORD process_id, HANDLE broker_job_port) + : wait_object(NULL), id(process_id), job_port(broker_job_port) { + } +}; + +void DeregisterPeerTracker(PeerTracker* peer) { + // Deregistration shouldn't fail, but we leak rather than crash if it does. + if (::UnregisterWaitEx(peer->wait_object, INVALID_HANDLE_VALUE)) { + delete peer; + } else { + NOTREACHED(); + } +} + +// Utility function to determine whether a token for the specified policy can +// be cached. +bool IsTokenCacheable(const sandbox::PolicyBase* policy) { + const sandbox::AppContainerAttributes* app_container = + policy->GetAppContainer(); + + // We cannot cache tokens with an app container or lowbox. + if (app_container || policy->GetLowBoxSid()) + return false; + + return true; +} + +// Utility function to pack token values into a key for the cache map. +uint32_t GenerateTokenCacheKey(const sandbox::PolicyBase* policy) { + const size_t kTokenShift = 3; + uint32_t key; + + DCHECK(IsTokenCacheable(policy)); + + // Make sure our token values aren't too large to pack into the key. + static_assert(sandbox::USER_LAST <= (1 << kTokenShift), + "TokenLevel too large"); + static_assert(sandbox::INTEGRITY_LEVEL_LAST <= (1 << kTokenShift), + "IntegrityLevel too large"); + static_assert(sizeof(key) < (kTokenShift * 3), + "Token key type too small"); + + // The key is the enum values shifted to avoid overlap and OR'd together. + key = policy->GetInitialTokenLevel(); + key <<= kTokenShift; + key |= policy->GetLockdownTokenLevel(); + key <<= kTokenShift; + key |= policy->GetIntegrityLevel(); + + return key; +} + +} // namespace + +namespace sandbox { + +BrokerServicesBase::BrokerServicesBase() + : thread_pool_(NULL), job_port_(NULL), no_targets_(NULL), + job_thread_(NULL) { +} + +// The broker uses a dedicated worker thread that services the job completion +// port to perform policy notifications and associated cleanup tasks. +ResultCode BrokerServicesBase::Init() { + if ((NULL != job_port_) || (NULL != thread_pool_)) + return SBOX_ERROR_UNEXPECTED_CALL; + + ::InitializeCriticalSection(&lock_); + + job_port_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + if (NULL == job_port_) + return SBOX_ERROR_GENERIC; + + no_targets_ = ::CreateEventW(NULL, TRUE, FALSE, NULL); + + job_thread_ = ::CreateThread(NULL, 0, // Default security and stack. + TargetEventsThread, this, NULL, NULL); + if (NULL == job_thread_) + return SBOX_ERROR_GENERIC; + + return SBOX_ALL_OK; +} + +// The destructor should only be called when the Broker process is terminating. +// Since BrokerServicesBase is a singleton, this is called from the CRT +// termination handlers, if this code lives on a DLL it is called during +// DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot +// wait for threads here. +BrokerServicesBase::~BrokerServicesBase() { + // If there is no port Init() was never called successfully. + if (!job_port_) + return; + + // Closing the port causes, that no more Job notifications are delivered to + // the worker thread and also causes the thread to exit. This is what we + // want to do since we are going to close all outstanding Jobs and notifying + // the policy objects ourselves. + ::PostQueuedCompletionStatus(job_port_, 0, THREAD_CTRL_QUIT, FALSE); + ::CloseHandle(job_port_); + + if (WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_, 1000)) { + // Cannot clean broker services. + NOTREACHED(); + return; + } + + JobTrackerList::iterator it; + for (it = tracker_list_.begin(); it != tracker_list_.end(); ++it) { + JobTracker* tracker = (*it); + FreeResources(tracker); + delete tracker; + } + ::CloseHandle(job_thread_); + delete thread_pool_; + ::CloseHandle(no_targets_); + + // Cancel the wait events and delete remaining peer trackers. + for (PeerTrackerMap::iterator it = peer_map_.begin(); + it != peer_map_.end(); ++it) { + DeregisterPeerTracker(it->second); + } + + // If job_port_ isn't NULL, assumes that the lock has been initialized. + if (job_port_) + ::DeleteCriticalSection(&lock_); + + // Close any token in the cache. + for (TokenCacheMap::iterator it = token_cache_.begin(); + it != token_cache_.end(); ++it) { + ::CloseHandle(it->second.first); + ::CloseHandle(it->second.second); + } +} + +TargetPolicy* BrokerServicesBase::CreatePolicy() { + // If you change the type of the object being created here you must also + // change the downcast to it in SpawnTarget(). + return new PolicyBase; +} + +void BrokerServicesBase::FreeResources(JobTracker* tracker) { + if (NULL != tracker->policy) { + BOOL res = ::TerminateJobObject(tracker->job, SBOX_ALL_OK); + DCHECK(res); + // Closing the job causes the target process to be destroyed so this + // needs to happen before calling OnJobEmpty(). + res = ::CloseHandle(tracker->job); + DCHECK(res); + // In OnJobEmpty() we don't actually use the job handle directly. + tracker->policy->OnJobEmpty(tracker->job); + tracker->policy->Release(); + tracker->policy = NULL; + } +} + +// The worker thread stays in a loop waiting for asynchronous notifications +// from the job objects. Right now we only care about knowing when the last +// process on a job terminates, but in general this is the place to tell +// the policy about events. +DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) { + if (NULL == param) + return 1; + + base::PlatformThread::SetName("BrokerEvent"); + + BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param); + HANDLE port = broker->job_port_; + HANDLE no_targets = broker->no_targets_; + + int target_counter = 0; + ::ResetEvent(no_targets); + + while (true) { + DWORD events = 0; + ULONG_PTR key = 0; + LPOVERLAPPED ovl = NULL; + + if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE)) + // this call fails if the port has been closed before we have a + // chance to service the last packet which is 'exit' anyway so + // this is not an error. + return 1; + + if (key > THREAD_CTRL_LAST) { + // The notification comes from a job object. There are nine notifications + // that jobs can send and some of them depend on the job attributes set. + JobTracker* tracker = reinterpret_cast<JobTracker*>(key); + + switch (events) { + case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: { + // The job object has signaled that the last process associated + // with it has terminated. Assuming there is no way for a process + // to appear out of thin air in this job, it safe to assume that + // we can tell the policy to destroy the target object, and for + // us to release our reference to the policy object. + FreeResources(tracker); + break; + } + + case JOB_OBJECT_MSG_NEW_PROCESS: { + ++target_counter; + if (1 == target_counter) { + ::ResetEvent(no_targets); + } + break; + } + + case JOB_OBJECT_MSG_EXIT_PROCESS: + case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: { + { + AutoLock lock(&broker->lock_); + broker->child_process_ids_.erase( + static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl))); + } + --target_counter; + if (0 == target_counter) + ::SetEvent(no_targets); + + DCHECK(target_counter >= 0); + break; + } + + case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: { + break; + } + + case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: { + BOOL res = ::TerminateJobObject(tracker->job, + SBOX_FATAL_MEMORY_EXCEEDED); + DCHECK(res); + break; + } + + default: { + NOTREACHED(); + break; + } + } + } else if (THREAD_CTRL_REMOVE_PEER == key) { + // Remove a process from our list of peers. + AutoLock lock(&broker->lock_); + PeerTrackerMap::iterator it = broker->peer_map_.find( + static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl))); + DeregisterPeerTracker(it->second); + broker->peer_map_.erase(it); + } else if (THREAD_CTRL_QUIT == key) { + // The broker object is being destroyed so the thread needs to exit. + return 0; + } else { + // We have not implemented more commands. + NOTREACHED(); + } + } + + NOTREACHED(); + return 0; +} + +// SpawnTarget does all the interesting sandbox setup and creates the target +// process inside the sandbox. +ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, + const wchar_t* command_line, + TargetPolicy* policy, + PROCESS_INFORMATION* target_info) { + if (!exe_path) + return SBOX_ERROR_BAD_PARAMS; + + if (!policy) + return SBOX_ERROR_BAD_PARAMS; + + // Even though the resources touched by SpawnTarget can be accessed in + // multiple threads, the method itself cannot be called from more than + // 1 thread. This is to protect the global variables used while setting up + // the child process. + static DWORD thread_id = ::GetCurrentThreadId(); + DCHECK(thread_id == ::GetCurrentThreadId()); + + AutoLock lock(&lock_); + + // This downcast is safe as long as we control CreatePolicy() + PolicyBase* policy_base = static_cast<PolicyBase*>(policy); + + if (policy_base->GetAppContainer() && policy_base->GetLowBoxSid()) + return SBOX_ERROR_BAD_PARAMS; + + // Construct the tokens and the job object that we are going to associate + // with the soon to be created target process. + HANDLE initial_token_temp; + HANDLE lockdown_token_temp; + ResultCode result = SBOX_ALL_OK; + + if (IsTokenCacheable(policy_base)) { + // Create the master tokens only once and save them in a cache. That way + // can just duplicate them to avoid hammering LSASS on every sandboxed + // process launch. + uint32_t token_key = GenerateTokenCacheKey(policy_base); + TokenCacheMap::iterator it = token_cache_.find(token_key); + if (it != token_cache_.end()) { + initial_token_temp = it->second.first; + lockdown_token_temp = it->second.second; + } else { + result = + policy_base->MakeTokens(&initial_token_temp, &lockdown_token_temp); + if (SBOX_ALL_OK != result) + return result; + token_cache_[token_key] = + std::pair<HANDLE, HANDLE>(initial_token_temp, lockdown_token_temp); + } + + if (!::DuplicateToken(initial_token_temp, SecurityImpersonation, + &initial_token_temp)) { + return SBOX_ERROR_GENERIC; + } + + if (!::DuplicateTokenEx(lockdown_token_temp, TOKEN_ALL_ACCESS, 0, + SecurityIdentification, TokenPrimary, + &lockdown_token_temp)) { + return SBOX_ERROR_GENERIC; + } + } else { + result = policy_base->MakeTokens(&initial_token_temp, &lockdown_token_temp); + if (SBOX_ALL_OK != result) + return result; + } + + base::win::ScopedHandle initial_token(initial_token_temp); + base::win::ScopedHandle lockdown_token(lockdown_token_temp); + + HANDLE job_temp; + result = policy_base->MakeJobObject(&job_temp); + if (SBOX_ALL_OK != result) + return result; + + base::win::ScopedHandle job(job_temp); + + // Initialize the startup information from the policy. + base::win::StartupInformation startup_info; + // The liftime of |mitigations| and |inherit_handle_list| have to be at least + // as long as |startup_info| because |UpdateProcThreadAttribute| requires that + // its |lpValue| parameter persist until |DeleteProcThreadAttributeList| is + // called; StartupInformation's destructor makes such a call. + DWORD64 mitigations; + + std::vector<HANDLE> inherited_handle_list; + + base::string16 desktop = policy_base->GetAlternateDesktop(); + if (!desktop.empty()) { + startup_info.startup_info()->lpDesktop = + const_cast<wchar_t*>(desktop.c_str()); + } + + bool inherit_handles = false; + + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + int attribute_count = 0; + const AppContainerAttributes* app_container = + policy_base->GetAppContainer(); + if (app_container) + ++attribute_count; + + size_t mitigations_size; + ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(), + &mitigations, &mitigations_size); + if (mitigations) + ++attribute_count; + + HANDLE stdout_handle = policy_base->GetStdoutHandle(); + HANDLE stderr_handle = policy_base->GetStderrHandle(); + + if (stdout_handle != INVALID_HANDLE_VALUE) + inherited_handle_list.push_back(stdout_handle); + + // Handles in the list must be unique. + if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE) + inherited_handle_list.push_back(stderr_handle); + + HandleList policy_handle_list = policy_base->GetHandlesBeingShared(); + + for (auto handle : policy_handle_list) + inherited_handle_list.push_back(handle); + + if (inherited_handle_list.size()) + ++attribute_count; + + if (!startup_info.InitializeProcThreadAttributeList(attribute_count)) + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + + if (app_container) { + result = app_container->ShareForStartup(&startup_info); + if (SBOX_ALL_OK != result) + return result; + } + + if (mitigations) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations, + mitigations_size)) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + } + + if (inherited_handle_list.size()) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + &inherited_handle_list[0], + sizeof(HANDLE) * inherited_handle_list.size())) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES; + startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE; + startup_info.startup_info()->hStdOutput = stdout_handle; + startup_info.startup_info()->hStdError = stderr_handle; + // Allowing inheritance of handles is only secure now that we + // have limited which handles will be inherited. + inherit_handles = true; + } + } + + // Construct the thread pool here in case it is expensive. + // The thread pool is shared by all the targets + if (NULL == thread_pool_) + thread_pool_ = new Win2kThreadPool(); + + // Create the TargetProces object and spawn the target suspended. Note that + // Brokerservices does not own the target object. It is owned by the Policy. + base::win::ScopedProcessInformation process_info; + TargetProcess* target = new TargetProcess(initial_token.Take(), + lockdown_token.Take(), + job.Get(), + thread_pool_); + + DWORD win_result = target->Create(exe_path, command_line, inherit_handles, + policy_base->GetLowBoxSid() ? true : false, + startup_info, &process_info); + + policy_base->ClearSharedHandles(); + + if (ERROR_SUCCESS != win_result) { + SpawnCleanup(target, win_result); + return SBOX_ERROR_CREATE_PROCESS; + } + + // Now the policy is the owner of the target. + if (!policy_base->AddTarget(target)) { + return SpawnCleanup(target, 0); + } + + // We are going to keep a pointer to the policy because we'll call it when + // the job object generates notifications using the completion port. + policy_base->AddRef(); + if (job.IsValid()) { + scoped_ptr<JobTracker> tracker(new JobTracker(job.Take(), policy_base)); + + // There is no obvious recovery after failure here. Previous version with + // SpawnCleanup() caused deletion of TargetProcess twice. crbug.com/480639 + CHECK(AssociateCompletionPort(tracker->job, job_port_, tracker.get())); + + // Save the tracker because in cleanup we might need to force closing + // the Jobs. + tracker_list_.push_back(tracker.release()); + child_process_ids_.insert(process_info.process_id()); + } else { + // We have to signal the event once here because the completion port will + // never get a message that this target is being terminated thus we should + // not block WaitForAllTargets until we have at least one target with job. + if (child_process_ids_.empty()) + ::SetEvent(no_targets_); + // We can not track the life time of such processes and it is responsibility + // of the host application to make sure that spawned targets without jobs + // are terminated when the main application don't need them anymore. + } + + *target_info = process_info.Take(); + return SBOX_ALL_OK; +} + + +ResultCode BrokerServicesBase::WaitForAllTargets() { + ::WaitForSingleObject(no_targets_, INFINITE); + return SBOX_ALL_OK; +} + +bool BrokerServicesBase::IsActiveTarget(DWORD process_id) { + AutoLock lock(&lock_); + return child_process_ids_.find(process_id) != child_process_ids_.end() || + peer_map_.find(process_id) != peer_map_.end(); +} + +VOID CALLBACK BrokerServicesBase::RemovePeer(PVOID parameter, BOOLEAN timeout) { + PeerTracker* peer = reinterpret_cast<PeerTracker*>(parameter); + // Don't check the return code because we this may fail (safely) at shutdown. + ::PostQueuedCompletionStatus( + peer->job_port, 0, THREAD_CTRL_REMOVE_PEER, + reinterpret_cast<LPOVERLAPPED>(static_cast<uintptr_t>(peer->id))); +} + +ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) { + scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process), + job_port_)); + if (!peer->id) + return SBOX_ERROR_GENERIC; + + HANDLE process_handle; + if (!::DuplicateHandle(::GetCurrentProcess(), peer_process, + ::GetCurrentProcess(), &process_handle, + SYNCHRONIZE, FALSE, 0)) { + return SBOX_ERROR_GENERIC; + } + peer->process.Set(process_handle); + + AutoLock lock(&lock_); + if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second) + return SBOX_ERROR_BAD_PARAMS; + + if (!::RegisterWaitForSingleObject( + &peer->wait_object, peer->process.Get(), RemovePeer, peer.get(), + INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) { + peer_map_.erase(peer->id); + return SBOX_ERROR_GENERIC; + } + + // Release the pointer since it will be cleaned up by the callback. + peer.release(); + return SBOX_ALL_OK; +} + +ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid, + const wchar_t* name) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return SBOX_ERROR_UNSUPPORTED; + + base::string16 old_name = LookupAppContainer(sid); + if (old_name.empty()) + return CreateAppContainer(sid, name); + + if (old_name != name) + return SBOX_ERROR_INVALID_APP_CONTAINER; + + return SBOX_ALL_OK; +} + +ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return SBOX_ERROR_UNSUPPORTED; + + base::string16 name = LookupAppContainer(sid); + if (name.empty()) + return SBOX_ERROR_INVALID_APP_CONTAINER; + + return DeleteAppContainer(sid); +} + +} // namespace sandbox diff --git a/sandbox/win/src/broker_services.h b/sandbox/win/src/broker_services.h new file mode 100644 index 0000000000..3e7a1790ee --- /dev/null +++ b/sandbox/win/src/broker_services.h @@ -0,0 +1,118 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_BROKER_SERVICES_H_ +#define SANDBOX_WIN_SRC_BROKER_SERVICES_H_ + +#include <list> +#include <map> +#include <set> +#include <utility> +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/job.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sharedmem_ipc_server.h" +#include "sandbox/win/src/win2k_threadpool.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +struct JobTracker; +struct PeerTracker; + +} // namespace + +namespace sandbox { + +class PolicyBase; + +// BrokerServicesBase --------------------------------------------------------- +// Broker implementation version 0 +// +// This is an implementation of the interface BrokerServices and +// of the associated TargetProcess interface. In this implementation +// TargetProcess is a friend of BrokerServices where the later manages a +// collection of the former. +class BrokerServicesBase final : public BrokerServices, + public SingletonBase<BrokerServicesBase> { + public: + BrokerServicesBase(); + + ~BrokerServicesBase(); + + // BrokerServices interface. + ResultCode Init() override; + TargetPolicy* CreatePolicy() override; + ResultCode SpawnTarget(const wchar_t* exe_path, + const wchar_t* command_line, + TargetPolicy* policy, + PROCESS_INFORMATION* target) override; + ResultCode WaitForAllTargets() override; + ResultCode AddTargetPeer(HANDLE peer_process) override; + ResultCode InstallAppContainer(const wchar_t* sid, + const wchar_t* name) override; + ResultCode UninstallAppContainer(const wchar_t* sid) override; + + // Checks if the supplied process ID matches one of the broker's active + // target processes + // Returns: + // true if there is an active target process for this ID, otherwise false. + bool IsActiveTarget(DWORD process_id); + + private: + // Releases the Job and notifies the associated Policy object to its + // resources as well. + static void FreeResources(JobTracker* tracker); + + // The routine that the worker thread executes. It is in charge of + // notifications and cleanup-related tasks. + static DWORD WINAPI TargetEventsThread(PVOID param); + + // Removes a target peer from the process list if it expires. + static VOID CALLBACK RemovePeer(PVOID parameter, BOOLEAN timeout); + + // The completion port used by the job objects to communicate events to + // the worker thread. + HANDLE job_port_; + + // Handle to a manual-reset event that is signaled when the total target + // process count reaches zero. + HANDLE no_targets_; + + // Handle to the worker thread that reacts to job notifications. + HANDLE job_thread_; + + // Lock used to protect the list of targets from being modified by 2 + // threads at the same time. + CRITICAL_SECTION lock_; + + // provides a pool of threads that are used to wait on the IPC calls. + ThreadProvider* thread_pool_; + + // List of the trackers for closing and cleanup purposes. + typedef std::list<JobTracker*> JobTrackerList; + JobTrackerList tracker_list_; + + // Maps peer process IDs to the saved handle and wait event. + // Prevents peer callbacks from accessing the broker after destruction. + typedef std::map<DWORD, PeerTracker*> PeerTrackerMap; + PeerTrackerMap peer_map_; + + // Provides a fast lookup to identify sandboxed processes that belong to a + // job. Consult |jobless_process_handles_| for handles of pocess without job. + std::set<DWORD> child_process_ids_; + + typedef std::map<uint32_t, std::pair<HANDLE, HANDLE>> TokenCacheMap; + TokenCacheMap token_cache_; + + DISALLOW_COPY_AND_ASSIGN(BrokerServicesBase); +}; + +} // namespace sandbox + + +#endif // SANDBOX_WIN_SRC_BROKER_SERVICES_H_ diff --git a/sandbox/win/src/crosscall_client.h b/sandbox/win/src/crosscall_client.h new file mode 100644 index 0000000000..5b1bce71b2 --- /dev/null +++ b/sandbox/win/src/crosscall_client.h @@ -0,0 +1,483 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_CROSSCALL_CLIENT_H_ +#define SANDBOX_SRC_CROSSCALL_CLIENT_H_ + +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/sandbox.h" + +// This header defines the CrossCall(..) family of templated functions +// Their purpose is to simulate the syntax of regular call but to generate +// and IPC from the client-side. +// +// The basic pattern is to +// 1) use template argument deduction to compute the size of each +// parameter and the appropriate copy method +// 2) pack the parameters in the appropriate ActualCallParams< > object +// 3) call the IPC interface IPCProvider::DoCall( ) +// +// The general interface of CrossCall is: +// ResultCode CrossCall(IPCProvider& ipc_provider, +// uint32 tag, +// const Par1& p1, const Par2& p2,...pn +// CrossCallReturn* answer) +// +// where: +// ipc_provider: is a specific implementation of the ipc transport see +// sharedmem_ipc_server.h for an example. +// tag : is the unique id for this IPC call. Is used to route the call to +// the appropriate service. +// p1, p2,.. pn : The input parameters of the IPC. Use only simple types +// and wide strings (can add support for others). +// answer : If the IPC was successful. The server-side answer is here. The +// interpretation of the answer is private to client and server. +// +// The return value is ALL_OK if the IPC was delivered to the server, other +// return codes indicate that the IPC transport failed to deliver it. +namespace sandbox { + +// this is the assumed channel size. This can be overridden in a given +// IPC implementation. +const uint32 kIPCChannelSize = 1024; + +// The copy helper uses templates to deduce the appropriate copy function to +// copy the input parameters in the buffer that is going to be send across the +// IPC. These template facility can be made more sophisticated as need arises. + +// The default copy helper. It catches the general case where no other +// specialized template matches better. We set the type to UINT32_TYPE, so this +// only works with objects whose size is 32 bits. +template<typename T> +class CopyHelper { + public: + CopyHelper(const T& t) : t_(t) {} + + // Returns the pointer to the start of the input. + const void* GetStart() const { + return &t_; + } + + // Update the stored value with the value in the buffer. This is not + // supported for this type. + bool Update(void* buffer) { + // Not supported; + return true; + } + + // Returns the size of the input in bytes. + uint32 GetSize() const { + return sizeof(T); + } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { + return false; + } + + // Returns this object's type. + ArgType GetType() { + static_assert(sizeof(T) == sizeof(uint32), "specialization needed"); + return UINT32_TYPE; + } + + private: + const T& t_; +}; + +// This copy helper template specialization if for the void pointer +// case both 32 and 64 bit. +template<> +class CopyHelper<void*> { + public: + CopyHelper(void* t) : t_(t) {} + + // Returns the pointer to the start of the input. + const void* GetStart() const { + return &t_; + } + + // Update the stored value with the value in the buffer. This is not + // supported for this type. + bool Update(void* buffer) { + // Not supported; + return true; + } + + // Returns the size of the input in bytes. + uint32 GetSize() const { + return sizeof(t_); + } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { + return false; + } + + // Returns this object's type. + ArgType GetType() { + return VOIDPTR_TYPE; + } + + private: + const void* t_; +}; + +// This copy helper template specialization catches the cases where the +// parameter is a pointer to a string. +template<> +class CopyHelper<const wchar_t*> { + public: + CopyHelper(const wchar_t* t) + : t_(t) { + } + + // Returns the pointer to the start of the string. + const void* GetStart() const { + return t_; + } + + // Update the stored value with the value in the buffer. This is not + // supported for this type. + bool Update(void* buffer) { + // Not supported; + return true; + } + + // Returns the size of the string in bytes. We define a NULL string to + // be of zero length. + uint32 GetSize() const { + __try { + return (!t_) ? 0 : static_cast<uint32>(StringLength(t_) * sizeof(t_[0])); + } + __except(EXCEPTION_EXECUTE_HANDLER) { + return kuint32max; + } + } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { + return false; + } + + ArgType GetType() { + return WCHAR_TYPE; + } + + private: + // We provide our not very optimized version of wcslen(), since we don't + // want to risk having the linker use the version in the CRT since the CRT + // might not be present when we do an early IPC call. + static size_t __cdecl StringLength(const wchar_t* wcs) { + const wchar_t *eos = wcs; + while (*eos++); + return static_cast<size_t>(eos - wcs - 1); + } + + const wchar_t* t_; +}; + +// Specialization for non-const strings. We just reuse the implementation of the +// const string specialization. +template<> +class CopyHelper<wchar_t*> : public CopyHelper<const wchar_t*> { + public: + typedef CopyHelper<const wchar_t*> Base; + CopyHelper(wchar_t* t) : Base(t) {} + + const void* GetStart() const { + return Base::GetStart(); + } + + bool Update(void* buffer) { + return Base::Update(buffer); + } + + uint32 GetSize() const { + return Base::GetSize(); + } + + bool IsInOut() { + return Base::IsInOut(); + } + + ArgType GetType() { + return Base::GetType(); + } +}; + +// Specialization for wchar_t arrays strings. We just reuse the implementation +// of the const string specialization. +template<size_t n> +class CopyHelper<const wchar_t[n]> : public CopyHelper<const wchar_t*> { + public: + typedef const wchar_t array[n]; + typedef CopyHelper<const wchar_t*> Base; + CopyHelper(array t) : Base(t) {} + + const void* GetStart() const { + return Base::GetStart(); + } + + bool Update(void* buffer) { + return Base::Update(buffer); + } + + uint32 GetSize() const { + return Base::GetSize(); + } + + bool IsInOut() { + return Base::IsInOut(); + } + + ArgType GetType() { + return Base::GetType(); + } +}; + +// Generic encapsulation class containing a pointer to a buffer and the +// size of the buffer. It is used by the IPC to be able to pass in/out +// parameters. +class InOutCountedBuffer : public CountedBuffer { + public: + InOutCountedBuffer(void* buffer, uint32 size) : CountedBuffer(buffer, size) {} +}; + +// This copy helper template specialization catches the cases where the +// parameter is a an input/output buffer. +template<> +class CopyHelper<InOutCountedBuffer> { + public: + CopyHelper(const InOutCountedBuffer t) : t_(t) {} + + // Returns the pointer to the start of the string. + const void* GetStart() const { + return t_.Buffer(); + } + + // Updates the buffer with the value from the new buffer in parameter. + bool Update(void* buffer) { + // We are touching user memory, this has to be done from inside a try + // except. + __try { + memcpy(t_.Buffer(), buffer, t_.Size()); + } + __except(EXCEPTION_EXECUTE_HANDLER) { + return false; + } + return true; + } + + // Returns the size of the string in bytes. We define a NULL string to + // be of zero length. + uint32 GetSize() const { + return t_.Size(); + } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { + return true; + } + + ArgType GetType() { + return INOUTPTR_TYPE; + } + + private: + const InOutCountedBuffer t_; +}; + +// The following two macros make it less error prone the generation +// of CrossCall functions with ever more input parameters. + +#define XCALL_GEN_PARAMS_OBJ(num, params) \ + typedef ActualCallParams<num, kIPCChannelSize> ActualParams; \ + void* raw_mem = ipc_provider.GetBuffer(); \ + if (NULL == raw_mem) \ + return SBOX_ERROR_NO_SPACE; \ + ActualParams* params = new(raw_mem) ActualParams(tag); + +#define XCALL_GEN_COPY_PARAM(num, params) \ + static_assert(kMaxIpcParams >= num, "too many parameters"); \ + CopyHelper<Par##num> ch##num(p##num); \ + if (!params->CopyParamIn(num - 1, ch##num.GetStart(), ch##num.GetSize(), \ + ch##num.IsInOut(), ch##num.GetType())) \ + return SBOX_ERROR_NO_SPACE; + +#define XCALL_GEN_UPDATE_PARAM(num, params) \ + if (!ch##num.Update(params->GetParamPtr(num-1))) {\ + ipc_provider.FreeBuffer(raw_mem); \ + return SBOX_ERROR_BAD_PARAMS; \ + } + +#define XCALL_GEN_FREE_CHANNEL() \ + ipc_provider.FreeBuffer(raw_mem); + +// CrossCall template with one input parameter +template <typename IPCProvider, typename Par1> +ResultCode CrossCall(IPCProvider& ipc_provider, uint32 tag, const Par1& p1, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(1, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + + return result; +} + +// CrossCall template with two input parameters. +template <typename IPCProvider, typename Par1, typename Par2> +ResultCode CrossCall(IPCProvider& ipc_provider, uint32 tag, const Par1& p1, + const Par2& p2, CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(2, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with three input parameters. +template <typename IPCProvider, typename Par1, typename Par2, typename Par3> +ResultCode CrossCall(IPCProvider& ipc_provider, uint32 tag, const Par1& p1, + const Par2& p2, const Par3& p3, CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(3, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with four input parameters. +template <typename IPCProvider, typename Par1, typename Par2, typename Par3, + typename Par4> +ResultCode CrossCall(IPCProvider& ipc_provider, uint32 tag, const Par1& p1, + const Par2& p2, const Par3& p3, const Par4& p4, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(4, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with five input parameters. +template <typename IPCProvider, typename Par1, typename Par2, typename Par3, + typename Par4, typename Par5> +ResultCode CrossCall(IPCProvider& ipc_provider, uint32 tag, const Par1& p1, + const Par2& p2, const Par3& p3, const Par4& p4, + const Par5& p5, CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(5, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + XCALL_GEN_COPY_PARAM(5, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_UPDATE_PARAM(5, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with six input parameters. +template <typename IPCProvider, typename Par1, typename Par2, typename Par3, + typename Par4, typename Par5, typename Par6> +ResultCode CrossCall(IPCProvider& ipc_provider, uint32 tag, const Par1& p1, + const Par2& p2, const Par3& p3, const Par4& p4, + const Par5& p5, const Par6& p6, CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(6, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + XCALL_GEN_COPY_PARAM(5, call_params); + XCALL_GEN_COPY_PARAM(6, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_UPDATE_PARAM(5, call_params); + XCALL_GEN_UPDATE_PARAM(6, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with seven input parameters. +template <typename IPCProvider, typename Par1, typename Par2, typename Par3, + typename Par4, typename Par5, typename Par6, typename Par7> +ResultCode CrossCall(IPCProvider& ipc_provider, uint32 tag, const Par1& p1, + const Par2& p2, const Par3& p3, const Par4& p4, + const Par5& p5, const Par6& p6, const Par7& p7, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(7, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + XCALL_GEN_COPY_PARAM(5, call_params); + XCALL_GEN_COPY_PARAM(6, call_params); + XCALL_GEN_COPY_PARAM(7, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_UPDATE_PARAM(5, call_params); + XCALL_GEN_UPDATE_PARAM(6, call_params); + XCALL_GEN_UPDATE_PARAM(7, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} +} // namespace sandbox + +#endif // SANDBOX_SRC_CROSSCALL_CLIENT_H__ diff --git a/sandbox/win/src/crosscall_params.h b/sandbox/win/src/crosscall_params.h new file mode 100644 index 0000000000..6facb202da --- /dev/null +++ b/sandbox/win/src/crosscall_params.h @@ -0,0 +1,296 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_CROSSCALL_PARAMS_H__ +#define SANDBOX_SRC_CROSSCALL_PARAMS_H__ + +#include <windows.h> +#include <lmaccess.h> + +#include <memory> + +#include "base/basictypes.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace { + +// Increases |value| until there is no need for padding given an int64 +// alignment. Returns the increased value. +uint32 Align(uint32 value) { + uint32 alignment = sizeof(int64); + return ((value + alignment - 1) / alignment) * alignment; +} + +} +// This header is part of CrossCall: the sandbox inter-process communication. +// This header defines the basic types used both in the client IPC and in the +// server IPC code. CrossCallParams and ActualCallParams model the input +// parameters of an IPC call and CrossCallReturn models the output params and +// the return value. +// +// An IPC call is defined by its 'tag' which is a (uint32) unique identifier +// that is used to route the IPC call to the proper server. Every tag implies +// a complete call signature including the order and type of each parameter. +// +// Like most IPC systems. CrossCall is designed to take as inputs 'simple' +// types such as integers and strings. Classes, generic arrays or pointers to +// them are not supported. +// +// Another limitation of CrossCall is that the return value and output +// parameters can only be uint32 integers. Returning complex structures or +// strings is not supported. + +namespace sandbox { + +// max number of extended return parameters. See CrossCallReturn +const size_t kExtendedReturnCount = 8; + +// Union of multiple types to be used as extended results +// in the CrossCallReturn. +union MultiType { + uint32 unsigned_int; + void* pointer; + HANDLE handle; + ULONG_PTR ulong_ptr; +}; + +// Maximum number of IPC parameters currently supported. +// To increase this value, we have to: +// - Add another Callback typedef to Dispatcher. +// - Add another case to the switch on SharedMemIPCServer::InvokeCallback. +// - Add another case to the switch in GetActualAndMaxBufferSize +const int kMaxIpcParams = 9; + +// Contains the information about a parameter in the ipc buffer. +struct ParamInfo { + ArgType type_; + uint32 offset_; + uint32 size_; +}; + +// Models the return value and the return parameters of an IPC call +// currently limited to one status code and eight generic return values +// which cannot be pointers to other data. For x64 ports this structure +// might have to use other integer types. +struct CrossCallReturn { + // the IPC tag. It should match the original IPC tag. + uint32 tag; + // The result of the IPC operation itself. + ResultCode call_outcome; + // the result of the IPC call as executed in the server. The interpretation + // of this value depends on the specific service. + union { + NTSTATUS nt_status; + DWORD win32_result; + }; + // Number of extended return values. + uint32 extended_count; + // for calls that should return a windows handle. It is found here. + HANDLE handle; + // The array of extended values. + MultiType extended[kExtendedReturnCount]; +}; + +// CrossCallParams base class that models the input params all packed in a +// single compact memory blob. The representation can vary but in general a +// given child of this class is meant to represent all input parameters +// necessary to make a IPC call. +// +// This class cannot have virtual members because its assumed the IPC +// parameters start from the 'this' pointer to the end, which is defined by +// one of the subclasses +// +// Objects of this class cannot be constructed directly. Only derived +// classes have the proper knowledge to construct it. +class CrossCallParams { + public: + // Returns the tag (ipc unique id) associated with this IPC. + uint32 GetTag() const { + return tag_; + } + + // Returns the beggining of the buffer where the IPC params can be stored. + // prior to an IPC call + const void* GetBuffer() const { + return this; + } + + // Returns how many parameter this IPC call should have. + const uint32 GetParamsCount() const { + return params_count_; + } + + // Returns a pointer to the CrossCallReturn structure. + CrossCallReturn* GetCallReturn() { + return &call_return; + } + + // Returns TRUE if this call contains InOut parameters. + const bool IsInOut() const { + return (1 == is_in_out_); + } + + // Tells the CrossCall object if it contains InOut parameters. + void SetIsInOut(bool value) { + if (value) + is_in_out_ = 1; + else + is_in_out_ = 0; + } + + protected: + // constructs the IPC call params. Called only from the derived classes + CrossCallParams(uint32 tag, uint32 params_count) + : tag_(tag), + params_count_(params_count), + is_in_out_(0) { + } + + private: + uint32 tag_; + uint32 is_in_out_; + CrossCallReturn call_return; + const uint32 params_count_; + DISALLOW_COPY_AND_ASSIGN(CrossCallParams); +}; + +// ActualCallParams models an specific IPC call parameters with respect to the +// storage allocation that the packed parameters should need. +// NUMBER_PARAMS: the number of parameters, valid from 1 to N +// BLOCK_SIZE: the total storage that the NUMBER_PARAMS parameters can take, +// typically the block size is defined by the channel size of the underlying +// ipc mechanism. +// In practice this class is used to levergage C++ capacity to properly +// calculate sizes and displacements given the possibility of the packed params +// blob to be complex. +// +// As is, this class assumes that the layout of the blob is as follows. Assume +// that NUMBER_PARAMS = 2 and a 32-bit build: +// +// [ tag 4 bytes] +// [ IsOnOut 4 bytes] +// [ call return 52 bytes] +// [ params count 4 bytes] +// [ parameter 0 type 4 bytes] +// [ parameter 0 offset 4 bytes] ---delta to ---\ +// [ parameter 0 size 4 bytes] | +// [ parameter 1 type 4 bytes] | +// [ parameter 1 offset 4 bytes] ---------------|--\ +// [ parameter 1 size 4 bytes] | | +// [ parameter 2 type 4 bytes] | | +// [ parameter 2 offset 4 bytes] ----------------------\ +// [ parameter 2 size 4 bytes] | | | +// |---------------------------| | | | +// | value 0 (x bytes) | <--------------/ | | +// | value 1 (y bytes) | <-----------------/ | +// | | | +// | end of buffer | <---------------------/ +// |---------------------------| +// +// Note that the actual number of params is NUMBER_PARAMS + 1 +// so that the size of each actual param can be computed from the difference +// between one parameter and the next down. The offset of the last param +// points to the end of the buffer and the type and size are undefined. +// +template <size_t NUMBER_PARAMS, size_t BLOCK_SIZE> +class ActualCallParams : public CrossCallParams { + public: + // constructor. Pass the ipc unique tag as input + explicit ActualCallParams(uint32 tag) + : CrossCallParams(tag, NUMBER_PARAMS) { + param_info_[0].offset_ = + static_cast<uint32>(parameters_ - reinterpret_cast<char*>(this)); + } + + // Testing-only constructor. Allows setting the |number_params| to a + // wrong value. + ActualCallParams(uint32 tag, uint32 number_params) + : CrossCallParams(tag, number_params) { + param_info_[0].offset_ = + static_cast<uint32>(parameters_ - reinterpret_cast<char*>(this)); + } + + // Testing-only method. Allows setting the apparent size to a wrong value. + // returns the previous size. + uint32 OverrideSize(uint32 new_size) { + uint32 previous_size = param_info_[NUMBER_PARAMS].offset_; + param_info_[NUMBER_PARAMS].offset_ = new_size; + return previous_size; + } + + // Copies each paramter into the internal buffer. For each you must supply: + // index: 0 for the first param, 1 for the next an so on + bool CopyParamIn(uint32 index, const void* parameter_address, uint32 size, + bool is_in_out, ArgType type) { + if (index >= NUMBER_PARAMS) { + return false; + } + + if (kuint32max == size) { + // Memory error while getting the size. + return false; + } + + if (size && !parameter_address) { + return false; + } + + if ((size > sizeof(*this)) || + (param_info_[index].offset_ > (sizeof(*this) - size))) { + // It does not fit, abort copy. + return false; + } + + char* dest = reinterpret_cast<char*>(this) + param_info_[index].offset_; + + // We might be touching user memory, this has to be done from inside a try + // except. + __try { + memcpy(dest, parameter_address, size); + } + __except(EXCEPTION_EXECUTE_HANDLER) { + return false; + } + + // Set the flag to tell the broker to update the buffer once the call is + // made. + if (is_in_out) + SetIsInOut(true); + + param_info_[index + 1].offset_ = Align(param_info_[index].offset_ + + size); + param_info_[index].size_ = size; + param_info_[index].type_ = type; + return true; + } + + // Returns a pointer to a parameter in the memory section. + void* GetParamPtr(size_t index) { + return reinterpret_cast<char*>(this) + param_info_[index].offset_; + } + + // Returns the total size of the buffer. Only valid once all the paramters + // have been copied in with CopyParamIn. + uint32 GetSize() const { + return param_info_[NUMBER_PARAMS].offset_; + } + + protected: + ActualCallParams() : CrossCallParams(0, NUMBER_PARAMS) { } + + private: + ParamInfo param_info_[NUMBER_PARAMS + 1]; + char parameters_[BLOCK_SIZE - sizeof(CrossCallParams) + - sizeof(ParamInfo) * (NUMBER_PARAMS + 1)]; + DISALLOW_COPY_AND_ASSIGN(ActualCallParams); +}; + +static_assert(sizeof(ActualCallParams<1, 1024>) == 1024, "bad size buffer"); +static_assert(sizeof(ActualCallParams<2, 1024>) == 1024, "bad size buffer"); +static_assert(sizeof(ActualCallParams<3, 1024>) == 1024, "bad size buffer"); + +} // namespace sandbox + +#endif // SANDBOX_SRC_CROSSCALL_PARAMS_H__ diff --git a/sandbox/win/src/crosscall_server.cc b/sandbox/win/src/crosscall_server.cc new file mode 100644 index 0000000000..6f8bd744f2 --- /dev/null +++ b/sandbox/win/src/crosscall_server.cc @@ -0,0 +1,307 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/crosscall_server.h" + +#include <string> +#include <vector> + +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_client.h" +#include "base/logging.h" + +// This code performs the ipc message validation. Potential security flaws +// on the ipc are likelier to be found in this code than in the rest of +// the ipc code. + +namespace { + +// The buffer for a message must match the max channel size. +const size_t kMaxBufferSize = sandbox::kIPCChannelSize; + +} + +namespace sandbox { + +// Returns the actual size for the parameters in an IPC buffer. Returns +// zero if the |param_count| is zero or too big. +uint32 GetActualBufferSize(uint32 param_count, void* buffer_base) { + // The template types are used to calculate the maximum expected size. + typedef ActualCallParams<1, kMaxBufferSize> ActualCP1; + typedef ActualCallParams<2, kMaxBufferSize> ActualCP2; + typedef ActualCallParams<3, kMaxBufferSize> ActualCP3; + typedef ActualCallParams<4, kMaxBufferSize> ActualCP4; + typedef ActualCallParams<5, kMaxBufferSize> ActualCP5; + typedef ActualCallParams<6, kMaxBufferSize> ActualCP6; + typedef ActualCallParams<7, kMaxBufferSize> ActualCP7; + typedef ActualCallParams<8, kMaxBufferSize> ActualCP8; + typedef ActualCallParams<9, kMaxBufferSize> ActualCP9; + + // Retrieve the actual size and the maximum size of the params buffer. + switch (param_count) { + case 0: + return 0; + case 1: + return reinterpret_cast<ActualCP1*>(buffer_base)->GetSize(); + case 2: + return reinterpret_cast<ActualCP2*>(buffer_base)->GetSize(); + case 3: + return reinterpret_cast<ActualCP3*>(buffer_base)->GetSize(); + case 4: + return reinterpret_cast<ActualCP4*>(buffer_base)->GetSize(); + case 5: + return reinterpret_cast<ActualCP5*>(buffer_base)->GetSize(); + case 6: + return reinterpret_cast<ActualCP6*>(buffer_base)->GetSize(); + case 7: + return reinterpret_cast<ActualCP7*>(buffer_base)->GetSize(); + case 8: + return reinterpret_cast<ActualCP8*>(buffer_base)->GetSize(); + case 9: + return reinterpret_cast<ActualCP9*>(buffer_base)->GetSize(); + default: + return 0; + } +} + +// Verifies that the declared sizes of an IPC buffer are within range. +bool IsSizeWithinRange(uint32 buffer_size, uint32 min_declared_size, + uint32 declared_size) { + if ((buffer_size < min_declared_size) || + (sizeof(CrossCallParamsEx) > min_declared_size)) { + // Minimal computed size bigger than existing buffer or param_count + // integer overflow. + return false; + } + + if ((declared_size > buffer_size) || (declared_size < min_declared_size)) { + // Declared size is bigger than buffer or smaller than computed size + // or param_count is equal to 0 or bigger than 9. + return false; + } + + return true; +} + +CrossCallParamsEx::CrossCallParamsEx() + :CrossCallParams(0, 0) { +} + +// We override the delete operator because the object's backing memory +// is hand allocated in CreateFromBuffer. We don't override the new operator +// because the constructors are private so there is no way to mismatch +// new & delete. +void CrossCallParamsEx::operator delete(void* raw_memory) throw() { + if (NULL == raw_memory) { + // C++ standard allows 'delete 0' behavior. + return; + } + delete[] reinterpret_cast<char*>(raw_memory); +} + +// This function uses a SEH try block so cannot use C++ objects that +// have destructors or else you get Compiler Error C2712. So no DCHECKs +// inside this function. +CrossCallParamsEx* CrossCallParamsEx::CreateFromBuffer(void* buffer_base, + uint32 buffer_size, + uint32* output_size) { + // IMPORTANT: Everything inside buffer_base and derived from it such + // as param_count and declared_size is untrusted. + if (NULL == buffer_base) { + return NULL; + } + if (buffer_size < sizeof(CrossCallParams)) { + return NULL; + } + if (buffer_size > kMaxBufferSize) { + return NULL; + } + + char* backing_mem = NULL; + uint32 param_count = 0; + uint32 declared_size; + uint32 min_declared_size; + CrossCallParamsEx* copied_params = NULL; + + // Touching the untrusted buffer is done under a SEH try block. This + // will catch memory access violations so we don't crash. + __try { + CrossCallParams* call_params = + reinterpret_cast<CrossCallParams*>(buffer_base); + + // Check against the minimum size given the number of stated params + // if too small we bail out. + param_count = call_params->GetParamsCount(); + min_declared_size = sizeof(CrossCallParams) + + ((param_count + 1) * sizeof(ParamInfo)); + + // Retrieve the declared size which if it fails returns 0. + declared_size = GetActualBufferSize(param_count, buffer_base); + + if (!IsSizeWithinRange(buffer_size, min_declared_size, declared_size)) + return NULL; + + // Now we copy the actual amount of the message. + *output_size = declared_size; + backing_mem = new char[declared_size]; + copied_params = reinterpret_cast<CrossCallParamsEx*>(backing_mem); + memcpy(backing_mem, call_params, declared_size); + + // Avoid compiler optimizations across this point. Any value stored in + // memory should be stored for real, and values previously read from memory + // should be actually read. + _ReadWriteBarrier(); + + min_declared_size = sizeof(CrossCallParams) + + ((param_count + 1) * sizeof(ParamInfo)); + + // Check that the copied buffer is still valid. + if (copied_params->GetParamsCount() != param_count || + GetActualBufferSize(param_count, backing_mem) != declared_size || + !IsSizeWithinRange(buffer_size, min_declared_size, declared_size)) { + delete [] backing_mem; + return NULL; + } + + } __except(EXCEPTION_EXECUTE_HANDLER) { + // In case of a windows exception we know it occurred while touching the + // untrusted buffer so we bail out as is. + delete [] backing_mem; + return NULL; + } + + const char* last_byte = &backing_mem[declared_size]; + const char* first_byte = &backing_mem[min_declared_size]; + + // Verify here that all and each parameters make sense. This is done in the + // local copy. + for (uint32 ix =0; ix != param_count; ++ix) { + uint32 size = 0; + ArgType type; + char* address = reinterpret_cast<char*>( + copied_params->GetRawParameter(ix, &size, &type)); + if ((NULL == address) || // No null params. + (INVALID_TYPE >= type) || (LAST_TYPE <= type) || // Unknown type. + (address < backing_mem) || // Start cannot point before buffer. + (address < first_byte) || // Start cannot point too low. + (address > last_byte) || // Start cannot point past buffer. + ((address + size) < address) || // Invalid size. + ((address + size) > last_byte)) { // End cannot point past buffer. + // Malformed. + delete[] backing_mem; + return NULL; + } + } + // The parameter buffer looks good. + return copied_params; +} + +// Accessors to the parameters in the raw buffer. +void* CrossCallParamsEx::GetRawParameter(uint32 index, uint32* size, + ArgType* type) { + if (index >= GetParamsCount()) { + return NULL; + } + // The size is always computed from the parameter minus the next + // parameter, this works because the message has an extra parameter slot + *size = param_info_[index].size_; + *type = param_info_[index].type_; + + return param_info_[index].offset_ + reinterpret_cast<char*>(this); +} + +// Covers common case for 32 bit integers. +bool CrossCallParamsEx::GetParameter32(uint32 index, uint32* param) { + uint32 size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if ((NULL == start) || (4 != size) || (UINT32_TYPE != type)) { + return false; + } + // Copy the 4 bytes. + *(reinterpret_cast<uint32*>(param)) = *(reinterpret_cast<uint32*>(start)); + return true; +} + +bool CrossCallParamsEx::GetParameterVoidPtr(uint32 index, void** param) { + uint32 size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if ((NULL == start) || (sizeof(void*) != size) || (VOIDPTR_TYPE != type)) { + return false; + } + *param = *(reinterpret_cast<void**>(start)); + return true; +} + +// Covers the common case of reading a string. Note that the string is not +// scanned for invalid characters. +bool CrossCallParamsEx::GetParameterStr(uint32 index, base::string16* string) { + uint32 size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if (WCHAR_TYPE != type) { + return false; + } + + // Check if this is an empty string. + if (size == 0) { + *string = L""; + return true; + } + + if ((NULL == start) || ((size % sizeof(wchar_t)) != 0)) { + return false; + } + string->append(reinterpret_cast<wchar_t*>(start), size/(sizeof(wchar_t))); + return true; +} + +bool CrossCallParamsEx::GetParameterPtr(uint32 index, uint32 expected_size, + void** pointer) { + uint32 size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + + if ((size != expected_size) || (INOUTPTR_TYPE != type)) { + return false; + } + + if (NULL == start) { + return false; + } + + *pointer = start; + return true; +} + +void SetCallError(ResultCode error, CrossCallReturn* call_return) { + call_return->call_outcome = error; + call_return->extended_count = 0; +} + +void SetCallSuccess(CrossCallReturn* call_return) { + call_return->call_outcome = SBOX_ALL_OK; +} + +Dispatcher* Dispatcher::OnMessageReady(IPCParams* ipc, + CallbackGeneric* callback) { + DCHECK(callback); + std::vector<IPCCall>::iterator it = ipc_calls_.begin(); + for (; it != ipc_calls_.end(); ++it) { + if (it->params.Matches(ipc)) { + *callback = it->callback; + return this; + } + } + return NULL; +} + +Dispatcher::Dispatcher() { +} + +Dispatcher::~Dispatcher() { +} + +} // namespace sandbox diff --git a/sandbox/win/src/crosscall_server.h b/sandbox/win/src/crosscall_server.h new file mode 100644 index 0000000000..41888c1203 --- /dev/null +++ b/sandbox/win/src/crosscall_server.h @@ -0,0 +1,226 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_CROSSCALL_SERVER_H_ +#define SANDBOX_SRC_CROSSCALL_SERVER_H_ + +#include <string> +#include <vector> +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_params.h" + +// This is the IPC server interface for CrossCall: The IPC for the Sandbox +// On the server, CrossCall needs two things: +// 1) threads: Or better said, someone to provide them, that is what the +// ThreadProvider interface is defined for. These thread(s) are +// the ones that will actually execute the IPC data retrieval. +// +// 2) a dispatcher: This interface represents the way to route and process +// an IPC call given the IPC tag. +// +// The other class included here CrossCallParamsEx is the server side version +// of the CrossCallParams class of /sandbox/crosscall_params.h The difference +// is that the sever version is paranoid about the correctness of the IPC +// message and will do all sorts of verifications. +// +// A general diagram of the interaction is as follows: +// +// ------------ +// | | +// ThreadProvider <--(1)Register--| IPC | +// | | Implemen | +// | | -tation | +// (2) | | OnMessage +// IPC fired --callback ------>| |--(3)---> Dispatcher +// | | +// ------------ +// +// The IPC implementation sits as a middleman between the handling of the +// specifics of scheduling a thread to service the IPC and the multiple +// entities that can potentially serve each particular IPC. +namespace sandbox { + +class InterceptionManager; + +// This function signature is required as the callback when an IPC call fires. +// context: a user-defined pointer that was set using ThreadProvider +// reason: 0 if the callback was fired because of a timeout. +// 1 if the callback was fired because of an event. +typedef void (__stdcall * CrossCallIPCCallback)(void* context, + unsigned char reason); + +// ThreadProvider models a thread factory. The idea is to decouple thread +// creation and lifetime from the inner guts of the IPC. The contract is +// simple: +// - the IPC implementation calls RegisterWait with a waitable object that +// becomes signaled when an IPC arrives and needs to be serviced. +// - when the waitable object becomes signaled, the thread provider conjures +// a thread that calls the callback (CrossCallIPCCallback) function +// - the callback function tries its best not to block and return quickly +// and should not assume that the next callback will use the same thread +// - when the callback returns the ThreadProvider owns again the thread +// and can destroy it or keep it around. +class ThreadProvider { + public: + // Registers a waitable object with the thread provider. + // client: A number to associate with all the RegisterWait calls, typically + // this is the address of the caller object. This parameter cannot + // be zero. + // waitable_object : a kernel object that can be waited on + // callback: a function pointer which is the function that will be called + // when the waitable object fires + // context: a user-provider pointer that is passed back to the callback + // when its called + virtual bool RegisterWait(const void* client, HANDLE waitable_object, + CrossCallIPCCallback callback, + void* context) = 0; + + // Removes all the registrations done with the same cookie parameter. + // This frees internal thread pool resources. + virtual bool UnRegisterWaits(void* cookie) = 0; + virtual ~ThreadProvider() {} +}; + +// Models the server-side of the original input parameters. +// Provides IPC buffer validation and it is capable of reading the parameters +// out of the IPC buffer. +class CrossCallParamsEx : public CrossCallParams { + public: + // Factory constructor. Pass an IPCbuffer (and buffer size) that contains a + // pending IPCcall. This constructor will: + // 1) validate the IPC buffer. returns NULL is the IPCbuffer is malformed. + // 2) make a copy of the IPCbuffer (parameter capture) + static CrossCallParamsEx* CreateFromBuffer(void* buffer_base, + uint32 buffer_size, + uint32* output_size); + + // Provides IPCinput parameter raw access: + // index : the parameter to read; 0 is the first parameter + // returns NULL if the parameter is non-existent. If it exists it also + // returns the size in *size + void* GetRawParameter(uint32 index, uint32* size, ArgType* type); + + // Gets a parameter that is four bytes in size. + // Returns false if the parameter does not exist or is not 32 bits wide. + bool GetParameter32(uint32 index, uint32* param); + + // Gets a parameter that is void pointer in size. + // Returns false if the parameter does not exist or is not void pointer sized. + bool GetParameterVoidPtr(uint32 index, void** param); + + // Gets a parameter that is a string. Returns false if the parameter does not + // exist. + bool GetParameterStr(uint32 index, base::string16* string); + + // Gets a parameter that is an in/out buffer. Returns false is the parameter + // does not exist or if the size of the actual parameter is not equal to the + // expected size. + bool GetParameterPtr(uint32 index, uint32 expected_size, void** pointer); + + // Frees the memory associated with the IPC parameters. + static void operator delete(void* raw_memory) throw(); + + private: + // Only the factory method CreateFromBuffer can construct these objects. + CrossCallParamsEx(); + + ParamInfo param_info_[1]; + DISALLOW_COPY_AND_ASSIGN(CrossCallParamsEx); +}; + +// Simple helper function that sets the members of CrossCallReturn +// to the proper state to signal a basic error. +void SetCallError(ResultCode error, CrossCallReturn* call_return); + +// Sets the internal status of call_return to signify the that IPC call +// completed successfully. +void SetCallSuccess(CrossCallReturn* call_return); + +// Represents the client process that initiated the IPC which boils down to the +// process handle and the job object handle that contains the client process. +struct ClientInfo { + HANDLE process; + HANDLE job_object; + DWORD process_id; +}; + +// All IPC-related information to be passed to the IPC handler. +struct IPCInfo { + int ipc_tag; + const ClientInfo* client_info; + CrossCallReturn return_info; +}; + +// This structure identifies IPC signatures. +struct IPCParams { + int ipc_tag; + ArgType args[kMaxIpcParams]; + + bool Matches(IPCParams* other) const { + return !memcmp(this, other, sizeof(*other)); + } +}; + +// Models an entity that can process an IPC message or it can route to another +// one that could handle it. When an IPC arrives the IPC implementation will: +// 1) call OnMessageReady() with the tag of the pending IPC. If the dispatcher +// returns NULL it means that it cannot handle this IPC but if it returns +// non-null, it must be the pointer to a dispatcher that can handle it. +// 2) When the IPC finally obtains a valid Dispatcher the IPC +// implementation creates a CrossCallParamsEx from the raw IPC buffer. +// 3) It calls the returned callback, with the IPC info and arguments. +class Dispatcher { + public: + // Called from the IPC implementation to handle a specific IPC message. + typedef bool (Dispatcher::*CallbackGeneric)(); + typedef bool (Dispatcher::*Callback0)(IPCInfo* ipc); + typedef bool (Dispatcher::*Callback1)(IPCInfo* ipc, void* p1); + typedef bool (Dispatcher::*Callback2)(IPCInfo* ipc, void* p1, void* p2); + typedef bool (Dispatcher::*Callback3)(IPCInfo* ipc, void* p1, void* p2, + void* p3); + typedef bool (Dispatcher::*Callback4)(IPCInfo* ipc, void* p1, void* p2, + void* p3, void* p4); + typedef bool (Dispatcher::*Callback5)(IPCInfo* ipc, void* p1, void* p2, + void* p3, void* p4, void* p5); + typedef bool (Dispatcher::*Callback6)(IPCInfo* ipc, void* p1, void* p2, + void* p3, void* p4, void* p5, void* p6); + typedef bool (Dispatcher::*Callback7)(IPCInfo* ipc, void* p1, void* p2, + void* p3, void* p4, void* p5, void* p6, + void* p7); + typedef bool (Dispatcher::*Callback8)(IPCInfo* ipc, void* p1, void* p2, + void* p3, void* p4, void* p5, void* p6, + void* p7, void* p8); + typedef bool (Dispatcher::*Callback9)(IPCInfo* ipc, void* p1, void* p2, + void* p3, void* p4, void* p5, void* p6, + void* p7, void* p8, void* p9); + + // Called from the IPC implementation when an IPC message is ready override + // on a derived class to handle a set of IPC messages. Return NULL if your + // subclass does not handle the message or return the pointer to the subclass + // that can handle it. + virtual Dispatcher* OnMessageReady(IPCParams* ipc, CallbackGeneric* callback); + + // Called when a target proces is created, to setup the interceptions related + // with the given service (IPC). + virtual bool SetupService(InterceptionManager* manager, int service) = 0; + + Dispatcher(); + virtual ~Dispatcher(); + + protected: + // Structure that defines an IPC Call with all the parameters and the handler. + struct IPCCall { + IPCParams params; + CallbackGeneric callback; + }; + + // List of IPC Calls supported by the class. + std::vector<IPCCall> ipc_calls_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_CROSSCALL_SERVER_H_ diff --git a/sandbox/win/src/eat_resolver.cc b/sandbox/win/src/eat_resolver.cc new file mode 100644 index 0000000000..1675ce84eb --- /dev/null +++ b/sandbox/win/src/eat_resolver.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/eat_resolver.h" + +#include "base/win/pe_image.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +NTSTATUS EatResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = Init(target_module, interceptor_module, target_name, + interceptor_name, interceptor_entry_point, + thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + if (!eat_entry_) + return STATUS_INVALID_PARAMETER; + +#if defined(_WIN64) + // We have two thunks, in order: the return path and the forward path. + if (!SetInternalThunk(thunk_storage, storage_bytes, NULL, target_)) + return STATUS_BUFFER_TOO_SMALL; + + size_t thunk_bytes = GetInternalThunkSize(); + storage_bytes -= thunk_bytes; + thunk_storage = reinterpret_cast<char*>(thunk_storage) + thunk_bytes; +#endif + + if (!SetInternalThunk(thunk_storage, storage_bytes, target_, interceptor_)) + return STATUS_BUFFER_TOO_SMALL; + + AutoProtectMemory memory; + ret = memory.ChangeProtection(eat_entry_, sizeof(DWORD), PAGE_READWRITE); + if (!NT_SUCCESS(ret)) + return ret; + + // Perform the patch. + *eat_entry_ = static_cast<DWORD>(reinterpret_cast<uintptr_t>(thunk_storage)) - + static_cast<DWORD>(reinterpret_cast<uintptr_t>(target_module)); + + if (NULL != storage_used) + *storage_used = GetThunkSize(); + + return ret; +} + +NTSTATUS EatResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + DCHECK_NT(address); + if (!module) + return STATUS_INVALID_PARAMETER; + + base::win::PEImage pe(module); + if (!pe.VerifyMagic()) + return STATUS_INVALID_IMAGE_FORMAT; + + eat_entry_ = pe.GetExportEntry(function_name); + + if (!eat_entry_) + return STATUS_PROCEDURE_NOT_FOUND; + + *address = pe.RVAToAddr(*eat_entry_); + + return STATUS_SUCCESS; +} + +size_t EatResolverThunk::GetThunkSize() const { +#if defined(_WIN64) + return GetInternalThunkSize() * 2; +#else + return GetInternalThunkSize(); +#endif +} + +} // namespace sandbox diff --git a/sandbox/win/src/eat_resolver.h b/sandbox/win/src/eat_resolver.h new file mode 100644 index 0000000000..1d9d430672 --- /dev/null +++ b/sandbox/win/src/eat_resolver.h @@ -0,0 +1,48 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_EAT_RESOLVER_H__ +#define SANDBOX_SRC_EAT_RESOLVER_H__ + +#include "base/basictypes.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/resolver.h" + +namespace sandbox { + +// This is the concrete resolver used to perform exports table interceptions. +class EatResolverThunk : public ResolverThunk { + public: + EatResolverThunk() : eat_entry_(NULL) {} + ~EatResolverThunk() override {} + + // Implementation of Resolver::Setup. + NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) override; + + // Implementation of Resolver::ResolveTarget. + NTSTATUS ResolveTarget(const void* module, + const char* function_name, + void** address) override; + + // Implementation of Resolver::GetThunkSize. + size_t GetThunkSize() const override; + + private: + // The entry to patch. + DWORD* eat_entry_; + + DISALLOW_COPY_AND_ASSIGN(EatResolverThunk); +}; + +} // namespace sandbox + + +#endif // SANDBOX_SRC_EAT_RESOLVER_H__ diff --git a/sandbox/win/src/file_policy_test.cc b/sandbox/win/src/file_policy_test.cc new file mode 100644 index 0000000000..8b5236251f --- /dev/null +++ b/sandbox/win/src/file_policy_test.cc @@ -0,0 +1,674 @@ +// Copyright (c) 2011 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 <algorithm> +#include <cctype> + +#include <windows.h> +#include <winioctl.h> + +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" +#include "sandbox/win/tests/common/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define BINDNTDLL(name) \ + name ## Function name = reinterpret_cast<name ## Function>( \ + ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name)) + +namespace sandbox { + +const ULONG kSharing = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; + +// Creates a file using different desired access. Returns if the call succeeded +// or not. The first argument in argv is the filename. The second argument +// determines the type of access and the dispositino of the file. +SBOX_TESTS_COMMAND int File_Create(int argc, wchar_t **argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring operation(argv[0]); + + if (operation == L"Read") { + base::win::ScopedHandle file1(CreateFile( + argv[1], GENERIC_READ, kSharing, NULL, OPEN_EXISTING, 0, NULL)); + base::win::ScopedHandle file2(CreateFile( + argv[1], FILE_EXECUTE, kSharing, NULL, OPEN_EXISTING, 0, NULL)); + + if (file1.IsValid() == file2.IsValid()) + return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; + return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR; + + } else if (operation == L"Write") { + base::win::ScopedHandle file1(CreateFile( + argv[1], GENERIC_ALL, kSharing, NULL, OPEN_EXISTING, 0, NULL)); + base::win::ScopedHandle file2(CreateFile( + argv[1], GENERIC_READ | FILE_WRITE_DATA, kSharing, NULL, OPEN_EXISTING, + 0, NULL)); + + if (file1.IsValid() == file2.IsValid()) + return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; + return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR; + + } else if (operation == L"ReadCreate") { + base::win::ScopedHandle file2(CreateFile( + argv[1], GENERIC_READ, kSharing, NULL, CREATE_NEW, 0, NULL)); + base::win::ScopedHandle file1(CreateFile( + argv[1], GENERIC_READ, kSharing, NULL, CREATE_ALWAYS, 0, NULL)); + + if (file1.IsValid() == file2.IsValid()) + return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; + return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR; + } + + return SBOX_TEST_INVALID_PARAMETER; +} + +SBOX_TESTS_COMMAND int File_Win32Create(int argc, wchar_t **argv) { + if (argc != 1) { + SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + base::string16 full_path = MakePathToSys(argv[0], false); + if (full_path.empty()) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + HANDLE file = ::CreateFileW(full_path.c_str(), GENERIC_READ, kSharing, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (INVALID_HANDLE_VALUE != file) { + ::CloseHandle(file); + return SBOX_TEST_SUCCEEDED; + } else { + if (ERROR_ACCESS_DENIED == ::GetLastError()) { + return SBOX_TEST_DENIED; + } else { + return SBOX_TEST_FAILED; + } + } + return SBOX_TEST_SUCCEEDED; +} + +// Creates the file in parameter using the NtCreateFile api and returns if the +// call succeeded or not. +SBOX_TESTS_COMMAND int File_CreateSys32(int argc, wchar_t **argv) { + BINDNTDLL(NtCreateFile); + BINDNTDLL(RtlInitUnicodeString); + if (!NtCreateFile || !RtlInitUnicodeString) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + base::string16 file(argv[0]); + if (0 != _wcsnicmp(file.c_str(), kNTDevicePrefix, kNTDevicePrefixLen)) + file = MakePathToSys(argv[0], true); + + UNICODE_STRING object_name; + RtlInitUnicodeString(&object_name, file.c_str()); + + OBJECT_ATTRIBUTES obj_attributes = {0}; + InitializeObjectAttributes(&obj_attributes, &object_name, + OBJ_CASE_INSENSITIVE, NULL, NULL); + + HANDLE handle; + IO_STATUS_BLOCK io_block = {0}; + NTSTATUS status = NtCreateFile(&handle, FILE_READ_DATA, &obj_attributes, + &io_block, NULL, 0, kSharing, FILE_OPEN, + 0, NULL, 0); + if (NT_SUCCESS(status)) { + ::CloseHandle(handle); + return SBOX_TEST_SUCCEEDED; + } else if (STATUS_ACCESS_DENIED == status) { + return SBOX_TEST_DENIED; + } else if (STATUS_OBJECT_NAME_NOT_FOUND == status) { + return SBOX_TEST_NOT_FOUND; + } + return SBOX_TEST_FAILED; +} + +// Opens the file in parameter using the NtOpenFile api and returns if the +// call succeeded or not. +SBOX_TESTS_COMMAND int File_OpenSys32(int argc, wchar_t **argv) { + BINDNTDLL(NtOpenFile); + BINDNTDLL(RtlInitUnicodeString); + if (!NtOpenFile || !RtlInitUnicodeString) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + base::string16 file = MakePathToSys(argv[0], true); + UNICODE_STRING object_name; + RtlInitUnicodeString(&object_name, file.c_str()); + + OBJECT_ATTRIBUTES obj_attributes = {0}; + InitializeObjectAttributes(&obj_attributes, &object_name, + OBJ_CASE_INSENSITIVE, NULL, NULL); + + HANDLE handle; + IO_STATUS_BLOCK io_block = {0}; + NTSTATUS status = NtOpenFile(&handle, FILE_READ_DATA, &obj_attributes, + &io_block, kSharing, 0); + if (NT_SUCCESS(status)) { + ::CloseHandle(handle); + return SBOX_TEST_SUCCEEDED; + } else if (STATUS_ACCESS_DENIED == status) { + return SBOX_TEST_DENIED; + } else if (STATUS_OBJECT_NAME_NOT_FOUND == status) { + return SBOX_TEST_NOT_FOUND; + } + return SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int File_GetDiskSpace(int argc, wchar_t **argv) { + base::string16 sys_path = MakePathToSys(L"", false); + if (sys_path.empty()) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + ULARGE_INTEGER free_user = {0}; + ULARGE_INTEGER total = {0}; + ULARGE_INTEGER free_total = {0}; + if (::GetDiskFreeSpaceExW(sys_path.c_str(), &free_user, &total, + &free_total)) { + if ((total.QuadPart != 0) && (free_total.QuadPart !=0)) { + return SBOX_TEST_SUCCEEDED; + } + } else { + if (ERROR_ACCESS_DENIED == ::GetLastError()) { + return SBOX_TEST_DENIED; + } else { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + } + return SBOX_TEST_SUCCEEDED; +} + +// Move a file using the MoveFileEx api and returns if the call succeeded or +// not. +SBOX_TESTS_COMMAND int File_Rename(int argc, wchar_t **argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (::MoveFileEx(argv[0], argv[1], 0)) + return SBOX_TEST_SUCCEEDED; + + if (::GetLastError() != ERROR_ACCESS_DENIED) + return SBOX_TEST_FAILED; + + return SBOX_TEST_DENIED; +} + +// Query the attributes of file in parameter using the NtQueryAttributesFile api +// and NtQueryFullAttributesFile and returns if the call succeeded or not. The +// second argument in argv is "d" or "f" telling if we expect the attributes to +// specify a file or a directory. The expected attribute has to match the real +// attributes for the call to be successful. +SBOX_TESTS_COMMAND int File_QueryAttributes(int argc, wchar_t **argv) { + BINDNTDLL(NtQueryAttributesFile); + BINDNTDLL(NtQueryFullAttributesFile); + BINDNTDLL(RtlInitUnicodeString); + if (!NtQueryAttributesFile || !NtQueryFullAttributesFile || + !RtlInitUnicodeString) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + bool expect_directory = (L'd' == argv[1][0]); + + UNICODE_STRING object_name; + base::string16 file = MakePathToSys(argv[0], true); + RtlInitUnicodeString(&object_name, file.c_str()); + + OBJECT_ATTRIBUTES obj_attributes = {0}; + InitializeObjectAttributes(&obj_attributes, &object_name, + OBJ_CASE_INSENSITIVE, NULL, NULL); + + FILE_BASIC_INFORMATION info = {0}; + FILE_NETWORK_OPEN_INFORMATION full_info = {0}; + NTSTATUS status1 = NtQueryAttributesFile(&obj_attributes, &info); + NTSTATUS status2 = NtQueryFullAttributesFile(&obj_attributes, &full_info); + + if (status1 != status2) + return SBOX_TEST_FAILED; + + if (NT_SUCCESS(status1)) { + if (info.FileAttributes != full_info.FileAttributes) + return SBOX_TEST_FAILED; + + bool is_directory1 = (info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + if (expect_directory == is_directory1) + return SBOX_TEST_SUCCEEDED; + } else if (STATUS_ACCESS_DENIED == status1) { + return SBOX_TEST_DENIED; + } else if (STATUS_OBJECT_NAME_NOT_FOUND == status1) { + return SBOX_TEST_NOT_FOUND; + } + + return SBOX_TEST_FAILED; +} + +TEST(FilePolicyTest, DenyNtCreateCalc) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, + L"calc.exe")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_CreateSys32 calc.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CreateSys32 calc.exe")); +} + +TEST(FilePolicyTest, AllowNtCreateCalc) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"calc.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CreateSys32 calc.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CreateSys32 calc.exe")); +} + +TEST(FilePolicyTest, AllowNtCreateWithNativePath) { + if (base::win::GetVersion() < base::win::VERSION_WIN7) + return; + + base::string16 calc = MakePathToSys(L"calc.exe", false); + base::string16 nt_path; + ASSERT_TRUE(GetNtPathFromWin32Path(calc, &nt_path)); + TestRunner runner; + runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, nt_path.c_str()); + + wchar_t buff[MAX_PATH]; + ::wsprintfW(buff, L"File_CreateSys32 %s", nt_path.c_str()); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(buff)); + + std::transform(nt_path.begin(), nt_path.end(), nt_path.begin(), std::tolower); + ::wsprintfW(buff, L"File_CreateSys32 %s", nt_path.c_str()); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(buff)); +} + +TEST(FilePolicyTest, AllowReadOnly) { + TestRunner runner; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, + temp_file_name)); + + wchar_t command_read[MAX_PATH + 20] = {0}; + wsprintf(command_read, L"File_Create Read \"%ls\"", temp_file_name); + wchar_t command_read_create[MAX_PATH + 20] = {0}; + wsprintf(command_read_create, L"File_Create ReadCreate \"%ls\"", + temp_file_name); + wchar_t command_write[MAX_PATH + 20] = {0}; + wsprintf(command_write, L"File_Create Write \"%ls\"", temp_file_name); + + // Verify that we cannot create the file after revert. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_read_create)); + + // Verify that we don't have write access after revert. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_write)); + + // Verify that we have read access after revert. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_read)); + + // Verify that we really have write access to the file. + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_write)); + + DeleteFile(temp_file_name); +} + +// Tests support of "\\\\.\\DeviceName" kind of paths. +TEST(FilePolicyTest, AllowImplicitDeviceName) { + if (base::win::GetVersion() < base::win::VERSION_WIN7) + return; + + TestRunner runner; + + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + std::wstring path; + EXPECT_TRUE(ConvertToLongPath(temp_file_name, &path)); + EXPECT_TRUE(GetNtPathFromWin32Path(path, &path)); + path = path.substr(sandbox::kNTDevicePrefixLen); + + wchar_t command[MAX_PATH + 20] = {0}; + wsprintf(command, L"File_Create Read \"\\\\.\\%ls\"", path.c_str()); + path = std::wstring(kNTPrefix) + path; + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, path.c_str())); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command)); + + DeleteFile(temp_file_name); +} + +TEST(FilePolicyTest, AllowWildcard) { + TestRunner runner; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + wcscat_s(temp_directory, MAX_PATH, L"*"); + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_directory)); + + wchar_t command_write[MAX_PATH + 20] = {0}; + wsprintf(command_write, L"File_Create Write \"%ls\"", temp_file_name); + + // Verify that we have write access after revert. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_write)); + + DeleteFile(temp_file_name); +} + +TEST(FilePolicyTest, AllowNtCreatePatternRule) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"App*.dll")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_OpenSys32 appmgmts.dll")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_OpenSys32 appwiz.cpl")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_OpenSys32 appmgmts.dll")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_OpenSys32 appwiz.cpl")); +} + +TEST(FilePolicyTest, CheckNotFound) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"n*.dll")); + + EXPECT_EQ(SBOX_TEST_NOT_FOUND, + runner.RunTest(L"File_OpenSys32 notfound.dll")); +} + +TEST(FilePolicyTest, CheckNoLeak) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_CreateSys32 notfound.exe")); +} + +TEST(FilePolicyTest, TestQueryAttributesFile) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, + L"appmgmts.dll")); + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, + L"notfound.exe")); + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"drivers")); + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_QUERY, + L"ipconfig.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_QueryAttributes drivers d")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_QueryAttributes appmgmts.dll f")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_QueryAttributes ipconfig.exe f")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"File_QueryAttributes ftp.exe f")); + + EXPECT_EQ(SBOX_TEST_NOT_FOUND, + runner.RunTest(L"File_QueryAttributes notfound.exe f")); +} + +// Makes sure that we don't leak information when there is not policy to allow +// a path. +TEST(FilePolicyTest, TestQueryAttributesFileNoPolicy) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"File_QueryAttributes ftp.exe f")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"File_QueryAttributes notfound.exe f")); +} + +TEST(FilePolicyTest, TestRename) { + TestRunner runner; + + // Give access to the temp directory. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name1[MAX_PATH]; + wchar_t temp_file_name2[MAX_PATH]; + wchar_t temp_file_name3[MAX_PATH]; + wchar_t temp_file_name4[MAX_PATH]; + wchar_t temp_file_name5[MAX_PATH]; + wchar_t temp_file_name6[MAX_PATH]; + wchar_t temp_file_name7[MAX_PATH]; + wchar_t temp_file_name8[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name1), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name2), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name3), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name4), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name5), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name6), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name7), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name8), 0u); + + + // Add rules to make file1->file2 succeed. + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name1)); + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name2)); + + // Add rules to make file3->file4 fail. + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name3)); + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, + temp_file_name4)); + + // Add rules to make file5->file6 fail. + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, + temp_file_name5)); + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name6)); + + // Add rules to make file7->no_pol_file fail. + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name7)); + + // Delete the files where the files are going to be renamed to. + ::DeleteFile(temp_file_name2); + ::DeleteFile(temp_file_name4); + ::DeleteFile(temp_file_name6); + ::DeleteFile(temp_file_name8); + + + wchar_t command[MAX_PATH*2 + 20] = {0}; + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name1, + temp_file_name2); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command)); + + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name3, + temp_file_name4); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name5, + temp_file_name6); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name7, + temp_file_name8); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + + + // Delete all the files in case they are still there. + ::DeleteFile(temp_file_name1); + ::DeleteFile(temp_file_name2); + ::DeleteFile(temp_file_name3); + ::DeleteFile(temp_file_name4); + ::DeleteFile(temp_file_name5); + ::DeleteFile(temp_file_name6); + ::DeleteFile(temp_file_name7); + ::DeleteFile(temp_file_name8); +} + +TEST(FilePolicyTest, OpenSys32FilesDenyBecauseOfDir) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, + L"notepad.exe")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_Win32Create notepad.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_Win32Create notepad.exe")); +} + +TEST(FilePolicyTest, OpenSys32FilesAllowNotepad) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, + L"notepad.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_Win32Create notepad.exe")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_Win32Create calc.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_Win32Create notepad.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_Win32Create calc.exe")); +} + +TEST(FilePolicyTest, FileGetDiskSpace) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_GetDiskSpace")); + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_GetDiskSpace")); + + // Add an 'allow' rule in the windows\system32 such that GetDiskFreeSpaceEx + // succeeds (it does an NtOpenFile) but windows\system32\notepad.exe is + // denied since there is no wild card in the rule. + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, L"")); + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_GetDiskSpace")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_GetDiskSpace")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_Win32Create notepad.exe")); +} + +TEST(FilePolicyTest, TestReparsePoint) { + TestRunner runner; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + // Delete the file and create a directory instead. + ASSERT_TRUE(::DeleteFile(temp_file_name)); + ASSERT_TRUE(::CreateDirectory(temp_file_name, NULL)); + + // Create a temporary file in the subfolder. + base::string16 subfolder = temp_file_name; + base::string16 temp_file_title = subfolder.substr(subfolder.rfind(L"\\") + 1); + base::string16 temp_file = subfolder + L"\\file_" + temp_file_title; + + HANDLE file = ::CreateFile(temp_file.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + CREATE_ALWAYS, 0, NULL); + ASSERT_TRUE(INVALID_HANDLE_VALUE != file); + ASSERT_TRUE(::CloseHandle(file)); + + // Create a temporary file in the temp directory. + base::string16 temp_dir = temp_directory; + base::string16 temp_file_in_temp = temp_dir + L"file_" + temp_file_title; + file = ::CreateFile(temp_file_in_temp.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + CREATE_ALWAYS, 0, NULL); + ASSERT_TRUE(file != NULL); + ASSERT_TRUE(::CloseHandle(file)); + + // Give write access to the temp directory. + base::string16 temp_dir_wildcard = temp_dir + L"*"; + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, + temp_dir_wildcard.c_str())); + + // Prepare the command to execute. + base::string16 command_write; + command_write += L"File_Create Write \""; + command_write += temp_file; + command_write += L"\""; + + // Verify that we have write access to the original file + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_write.c_str())); + + // Replace the subfolder by a reparse point to %temp%. + ::DeleteFile(temp_file.c_str()); + HANDLE dir = ::CreateFile(subfolder.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + EXPECT_TRUE(INVALID_HANDLE_VALUE != dir); + + base::string16 temp_dir_nt; + temp_dir_nt += L"\\??\\"; + temp_dir_nt += temp_dir; + EXPECT_TRUE(SetReparsePoint(dir, temp_dir_nt.c_str())); + EXPECT_TRUE(::CloseHandle(dir)); + + // Try to open the file again. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_write.c_str())); + + // Remove the reparse point. + dir = ::CreateFile(subfolder.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + NULL); + EXPECT_TRUE(INVALID_HANDLE_VALUE != dir); + EXPECT_TRUE(DeleteReparsePoint(dir)); + EXPECT_TRUE(::CloseHandle(dir)); + + // Cleanup. + EXPECT_TRUE(::DeleteFile(temp_file_in_temp.c_str())); + EXPECT_TRUE(::RemoveDirectory(subfolder.c_str())); +} + +TEST(FilePolicyTest, CheckExistingNTPrefixEscape) { + base::string16 name = L"\\??\\NAME"; + + base::string16 result = FixNTPrefixForMatch(name); + + EXPECT_STREQ(result.c_str(), L"\\/?/?\\NAME"); +} + +TEST(FilePolicyTest, CheckEscapedNTPrefixNoEscape) { + base::string16 name = L"\\/?/?\\NAME"; + + base::string16 result = FixNTPrefixForMatch(name); + + EXPECT_STREQ(result.c_str(), name.c_str()); +} + +TEST(FilePolicyTest, CheckMissingNTPrefixEscape) { + base::string16 name = L"C:\\NAME"; + + base::string16 result = FixNTPrefixForMatch(name); + + EXPECT_STREQ(result.c_str(), L"\\/?/?\\C:\\NAME"); +} + +} // namespace sandbox diff --git a/sandbox/win/src/filesystem_dispatcher.cc b/sandbox/win/src/filesystem_dispatcher.cc new file mode 100644 index 0000000000..4561be4202 --- /dev/null +++ b/sandbox/win/src/filesystem_dispatcher.cc @@ -0,0 +1,314 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/filesystem_dispatcher.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/filesystem_interception.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +FilesystemDispatcher::FilesystemDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IPC_NTCREATEFILE_TAG, WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, + UINT32_TYPE, UINT32_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>(&FilesystemDispatcher::NtCreateFile) + }; + + static const IPCCall open_file = { + {IPC_NTOPENFILE_TAG, WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, + UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>(&FilesystemDispatcher::NtOpenFile) + }; + + static const IPCCall attribs = { + {IPC_NTQUERYATTRIBUTESFILE_TAG, WCHAR_TYPE, UINT32_TYPE, INOUTPTR_TYPE}, + reinterpret_cast<CallbackGeneric>( + &FilesystemDispatcher::NtQueryAttributesFile) + }; + + static const IPCCall full_attribs = { + {IPC_NTQUERYFULLATTRIBUTESFILE_TAG, WCHAR_TYPE, UINT32_TYPE, INOUTPTR_TYPE}, + reinterpret_cast<CallbackGeneric>( + &FilesystemDispatcher::NtQueryFullAttributesFile) + }; + + static const IPCCall set_info = { + {IPC_NTSETINFO_RENAME_TAG, VOIDPTR_TYPE, INOUTPTR_TYPE, INOUTPTR_TYPE, + UINT32_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>( + &FilesystemDispatcher::NtSetInformationFile) + }; + + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(open_file); + ipc_calls_.push_back(attribs); + ipc_calls_.push_back(full_attribs); + ipc_calls_.push_back(set_info); +} + +bool FilesystemDispatcher::SetupService(InterceptionManager* manager, + int service) { + switch (service) { + case IPC_NTCREATEFILE_TAG: + return INTERCEPT_NT(manager, NtCreateFile, CREATE_FILE_ID, 48); + + case IPC_NTOPENFILE_TAG: + return INTERCEPT_NT(manager, NtOpenFile, OPEN_FILE_ID, 28); + + case IPC_NTQUERYATTRIBUTESFILE_TAG: + return INTERCEPT_NT(manager, NtQueryAttributesFile, QUERY_ATTRIB_FILE_ID, + 12); + + case IPC_NTQUERYFULLATTRIBUTESFILE_TAG: + return INTERCEPT_NT(manager, NtQueryFullAttributesFile, + QUERY_FULL_ATTRIB_FILE_ID, 12); + + case IPC_NTSETINFO_RENAME_TAG: + return INTERCEPT_NT(manager, NtSetInformationFile, SET_INFO_FILE_ID, 24); + + default: + return false; + } +} + +bool FilesystemDispatcher::NtCreateFile(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + uint32 desired_access, + uint32 file_attributes, + uint32 share_access, + uint32 create_disposition, + uint32 create_options) { + if (!PreProcessName(*name, name)) { + // The path requested might contain a reparse point. + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + const wchar_t* filename = name->c_str(); + + uint32 broker = TRUE; + CountedParameterSet<OpenFile> params; + params[OpenFile::NAME] = ParamPickerMake(filename); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access); + params[OpenFile::DISPOSITION] = ParamPickerMake(create_disposition); + params[OpenFile::OPTIONS] = ParamPickerMake(create_options); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = policy_base_->EvalPolicy(IPC_NTCREATEFILE_TAG, + params.GetBase()); + HANDLE handle; + ULONG_PTR io_information = 0; + NTSTATUS nt_status; + if (!FileSystemPolicy::CreateFileAction(result, *ipc->client_info, *name, + attributes, desired_access, + file_attributes, share_access, + create_disposition, create_options, + &handle, &nt_status, + &io_information)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + // Return operation status on the IPC. + ipc->return_info.extended[0].ulong_ptr = io_information; + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +bool FilesystemDispatcher::NtOpenFile(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + uint32 desired_access, + uint32 share_access, + uint32 open_options) { + if (!PreProcessName(*name, name)) { + // The path requested might contain a reparse point. + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + const wchar_t* filename = name->c_str(); + + uint32 broker = TRUE; + uint32 create_disposition = FILE_OPEN; + CountedParameterSet<OpenFile> params; + params[OpenFile::NAME] = ParamPickerMake(filename); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access); + params[OpenFile::DISPOSITION] = ParamPickerMake(create_disposition); + params[OpenFile::OPTIONS] = ParamPickerMake(open_options); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = policy_base_->EvalPolicy(IPC_NTOPENFILE_TAG, + params.GetBase()); + HANDLE handle; + ULONG_PTR io_information = 0; + NTSTATUS nt_status; + if (!FileSystemPolicy::OpenFileAction(result, *ipc->client_info, *name, + attributes, desired_access, + share_access, open_options, &handle, + &nt_status, &io_information)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + // Return operation status on the IPC. + ipc->return_info.extended[0].ulong_ptr = io_information; + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +bool FilesystemDispatcher::NtQueryAttributesFile(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + CountedBuffer* info) { + if (sizeof(FILE_BASIC_INFORMATION) != info->Size()) + return false; + + if (!PreProcessName(*name, name)) { + // The path requested might contain a reparse point. + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + uint32 broker = TRUE; + const wchar_t* filename = name->c_str(); + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(filename); + params[FileName::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = policy_base_->EvalPolicy(IPC_NTQUERYATTRIBUTESFILE_TAG, + params.GetBase()); + + FILE_BASIC_INFORMATION* information = + reinterpret_cast<FILE_BASIC_INFORMATION*>(info->Buffer()); + NTSTATUS nt_status; + if (!FileSystemPolicy::QueryAttributesFileAction(result, *ipc->client_info, + *name, attributes, + information, &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + return true; +} + +bool FilesystemDispatcher::NtQueryFullAttributesFile(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + CountedBuffer* info) { + if (sizeof(FILE_NETWORK_OPEN_INFORMATION) != info->Size()) + return false; + + if (!PreProcessName(*name, name)) { + // The path requested might contain a reparse point. + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + uint32 broker = TRUE; + const wchar_t* filename = name->c_str(); + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(filename); + params[FileName::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = policy_base_->EvalPolicy( + IPC_NTQUERYFULLATTRIBUTESFILE_TAG, params.GetBase()); + + FILE_NETWORK_OPEN_INFORMATION* information = + reinterpret_cast<FILE_NETWORK_OPEN_INFORMATION*>(info->Buffer()); + NTSTATUS nt_status; + if (!FileSystemPolicy::QueryFullAttributesFileAction(result, + *ipc->client_info, + *name, attributes, + information, + &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + return true; +} + +bool FilesystemDispatcher::NtSetInformationFile(IPCInfo* ipc, + HANDLE handle, + CountedBuffer* status, + CountedBuffer* info, + uint32 length, + uint32 info_class) { + if (sizeof(IO_STATUS_BLOCK) != status->Size()) + return false; + if (length != info->Size()) + return false; + + FILE_RENAME_INFORMATION* rename_info = + reinterpret_cast<FILE_RENAME_INFORMATION*>(info->Buffer()); + + if (!IsSupportedRenameCall(rename_info, length, info_class)) + return false; + + base::string16 name; + name.assign(rename_info->FileName, rename_info->FileNameLength / + sizeof(rename_info->FileName[0])); + if (!PreProcessName(name, &name)) { + // The path requested might contain a reparse point. + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + uint32 broker = TRUE; + const wchar_t* filename = name.c_str(); + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(filename); + params[FileName::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = policy_base_->EvalPolicy(IPC_NTSETINFO_RENAME_TAG, + params.GetBase()); + + IO_STATUS_BLOCK* io_status = + reinterpret_cast<IO_STATUS_BLOCK*>(status->Buffer()); + NTSTATUS nt_status; + if (!FileSystemPolicy::SetInformationFileAction(result, *ipc->client_info, + handle, rename_info, length, + info_class, io_status, + &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/filesystem_dispatcher.h b/sandbox/win/src/filesystem_dispatcher.h new file mode 100644 index 0000000000..192d36cab0 --- /dev/null +++ b/sandbox/win/src/filesystem_dispatcher.h @@ -0,0 +1,72 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_FILESYSTEM_DISPATCHER_H__ +#define SANDBOX_SRC_FILESYSTEM_DISPATCHER_H__ + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles file system-related IPC calls. +class FilesystemDispatcher : public Dispatcher { + public: + explicit FilesystemDispatcher(PolicyBase* policy_base); + ~FilesystemDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, int service) override; + + private: + // Processes IPC requests coming from calls to NtCreateFile in the target. + bool NtCreateFile(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + uint32 desired_access, + uint32 file_attributes, + uint32 share_access, + uint32 create_disposition, + uint32 create_options); + + // Processes IPC requests coming from calls to NtOpenFile in the target. + bool NtOpenFile(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + uint32 desired_access, + uint32 share_access, + uint32 create_options); + + // Processes IPC requests coming from calls to NtQueryAttributesFile in the + // target. + bool NtQueryAttributesFile(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + CountedBuffer* info); + + // Processes IPC requests coming from calls to NtQueryFullAttributesFile in + // the target. + bool NtQueryFullAttributesFile(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + CountedBuffer* info); + + // Processes IPC requests coming from calls to NtSetInformationFile with the + // rename information class. + bool NtSetInformationFile(IPCInfo* ipc, + HANDLE handle, + CountedBuffer* status, + CountedBuffer* info, + uint32 length, + uint32 info_class); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(FilesystemDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_FILESYSTEM_DISPATCHER_H__ diff --git a/sandbox/win/src/filesystem_interception.cc b/sandbox/win/src/filesystem_interception.cc new file mode 100644 index 0000000000..459f7ace2d --- /dev/null +++ b/sandbox/win/src/filesystem_interception.cc @@ -0,0 +1,367 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/filesystem_interception.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +NTSTATUS WINAPI TargetNtCreateFile(NtCreateFileFunction orig_CreateFile, + PHANDLE file, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, + ULONG file_attributes, ULONG sharing, + ULONG disposition, ULONG options, + PVOID ea_buffer, ULONG ea_length) { + // Check if the process can open it first. + NTSTATUS status = orig_CreateFile(file, desired_access, object_attributes, + io_status, allocation_size, + file_attributes, sharing, disposition, + options, ea_buffer, ea_length); + if (STATUS_ACCESS_DENIED != status) + return status; + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + wchar_t* name = NULL; + do { + if (!ValidParameter(file, sizeof(HANDLE), WRITE)) + break; + if (!ValidParameter(io_status, sizeof(IO_STATUS_BLOCK), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + uint32 attributes = 0; + NTSTATUS ret = AllocAndCopyName(object_attributes, &name, &attributes, + NULL); + if (!NT_SUCCESS(ret) || NULL == name) + break; + + uint32 desired_access_uint32 = desired_access; + uint32 options_uint32 = options; + uint32 disposition_uint32 = disposition; + uint32 broker = FALSE; + CountedParameterSet<OpenFile> params; + params[OpenFile::NAME] = ParamPickerMake(name); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access_uint32); + params[OpenFile::DISPOSITION] = ParamPickerMake(disposition_uint32); + params[OpenFile::OPTIONS] = ParamPickerMake(options_uint32); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IPC_NTCREATEFILE_TAG, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + // The following call must match in the parameters with + // FilesystemDispatcher::ProcessNtCreateFile. + ResultCode code = CrossCall(ipc, IPC_NTCREATEFILE_TAG, name, attributes, + desired_access_uint32, file_attributes, sharing, + disposition, options_uint32, &answer); + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + if (!NT_SUCCESS(answer.nt_status)) + break; + + __try { + *file = answer.handle; + io_status->Status = answer.nt_status; + io_status->Information = answer.extended[0].ulong_ptr; + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + } while (false); + + if (name) + operator delete(name, NT_ALLOC); + + return status; +} + +NTSTATUS WINAPI TargetNtOpenFile(NtOpenFileFunction orig_OpenFile, PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, ULONG sharing, + ULONG options) { + // Check if the process can open it first. + NTSTATUS status = orig_OpenFile(file, desired_access, object_attributes, + io_status, sharing, options); + if (STATUS_ACCESS_DENIED != status) + return status; + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + wchar_t* name = NULL; + do { + if (!ValidParameter(file, sizeof(HANDLE), WRITE)) + break; + if (!ValidParameter(io_status, sizeof(IO_STATUS_BLOCK), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + uint32 attributes; + NTSTATUS ret = AllocAndCopyName(object_attributes, &name, &attributes, + NULL); + if (!NT_SUCCESS(ret) || NULL == name) + break; + + uint32 desired_access_uint32 = desired_access; + uint32 options_uint32 = options; + uint32 disposition_uint32 = FILE_OPEN; + uint32 broker = FALSE; + CountedParameterSet<OpenFile> params; + params[OpenFile::NAME] = ParamPickerMake(name); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access_uint32); + params[OpenFile::DISPOSITION] = ParamPickerMake(disposition_uint32); + params[OpenFile::OPTIONS] = ParamPickerMake(options_uint32); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IPC_NTOPENFILE_TAG, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_NTOPENFILE_TAG, name, attributes, + desired_access_uint32, sharing, options_uint32, + &answer); + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + if (!NT_SUCCESS(answer.nt_status)) + break; + + __try { + *file = answer.handle; + io_status->Status = answer.nt_status; + io_status->Information = answer.extended[0].ulong_ptr; + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + } while (false); + + if (name) + operator delete(name, NT_ALLOC); + + return status; +} + +NTSTATUS WINAPI TargetNtQueryAttributesFile( + NtQueryAttributesFileFunction orig_QueryAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes) { + // Check if the process can query it first. + NTSTATUS status = orig_QueryAttributes(object_attributes, file_attributes); + if (STATUS_ACCESS_DENIED != status) + return status; + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + wchar_t* name = NULL; + do { + if (!ValidParameter(file_attributes, sizeof(FILE_BASIC_INFORMATION), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + uint32 attributes = 0; + NTSTATUS ret = AllocAndCopyName(object_attributes, &name, &attributes, + NULL); + if (!NT_SUCCESS(ret) || NULL == name) + break; + + InOutCountedBuffer file_info(file_attributes, + sizeof(FILE_BASIC_INFORMATION)); + + uint32 broker = FALSE; + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(name); + params[FileName::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IPC_NTQUERYATTRIBUTESFILE_TAG, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_NTQUERYATTRIBUTESFILE_TAG, name, + attributes, file_info, &answer); + + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + } while (false); + + if (name) + operator delete(name, NT_ALLOC); + + return status; +} + +NTSTATUS WINAPI TargetNtQueryFullAttributesFile( + NtQueryFullAttributesFileFunction orig_QueryFullAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes) { + // Check if the process can query it first. + NTSTATUS status = orig_QueryFullAttributes(object_attributes, + file_attributes); + if (STATUS_ACCESS_DENIED != status) + return status; + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + wchar_t* name = NULL; + do { + if (!ValidParameter(file_attributes, sizeof(FILE_NETWORK_OPEN_INFORMATION), + WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + uint32 attributes = 0; + NTSTATUS ret = AllocAndCopyName(object_attributes, &name, &attributes, + NULL); + if (!NT_SUCCESS(ret) || NULL == name) + break; + + InOutCountedBuffer file_info(file_attributes, + sizeof(FILE_NETWORK_OPEN_INFORMATION)); + + uint32 broker = FALSE; + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(name); + params[FileName::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_NTQUERYFULLATTRIBUTESFILE_TAG, name, + attributes, file_info, &answer); + + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + } while (false); + + if (name) + operator delete(name, NT_ALLOC); + + return status; +} + +NTSTATUS WINAPI TargetNtSetInformationFile( + NtSetInformationFileFunction orig_SetInformationFile, HANDLE file, + PIO_STATUS_BLOCK io_status, PVOID file_info, ULONG length, + FILE_INFORMATION_CLASS file_info_class) { + // Check if the process can open it first. + NTSTATUS status = orig_SetInformationFile(file, io_status, file_info, length, + file_info_class); + if (STATUS_ACCESS_DENIED != status) + return status; + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + wchar_t* name = NULL; + do { + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + if (!ValidParameter(io_status, sizeof(IO_STATUS_BLOCK), WRITE)) + break; + + if (!ValidParameter(file_info, length, READ)) + break; + + FILE_RENAME_INFORMATION* file_rename_info = + reinterpret_cast<FILE_RENAME_INFORMATION*>(file_info); + OBJECT_ATTRIBUTES object_attributes; + UNICODE_STRING object_name; + InitializeObjectAttributes(&object_attributes, &object_name, 0, NULL, NULL); + + __try { + if (!IsSupportedRenameCall(file_rename_info, length, file_info_class)) + break; + + object_attributes.RootDirectory = file_rename_info->RootDirectory; + object_name.Buffer = file_rename_info->FileName; + object_name.Length = object_name.MaximumLength = + static_cast<USHORT>(file_rename_info->FileNameLength); + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + + NTSTATUS ret = AllocAndCopyName(&object_attributes, &name, NULL, NULL); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32 broker = FALSE; + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(name); + params[FileName::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IPC_NTSETINFO_RENAME_TAG, params.GetBase())) + break; + + InOutCountedBuffer io_status_buffer(io_status, sizeof(IO_STATUS_BLOCK)); + // This is actually not an InOut buffer, only In, but using InOut facility + // really helps to simplify the code. + InOutCountedBuffer file_info_buffer(file_info, length); + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_NTSETINFO_RENAME_TAG, file, + io_status_buffer, file_info_buffer, length, + file_info_class, &answer); + + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + } while (false); + + if (name) + operator delete(name, NT_ALLOC); + + return status; +} + +} // namespace sandbox diff --git a/sandbox/win/src/filesystem_interception.h b/sandbox/win/src/filesystem_interception.h new file mode 100644 index 0000000000..2fafb4499b --- /dev/null +++ b/sandbox/win/src/filesystem_interception.h @@ -0,0 +1,53 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_FILESYSTEM_INTERCEPTION_H__ +#define SANDBOX_SRC_FILESYSTEM_INTERCEPTION_H__ + +namespace sandbox { + +extern "C" { + +// Interception of NtCreateFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateFile( + NtCreateFileFunction orig_CreateFile, PHANDLE file, + ACCESS_MASK desired_access, POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, PLARGE_INTEGER allocation_size, + ULONG file_attributes, ULONG sharing, ULONG disposition, ULONG options, + PVOID ea_buffer, ULONG ea_length); + +// Interception of NtOpenFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenFile( + NtOpenFileFunction orig_OpenFile, PHANDLE file, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, PIO_STATUS_BLOCK io_status, + ULONG sharing, ULONG options); + +// Interception of NtQueryAtttributesFile on the child process. +// It should never be called directly. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryAttributesFile( + NtQueryAttributesFileFunction orig_QueryAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes); + +// Interception of NtQueryFullAtttributesFile on the child process. +// It should never be called directly. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryFullAttributesFile( + NtQueryFullAttributesFileFunction orig_QueryAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes); + +// Interception of NtSetInformationFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtSetInformationFile( + NtSetInformationFileFunction orig_SetInformationFile, HANDLE file, + PIO_STATUS_BLOCK io_status, PVOID file_information, ULONG length, + FILE_INFORMATION_CLASS file_information_class); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_FILESYSTEM_INTERCEPTION_H__ diff --git a/sandbox/win/src/filesystem_policy.cc b/sandbox/win/src/filesystem_policy.cc new file mode 100644 index 0000000000..0349ff304e --- /dev/null +++ b/sandbox/win/src/filesystem_policy.cc @@ -0,0 +1,430 @@ +// Copyright (c) 2011 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 <string> + +#include "sandbox/win/src/filesystem_policy.h" + +#include "base/logging.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +NTSTATUS NtCreateFileInTarget(HANDLE* target_file_handle, + ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES* obj_attributes, + IO_STATUS_BLOCK* io_status_block, + ULONG file_attributes, + ULONG share_access, + ULONG create_disposition, + ULONG create_options, + PVOID ea_buffer, + ULONG ea_lenght, + HANDLE target_process) { + NtCreateFileFunction NtCreateFile = NULL; + ResolveNTFunctionPtr("NtCreateFile", &NtCreateFile); + + HANDLE local_handle = INVALID_HANDLE_VALUE; + NTSTATUS status = NtCreateFile(&local_handle, desired_access, obj_attributes, + io_status_block, NULL, file_attributes, + share_access, create_disposition, + create_options, ea_buffer, ea_lenght); + if (!NT_SUCCESS(status)) { + return status; + } + + if (!sandbox::SameObject(local_handle, obj_attributes->ObjectName->Buffer)) { + // The handle points somewhere else. Fail the operation. + ::CloseHandle(local_handle); + return STATUS_ACCESS_DENIED; + } + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + target_process, target_file_handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return STATUS_SUCCESS; +} + +// Get an initialized anonymous level Security QOS. +SECURITY_QUALITY_OF_SERVICE GetAnonymousQOS() { + SECURITY_QUALITY_OF_SERVICE security_qos = {0}; + security_qos.Length = sizeof(security_qos); + security_qos.ImpersonationLevel = SecurityAnonymous; + // Set dynamic tracking so that a pipe doesn't capture the broker's token + security_qos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; + security_qos.EffectiveOnly = TRUE; + + return security_qos; +} + +} // namespace. + +namespace sandbox { + +bool FileSystemPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + base::string16 mod_name(name); + if (mod_name.empty()) { + return false; + } + + if (!PreProcessName(mod_name, &mod_name)) { + // The path to be added might contain a reparse point. + NOTREACHED(); + return false; + } + + // TODO(cpu) bug 32224: This prefix add is a hack because we don't have the + // infrastructure to normalize names. In any case we need to escape the + // question marks. + if (_wcsnicmp(mod_name.c_str(), kNTDevicePrefix, kNTDevicePrefixLen)) { + mod_name = FixNTPrefixForMatch(mod_name); + name = mod_name.c_str(); + } + + EvalResult result = ASK_BROKER; + + // List of supported calls for the filesystem. + const unsigned kCallNtCreateFile = 0x1; + const unsigned kCallNtOpenFile = 0x2; + const unsigned kCallNtQueryAttributesFile = 0x4; + const unsigned kCallNtQueryFullAttributesFile = 0x8; + const unsigned kCallNtSetInfoRename = 0x10; + + DWORD rule_to_add = kCallNtOpenFile | kCallNtCreateFile | + kCallNtQueryAttributesFile | + kCallNtQueryFullAttributesFile | kCallNtSetInfoRename; + + PolicyRule create(result); + PolicyRule open(result); + PolicyRule query(result); + PolicyRule query_full(result); + PolicyRule rename(result); + + switch (semantics) { + case TargetPolicy::FILES_ALLOW_DIR_ANY: { + open.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); + create.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); + break; + } + case TargetPolicy::FILES_ALLOW_READONLY: { + // We consider all flags that are not known to be readonly as potentially + // used for write. + DWORD allowed_flags = FILE_READ_DATA | FILE_READ_ATTRIBUTES | + FILE_READ_EA | SYNCHRONIZE | FILE_EXECUTE | + GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL; + DWORD restricted_flags = ~allowed_flags; + open.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); + open.AddNumberMatch(IF, OpenFile::DISPOSITION, FILE_OPEN, EQUAL); + create.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); + create.AddNumberMatch(IF, OpenFile::DISPOSITION, FILE_OPEN, EQUAL); + + // Read only access don't work for rename. + rule_to_add &= ~kCallNtSetInfoRename; + break; + } + case TargetPolicy::FILES_ALLOW_QUERY: { + // Here we don't want to add policy for the open or the create. + rule_to_add &= ~(kCallNtOpenFile | kCallNtCreateFile | + kCallNtSetInfoRename); + break; + } + case TargetPolicy::FILES_ALLOW_ANY: { + break; + } + default: { + NOTREACHED(); + return false; + } + } + + if ((rule_to_add & kCallNtCreateFile) && + (!create.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IPC_NTCREATEFILE_TAG, &create))) { + return false; + } + + if ((rule_to_add & kCallNtOpenFile) && + (!open.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IPC_NTOPENFILE_TAG, &open))) { + return false; + } + + if ((rule_to_add & kCallNtQueryAttributesFile) && + (!query.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &query))) { + return false; + } + + if ((rule_to_add & kCallNtQueryFullAttributesFile) && + (!query_full.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) + || !policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, + &query_full))) { + return false; + } + + if ((rule_to_add & kCallNtSetInfoRename) && + (!rename.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &rename))) { + return false; + } + + return true; +} + +// Right now we insert two rules, to be evaluated before any user supplied rule: +// - go to the broker if the path doesn't look like the paths that we push on +// the policy (namely \??\something). +// - go to the broker if it looks like this is a short-name path. +// +// It is possible to add a rule to go to the broker in any case; it would look +// something like: +// rule = new PolicyRule(ASK_BROKER); +// rule->AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); +// policy->AddRule(service, rule); +bool FileSystemPolicy::SetInitialRules(LowLevelPolicy* policy) { + PolicyRule format(ASK_BROKER); + PolicyRule short_name(ASK_BROKER); + + bool rv = format.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); + rv &= format.AddStringMatch(IF_NOT, FileName::NAME, L"\\/?/?\\*", + CASE_SENSITIVE); + + rv &= short_name.AddNumberMatch(IF_NOT, FileName::BROKER, TRUE, AND); + rv &= short_name.AddStringMatch(IF, FileName::NAME, L"*~*", CASE_SENSITIVE); + + if (!rv || !policy->AddRule(IPC_NTCREATEFILE_TAG, &format)) + return false; + + if (!policy->AddRule(IPC_NTCREATEFILE_TAG, &short_name)) + return false; + + if (!policy->AddRule(IPC_NTOPENFILE_TAG, &format)) + return false; + + if (!policy->AddRule(IPC_NTOPENFILE_TAG, &short_name)) + return false; + + if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &format)) + return false; + + if (!policy->AddRule(IPC_NTQUERYATTRIBUTESFILE_TAG, &short_name)) + return false; + + if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &format)) + return false; + + if (!policy->AddRule(IPC_NTQUERYFULLATTRIBUTESFILE_TAG, &short_name)) + return false; + + if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &format)) + return false; + + if (!policy->AddRule(IPC_NTSETINFO_RENAME_TAG, &short_name)) + return false; + + return true; +} + +bool FileSystemPolicy::CreateFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &file, + uint32 attributes, + uint32 desired_access, + uint32 file_attributes, + uint32 share_access, + uint32 create_disposition, + uint32 create_options, + HANDLE *handle, + NTSTATUS* nt_status, + ULONG_PTR *io_information) { + // The only action supported is ASK_BROKER which means create the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + IO_STATUS_BLOCK io_block = {0}; + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, NULL, &obj_attributes, + &uni_name, IsPipe(file) ? &security_qos : NULL); + *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes, + &io_block, file_attributes, share_access, + create_disposition, create_options, NULL, + 0, client_info.process); + + *io_information = io_block.Information; + return true; +} + +bool FileSystemPolicy::OpenFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &file, + uint32 attributes, + uint32 desired_access, + uint32 share_access, + uint32 open_options, + HANDLE *handle, + NTSTATUS* nt_status, + ULONG_PTR *io_information) { + // The only action supported is ASK_BROKER which means open the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return true; + } + // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and + // CreateDisposition = FILE_OPEN. + IO_STATUS_BLOCK io_block = {0}; + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, NULL, &obj_attributes, + &uni_name, IsPipe(file) ? &security_qos : NULL); + *nt_status = NtCreateFileInTarget(handle, desired_access, &obj_attributes, + &io_block, 0, share_access, FILE_OPEN, + open_options, NULL, 0, + client_info.process); + + *io_information = io_block.Information; + return true; +} + +bool FileSystemPolicy::QueryAttributesFileAction( + EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &file, + uint32 attributes, + FILE_BASIC_INFORMATION* file_info, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means query the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return true; + } + + NtQueryAttributesFileFunction NtQueryAttributesFile = NULL; + ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile); + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, NULL, &obj_attributes, + &uni_name, IsPipe(file) ? &security_qos : NULL); + *nt_status = NtQueryAttributesFile(&obj_attributes, file_info); + + return true; +} + +bool FileSystemPolicy::QueryFullAttributesFileAction( + EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &file, + uint32 attributes, + FILE_NETWORK_OPEN_INFORMATION* file_info, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means query the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return true; + } + + NtQueryFullAttributesFileFunction NtQueryFullAttributesFile = NULL; + ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile); + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, NULL, &obj_attributes, + &uni_name, IsPipe(file) ? &security_qos : NULL); + *nt_status = NtQueryFullAttributesFile(&obj_attributes, file_info); + + return true; +} + +bool FileSystemPolicy::SetInformationFileAction( + EvalResult eval_result, const ClientInfo& client_info, + HANDLE target_file_handle, void* file_info, uint32 length, + uint32 info_class, IO_STATUS_BLOCK* io_block, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means open the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return true; + } + + NtSetInformationFileFunction NtSetInformationFile = NULL; + ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile); + + HANDLE local_handle = NULL; + if (!::DuplicateHandle(client_info.process, target_file_handle, + ::GetCurrentProcess(), &local_handle, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + *nt_status = STATUS_ACCESS_DENIED; + return true; + } + + base::win::ScopedHandle handle(local_handle); + + FILE_INFORMATION_CLASS file_info_class = + static_cast<FILE_INFORMATION_CLASS>(info_class); + *nt_status = NtSetInformationFile(local_handle, io_block, file_info, length, + file_info_class); + + return true; +} + +bool PreProcessName(const base::string16& path, base::string16* new_path) { + ConvertToLongPath(path, new_path); + + bool reparsed = false; + if (ERROR_SUCCESS != IsReparsePoint(*new_path, &reparsed)) + return false; + + // We can't process reparsed file. + return !reparsed; +} + +base::string16 FixNTPrefixForMatch(const base::string16& name) { + base::string16 mod_name = name; + + // NT prefix escaped for rule matcher + const wchar_t kNTPrefixEscaped[] = L"\\/?/?\\"; + const int kNTPrefixEscapedLen = arraysize(kNTPrefixEscaped) - 1; + + if (0 != mod_name.compare(0, kNTPrefixLen, kNTPrefix)) { + if (0 != mod_name.compare(0, kNTPrefixEscapedLen, kNTPrefixEscaped)) { + // TODO(nsylvain): Find a better way to do name resolution. Right now we + // take the name and we expand it. + mod_name.insert(0, kNTPrefixEscaped); + } + } else { + // Start of name matches NT prefix, replace with escaped format + // Fixes bug: 334882 + mod_name.replace(0, kNTPrefixLen, kNTPrefixEscaped); + } + + return mod_name; +} + +} // namespace sandbox diff --git a/sandbox/win/src/filesystem_policy.h b/sandbox/win/src/filesystem_policy.h new file mode 100644 index 0000000000..ce28344c53 --- /dev/null +++ b/sandbox/win/src/filesystem_policy.h @@ -0,0 +1,113 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_FILESYSTEM_POLICY_H__ +#define SANDBOX_SRC_FILESYSTEM_POLICY_H__ + +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum EvalResult; + +// This class centralizes most of the knowledge related to file system policy +class FileSystemPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for File IO, in particular open or create actions. + // 'name' is the file or directory name. + // 'semantics' is the desired semantics for the open or create. + // 'policy' is the policy generator to which the rules are going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Add basic file system rules. + static bool SetInitialRules(LowLevelPolicy* policy); + + // Performs the desired policy action on a create request with an + // API that is compatible with the IPC-received parameters. + // 'client_info' : the target process that is making the request. + // 'eval_result' : The desired policy action to accomplish. + // 'file' : The target file or directory. + static bool CreateFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &file, + uint32 attributes, + uint32 desired_access, + uint32 file_attributes, + uint32 share_access, + uint32 create_disposition, + uint32 create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG_PTR* io_information); + + // Performs the desired policy action on an open request with an + // API that is compatible with the IPC-received parameters. + // 'client_info' : the target process that is making the request. + // 'eval_result' : The desired policy action to accomplish. + // 'file' : The target file or directory. + static bool OpenFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &file, + uint32 attributes, + uint32 desired_access, + uint32 share_access, + uint32 open_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG_PTR* io_information); + + // Performs the desired policy action on a query request with an + // API that is compatible with the IPC-received parameters. + static bool QueryAttributesFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &file, + uint32 attributes, + FILE_BASIC_INFORMATION* file_info, + NTSTATUS* nt_status); + + // Performs the desired policy action on a query request with an + // API that is compatible with the IPC-received parameters. + static bool QueryFullAttributesFileAction( + EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &file, + uint32 attributes, + FILE_NETWORK_OPEN_INFORMATION* file_info, + NTSTATUS* nt_status); + + // Performs the desired policy action on a set_info request with an + // API that is compatible with the IPC-received parameters. + static bool SetInformationFileAction(EvalResult eval_result, + const ClientInfo& client_info, + HANDLE target_file_handle, + void* file_info, + uint32 length, + uint32 info_class, + IO_STATUS_BLOCK* io_block, + NTSTATUS* nt_status); +}; + +// Expands the path and check if it's a reparse point. Returns false if +// we cannot determine or if there is an unexpected error. In that case +// the path cannot be trusted. +bool PreProcessName(const base::string16& path, base::string16* new_path); + +// Corrects global paths to have a correctly escaped NT prefix at the +// beginning. If the name has no NT prefix (either normal or escaped) +// add the escaped form to the string +base::string16 FixNTPrefixForMatch(const base::string16& name); + +} // namespace sandbox + +#endif // SANDBOX_SRC_FILESYSTEM_POLICY_H__ diff --git a/sandbox/win/src/handle_closer.cc b/sandbox/win/src/handle_closer.cc new file mode 100644 index 0000000000..2e3a78223b --- /dev/null +++ b/sandbox/win/src/handle_closer.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_closer.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +template<typename T> T RoundUpToWordSize(T v) { + if (size_t mod = v % sizeof(size_t)) + v += sizeof(size_t) - mod; + return v; +} + +template<typename T> T* RoundUpToWordSize(T* v) { + return reinterpret_cast<T*>(RoundUpToWordSize(reinterpret_cast<size_t>(v))); +} + +} // namespace + +namespace sandbox { + +// Memory buffer mapped from the parent, with the list of handles. +SANDBOX_INTERCEPT HandleCloserInfo* g_handles_to_close; + +HandleCloser::HandleCloser() { +} + +HandleCloser::~HandleCloser() { +} + +ResultCode HandleCloser::AddHandle(const base::char16* handle_type, + const base::char16* handle_name) { + if (!handle_type) + return SBOX_ERROR_BAD_PARAMS; + + base::string16 resolved_name; + if (handle_name) { + resolved_name = handle_name; + if (handle_type == base::string16(L"Key")) + if (!ResolveRegistryName(resolved_name, &resolved_name)) + return SBOX_ERROR_BAD_PARAMS; + } + + HandleMap::iterator names = handles_to_close_.find(handle_type); + if (names == handles_to_close_.end()) { // We have no entries for this type. + std::pair<HandleMap::iterator, bool> result = handles_to_close_.insert( + HandleMap::value_type(handle_type, HandleMap::mapped_type())); + names = result.first; + if (handle_name) + names->second.insert(resolved_name); + } else if (!handle_name) { // Now we need to close all handles of this type. + names->second.clear(); + } else if (!names->second.empty()) { // Add another name for this type. + names->second.insert(resolved_name); + } // If we're already closing all handles of type then we're done. + + return SBOX_ALL_OK; +} + +size_t HandleCloser::GetBufferSize() { + size_t bytes_total = offsetof(HandleCloserInfo, handle_entries); + + for (HandleMap::iterator i = handles_to_close_.begin(); + i != handles_to_close_.end(); ++i) { + size_t bytes_entry = offsetof(HandleListEntry, handle_type) + + (i->first.size() + 1) * sizeof(base::char16); + for (HandleMap::mapped_type::iterator j = i->second.begin(); + j != i->second.end(); ++j) { + bytes_entry += ((*j).size() + 1) * sizeof(base::char16); + } + + // Round up to the nearest multiple of word size. + bytes_entry = RoundUpToWordSize(bytes_entry); + bytes_total += bytes_entry; + } + + return bytes_total; +} + +bool HandleCloser::InitializeTargetHandles(TargetProcess* target) { + // Do nothing on an empty list (global pointer already initialized to NULL). + if (handles_to_close_.empty()) + return true; + + size_t bytes_needed = GetBufferSize(); + scoped_ptr<size_t[]> local_buffer( + new size_t[bytes_needed / sizeof(size_t)]); + + if (!SetupHandleList(local_buffer.get(), bytes_needed)) + return false; + + HANDLE child = target->Process(); + + // Allocate memory in the target process without specifying the address + void* remote_data = ::VirtualAllocEx(child, NULL, bytes_needed, + MEM_COMMIT, PAGE_READWRITE); + if (NULL == remote_data) + return false; + + // Copy the handle buffer over. + SIZE_T bytes_written; + BOOL result = ::WriteProcessMemory(child, remote_data, local_buffer.get(), + bytes_needed, &bytes_written); + if (!result || bytes_written != bytes_needed) { + ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE); + return false; + } + + g_handles_to_close = reinterpret_cast<HandleCloserInfo*>(remote_data); + + ResultCode rc = target->TransferVariable("g_handles_to_close", + &g_handles_to_close, + sizeof(g_handles_to_close)); + + return (SBOX_ALL_OK == rc); +} + +bool HandleCloser::SetupHandleList(void* buffer, size_t buffer_bytes) { + ::ZeroMemory(buffer, buffer_bytes); + HandleCloserInfo* handle_info = reinterpret_cast<HandleCloserInfo*>(buffer); + handle_info->record_bytes = buffer_bytes; + handle_info->num_handle_types = handles_to_close_.size(); + + base::char16* output = reinterpret_cast<base::char16*>( + &handle_info->handle_entries[0]); + base::char16* end = reinterpret_cast<base::char16*>( + reinterpret_cast<char*>(buffer) + buffer_bytes); + for (HandleMap::iterator i = handles_to_close_.begin(); + i != handles_to_close_.end(); ++i) { + if (output >= end) + return false; + HandleListEntry* list_entry = reinterpret_cast<HandleListEntry*>(output); + output = &list_entry->handle_type[0]; + + // Copy the typename and set the offset and count. + i->first._Copy_s(output, i->first.size(), i->first.size()); + *(output += i->first.size()) = L'\0'; + output++; + list_entry->offset_to_names = reinterpret_cast<char*>(output) - + reinterpret_cast<char*>(list_entry); + list_entry->name_count = i->second.size(); + + // Copy the handle names. + for (HandleMap::mapped_type::iterator j = i->second.begin(); + j != i->second.end(); ++j) { + output = std::copy((*j).begin(), (*j).end(), output) + 1; + } + + // Round up to the nearest multiple of sizeof(size_t). + output = RoundUpToWordSize(output); + list_entry->record_bytes = reinterpret_cast<char*>(output) - + reinterpret_cast<char*>(list_entry); + } + + DCHECK_EQ(reinterpret_cast<size_t>(output), reinterpret_cast<size_t>(end)); + return output <= end; +} + +bool GetHandleName(HANDLE handle, base::string16* handle_name) { + static NtQueryObject QueryObject = NULL; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + ULONG size = MAX_PATH; + scoped_ptr<UNICODE_STRING, base::FreeDeleter> name; + NTSTATUS result; + + do { + name.reset(static_cast<UNICODE_STRING*>(malloc(size))); + DCHECK(name.get()); + result = QueryObject(handle, ObjectNameInformation, name.get(), + size, &size); + } while (result == STATUS_INFO_LENGTH_MISMATCH || + result == STATUS_BUFFER_OVERFLOW); + + if (NT_SUCCESS(result) && name->Buffer && name->Length) + handle_name->assign(name->Buffer, name->Length / sizeof(wchar_t)); + else + handle_name->clear(); + + return NT_SUCCESS(result); +} + +} // namespace sandbox diff --git a/sandbox/win/src/handle_closer.h b/sandbox/win/src/handle_closer.h new file mode 100644 index 0000000000..2b43a6eaf1 --- /dev/null +++ b/sandbox/win/src/handle_closer.h @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_HANDLE_CLOSER_H_ +#define SANDBOX_SRC_HANDLE_CLOSER_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/target_process.h" + +namespace sandbox { + +// This is a map of handle-types to names that we need to close in the +// target process. A null set means we need to close all handles of the +// given type. +typedef std::map<const base::string16, std::set<base::string16> > HandleMap; + +// Type and set of corresponding handle names to close. +struct HandleListEntry { + size_t record_bytes; // Rounded to sizeof(size_t) bytes. + size_t offset_to_names; // Nul terminated strings of name_count names. + size_t name_count; + base::char16 handle_type[1]; +}; + +// Global parameters and a pointer to the list of entries. +struct HandleCloserInfo { + size_t record_bytes; // Rounded to sizeof(size_t) bytes. + size_t num_handle_types; + struct HandleListEntry handle_entries[1]; +}; + +SANDBOX_INTERCEPT HandleCloserInfo* g_handle_closer_info; + +// Adds handles to close after lockdown. +class HandleCloser { + public: + HandleCloser(); + ~HandleCloser(); + + // Adds a handle that will be closed in the target process after lockdown. + // A NULL value for handle_name indicates all handles of the specified type. + // An empty string for handle_name indicates the handle is unnamed. + ResultCode AddHandle(const base::char16* handle_type, + const base::char16* handle_name); + + // Serializes and copies the closer table into the target process. + bool InitializeTargetHandles(TargetProcess* target); + + private: + // Calculates the memory needed to copy the serialized handles list (rounded + // to the nearest machine-word size). + size_t GetBufferSize(); + + // Serializes the handle list into the target process. + bool SetupHandleList(void* buffer, size_t buffer_bytes); + + HandleMap handles_to_close_; + + DISALLOW_COPY_AND_ASSIGN(HandleCloser); +}; + +// Returns the object manager's name associated with a handle +bool GetHandleName(HANDLE handle, base::string16* handle_name); + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_CLOSER_H_ diff --git a/sandbox/win/src/handle_closer_agent.cc b/sandbox/win/src/handle_closer_agent.cc new file mode 100644 index 0000000000..9a79d556de --- /dev/null +++ b/sandbox/win/src/handle_closer_agent.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_closer_agent.h" + +#include "base/logging.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// Returns type infomation for an NT object. This routine is expected to be +// called for invalid handles so it catches STATUS_INVALID_HANDLE exceptions +// that can be generated when handle tracing is enabled. +NTSTATUS QueryObjectTypeInformation(HANDLE handle, + void* buffer, + ULONG* size) { + static NtQueryObject QueryObject = NULL; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + NTSTATUS status = STATUS_UNSUCCESSFUL; + __try { + status = QueryObject(handle, ObjectTypeInformation, buffer, *size, size); + } __except(GetExceptionCode() == STATUS_INVALID_HANDLE ? + EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { + status = STATUS_INVALID_HANDLE; + } + return status; +} + +} // namespace + +namespace sandbox { + +// Memory buffer mapped from the parent, with the list of handles. +SANDBOX_INTERCEPT HandleCloserInfo* g_handles_to_close = NULL; + +bool HandleCloserAgent::NeedsHandlesClosed() { + return g_handles_to_close != NULL; +} + +HandleCloserAgent::HandleCloserAgent() + : dummy_handle_(::CreateEvent(NULL, FALSE, FALSE, NULL)) { +} + +HandleCloserAgent::~HandleCloserAgent() { +} + +// Attempts to stuff |closed_handle| with a duplicated handle for a dummy Event +// with no access. This should allow the handle to be closed, to avoid +// generating EXCEPTION_INVALID_HANDLE on shutdown, but nothing else. For now +// the only supported |type| is Event or File. +bool HandleCloserAgent::AttemptToStuffHandleSlot(HANDLE closed_handle, + const base::string16& type) { + // Only attempt to stuff Files and Events at the moment. + if (type != L"Event" && type != L"File") { + return true; + } + + if (!dummy_handle_.IsValid()) + return false; + + // This should never happen, as g_dummy is created before closing to_stuff. + DCHECK(dummy_handle_.Get() != closed_handle); + + std::vector<HANDLE> to_close; + HANDLE dup_dummy = NULL; + size_t count = 16; + + do { + if (!::DuplicateHandle(::GetCurrentProcess(), dummy_handle_.Get(), + ::GetCurrentProcess(), &dup_dummy, 0, FALSE, 0)) + break; + if (dup_dummy != closed_handle) + to_close.push_back(dup_dummy); + } while (count-- && + reinterpret_cast<uintptr_t>(dup_dummy) < + reinterpret_cast<uintptr_t>(closed_handle)); + + for (auto h : to_close) + ::CloseHandle(h); + + // Useful to know when we're not able to stuff handles. + DCHECK(dup_dummy == closed_handle); + + return dup_dummy == closed_handle; +} + +// Reads g_handles_to_close and creates the lookup map. +void HandleCloserAgent::InitializeHandlesToClose() { + CHECK(g_handles_to_close != NULL); + + // Grab the header. + HandleListEntry* entry = g_handles_to_close->handle_entries; + for (size_t i = 0; i < g_handles_to_close->num_handle_types; ++i) { + // Set the type name. + base::char16* input = entry->handle_type; + HandleMap::mapped_type& handle_names = handles_to_close_[input]; + input = reinterpret_cast<base::char16*>(reinterpret_cast<char*>(entry) + + entry->offset_to_names); + // Grab all the handle names. + for (size_t j = 0; j < entry->name_count; ++j) { + std::pair<HandleMap::mapped_type::iterator, bool> name + = handle_names.insert(input); + CHECK(name.second); + input += name.first->size() + 1; + } + + // Move on to the next entry. + entry = reinterpret_cast<HandleListEntry*>(reinterpret_cast<char*>(entry) + + entry->record_bytes); + + DCHECK(reinterpret_cast<base::char16*>(entry) >= input); + DCHECK(reinterpret_cast<base::char16*>(entry) - input < + sizeof(size_t) / sizeof(base::char16)); + } + + // Clean up the memory we copied over. + ::VirtualFree(g_handles_to_close, 0, MEM_RELEASE); + g_handles_to_close = NULL; +} + +bool HandleCloserAgent::CloseHandles() { + DWORD handle_count = UINT_MAX; + const int kInvalidHandleThreshold = 100; + const size_t kHandleOffset = 4; // Handles are always a multiple of 4. + + if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) + return false; + + // Set up buffers for the type info and the name. + std::vector<BYTE> type_info_buffer(sizeof(OBJECT_TYPE_INFORMATION) + + 32 * sizeof(wchar_t)); + OBJECT_TYPE_INFORMATION* type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); + base::string16 handle_name; + HANDLE handle = NULL; + int invalid_count = 0; + + // Keep incrementing until we hit the number of handles reported by + // GetProcessHandleCount(). If we hit a very long sequence of invalid + // handles we assume that we've run past the end of the table. + while (handle_count && invalid_count < kInvalidHandleThreshold) { + reinterpret_cast<size_t&>(handle) += kHandleOffset; + NTSTATUS rc; + + // Get the type name, reusing the buffer. + ULONG size = static_cast<ULONG>(type_info_buffer.size()); + rc = QueryObjectTypeInformation(handle, type_info, &size); + while (rc == STATUS_INFO_LENGTH_MISMATCH || + rc == STATUS_BUFFER_OVERFLOW) { + type_info_buffer.resize(size + sizeof(wchar_t)); + type_info = reinterpret_cast<OBJECT_TYPE_INFORMATION*>( + &(type_info_buffer[0])); + rc = QueryObjectTypeInformation(handle, type_info, &size); + // Leave padding for the nul terminator. + if (NT_SUCCESS(rc) && size == type_info_buffer.size()) + rc = STATUS_INFO_LENGTH_MISMATCH; + } + if (!NT_SUCCESS(rc) || !type_info->Name.Buffer) { + ++invalid_count; + continue; + } + + --handle_count; + type_info->Name.Buffer[type_info->Name.Length / sizeof(wchar_t)] = L'\0'; + + // Check if we're looking for this type of handle. + HandleMap::iterator result = + handles_to_close_.find(type_info->Name.Buffer); + if (result != handles_to_close_.end()) { + HandleMap::mapped_type& names = result->second; + // Empty set means close all handles of this type; otherwise check name. + if (!names.empty()) { + // Move on to the next handle if this name doesn't match. + if (!GetHandleName(handle, &handle_name) || !names.count(handle_name)) + continue; + } + + if (!::SetHandleInformation(handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0)) + return false; + if (!::CloseHandle(handle)) + return false; + // Attempt to stuff this handle with a new dummy Event. + AttemptToStuffHandleSlot(handle, result->first); + } + } + + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/handle_closer_agent.h b/sandbox/win/src/handle_closer_agent.h new file mode 100644 index 0000000000..a3e15024d4 --- /dev/null +++ b/sandbox/win/src/handle_closer_agent.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_HANDLE_CLOSER_AGENT_H_ +#define SANDBOX_SRC_HANDLE_CLOSER_AGENT_H_ + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/handle_closer.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +// Target process code to close the handle list copied over from the broker. +class HandleCloserAgent { + public: + HandleCloserAgent(); + ~HandleCloserAgent(); + + // Reads the serialized list from the broker and creates the lookup map. + void InitializeHandlesToClose(); + + // Closes any handles matching those in the lookup map. + bool CloseHandles(); + + // True if we have handles waiting to be closed. + static bool NeedsHandlesClosed(); + + private: + // Attempt to stuff a closed handle with a dummy Event. + bool AttemptToStuffHandleSlot(HANDLE closed_handle, + const base::string16& type); + + HandleMap handles_to_close_; + base::win::ScopedHandle dummy_handle_; + + DISALLOW_COPY_AND_ASSIGN(HandleCloserAgent); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_CLOSER_AGENT_H_ diff --git a/sandbox/win/src/handle_closer_test.cc b/sandbox/win/src/handle_closer_test.cc new file mode 100644 index 0000000000..f1f80e8888 --- /dev/null +++ b/sandbox/win/src/handle_closer_test.cc @@ -0,0 +1,297 @@ +// Copyright (c) 2011 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 "base/strings/stringprintf.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/handle_closer_agent.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const wchar_t *kFileExtensions[] = { L".1", L".2", L".3", L".4" }; + +// Returns a handle to a unique marker file that can be retrieved between runs. +HANDLE GetMarkerFile(const wchar_t *extension) { + wchar_t path_buffer[MAX_PATH + 1]; + CHECK(::GetTempPath(MAX_PATH, path_buffer)); + base::string16 marker_path = path_buffer; + marker_path += L"\\sbox_marker_"; + + // Generate a unique value from the exe's size and timestamp. + CHECK(::GetModuleFileName(NULL, path_buffer, MAX_PATH)); + base::win::ScopedHandle module(::CreateFile(path_buffer, + FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + CHECK(module.IsValid()); + FILETIME timestamp; + CHECK(::GetFileTime(module.Get(), ×tamp, NULL, NULL)); + marker_path += base::StringPrintf(L"%08x%08x%08x", + ::GetFileSize(module.Get(), NULL), + timestamp.dwLowDateTime, + timestamp.dwHighDateTime); + marker_path += extension; + + // Make the file delete-on-close so cleanup is automatic. + return CreateFile(marker_path.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL); +} + +// Returns type infomation for an NT object. This routine is expected to be +// called for invalid handles so it catches STATUS_INVALID_HANDLE exceptions +// that can be generated when handle tracing is enabled. +NTSTATUS QueryObjectTypeInformation(HANDLE handle, void* buffer, ULONG* size) { + static NtQueryObject QueryObject = NULL; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + NTSTATUS status = STATUS_UNSUCCESSFUL; + __try { + status = QueryObject(handle, ObjectTypeInformation, buffer, *size, size); + } + __except(GetExceptionCode() == STATUS_INVALID_HANDLE + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) { + status = STATUS_INVALID_HANDLE; + } + return status; +} + +// Used by the thread pool tests. +HANDLE finish_event; +const int kWaitCount = 20; + +} // namespace + +namespace sandbox { + +// Checks for the presence of a list of files (in object path form). +// Format: CheckForFileHandle (Y|N) \path\to\file1 [\path\to\file2 ...] +// - Y or N depending if the file should exist or not. +SBOX_TESTS_COMMAND int CheckForFileHandles(int argc, wchar_t **argv) { + if (argc < 2) + return SBOX_TEST_FAILED_TO_RUN_TEST; + bool should_find = argv[0][0] == L'Y'; + if (argv[0][1] != L'\0' || !should_find && argv[0][0] != L'N') + return SBOX_TEST_FAILED_TO_RUN_TEST; + + static int state = BEFORE_INIT; + switch (state++) { + case BEFORE_INIT: + // Create a unique marker file that is open while the test is running. + // The handles leak, but it will be closed by the test or on exit. + for (int i = 0; i < arraysize(kFileExtensions); ++i) + CHECK_NE(GetMarkerFile(kFileExtensions[i]), INVALID_HANDLE_VALUE); + return SBOX_TEST_SUCCEEDED; + + case AFTER_REVERT: { + // Brute force the handle table to find what we're looking for. + DWORD handle_count = UINT_MAX; + const int kInvalidHandleThreshold = 100; + const size_t kHandleOffset = 4; // Handles are always a multiple of 4. + HANDLE handle = NULL; + int invalid_count = 0; + base::string16 handle_name; + + if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) + return SBOX_TEST_FAILED_TO_RUN_TEST; + + while (handle_count && invalid_count < kInvalidHandleThreshold) { + reinterpret_cast<size_t&>(handle) += kHandleOffset; + if (GetHandleName(handle, &handle_name)) { + for (int i = 1; i < argc; ++i) { + if (handle_name == argv[i]) + return should_find ? SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED; + } + --handle_count; + } else { + ++invalid_count; + } + } + + return should_find ? SBOX_TEST_FAILED : SBOX_TEST_SUCCEEDED; + } + + default: // Do nothing. + break; + } + + return SBOX_TEST_SUCCEEDED; +} + +// Checks that supplied handle is an Event and it's not waitable. +// Format: CheckForEventHandles +SBOX_TESTS_COMMAND int CheckForEventHandles(int argc, wchar_t** argv) { + static int state = BEFORE_INIT; + static std::vector<HANDLE> to_check; + + switch (state++) { + case BEFORE_INIT: + // Create a unique marker file that is open while the test is running. + for (int i = 0; i < arraysize(kFileExtensions); ++i) { + HANDLE handle = GetMarkerFile(kFileExtensions[i]); + CHECK_NE(handle, INVALID_HANDLE_VALUE); + to_check.push_back(handle); + } + return SBOX_TEST_SUCCEEDED; + + case AFTER_REVERT: + for (auto handle : to_check) { + // Set up buffers for the type info and the name. + std::vector<BYTE> type_info_buffer(sizeof(OBJECT_TYPE_INFORMATION) + + 32 * sizeof(wchar_t)); + OBJECT_TYPE_INFORMATION* type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); + NTSTATUS rc; + + // Get the type name, reusing the buffer. + ULONG size = static_cast<ULONG>(type_info_buffer.size()); + rc = QueryObjectTypeInformation(handle, type_info, &size); + while (rc == STATUS_INFO_LENGTH_MISMATCH || + rc == STATUS_BUFFER_OVERFLOW) { + type_info_buffer.resize(size + sizeof(wchar_t)); + type_info = reinterpret_cast<OBJECT_TYPE_INFORMATION*>( + &(type_info_buffer[0])); + rc = QueryObjectTypeInformation(handle, type_info, &size); + // Leave padding for the nul terminator. + if (NT_SUCCESS(rc) && size == type_info_buffer.size()) + rc = STATUS_INFO_LENGTH_MISMATCH; + } + + CHECK(NT_SUCCESS(rc)); + CHECK(type_info->Name.Buffer); + + type_info->Name.Buffer[type_info->Name.Length / sizeof(wchar_t)] = + L'\0'; + + // Should be an Event now. + CHECK_EQ(wcslen(type_info->Name.Buffer), 5U); + CHECK_EQ(wcscmp(L"Event", type_info->Name.Buffer), 0); + + // Should not be able to wait. + CHECK_EQ(WaitForSingleObject(handle, INFINITE), WAIT_FAILED); + + // Should be able to close. + CHECK_EQ(TRUE, CloseHandle(handle)); + } + return SBOX_TEST_SUCCEEDED; + + default: // Do nothing. + break; + } + + return SBOX_TEST_SUCCEEDED; +} + +TEST(HandleCloserTest, CheckForMarkerFiles) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + + base::string16 command = base::string16(L"CheckForFileHandles Y"); + for (int i = 0; i < arraysize(kFileExtensions); ++i) { + base::string16 handle_name; + base::win::ScopedHandle marker(GetMarkerFile(kFileExtensions[i])); + CHECK(marker.IsValid()); + CHECK(sandbox::GetHandleName(marker.Get(), &handle_name)); + command += (L" "); + command += handle_name; + } + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command.c_str())) << + "Failed: " << command; +} + +TEST(HandleCloserTest, CloseMarkerFiles) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + base::string16 command = base::string16(L"CheckForFileHandles N"); + for (int i = 0; i < arraysize(kFileExtensions); ++i) { + base::string16 handle_name; + base::win::ScopedHandle marker(GetMarkerFile(kFileExtensions[i])); + CHECK(marker.IsValid()); + CHECK(sandbox::GetHandleName(marker.Get(), &handle_name)); + CHECK_EQ(policy->AddKernelObjectToClose(L"File", handle_name.c_str()), + SBOX_ALL_OK); + command += (L" "); + command += handle_name; + } + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command.c_str())) << + "Failed: " << command; +} + +TEST(HandleCloserTest, CheckStuffedHandle) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + for (int i = 0; i < arraysize(kFileExtensions); ++i) { + base::string16 handle_name; + base::win::ScopedHandle marker(GetMarkerFile(kFileExtensions[i])); + CHECK(marker.IsValid()); + CHECK(sandbox::GetHandleName(marker.Get(), &handle_name)); + CHECK_EQ(policy->AddKernelObjectToClose(L"File", handle_name.c_str()), + SBOX_ALL_OK); + } + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckForEventHandles")); +} + +void WINAPI ThreadPoolTask(void* event, BOOLEAN timeout) { + static volatile LONG waiters_remaining = kWaitCount; + CHECK(!timeout); + CHECK(::CloseHandle(event)); + if (::InterlockedDecrement(&waiters_remaining) == 0) + CHECK(::SetEvent(finish_event)); +} + +// Run a thread pool inside a sandbox without a CSRSS connection. +SBOX_TESTS_COMMAND int RunThreadPool(int argc, wchar_t **argv) { + HANDLE wait_list[20]; + finish_event = ::CreateEvent(NULL, TRUE, FALSE, NULL); + CHECK(finish_event); + + // Set up a bunch of waiters. + HANDLE pool = NULL; + for (int i = 0; i < kWaitCount; ++i) { + HANDLE event = ::CreateEvent(NULL, TRUE, FALSE, NULL); + CHECK(event); + CHECK(::RegisterWaitForSingleObject(&pool, event, ThreadPoolTask, event, + INFINITE, WT_EXECUTEONLYONCE)); + wait_list[i] = event; + } + + // Signal all the waiters. + for (int i = 0; i < kWaitCount; ++i) + CHECK(::SetEvent(wait_list[i])); + + CHECK_EQ(::WaitForSingleObject(finish_event, INFINITE), WAIT_OBJECT_0); + CHECK(::CloseHandle(finish_event)); + + return SBOX_TEST_SUCCEEDED; +} + +TEST(HandleCloserTest, RunThreadPool) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(AFTER_REVERT); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + // Sever the CSRSS connection by closing ALPC ports inside the sandbox. + CHECK_EQ(policy->AddKernelObjectToClose(L"ALPC Port", NULL), SBOX_ALL_OK); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"RunThreadPool")); +} + +} // namespace sandbox diff --git a/sandbox/win/src/handle_dispatcher.cc b/sandbox/win/src/handle_dispatcher.cc new file mode 100644 index 0000000000..66308f4b4a --- /dev/null +++ b/sandbox/win/src/handle_dispatcher.cc @@ -0,0 +1,90 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_dispatcher.h" + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/handle_interception.h" +#include "sandbox/win/src/handle_policy.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" + +namespace sandbox { + +HandleDispatcher::HandleDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall duplicate_handle_proxy = { + {IPC_DUPLICATEHANDLEPROXY_TAG, VOIDPTR_TYPE, UINT32_TYPE, UINT32_TYPE, + UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>(&HandleDispatcher::DuplicateHandleProxy) + }; + + ipc_calls_.push_back(duplicate_handle_proxy); +} + +bool HandleDispatcher::SetupService(InterceptionManager* manager, + int service) { + // We perform no interceptions for handles right now. + switch (service) { + case IPC_DUPLICATEHANDLEPROXY_TAG: + return true; + } + + return false; +} + +bool HandleDispatcher::DuplicateHandleProxy(IPCInfo* ipc, + HANDLE source_handle, + uint32 target_process_id, + uint32 desired_access, + uint32 options) { + static NtQueryObject QueryObject = NULL; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + // Get a copy of the handle for use in the broker process. + HANDLE handle_temp; + if (!::DuplicateHandle(ipc->client_info->process, source_handle, + ::GetCurrentProcess(), &handle_temp, + 0, FALSE, DUPLICATE_SAME_ACCESS | options)) { + ipc->return_info.win32_result = ::GetLastError(); + return false; + } + options &= ~DUPLICATE_CLOSE_SOURCE; + base::win::ScopedHandle handle(handle_temp); + + // Get the object type (32 characters is safe; current max is 14). + BYTE buffer[sizeof(OBJECT_TYPE_INFORMATION) + 32 * sizeof(wchar_t)]; + OBJECT_TYPE_INFORMATION* type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(buffer); + ULONG size = sizeof(buffer) - sizeof(wchar_t); + NTSTATUS error = + QueryObject(handle.Get(), ObjectTypeInformation, type_info, size, &size); + if (!NT_SUCCESS(error)) { + ipc->return_info.nt_status = error; + return false; + } + type_info->Name.Buffer[type_info->Name.Length / sizeof(wchar_t)] = L'\0'; + + CountedParameterSet<HandleTarget> params; + params[HandleTarget::NAME] = ParamPickerMake(type_info->Name.Buffer); + params[HandleTarget::TARGET] = ParamPickerMake(target_process_id); + + EvalResult eval = policy_base_->EvalPolicy(IPC_DUPLICATEHANDLEPROXY_TAG, + params.GetBase()); + ipc->return_info.win32_result = + HandlePolicy::DuplicateHandleProxyAction(eval, handle.Get(), + target_process_id, + &ipc->return_info.handle, + desired_access, options); + return true; +} + +} // namespace sandbox + diff --git a/sandbox/win/src/handle_dispatcher.h b/sandbox/win/src/handle_dispatcher.h new file mode 100644 index 0000000000..84a22e19b3 --- /dev/null +++ b/sandbox/win/src/handle_dispatcher.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_HANDLE_DISPATCHER_H_ +#define SANDBOX_SRC_HANDLE_DISPATCHER_H_ + +#include "base/basictypes.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles handle-related IPC calls. +class HandleDispatcher : public Dispatcher { + public: + explicit HandleDispatcher(PolicyBase* policy_base); + ~HandleDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, int service) override; + + private: + // Processes IPC requests coming from calls to + // TargetServices::DuplicateHandle() in the target. + bool DuplicateHandleProxy(IPCInfo* ipc, + HANDLE source_handle, + uint32 target_process_id, + uint32 desired_access, + uint32 options); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(HandleDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_DISPATCHER_H_ + diff --git a/sandbox/win/src/handle_inheritance_test.cc b/sandbox/win/src/handle_inheritance_test.cc new file mode 100644 index 0000000000..37974e3106 --- /dev/null +++ b/sandbox/win/src/handle_inheritance_test.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdio.h> + +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int HandleInheritanceTests_PrintToStdout(int argc, + wchar_t** argv) { + printf("Example output to stdout\n"); + return SBOX_TEST_SUCCEEDED; +} + +TEST(HandleInheritanceTests, TestStdoutInheritance) { + base::ScopedTempDir temp_directory; + base::FilePath temp_file_name; + ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); + ASSERT_TRUE(CreateTemporaryFileInDir(temp_directory.path(), &temp_file_name)); + + SECURITY_ATTRIBUTES attrs = {}; + attrs.nLength = sizeof(attrs); + attrs.bInheritHandle = TRUE; + base::win::ScopedHandle tmp_handle( + CreateFile(temp_file_name.value().c_str(), GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, + &attrs, OPEN_EXISTING, 0, NULL)); + ASSERT_TRUE(tmp_handle.IsValid()); + + TestRunner runner; + ASSERT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetStdoutHandle(tmp_handle.Get())); + int result = runner.RunTest(L"HandleInheritanceTests_PrintToStdout"); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, result); + + std::string data; + ASSERT_TRUE(base::ReadFileToString(base::FilePath(temp_file_name), &data)); + // Redirection uses a feature that was added in Windows Vista. + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + ASSERT_EQ("Example output to stdout\r\n", data); + } else { + ASSERT_EQ("", data); + } +} + +} diff --git a/sandbox/win/src/handle_interception.cc b/sandbox/win/src/handle_interception.cc new file mode 100644 index 0000000000..a0df8d653d --- /dev/null +++ b/sandbox/win/src/handle_interception.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_interception.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +ResultCode DuplicateHandleProxy(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) { + *target_handle = NULL; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + return SBOX_ERROR_NO_SPACE; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_DUPLICATEHANDLEPROXY_TAG, + source_handle, target_process_id, + desired_access, options, &answer); + if (SBOX_ALL_OK != code) + return code; + + if (answer.win32_result) { + ::SetLastError(answer.win32_result); + return SBOX_ERROR_GENERIC; + } + + *target_handle = answer.handle; + return SBOX_ALL_OK; +} + +} // namespace sandbox + diff --git a/sandbox/win/src/handle_interception.h b/sandbox/win/src/handle_interception.h new file mode 100644 index 0000000000..6f60811f17 --- /dev/null +++ b/sandbox/win/src/handle_interception.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_HANDLE_INTERCEPTION_H_ +#define SANDBOX_SRC_HANDLE_INTERCEPTION_H_ + +namespace sandbox { + +// TODO(jschuh) Add an interception to catch dangerous DuplicateHandle calls. + +ResultCode DuplicateHandleProxy(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options); + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_INTERCEPTION_H_ + diff --git a/sandbox/win/src/handle_policy.cc b/sandbox/win/src/handle_policy.cc new file mode 100644 index 0000000000..1023030792 --- /dev/null +++ b/sandbox/win/src/handle_policy.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_policy.h" + +#include <string> + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/broker_services.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" + +namespace sandbox { + +bool HandlePolicy::GenerateRules(const wchar_t* type_name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + PolicyRule duplicate_rule(ASK_BROKER); + + switch (semantics) { + case TargetPolicy::HANDLES_DUP_ANY: { + if (!duplicate_rule.AddNumberMatch(IF_NOT, HandleTarget::TARGET, + ::GetCurrentProcessId(), EQUAL)) { + return false; + } + break; + } + + case TargetPolicy::HANDLES_DUP_BROKER: { + if (!duplicate_rule.AddNumberMatch(IF, HandleTarget::TARGET, + ::GetCurrentProcessId(), EQUAL)) { + return false; + } + break; + } + + default: + return false; + } + if (!duplicate_rule.AddStringMatch(IF, HandleTarget::NAME, type_name, + CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IPC_DUPLICATEHANDLEPROXY_TAG, &duplicate_rule)) { + return false; + } + return true; +} + +DWORD HandlePolicy::DuplicateHandleProxyAction(EvalResult eval_result, + HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) { + // The only action supported is ASK_BROKER which means duplicate the handle. + if (ASK_BROKER != eval_result) { + return ERROR_ACCESS_DENIED; + } + + base::win::ScopedHandle remote_target_process; + if (target_process_id != ::GetCurrentProcessId()) { + // Sandboxed children are dynamic, so we check that manually. + if (!BrokerServicesBase::GetInstance()->IsActiveTarget(target_process_id)) { + return ERROR_ACCESS_DENIED; + } + + remote_target_process.Set(::OpenProcess(PROCESS_DUP_HANDLE, FALSE, + target_process_id)); + if (!remote_target_process.IsValid()) + return ::GetLastError(); + } + + // If the policy didn't block us and we have no valid target, then the broker + // (this process) is the valid target. + HANDLE target_process = remote_target_process.IsValid() ? + remote_target_process.Get() : ::GetCurrentProcess(); + if (!::DuplicateHandle(::GetCurrentProcess(), source_handle, target_process, + target_handle, desired_access, FALSE, + options)) { + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +} // namespace sandbox + diff --git a/sandbox/win/src/handle_policy.h b/sandbox/win/src/handle_policy.h new file mode 100644 index 0000000000..ffe54b8ae2 --- /dev/null +++ b/sandbox/win/src/handle_policy.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_HANDLE_POLICY_H_ +#define SANDBOX_SRC_HANDLE_POLICY_H_ + +#include <string> + +#include "base/basictypes.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum EvalResult; + +// This class centralizes most of the knowledge related to handle policy. +class HandlePolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for handles, in particular duplicate action. + static bool GenerateRules(const wchar_t* type_name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Processes a 'TargetPolicy::DuplicateHandle()' request from the target. + static DWORD DuplicateHandleProxyAction(EvalResult eval_result, + HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_POLICY_H_ + diff --git a/sandbox/win/src/handle_policy_test.cc b/sandbox/win/src/handle_policy_test.cc new file mode 100644 index 0000000000..11382da811 --- /dev/null +++ b/sandbox/win/src/handle_policy_test.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/stringprintf.h" +#include "sandbox/win/src/handle_policy.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Just waits for the supplied number of milliseconds. +SBOX_TESTS_COMMAND int Handle_WaitProcess(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + ::Sleep(::wcstoul(argv[0], NULL, 10)); + return SBOX_TEST_TIMED_OUT; +} + +// Attempts to duplicate an event handle into the target process. +SBOX_TESTS_COMMAND int Handle_DuplicateEvent(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + // Create a test event to use as a handle. + base::win::ScopedHandle test_event; + test_event.Set(::CreateEvent(NULL, TRUE, TRUE, NULL)); + if (!test_event.IsValid()) + return SBOX_TEST_FIRST_ERROR; + + // Get the target process ID. + DWORD target_process_id = ::wcstoul(argv[0], NULL, 10); + + HANDLE handle = NULL; + ResultCode result = SandboxFactory::GetTargetServices()->DuplicateHandle( + test_event.Get(), target_process_id, &handle, 0, DUPLICATE_SAME_ACCESS); + + return (result == SBOX_ALL_OK) ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; +} + +// Tests that duplicating an object works only when the policy allows it. +TEST(HandlePolicyTest, DuplicateHandle) { + TestRunner target; + TestRunner runner; + + // Kick off an asynchronous target process for testing. + target.SetAsynchronous(true); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, target.RunTest(L"Handle_WaitProcess 30000")); + + // First test that we fail to open the event. + base::string16 cmd_line = base::StringPrintf(L"Handle_DuplicateEvent %d", + target.process_id()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + // Now successfully open the event after adding a duplicate handle rule. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_ANY, + L"Event")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(cmd_line.c_str())); +} + +// Tests that duplicating an object works only when the policy allows it. +TEST(HandlePolicyTest, DuplicatePeerHandle) { + TestRunner target; + TestRunner runner; + + // Kick off an asynchronous target process for testing. + target.SetAsynchronous(true); + target.SetUnsandboxed(true); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, target.RunTest(L"Handle_WaitProcess 30000")); + + // First test that we fail to open the event. + base::string16 cmd_line = base::StringPrintf(L"Handle_DuplicateEvent %d", + target.process_id()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + // Now successfully open the event after adding a duplicate handle rule. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_ANY, + L"Event")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(cmd_line.c_str())); +} + +// Tests that duplicating an object works only when the policy allows it. +TEST(HandlePolicyTest, DuplicateBrokerHandle) { + TestRunner runner; + + // First test that we fail to open the event. + base::string16 cmd_line = base::StringPrintf(L"Handle_DuplicateEvent %d", + ::GetCurrentProcessId()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + // Add the peer rule and make sure we fail again. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_ANY, + L"Event")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + + // Now successfully open the event after adding a broker handle rule. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_BROKER, + L"Event")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(cmd_line.c_str())); +} + +} // namespace sandbox + diff --git a/sandbox/win/src/handle_table.cc b/sandbox/win/src/handle_table.cc new file mode 100644 index 0000000000..5ebcf99460 --- /dev/null +++ b/sandbox/win/src/handle_table.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_table.h" + +#include <algorithm> +#include <cstdlib> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +bool CompareHandleEntries(const SYSTEM_HANDLE_INFORMATION& a, + const SYSTEM_HANDLE_INFORMATION& b) { + return a.ProcessId < b.ProcessId; +} + +} // namespace + +namespace sandbox { + +const base::char16* HandleTable::kTypeProcess = L"Process"; +const base::char16* HandleTable::kTypeThread = L"Thread"; +const base::char16* HandleTable::kTypeFile = L"File"; +const base::char16* HandleTable::kTypeDirectory = L"Directory"; +const base::char16* HandleTable::kTypeKey = L"Key"; +const base::char16* HandleTable::kTypeWindowStation = L"WindowStation"; +const base::char16* HandleTable::kTypeDesktop = L"Desktop"; +const base::char16* HandleTable::kTypeService = L"Service"; +const base::char16* HandleTable::kTypeMutex = L"Mutex"; +const base::char16* HandleTable::kTypeSemaphore = L"Semaphore"; +const base::char16* HandleTable::kTypeEvent = L"Event"; +const base::char16* HandleTable::kTypeTimer = L"Timer"; +const base::char16* HandleTable::kTypeNamedPipe = L"NamedPipe"; +const base::char16* HandleTable::kTypeJobObject = L"JobObject"; +const base::char16* HandleTable::kTypeFileMap = L"FileMap"; +const base::char16* HandleTable::kTypeAlpcPort = L"ALPC Port"; + +HandleTable::HandleTable() { + static NtQuerySystemInformation QuerySystemInformation = NULL; + if (!QuerySystemInformation) + ResolveNTFunctionPtr("NtQuerySystemInformation", &QuerySystemInformation); + + ULONG size = 0x15000; + NTSTATUS result; + do { + handle_info_buffer_.resize(size); + result = QuerySystemInformation(SystemHandleInformation, + handle_info_internal(), size, &size); + } while (result == STATUS_INFO_LENGTH_MISMATCH); + + // We failed, so make an empty table. + if (!NT_SUCCESS(result)) { + handle_info_buffer_.resize(0); + return; + } + + // Sort it to make process lookups faster. + std::sort(handle_info_internal()->Information, + handle_info_internal()->Information + + handle_info_internal()->NumberOfHandles, CompareHandleEntries); +} + +HandleTable::~HandleTable() { +} + +HandleTable::Iterator HandleTable::HandlesForProcess(ULONG process_id) const { + SYSTEM_HANDLE_INFORMATION key; + key.ProcessId = static_cast<USHORT>(process_id); + + const SYSTEM_HANDLE_INFORMATION* start = handle_info()->Information; + const SYSTEM_HANDLE_INFORMATION* finish = + &handle_info()->Information[handle_info()->NumberOfHandles]; + + start = std::lower_bound(start, finish, key, CompareHandleEntries); + if (start->ProcessId != process_id) + return Iterator(*this, finish, finish); + finish = std::upper_bound(start, finish, key, CompareHandleEntries); + return Iterator(*this, start, finish); +} + +HandleTable::HandleEntry::HandleEntry( + const SYSTEM_HANDLE_INFORMATION* handle_info_entry) + : handle_entry_(handle_info_entry), last_entry_(0) { +} + +HandleTable::HandleEntry::~HandleEntry() { +} + +void HandleTable::HandleEntry::UpdateInfo(UpdateType flag) { + static NtQueryObject QueryObject = NULL; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + NTSTATUS result; + + // Always update the basic type info, but grab the names as needed. + if (needs_info_update()) { + handle_name_.clear(); + type_name_.clear(); + last_entry_ = handle_entry_; + + // Most handle names are very short, so start small and reuse this buffer. + if (type_info_buffer_.empty()) + type_info_buffer_.resize(sizeof(OBJECT_TYPE_INFORMATION) + + (32 * sizeof(wchar_t))); + ULONG size = static_cast<ULONG>(type_info_buffer_.size()); + result = QueryObject(reinterpret_cast<HANDLE>(handle_entry_->Handle), + ObjectTypeInformation, type_info_internal(), size, &size); + while (result == STATUS_INFO_LENGTH_MISMATCH) { + type_info_buffer_.resize(size); + result = QueryObject(reinterpret_cast<HANDLE>(handle_entry_->Handle), + ObjectTypeInformation, type_info_internal(), size, &size); + } + + if (!NT_SUCCESS(result)) { + type_info_buffer_.clear(); + return; + } + } + + // Don't bother copying out names until we ask for them, and then cache them. + switch (flag) { + case UPDATE_INFO_AND_NAME: + if (type_info_buffer_.size() && handle_name_.empty()) { + ULONG size = MAX_PATH; + scoped_ptr<UNICODE_STRING, base::FreeDeleter> name; + do { + name.reset(static_cast<UNICODE_STRING*>(malloc(size))); + DCHECK(name.get()); + result = QueryObject(reinterpret_cast<HANDLE>( + handle_entry_->Handle), ObjectNameInformation, name.get(), + size, &size); + } while (result == STATUS_INFO_LENGTH_MISMATCH); + + if (NT_SUCCESS(result)) { + handle_name_.assign(name->Buffer, name->Length / sizeof(wchar_t)); + } + } + break; + + case UPDATE_INFO_AND_TYPE_NAME: + if (!type_info_buffer_.empty() && type_info_internal()->Name.Buffer && + type_name_.empty()) { + type_name_.assign(type_info_internal()->Name.Buffer, + type_info_internal()->Name.Length / sizeof(wchar_t)); + } + break; + } +} + +const OBJECT_TYPE_INFORMATION* HandleTable::HandleEntry::TypeInfo() { + UpdateInfo(UPDATE_INFO_ONLY); + return type_info_buffer_.empty() ? NULL : type_info_internal(); +} + +const base::string16& HandleTable::HandleEntry::Name() { + UpdateInfo(UPDATE_INFO_AND_NAME); + return handle_name_; +} + +const base::string16& HandleTable::HandleEntry::Type() { + UpdateInfo(UPDATE_INFO_AND_TYPE_NAME); + return type_name_; +} + +bool HandleTable::HandleEntry::IsType(const base::string16& type_string) { + UpdateInfo(UPDATE_INFO_ONLY); + if (type_info_buffer_.empty()) + return false; + return type_string.compare(0, + type_info_internal()->Name.Length / sizeof(wchar_t), + type_info_internal()->Name.Buffer) == 0; +} + +HandleTable::Iterator::Iterator(const HandleTable& table, + const SYSTEM_HANDLE_INFORMATION* start, + const SYSTEM_HANDLE_INFORMATION* end) + : table_(table), current_(start), end_(end) { +} + +HandleTable::Iterator::Iterator(const Iterator& it) + : table_(it.table_), current_(it.current_.handle_entry_), end_(it.end_) { +} + +} // namespace sandbox diff --git a/sandbox/win/src/handle_table.h b/sandbox/win/src/handle_table.h new file mode 100644 index 0000000000..47f625d9e3 --- /dev/null +++ b/sandbox/win/src/handle_table.h @@ -0,0 +1,162 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_HANDLE_TABLE_H_ +#define SANDBOX_SRC_HANDLE_TABLE_H_ + +#include <windows.h> +#include <vector> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +// HandleTable retrieves the global handle table and provides helper classes +// for iterating through the table and retrieving handle info. +class HandleTable { + public: + static const base::char16* HandleTable::kTypeProcess; + static const base::char16* HandleTable::kTypeThread; + static const base::char16* HandleTable::kTypeFile; + static const base::char16* HandleTable::kTypeDirectory; + static const base::char16* HandleTable::kTypeKey; + static const base::char16* HandleTable::kTypeWindowStation; + static const base::char16* HandleTable::kTypeDesktop; + static const base::char16* HandleTable::kTypeService; + static const base::char16* HandleTable::kTypeMutex; + static const base::char16* HandleTable::kTypeSemaphore; + static const base::char16* HandleTable::kTypeEvent; + static const base::char16* HandleTable::kTypeTimer; + static const base::char16* HandleTable::kTypeNamedPipe; + static const base::char16* HandleTable::kTypeJobObject; + static const base::char16* HandleTable::kTypeFileMap; + static const base::char16* HandleTable::kTypeAlpcPort; + + class Iterator; + + // Used by the iterator to provide simple caching accessors to handle data. + class HandleEntry { + public: + ~HandleEntry(); + + bool operator==(const HandleEntry& rhs) const { + return handle_entry_ == rhs.handle_entry_; + } + + bool operator!=(const HandleEntry& rhs) const { + return handle_entry_ != rhs.handle_entry_; + } + + const SYSTEM_HANDLE_INFORMATION* handle_entry() const { + return handle_entry_; + } + + const OBJECT_TYPE_INFORMATION* TypeInfo(); + + const base::string16& Name(); + + const base::string16& Type(); + + bool IsType(const base::string16& type_string); + + private: + friend class Iterator; + friend class HandleTable; + + enum UpdateType { + UPDATE_INFO_ONLY, + UPDATE_INFO_AND_NAME, + UPDATE_INFO_AND_TYPE_NAME, + }; + + explicit HandleEntry(const SYSTEM_HANDLE_INFORMATION* handle_info_entry); + + bool needs_info_update() { return handle_entry_ != last_entry_; } + + void UpdateInfo(UpdateType flag); + + OBJECT_TYPE_INFORMATION* type_info_internal() { + return reinterpret_cast<OBJECT_TYPE_INFORMATION*>( + &(type_info_buffer_[0])); + } + + const SYSTEM_HANDLE_INFORMATION* handle_entry_; + const SYSTEM_HANDLE_INFORMATION* last_entry_; + std::vector<BYTE> type_info_buffer_; + base::string16 handle_name_; + base::string16 type_name_; + + DISALLOW_COPY_AND_ASSIGN(HandleEntry); + }; + + class Iterator { + public: + Iterator(const HandleTable& table, const SYSTEM_HANDLE_INFORMATION* start, + const SYSTEM_HANDLE_INFORMATION* stop); + + Iterator(const Iterator& it); + + Iterator& operator++() { + if (++(current_.handle_entry_) == end_) + current_.handle_entry_ = table_.end(); + return *this; + } + + bool operator==(const Iterator& rhs) const { + return current_ == rhs.current_; + } + + bool operator!=(const Iterator& rhs) const { + return current_ != rhs.current_; + } + + HandleEntry& operator*() { return current_; } + + operator const SYSTEM_HANDLE_INFORMATION*() { + return current_.handle_entry_; + } + + HandleEntry* operator->() { return ¤t_; } + + private: + const HandleTable& table_; + HandleEntry current_; + const SYSTEM_HANDLE_INFORMATION* end_; + }; + + HandleTable(); + ~HandleTable(); + + Iterator begin() const { + return Iterator(*this, handle_info()->Information, + &handle_info()->Information[handle_info()->NumberOfHandles]); + } + + const SYSTEM_HANDLE_INFORMATION_EX* handle_info() const { + return reinterpret_cast<const SYSTEM_HANDLE_INFORMATION_EX*>( + &(handle_info_buffer_[0])); + } + + // Returns an iterator to the handles for only the supplied process ID. + Iterator HandlesForProcess(ULONG process_id) const; + const SYSTEM_HANDLE_INFORMATION* end() const { + return &handle_info()->Information[handle_info()->NumberOfHandles]; + } + + private: + SYSTEM_HANDLE_INFORMATION_EX* handle_info_internal() { + return reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>( + &(handle_info_buffer_[0])); + } + + std::vector<BYTE> handle_info_buffer_; + + DISALLOW_COPY_AND_ASSIGN(HandleTable); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_TABLE_H_ diff --git a/sandbox/win/src/integrity_level_test.cc b/sandbox/win/src/integrity_level_test.cc new file mode 100644 index 0000000000..f962033ca5 --- /dev/null +++ b/sandbox/win/src/integrity_level_test.cc @@ -0,0 +1,91 @@ +// Copyright (c) 2011 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 <windows.h> +#include <atlsecurity.h> + +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/tests/common/controller.h" + +namespace sandbox { + + +SBOX_TESTS_COMMAND int CheckIntegrityLevel(int argc, wchar_t **argv) { + ATL::CAccessToken token; + if (!token.GetEffectiveToken(TOKEN_READ)) + return SBOX_TEST_FAILED; + + char* buffer[100]; + DWORD buf_size = 100; + if (!::GetTokenInformation(token.GetHandle(), TokenIntegrityLevel, + reinterpret_cast<void*>(buffer), buf_size, + &buf_size)) + return SBOX_TEST_FAILED; + + TOKEN_MANDATORY_LABEL* label = + reinterpret_cast<TOKEN_MANDATORY_LABEL*>(buffer); + + PSID sid_low = NULL; + if (!::ConvertStringSidToSid(L"S-1-16-4096", &sid_low)) + return SBOX_TEST_FAILED; + + BOOL is_low_sid = ::EqualSid(label->Label.Sid, sid_low); + + ::LocalFree(sid_low); + + if (is_low_sid) + return SBOX_TEST_SUCCEEDED; + + return SBOX_TEST_DENIED; +} + +TEST(IntegrityLevelTest, TestLowILReal) { + if (base::win::GetVersion() != base::win::VERSION_VISTA) + return; + + TestRunner runner(JOB_LOCKDOWN, USER_INTERACTIVE, USER_INTERACTIVE); + + runner.SetTimeout(INFINITE); + + runner.GetPolicy()->SetAlternateDesktop(true); + runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckIntegrityLevel")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckIntegrityLevel")); +} + +TEST(DelayedIntegrityLevelTest, TestLowILDelayed) { + if (base::win::GetVersion() != base::win::VERSION_VISTA) + return; + + TestRunner runner(JOB_LOCKDOWN, USER_INTERACTIVE, USER_INTERACTIVE); + + runner.SetTimeout(INFINITE); + + runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_LOW); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckIntegrityLevel")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"CheckIntegrityLevel")); +} + +TEST(IntegrityLevelTest, TestNoILChange) { + if (base::win::GetVersion() != base::win::VERSION_VISTA) + return; + + TestRunner runner(JOB_LOCKDOWN, USER_INTERACTIVE, USER_INTERACTIVE); + + runner.SetTimeout(INFINITE); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"CheckIntegrityLevel")); +} + +} // namespace sandbox diff --git a/sandbox/win/src/interception.cc b/sandbox/win/src/interception.cc new file mode 100644 index 0000000000..60dd4400b8 --- /dev/null +++ b/sandbox/win/src/interception.cc @@ -0,0 +1,554 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// For information about interceptions as a whole see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#include <set> + +#include "sandbox/win/src/interception.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/win/pe_image.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interception_internal.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/service_resolver.h" +#include "sandbox/win/src/target_interceptions.h" +#include "sandbox/win/src/target_process.h" +#include "sandbox/win/src/wow64.h" + +namespace { + +const char kMapViewOfSectionName[] = "NtMapViewOfSection"; +const char kUnmapViewOfSectionName[] = "NtUnmapViewOfSection"; + +// Standard allocation granularity and page size for Windows. +const size_t kAllocGranularity = 65536; +const size_t kPageSize = 4096; + +// Find a random offset within 64k and aligned to ceil(log2(size)). +size_t GetGranularAlignedRandomOffset(size_t size) { + CHECK_LE(size, kAllocGranularity); + unsigned int offset; + + do { + rand_s(&offset); + offset &= (kAllocGranularity - 1); + } while (offset > (kAllocGranularity - size)); + + // Find an alignment between 64 and the page size (4096). + size_t align_size = kPageSize; + for (size_t new_size = align_size / 2; new_size >= size; new_size /= 2) { + align_size = new_size; + } + return offset & ~(align_size - 1); +} + +} // namespace + +namespace sandbox { + +SANDBOX_INTERCEPT SharedMemory* g_interceptions; + +// Table of the unpatched functions that we intercept. Mapped from the parent. +SANDBOX_INTERCEPT OriginalFunctions g_originals = { NULL }; + +// Magic constant that identifies that this function is not to be patched. +const char kUnloadDLLDummyFunction[] = "@"; + +InterceptionManager::InterceptionData::InterceptionData() { +} + +InterceptionManager::InterceptionData::~InterceptionData() { +} + +InterceptionManager::InterceptionManager(TargetProcess* child_process, + bool relaxed) + : child_(child_process), names_used_(false), relaxed_(relaxed) { + child_->AddRef(); +} +InterceptionManager::~InterceptionManager() { + child_->Release(); +} + +bool InterceptionManager::AddToPatchedFunctions( + const wchar_t* dll_name, const char* function_name, + InterceptionType interception_type, const void* replacement_code_address, + InterceptorId id) { + InterceptionData function; + function.type = interception_type; + function.id = id; + function.dll = dll_name; + function.function = function_name; + function.interceptor_address = replacement_code_address; + + interceptions_.push_back(function); + return true; +} + +bool InterceptionManager::AddToPatchedFunctions( + const wchar_t* dll_name, const char* function_name, + InterceptionType interception_type, const char* replacement_function_name, + InterceptorId id) { + InterceptionData function; + function.type = interception_type; + function.id = id; + function.dll = dll_name; + function.function = function_name; + function.interceptor = replacement_function_name; + function.interceptor_address = NULL; + + interceptions_.push_back(function); + names_used_ = true; + return true; +} + +bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) { + InterceptionData module_to_unload; + module_to_unload.type = INTERCEPTION_UNLOAD_MODULE; + module_to_unload.dll = dll_name; + // The next two are dummy values that make the structures regular, instead + // of having special cases. They should not be used. + module_to_unload.function = kUnloadDLLDummyFunction; + module_to_unload.interceptor_address = reinterpret_cast<void*>(1); + + interceptions_.push_back(module_to_unload); + return true; +} + +bool InterceptionManager::InitializeInterceptions() { + if (interceptions_.empty()) + return true; // Nothing to do here + + size_t buffer_bytes = GetBufferSize(); + scoped_ptr<char[]> local_buffer(new char[buffer_bytes]); + + if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes)) + return false; + + void* remote_buffer; + if (!CopyDataToChild(local_buffer.get(), buffer_bytes, &remote_buffer)) + return false; + + bool hot_patch_needed = (0 != buffer_bytes); + if (!PatchNtdll(hot_patch_needed)) + return false; + + g_interceptions = reinterpret_cast<SharedMemory*>(remote_buffer); + ResultCode rc = child_->TransferVariable("g_interceptions", + &g_interceptions, + sizeof(g_interceptions)); + return (SBOX_ALL_OK == rc); +} + +size_t InterceptionManager::GetBufferSize() const { + std::set<base::string16> dlls; + size_t buffer_bytes = 0; + + std::list<InterceptionData>::const_iterator it = interceptions_.begin(); + for (; it != interceptions_.end(); ++it) { + // skip interceptions that are performed from the parent + if (!IsInterceptionPerformedByChild(*it)) + continue; + + if (!dlls.count(it->dll)) { + // NULL terminate the dll name on the structure + size_t dll_name_bytes = (it->dll.size() + 1) * sizeof(wchar_t); + + // include the dll related size + buffer_bytes += RoundUpToMultiple(offsetof(DllPatchInfo, dll_name) + + dll_name_bytes, sizeof(size_t)); + dlls.insert(it->dll); + } + + // we have to NULL terminate the strings on the structure + size_t strings_chars = it->function.size() + it->interceptor.size() + 2; + + // a new FunctionInfo is required per function + size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars; + record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t)); + buffer_bytes += record_bytes; + } + + if (0 != buffer_bytes) + // add the part of SharedMemory that we have not counted yet + buffer_bytes += offsetof(SharedMemory, dll_list); + + return buffer_bytes; +} + +// Basically, walk the list of interceptions moving them to the config buffer, +// but keeping together all interceptions that belong to the same dll. +// The config buffer is a local buffer, not the one allocated on the child. +bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) { + if (0 == buffer_bytes) + return true; + + DCHECK(buffer_bytes > sizeof(SharedMemory)); + + SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer); + DllPatchInfo* dll_info = shared_memory->dll_list; + int num_dlls = 0; + + shared_memory->interceptor_base = names_used_ ? child_->MainModule() : NULL; + + buffer_bytes -= offsetof(SharedMemory, dll_list); + buffer = dll_info; + + std::list<InterceptionData>::iterator it = interceptions_.begin(); + for (; it != interceptions_.end();) { + // skip interceptions that are performed from the parent + if (!IsInterceptionPerformedByChild(*it)) { + ++it; + continue; + } + + const base::string16 dll = it->dll; + if (!SetupDllInfo(*it, &buffer, &buffer_bytes)) + return false; + + // walk the interceptions from this point, saving the ones that are + // performed on this dll, and removing the entry from the list. + // advance the iterator before removing the element from the list + std::list<InterceptionData>::iterator rest = it; + for (; rest != interceptions_.end();) { + if (rest->dll == dll) { + if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info)) + return false; + if (it == rest) + ++it; + rest = interceptions_.erase(rest); + } else { + ++rest; + } + } + dll_info = reinterpret_cast<DllPatchInfo*>(buffer); + ++num_dlls; + } + + shared_memory->num_intercepted_dlls = num_dlls; + return true; +} + +// Fills up just the part that depends on the dll, not the info that depends on +// the actual interception. +bool InterceptionManager::SetupDllInfo(const InterceptionData& data, + void** buffer, + size_t* buffer_bytes) const { + DCHECK(buffer_bytes); + DCHECK(buffer); + DCHECK(*buffer); + + DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer); + + // the strings have to be zero terminated + size_t required = offsetof(DllPatchInfo, dll_name) + + (data.dll.size() + 1) * sizeof(wchar_t); + required = RoundUpToMultiple(required, sizeof(size_t)); + if (*buffer_bytes < required) + return false; + + *buffer_bytes -= required; + *buffer = reinterpret_cast<char*>(*buffer) + required; + + // set up the dll info to be what we know about it at this time + dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE); + dll_info->record_bytes = required; + dll_info->offset_to_functions = required; + dll_info->num_functions = 0; + data.dll._Copy_s(dll_info->dll_name, data.dll.size(), data.dll.size()); + dll_info->dll_name[data.dll.size()] = L'\0'; + + return true; +} + +bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data, + void** buffer, + size_t* buffer_bytes, + DllPatchInfo* dll_info) const { + DCHECK(buffer_bytes); + DCHECK(buffer); + DCHECK(*buffer); + + if ((dll_info->unload_module) && + (data.function != kUnloadDLLDummyFunction)) { + // Can't specify a dll for both patch and unload. + NOTREACHED(); + } + + FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer); + + size_t name_bytes = data.function.size(); + size_t interceptor_bytes = data.interceptor.size(); + + // the strings at the end of the structure are zero terminated + size_t required = offsetof(FunctionInfo, function) + + name_bytes + interceptor_bytes + 2; + required = RoundUpToMultiple(required, sizeof(size_t)); + if (*buffer_bytes < required) + return false; + + // update the caller's values + *buffer_bytes -= required; + *buffer = reinterpret_cast<char*>(*buffer) + required; + + function->record_bytes = required; + function->type = data.type; + function->id = data.id; + function->interceptor_address = data.interceptor_address; + char* names = function->function; + + data.function._Copy_s(names, name_bytes, name_bytes); + names += name_bytes; + *names++ = '\0'; + + // interceptor follows the function_name + data.interceptor._Copy_s(names, interceptor_bytes, interceptor_bytes); + names += interceptor_bytes; + *names++ = '\0'; + + // update the dll table + dll_info->num_functions++; + dll_info->record_bytes += required; + + return true; +} + +bool InterceptionManager::CopyDataToChild(const void* local_buffer, + size_t buffer_bytes, + void** remote_buffer) const { + DCHECK(NULL != remote_buffer); + if (0 == buffer_bytes) { + *remote_buffer = NULL; + return true; + } + + HANDLE child = child_->Process(); + + // Allocate memory on the target process without specifying the address + void* remote_data = ::VirtualAllocEx(child, NULL, buffer_bytes, + MEM_COMMIT, PAGE_READWRITE); + if (NULL == remote_data) + return false; + + SIZE_T bytes_written; + BOOL success = ::WriteProcessMemory(child, remote_data, local_buffer, + buffer_bytes, &bytes_written); + if (FALSE == success || bytes_written != buffer_bytes) { + ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE); + return false; + } + + *remote_buffer = remote_data; + + return true; +} + +// Only return true if the child should be able to perform this interception. +bool InterceptionManager::IsInterceptionPerformedByChild( + const InterceptionData& data) const { + if (INTERCEPTION_INVALID == data.type) + return false; + + if (INTERCEPTION_SERVICE_CALL == data.type) + return false; + + if (data.type >= INTERCEPTION_LAST) + return false; + + base::string16 ntdll(kNtdllName); + if (ntdll == data.dll) + return false; // ntdll has to be intercepted from the parent + + return true; +} + +bool InterceptionManager::PatchNtdll(bool hot_patch_needed) { + // Maybe there is nothing to do + if (!hot_patch_needed && interceptions_.empty()) + return true; + + if (hot_patch_needed) { +#if SANDBOX_EXPORTS + // Make sure the functions are not excluded by the linker. +#if defined(_WIN64) + #pragma comment(linker, "/include:TargetNtMapViewOfSection64") + #pragma comment(linker, "/include:TargetNtUnmapViewOfSection64") +#else + #pragma comment(linker, "/include:_TargetNtMapViewOfSection@44") + #pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12") +#endif +#endif + ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44); + ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12); + } + + // Reserve a full 64k memory range in the child process. + HANDLE child = child_->Process(); + BYTE* thunk_base = reinterpret_cast<BYTE*>( + ::VirtualAllocEx(child, NULL, kAllocGranularity, + MEM_RESERVE, PAGE_NOACCESS)); + + // Find an aligned, random location within the reserved range. + size_t thunk_bytes = interceptions_.size() * sizeof(ThunkData) + + sizeof(DllInterceptionData); + size_t thunk_offset = GetGranularAlignedRandomOffset(thunk_bytes); + + // Split the base and offset along page boundaries. + thunk_base += thunk_offset & ~(kPageSize - 1); + thunk_offset &= kPageSize - 1; + + // Make an aligned, padded allocation, and move the pointer to our chunk. + size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & ~(kPageSize - 1); + thunk_base = reinterpret_cast<BYTE*>( + ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded, + MEM_COMMIT, PAGE_EXECUTE_READWRITE)); + CHECK(thunk_base); // If this fails we'd crash anyway on an invalid access. + DllInterceptionData* thunks = reinterpret_cast<DllInterceptionData*>( + thunk_base + thunk_offset); + + DllInterceptionData dll_data; + dll_data.data_bytes = thunk_bytes; + dll_data.num_thunks = 0; + dll_data.used_bytes = offsetof(DllInterceptionData, thunks); + + // Reset all helpers for a new child. + memset(g_originals, 0, sizeof(g_originals)); + + // this should write all the individual thunks to the child's memory + if (!PatchClientFunctions(thunks, thunk_bytes, &dll_data)) + return false; + + // and now write the first part of the table to the child's memory + SIZE_T written; + bool ok = FALSE != ::WriteProcessMemory(child, thunks, &dll_data, + offsetof(DllInterceptionData, thunks), + &written); + + if (!ok || (offsetof(DllInterceptionData, thunks) != written)) + return false; + + // Attempt to protect all the thunks, but ignore failure + DWORD old_protection; + ::VirtualProtectEx(child, thunks, thunk_bytes, + PAGE_EXECUTE_READ, &old_protection); + + ResultCode ret = child_->TransferVariable("g_originals", g_originals, + sizeof(g_originals)); + return (SBOX_ALL_OK == ret); +} + +bool InterceptionManager::PatchClientFunctions(DllInterceptionData* thunks, + size_t thunk_bytes, + DllInterceptionData* dll_data) { + DCHECK(NULL != thunks); + DCHECK(NULL != dll_data); + + HMODULE ntdll_base = ::GetModuleHandle(kNtdllName); + if (!ntdll_base) + return false; + + base::win::PEImage ntdll_image(ntdll_base); + + // Bypass purify's interception. + wchar_t* loader_get = reinterpret_cast<wchar_t*>( + ntdll_image.GetProcAddress("LdrGetDllHandle")); + if (loader_get) { + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + loader_get, &ntdll_base)) + return false; + } + + if (base::win::GetVersion() <= base::win::VERSION_VISTA) { + Wow64 WowHelper(child_, ntdll_base); + if (!WowHelper.WaitForNtdll()) + return false; + } + + char* interceptor_base = NULL; + +#if SANDBOX_EXPORTS + interceptor_base = reinterpret_cast<char*>(child_->MainModule()); + HMODULE local_interceptor = ::LoadLibrary(child_->Name()); +#endif + + ServiceResolverThunk* thunk; +#if defined(_WIN64) + thunk = new ServiceResolverThunk(child_->Process(), relaxed_); +#else + base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); + if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) { + if (os_info->version() >= base::win::VERSION_WIN8) + thunk = new Wow64W8ResolverThunk(child_->Process(), relaxed_); + else + thunk = new Wow64ResolverThunk(child_->Process(), relaxed_); + } else if (os_info->version() >= base::win::VERSION_WIN8) { + thunk = new Win8ResolverThunk(child_->Process(), relaxed_); + } else { + thunk = new ServiceResolverThunk(child_->Process(), relaxed_); + } +#endif + + std::list<InterceptionData>::iterator it = interceptions_.begin(); + for (; it != interceptions_.end(); ++it) { + const base::string16 ntdll(kNtdllName); + if (it->dll != ntdll) + break; + + if (INTERCEPTION_SERVICE_CALL != it->type) + break; + +#if SANDBOX_EXPORTS + // We may be trying to patch by function name. + if (NULL == it->interceptor_address) { + const char* address; + NTSTATUS ret = thunk->ResolveInterceptor(local_interceptor, + it->interceptor.c_str(), + reinterpret_cast<const void**>( + &address)); + if (!NT_SUCCESS(ret)) + break; + + // Translate the local address to an address on the child. + it->interceptor_address = interceptor_base + (address - + reinterpret_cast<char*>(local_interceptor)); + } +#endif + NTSTATUS ret = thunk->Setup(ntdll_base, + interceptor_base, + it->function.c_str(), + it->interceptor.c_str(), + it->interceptor_address, + &thunks->thunks[dll_data->num_thunks], + thunk_bytes - dll_data->used_bytes, + NULL); + if (!NT_SUCCESS(ret)) + break; + + DCHECK(!g_originals[it->id]); + g_originals[it->id] = &thunks->thunks[dll_data->num_thunks]; + + dll_data->num_thunks++; + dll_data->used_bytes += sizeof(ThunkData); + } + + delete(thunk); + +#if SANDBOX_EXPORTS + if (NULL != local_interceptor) + ::FreeLibrary(local_interceptor); +#endif + + if (it != interceptions_.end()) + return false; + + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/interception.h b/sandbox/win/src/interception.h new file mode 100644 index 0000000000..728dc74e45 --- /dev/null +++ b/sandbox/win/src/interception.h @@ -0,0 +1,284 @@ +// Copyright (c) 2011 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. + +// Defines InterceptionManager, the class in charge of setting up interceptions +// for the sandboxed process. For more details see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#ifndef SANDBOX_SRC_INTERCEPTION_H_ +#define SANDBOX_SRC_INTERCEPTION_H_ + +#include <list> +#include <string> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +class TargetProcess; +enum InterceptorId; + +// Internal structures used for communication between the broker and the target. +struct DllPatchInfo; +struct DllInterceptionData; + +// The InterceptionManager executes on the parent application, and it is in +// charge of setting up the desired interceptions, and placing the Interception +// Agent into the child application. +// +// The exposed API consists of two methods: AddToPatchedFunctions to set up a +// particular interception, and InitializeInterceptions to actually go ahead and +// perform all interceptions and transfer data to the child application. +// +// The typical usage is something like this: +// +// InterceptionManager interception_manager(child); +// if (!interception_manager.AddToPatchedFunctions( +// L"ntdll.dll", "NtCreateFile", +// sandbox::INTERCEPTION_SERVICE_CALL, &MyNtCreateFile, MY_ID_1)) +// return false; +// +// if (!interception_manager.AddToPatchedFunctions( +// L"kernel32.dll", "CreateDirectoryW", +// sandbox::INTERCEPTION_EAT, L"MyCreateDirectoryW@12", MY_ID_2)) +// return false; +// +// if (!interception_manager.InitializeInterceptions()) { +// DWORD error = ::GetLastError(); +// return false; +// } +// +// Any required syncronization must be performed outside this class. Also, it is +// not possible to perform further interceptions after InitializeInterceptions +// is called. +// +class InterceptionManager { + // The unit test will access private members. + // Allow tests to be marked DISABLED_. Note that FLAKY_ and FAILS_ prefixes + // do not work with sandbox tests. + FRIEND_TEST_ALL_PREFIXES(InterceptionManagerTest, BufferLayout1); + FRIEND_TEST_ALL_PREFIXES(InterceptionManagerTest, BufferLayout2); + + public: + // An interception manager performs interceptions on a given child process. + // If we are allowed to intercept functions that have been patched by somebody + // else, relaxed should be set to true. + // Note: We increase the child's reference count internally. + InterceptionManager(TargetProcess* child_process, bool relaxed); + ~InterceptionManager(); + + // Patches function_name inside dll_name to point to replacement_code_address. + // function_name has to be an exported symbol of dll_name. + // Returns true on success. + // + // The new function should match the prototype and calling convention of the + // function to intercept except for one extra argument (the first one) that + // contains a pointer to the original function, to simplify the development + // of interceptors (for IA32). In x64, there is no extra argument to the + // interceptor, so the provided InterceptorId is used to keep a table of + // intercepted functions so that the interceptor can index that table to get + // the pointer that would have been the first argument (g_originals[id]). + // + // For example, to intercept NtClose, the following code could be used: + // + // typedef NTSTATUS (WINAPI *NtCloseFunction) (IN HANDLE Handle); + // NTSTATUS WINAPI MyNtCose(IN NtCloseFunction OriginalClose, + // IN HANDLE Handle) { + // // do something + // // call the original function + // return OriginalClose(Handle); + // } + // + // And in x64: + // + // typedef NTSTATUS (WINAPI *NtCloseFunction) (IN HANDLE Handle); + // NTSTATUS WINAPI MyNtCose64(IN HANDLE Handle) { + // // do something + // // call the original function + // NtCloseFunction OriginalClose = g_originals[NT_CLOSE_ID]; + // return OriginalClose(Handle); + // } + bool AddToPatchedFunctions(const wchar_t* dll_name, + const char* function_name, + InterceptionType interception_type, + const void* replacement_code_address, + InterceptorId id); + + // Patches function_name inside dll_name to point to + // replacement_function_name. + bool AddToPatchedFunctions(const wchar_t* dll_name, + const char* function_name, + InterceptionType interception_type, + const char* replacement_function_name, + InterceptorId id); + + // The interception agent will unload the dll with dll_name. + bool AddToUnloadModules(const wchar_t* dll_name); + + // Initializes all interceptions on the client. + // Returns true on success. + // + // The child process must be created suspended, and cannot be resumed until + // after this method returns. In addition, no action should be performed on + // the child that may cause it to resume momentarily, such as injecting + // threads or APCs. + // + // This function must be called only once, after all interceptions have been + // set up using AddToPatchedFunctions. + bool InitializeInterceptions(); + + private: + // Used to store the interception information until the actual set-up. + struct InterceptionData { + InterceptionData(); + ~InterceptionData(); + + InterceptionType type; // Interception type. + InterceptorId id; // Interceptor id. + base::string16 dll; // Name of dll to intercept. + std::string function; // Name of function to intercept. + std::string interceptor; // Name of interceptor function. + const void* interceptor_address; // Interceptor's entry point. + }; + + // Calculates the size of the required configuration buffer. + size_t GetBufferSize() const; + + // Rounds up the size of a given buffer, considering alignment (padding). + // value is the current size of the buffer, and alignment is specified in + // bytes. + static inline size_t RoundUpToMultiple(size_t value, size_t alignment) { + return ((value + alignment -1) / alignment) * alignment; + } + + // Sets up a given buffer with all the information that has to be transfered + // to the child. + // Returns true on success. + // + // The buffer size should be at least the value returned by GetBufferSize + bool SetupConfigBuffer(void* buffer, size_t buffer_bytes); + + // Fills up the part of the transfer buffer that corresponds to information + // about one dll to patch. + // data is the first recorded interception for this dll. + // Returns true on success. + // + // On successful return, buffer will be advanced from it's current position + // to the point where the next block of configuration data should be written + // (the actual interception info), and the current size of the buffer will + // decrease to account the space used by this method. + bool SetupDllInfo(const InterceptionData& data, + void** buffer, size_t* buffer_bytes) const; + + // Fills up the part of the transfer buffer that corresponds to a single + // function to patch. + // dll_info points to the dll being updated with the interception stored on + // data. The buffer pointer and remaining size are updated by this call. + // Returns true on success. + bool SetupInterceptionInfo(const InterceptionData& data, void** buffer, + size_t* buffer_bytes, + DllPatchInfo* dll_info) const; + + // Returns true if this interception is to be performed by the child + // as opposed to from the parent. + bool IsInterceptionPerformedByChild(const InterceptionData& data) const; + + // Allocates a buffer on the child's address space (returned on + // remote_buffer), and fills it with the contents of a local buffer. + // Returns true on success. + bool CopyDataToChild(const void* local_buffer, size_t buffer_bytes, + void** remote_buffer) const; + + // Performs the cold patch (from the parent) of ntdll. + // Returns true on success. + // + // This method will insert additional interceptions to launch the interceptor + // agent on the child process, if there are additional interceptions to do. + bool PatchNtdll(bool hot_patch_needed); + + // Peforms the actual interceptions on ntdll. + // thunks is the memory to store all the thunks for this dll (on the child), + // and dll_data is a local buffer to hold global dll interception info. + // Returns true on success. + bool PatchClientFunctions(DllInterceptionData* thunks, + size_t thunk_bytes, + DllInterceptionData* dll_data); + + // The process to intercept. + TargetProcess* child_; + // Holds all interception info until the call to initialize (perform the + // actual patch). + std::list<InterceptionData> interceptions_; + + // Keep track of patches added by name. + bool names_used_; + + // true if we are allowed to patch already-patched functions. + bool relaxed_; + + DISALLOW_COPY_AND_ASSIGN(InterceptionManager); +}; + +// This macro simply calls interception_manager.AddToPatchedFunctions with +// the given service to intercept (INTERCEPTION_SERVICE_CALL), and assumes that +// the interceptor is called "TargetXXX", where XXX is the name of the service. +// Note that num_params is the number of bytes to pop out of the stack for +// the exported interceptor, following the calling convention of a service call +// (WINAPI = with the "C" underscore). +#if SANDBOX_EXPORTS +#if defined(_WIN64) +#define MAKE_SERVICE_NAME(service, params) "Target" # service "64" +#else +#define MAKE_SERVICE_NAME(service, params) "_Target" # service "@" # params +#endif + +#define ADD_NT_INTERCEPTION(service, id, num_params) \ + AddToPatchedFunctions(kNtdllName, #service, \ + sandbox::INTERCEPTION_SERVICE_CALL, \ + MAKE_SERVICE_NAME(service, num_params), id) + +#define INTERCEPT_NT(manager, service, id, num_params) \ + ((&Target##service) ? \ + manager->ADD_NT_INTERCEPTION(service, id, num_params) : false) + +// When intercepting the EAT it is important that the patched version of the +// function not call any functions imported from system libraries unless +// |TargetServices::InitCalled()| returns true, because it is only then that +// we are guaranteed that our IAT has been initialized. +#define INTERCEPT_EAT(manager, dll, function, id, num_params) \ + ((&Target##function) ? \ + manager->AddToPatchedFunctions(dll, #function, sandbox::INTERCEPTION_EAT, \ + MAKE_SERVICE_NAME(function, num_params), \ + id) : \ + false) +#else // SANDBOX_EXPORTS +#if defined(_WIN64) +#define MAKE_SERVICE_NAME(service) &Target##service##64 +#else +#define MAKE_SERVICE_NAME(service) &Target##service +#endif + +#define ADD_NT_INTERCEPTION(service, id, num_params) \ + AddToPatchedFunctions(kNtdllName, #service, \ + sandbox::INTERCEPTION_SERVICE_CALL, \ + MAKE_SERVICE_NAME(service), id) + +#define INTERCEPT_NT(manager, service, id, num_params) \ + manager->ADD_NT_INTERCEPTION(service, id, num_params) + +// When intercepting the EAT it is important that the patched version of the +// function not call any functions imported from system libraries unless +// |TargetServices::InitCalled()| returns true, because it is only then that +// we are guaranteed that our IAT has been initialized. +#define INTERCEPT_EAT(manager, dll, function, id, num_params) \ + manager->AddToPatchedFunctions(dll, #function, sandbox::INTERCEPTION_EAT, \ + MAKE_SERVICE_NAME(function), id) +#endif // SANDBOX_EXPORTS + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTION_H_ diff --git a/sandbox/win/src/interception_agent.cc b/sandbox/win/src/interception_agent.cc new file mode 100644 index 0000000000..b2a66c471f --- /dev/null +++ b/sandbox/win/src/interception_agent.cc @@ -0,0 +1,233 @@ +// Copyright (c) 2006-2010 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. + +// For information about interceptions as a whole see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#include "sandbox/win/src/interception_agent.h" + +#include "sandbox/win/src/interception_internal.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/eat_resolver.h" +#include "sandbox/win/src/sidestep_resolver.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +// Returns true if target lies between base and base + range. +bool IsWithinRange(const void* base, size_t range, const void* target) { + const char* end = reinterpret_cast<const char*>(base) + range; + return reinterpret_cast<const char*>(target) < end; +} + +} // namespace + +namespace sandbox { + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +// The list of intercepted functions back-pointers. +SANDBOX_INTERCEPT OriginalFunctions g_originals; + +// Memory buffer mapped from the parent, with the list of interceptions. +SANDBOX_INTERCEPT SharedMemory* g_interceptions = NULL; + +InterceptionAgent* InterceptionAgent::GetInterceptionAgent() { + static InterceptionAgent* s_singleton = NULL; + if (!s_singleton) { + if (!g_interceptions) + return NULL; + + size_t array_bytes = g_interceptions->num_intercepted_dlls * sizeof(void*); + s_singleton = reinterpret_cast<InterceptionAgent*>( + new(NT_ALLOC) char[array_bytes + sizeof(InterceptionAgent)]); + + bool success = s_singleton->Init(g_interceptions); + if (!success) { + operator delete(s_singleton, NT_ALLOC); + s_singleton = NULL; + } + } + return s_singleton; +} + +bool InterceptionAgent::Init(SharedMemory* shared_memory) { + interceptions_ = shared_memory; + for (int i = 0 ; i < shared_memory->num_intercepted_dlls; i++) + dlls_[i] = NULL; + return true; +} + +bool InterceptionAgent::DllMatch(const UNICODE_STRING* full_path, + const UNICODE_STRING* name, + const DllPatchInfo* dll_info) { + UNICODE_STRING current_name; + current_name.Length = static_cast<USHORT>(g_nt.wcslen(dll_info->dll_name) * + sizeof(wchar_t)); + current_name.MaximumLength = current_name.Length; + current_name.Buffer = const_cast<wchar_t*>(dll_info->dll_name); + + BOOLEAN case_insensitive = TRUE; + if (full_path && + !g_nt.RtlCompareUnicodeString(¤t_name, full_path, case_insensitive)) + return true; + + if (name && + !g_nt.RtlCompareUnicodeString(¤t_name, name, case_insensitive)) + return true; + + return false; +} + +bool InterceptionAgent::OnDllLoad(const UNICODE_STRING* full_path, + const UNICODE_STRING* name, + void* base_address) { + DllPatchInfo* dll_info = interceptions_->dll_list; + int i = 0; + for (; i < interceptions_->num_intercepted_dlls; i++) { + if (DllMatch(full_path, name, dll_info)) + break; + + dll_info = reinterpret_cast<DllPatchInfo*>( + reinterpret_cast<char*>(dll_info) + dll_info->record_bytes); + } + + // Return now if the dll is not in our list of interest. + if (i == interceptions_->num_intercepted_dlls) + return true; + + // The dll must be unloaded. + if (dll_info->unload_module) + return false; + + // Purify causes this condition to trigger. + if (dlls_[i]) + return true; + + size_t buffer_bytes = offsetof(DllInterceptionData, thunks) + + dll_info->num_functions * sizeof(ThunkData); + dlls_[i] = reinterpret_cast<DllInterceptionData*>( + new(NT_PAGE, base_address) char[buffer_bytes]); + + DCHECK_NT(dlls_[i]); + if (!dlls_[i]) + return true; + + dlls_[i]->data_bytes = buffer_bytes; + dlls_[i]->num_thunks = 0; + dlls_[i]->base = base_address; + dlls_[i]->used_bytes = offsetof(DllInterceptionData, thunks); + + VERIFY(PatchDll(dll_info, dlls_[i])); + + ULONG old_protect; + SIZE_T real_size = buffer_bytes; + void* to_protect = dlls_[i]; + VERIFY_SUCCESS(g_nt.ProtectVirtualMemory(NtCurrentProcess, &to_protect, + &real_size, PAGE_EXECUTE_READ, + &old_protect)); + return true; +} + +void InterceptionAgent::OnDllUnload(void* base_address) { + for (int i = 0; i < interceptions_->num_intercepted_dlls; i++) { + if (dlls_[i] && dlls_[i]->base == base_address) { + operator delete(dlls_[i], NT_PAGE); + dlls_[i] = NULL; + break; + } + } +} + +// TODO(rvargas): We have to deal with prebinded dlls. I see two options: change +// the timestamp of the patched dll, or modify the info on the prebinded dll. +// the first approach messes matching of debug symbols, the second one is more +// complicated. +bool InterceptionAgent::PatchDll(const DllPatchInfo* dll_info, + DllInterceptionData* thunks) { + DCHECK_NT(NULL != thunks); + DCHECK_NT(NULL != dll_info); + + const FunctionInfo* function = reinterpret_cast<const FunctionInfo*>( + reinterpret_cast<const char*>(dll_info) + dll_info->offset_to_functions); + + for (int i = 0; i < dll_info->num_functions; i++) { + if (!IsWithinRange(dll_info, dll_info->record_bytes, function->function)) { + NOTREACHED_NT(); + return false; + } + + ResolverThunk* resolver = GetResolver(function->type); + if (!resolver) + return false; + + const char* interceptor = function->function + + g_nt.strlen(function->function) + 1; + + if (!IsWithinRange(function, function->record_bytes, interceptor) || + !IsWithinRange(dll_info, dll_info->record_bytes, interceptor)) { + NOTREACHED_NT(); + return false; + } + + NTSTATUS ret = resolver->Setup(thunks->base, + interceptions_->interceptor_base, + function->function, + interceptor, + function->interceptor_address, + &thunks->thunks[i], + sizeof(ThunkData), + NULL); + if (!NT_SUCCESS(ret)) { + NOTREACHED_NT(); + return false; + } + + DCHECK_NT(!g_originals[function->id]); + g_originals[function->id] = &thunks->thunks[i]; + + thunks->num_thunks++; + thunks->used_bytes += sizeof(ThunkData); + + function = reinterpret_cast<const FunctionInfo*>( + reinterpret_cast<const char*>(function) + function->record_bytes); + } + + return true; +} + +// This method is called from within the loader lock +ResolverThunk* InterceptionAgent::GetResolver(InterceptionType type) { + static EatResolverThunk* eat_resolver = NULL; + static SidestepResolverThunk* sidestep_resolver = NULL; + static SmartSidestepResolverThunk* smart_sidestep_resolver = NULL; + + if (!eat_resolver) + eat_resolver = new(NT_ALLOC) EatResolverThunk; + +#if !defined(_WIN64) + // Sidestep is not supported for x64. + if (!sidestep_resolver) + sidestep_resolver = new(NT_ALLOC) SidestepResolverThunk; + + if (!smart_sidestep_resolver) + smart_sidestep_resolver = new(NT_ALLOC) SmartSidestepResolverThunk; +#endif + + switch (type) { + case INTERCEPTION_EAT: + return eat_resolver; + case INTERCEPTION_SIDESTEP: + return sidestep_resolver; + case INTERCEPTION_SMART_SIDESTEP: + return smart_sidestep_resolver; + default: + NOTREACHED_NT(); + } + + return NULL; +} + +} // namespace sandbox diff --git a/sandbox/win/src/interception_agent.h b/sandbox/win/src/interception_agent.h new file mode 100644 index 0000000000..2762c611fd --- /dev/null +++ b/sandbox/win/src/interception_agent.h @@ -0,0 +1,87 @@ +// Copyright (c) 2006-2008 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. + +// Defines InterceptionAgent, the class in charge of setting up interceptions +// from the inside of the sandboxed process. For more details see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#ifndef SANDBOX_SRC_INTERCEPTION_AGENT_H__ +#define SANDBOX_SRC_INTERCEPTION_AGENT_H__ + +#include "base/basictypes.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +// Internal structures used for communication between the broker and the target. +struct DllInterceptionData; +struct SharedMemory; +struct DllPatchInfo; + +class ResolverThunk; + +// The InterceptionAgent executes on the target application, and it is in charge +// of setting up the desired interceptions or indicating what module needs to +// be unloaded. +// +// The exposed API consists of three methods: GetInterceptionAgent to retrieve +// the single class instance, OnDllLoad and OnDllUnload to process a dll being +// loaded and unloaded respectively. +// +// This class assumes that it will get called for every dll being loaded, +// starting with kernel32, so the singleton will be instantiated from within the +// loader lock. +class InterceptionAgent { + public: + // Returns the single InterceptionAgent object for this process. + static InterceptionAgent* GetInterceptionAgent(); + + // This method should be invoked whenever a new dll is loaded to perform the + // required patches. If the return value is false, this dll should not be + // allowed to load. + // + // full_path is the (optional) full name of the module being loaded and name + // is the internal module name. If full_path is provided, it will be used + // before the internal name to determine if we care about this dll. + bool OnDllLoad(const UNICODE_STRING* full_path, const UNICODE_STRING* name, + void* base_address); + + // Performs cleanup when a dll is unloaded. + void OnDllUnload(void* base_address); + + private: + ~InterceptionAgent() {} + + // Performs initialization of the singleton. + bool Init(SharedMemory* shared_memory); + + // Returns true if we are interested on this dll. dll_info is an entry of the + // list of intercepted dlls. + bool DllMatch(const UNICODE_STRING* full_path, const UNICODE_STRING* name, + const DllPatchInfo* dll_info); + + // Performs the patching of the dll loaded at base_address. + // The patches to perform are described on dll_info, and thunks is the thunk + // storage for the whole dll. + // Returns true on success. + bool PatchDll(const DllPatchInfo* dll_info, DllInterceptionData* thunks); + + // Returns a resolver for a given interception type. + ResolverThunk* GetResolver(InterceptionType type); + + // Shared memory containing the list of functions to intercept. + SharedMemory* interceptions_; + + // Array of thunk data buffers for the intercepted dlls. This object singleton + // is allocated with a placement new with enough space to hold the complete + // array of pointers, not just the first element. + DllInterceptionData* dlls_[1]; + + DISALLOW_IMPLICIT_CONSTRUCTORS(InterceptionAgent); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTION_AGENT_H__ diff --git a/sandbox/win/src/interception_internal.h b/sandbox/win/src/interception_internal.h new file mode 100644 index 0000000000..810478addb --- /dev/null +++ b/sandbox/win/src/interception_internal.h @@ -0,0 +1,76 @@ +// Copyright (c) 2006-2010 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. + +// Defines InterceptionManager, the class in charge of setting up interceptions +// for the sandboxed process. For more details see: +// http://dev.chromium.org/developers/design-documents/sandbox . + +#ifndef SANDBOX_SRC_INTERCEPTION_INTERNAL_H_ +#define SANDBOX_SRC_INTERCEPTION_INTERNAL_H_ + +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +const int kMaxThunkDataBytes = 64; + +enum InterceptorId; + +// The following structures contain variable size fields at the end, and will be +// used to transfer information between two processes. In order to guarantee +// our ability to follow the chain of structures, the alignment should be fixed, +// hence this pragma. +#pragma pack(push, 4) + +// Structures for the shared memory that contains patching information +// for the InterceptionAgent. +// A single interception: +struct FunctionInfo { + size_t record_bytes; // rounded to sizeof(size_t) bytes + InterceptionType type; + InterceptorId id; + const void* interceptor_address; + char function[1]; // placeholder for null terminated name + // char interceptor[] // followed by the interceptor function +}; + +// A single dll: +struct DllPatchInfo { + size_t record_bytes; // rounded to sizeof(size_t) bytes + size_t offset_to_functions; + int num_functions; + bool unload_module; + wchar_t dll_name[1]; // placeholder for null terminated name + // FunctionInfo function_info[] // followed by the functions to intercept +}; + +// All interceptions: +struct SharedMemory { + int num_intercepted_dlls; + void* interceptor_base; + DllPatchInfo dll_list[1]; // placeholder for the list of dlls +}; + +// Dummy single thunk: +struct ThunkData { + char data[kMaxThunkDataBytes]; +}; + +// In-memory representation of the interceptions for a given dll: +struct DllInterceptionData { + size_t data_bytes; + size_t used_bytes; + void* base; + int num_thunks; +#if defined(_WIN64) + int dummy; // Improve alignment. +#endif + ThunkData thunks[1]; +}; + +#pragma pack(pop) + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTION_INTERNAL_H_ diff --git a/sandbox/win/src/interception_unittest.cc b/sandbox/win/src/interception_unittest.cc new file mode 100644 index 0000000000..0fc9b7cbe2 --- /dev/null +++ b/sandbox/win/src/interception_unittest.cc @@ -0,0 +1,212 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for InterceptionManager. +// The tests require private information so the whole interception.cc file is +// included from this file. + +#include <windows.h> + +#include "base/memory/scoped_ptr.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/interception_internal.h" +#include "sandbox/win/src/target_process.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Walks the settings buffer, verifying that the values make sense and counting +// objects. +// Arguments: +// buffer (in): the buffer to walk. +// size (in): buffer size +// num_dlls (out): count of the dlls on the buffer. +// num_function (out): count of intercepted functions. +// num_names (out): count of named interceptor functions. +void WalkBuffer(void* buffer, size_t size, int* num_dlls, int* num_functions, + int* num_names) { + ASSERT_TRUE(NULL != buffer); + ASSERT_TRUE(NULL != num_functions); + ASSERT_TRUE(NULL != num_names); + *num_dlls = *num_functions = *num_names = 0; + SharedMemory *memory = reinterpret_cast<SharedMemory*>(buffer); + + ASSERT_GT(size, sizeof(SharedMemory)); + DllPatchInfo *dll = &memory->dll_list[0]; + + for (int i = 0; i < memory->num_intercepted_dlls; i++) { + ASSERT_NE(0u, wcslen(dll->dll_name)); + ASSERT_EQ(0u, dll->record_bytes % sizeof(size_t)); + ASSERT_EQ(0u, dll->offset_to_functions % sizeof(size_t)); + ASSERT_NE(0, dll->num_functions); + + FunctionInfo *function = reinterpret_cast<FunctionInfo*>( + reinterpret_cast<char*>(dll) + dll->offset_to_functions); + + for (int j = 0; j < dll->num_functions; j++) { + ASSERT_EQ(0u, function->record_bytes % sizeof(size_t)); + + char* name = function->function; + size_t length = strlen(name); + ASSERT_NE(0u, length); + name += length + 1; + + // look for overflows + ASSERT_GT(reinterpret_cast<char*>(buffer) + size, name + strlen(name)); + + // look for a named interceptor + if (strlen(name)) { + (*num_names)++; + EXPECT_TRUE(NULL == function->interceptor_address); + } else { + EXPECT_TRUE(NULL != function->interceptor_address); + } + + (*num_functions)++; + function = reinterpret_cast<FunctionInfo*>( + reinterpret_cast<char*>(function) + function->record_bytes); + } + + (*num_dlls)++; + dll = reinterpret_cast<DllPatchInfo*>(reinterpret_cast<char*>(dll) + + dll->record_bytes); + } +} + +TEST(InterceptionManagerTest, BufferLayout1) { + wchar_t exe_name[MAX_PATH]; + ASSERT_NE(0u, GetModuleFileName(NULL, exe_name, MAX_PATH - 1)); + + TargetProcess *target = MakeTestTargetProcess(::GetCurrentProcess(), + ::GetModuleHandle(exe_name)); + + InterceptionManager interceptions(target, true); + + // Any pointer will do for a function pointer. + void* function = &interceptions; + + // We don't care about the interceptor id. + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtCreateFile", + INTERCEPTION_SERVICE_CALL, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "CreateFileEx", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "SomeFileEx", + INTERCEPTION_SMART_SIDESTEP, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"user32.dll", "FindWindow", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "CreateMutex", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"user32.dll", "PostMsg", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"user32.dll", "PostMsg", + INTERCEPTION_EAT, "replacement", + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtClose", + INTERCEPTION_SERVICE_CALL, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtOpenFile", + INTERCEPTION_SIDESTEP, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"some.dll", "Superfn", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_EAT, "a", OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_SIDESTEP, "ab", OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_EAT, "abc", OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"a.dll", "p", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"b.dll", + "TheIncredibleCallToSaveTheWorld", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"a.dll", "BIsLame", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"a.dll", "ARules", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + + // Verify that all interceptions were added + ASSERT_EQ(18, interceptions.interceptions_.size()); + + size_t buffer_size = interceptions.GetBufferSize(); + scoped_ptr<BYTE[]> local_buffer(new BYTE[buffer_size]); + + ASSERT_TRUE(interceptions.SetupConfigBuffer(local_buffer.get(), + buffer_size)); + + // At this point, the interceptions should have been separated into two + // groups: one group with the local ("cold") interceptions, consisting of + // everything from ntdll and stuff set as INTRECEPTION_SERVICE_CALL, and + // another group with the interceptions belonging to dlls that will be "hot" + // patched on the client. The second group lives on local_buffer, and the + // first group remains on the list of interceptions (inside the object + // "interceptions"). There are 3 local interceptions (of ntdll); the + // other 15 have to be sent to the child to be performed "hot". + EXPECT_EQ(3, interceptions.interceptions_.size()); + + int num_dlls, num_functions, num_names; + WalkBuffer(local_buffer.get(), buffer_size, &num_dlls, &num_functions, + &num_names); + + // The 15 interceptions on the buffer (to the child) should be grouped on 6 + // dlls. Only four interceptions are using an explicit name for the + // interceptor function. + EXPECT_EQ(6, num_dlls); + EXPECT_EQ(15, num_functions); + EXPECT_EQ(4, num_names); +} + +TEST(InterceptionManagerTest, BufferLayout2) { + wchar_t exe_name[MAX_PATH]; + ASSERT_NE(0u, GetModuleFileName(NULL, exe_name, MAX_PATH - 1)); + + TargetProcess *target = MakeTestTargetProcess(::GetCurrentProcess(), + ::GetModuleHandle(exe_name)); + + InterceptionManager interceptions(target, true); + + // Any pointer will do for a function pointer. + void* function = &interceptions; + interceptions.AddToUnloadModules(L"some01.dll"); + // We don't care about the interceptor id. + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtCreateFile", + INTERCEPTION_SERVICE_CALL, function, + OPEN_FILE_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "CreateFileEx", + INTERCEPTION_EAT, function, OPEN_FILE_ID); + interceptions.AddToUnloadModules(L"some02.dll"); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "SomeFileEx", + INTERCEPTION_SMART_SIDESTEP, function, + OPEN_FILE_ID); + // Verify that all interceptions were added + ASSERT_EQ(5, interceptions.interceptions_.size()); + + size_t buffer_size = interceptions.GetBufferSize(); + scoped_ptr<BYTE[]> local_buffer(new BYTE[buffer_size]); + + ASSERT_TRUE(interceptions.SetupConfigBuffer(local_buffer.get(), + buffer_size)); + + // At this point, the interceptions should have been separated into two + // groups: one group with the local ("cold") interceptions, and another + // group with the interceptions belonging to dlls that will be "hot" + // patched on the client. The second group lives on local_buffer, and the + // first group remains on the list of interceptions, in this case just one. + EXPECT_EQ(1, interceptions.interceptions_.size()); + + int num_dlls, num_functions, num_names; + WalkBuffer(local_buffer.get(), buffer_size, &num_dlls, &num_functions, + &num_names); + + EXPECT_EQ(3, num_dlls); + EXPECT_EQ(4, num_functions); + EXPECT_EQ(0, num_names); +} + +} // namespace sandbox diff --git a/sandbox/win/src/interceptors.h b/sandbox/win/src/interceptors.h new file mode 100644 index 0000000000..a17447aa18 --- /dev/null +++ b/sandbox/win/src/interceptors.h @@ -0,0 +1,55 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_INTERCEPTORS_H_ +#define SANDBOX_SRC_INTERCEPTORS_H_ + +#if defined(_WIN64) +#include "sandbox/win/src/interceptors_64.h" +#endif + +namespace sandbox { + +enum InterceptorId { + // Internal use: + MAP_VIEW_OF_SECTION_ID = 0, + UNMAP_VIEW_OF_SECTION_ID, + // Policy broker: + SET_INFORMATION_THREAD_ID, + OPEN_THREAD_TOKEN_ID, + OPEN_THREAD_TOKEN_EX_ID, + OPEN_TREAD_ID, + OPEN_PROCESS_ID, + OPEN_PROCESS_TOKEN_ID, + OPEN_PROCESS_TOKEN_EX_ID, + // Filesystem dispatcher: + CREATE_FILE_ID, + OPEN_FILE_ID, + QUERY_ATTRIB_FILE_ID, + QUERY_FULL_ATTRIB_FILE_ID, + SET_INFO_FILE_ID, + // Named pipe dispatcher: + CREATE_NAMED_PIPE_ID, + // Process-thread dispatcher: + CREATE_PROCESSW_ID, + CREATE_PROCESSA_ID, + // Registry dispatcher: + CREATE_KEY_ID, + OPEN_KEY_ID, + OPEN_KEY_EX_ID, + // Sync dispatcher: + CREATE_EVENT_ID, + OPEN_EVENT_ID, + // Process mitigations Win32k dispatcher: + GDIINITIALIZE_ID, + GETSTOCKOBJECT_ID, + REGISTERCLASSW_ID, + INTERCEPTOR_MAX_ID +}; + +typedef void* OriginalFunctions[INTERCEPTOR_MAX_ID]; + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTORS_H_ diff --git a/sandbox/win/src/interceptors_64.cc b/sandbox/win/src/interceptors_64.cc new file mode 100644 index 0000000000..ef0b5f0017 --- /dev/null +++ b/sandbox/win/src/interceptors_64.cc @@ -0,0 +1,278 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/interceptors_64.h" + +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/filesystem_interception.h" +#include "sandbox/win/src/named_pipe_interception.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/process_mitigations_win32k_interception.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/registry_interception.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sync_interception.h" +#include "sandbox/win/src/target_interceptions.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; +SANDBOX_INTERCEPT OriginalFunctions g_originals; + +NTSTATUS WINAPI TargetNtMapViewOfSection64( + HANDLE section, HANDLE process, PVOID *base, ULONG_PTR zero_bits, + SIZE_T commit_size, PLARGE_INTEGER offset, PSIZE_T view_size, + SECTION_INHERIT inherit, ULONG allocation_type, ULONG protect) { + NtMapViewOfSectionFunction orig_fn = reinterpret_cast< + NtMapViewOfSectionFunction>(g_originals[MAP_VIEW_OF_SECTION_ID]); + + return TargetNtMapViewOfSection(orig_fn, section, process, base, zero_bits, + commit_size, offset, view_size, inherit, + allocation_type, protect); +} + +NTSTATUS WINAPI TargetNtUnmapViewOfSection64(HANDLE process, PVOID base) { + NtUnmapViewOfSectionFunction orig_fn = reinterpret_cast< + NtUnmapViewOfSectionFunction>(g_originals[UNMAP_VIEW_OF_SECTION_ID]); + return TargetNtUnmapViewOfSection(orig_fn, process, base); +} + +// ----------------------------------------------------------------------- + +NTSTATUS WINAPI TargetNtSetInformationThread64( + HANDLE thread, NT_THREAD_INFORMATION_CLASS thread_info_class, + PVOID thread_information, ULONG thread_information_bytes) { + NtSetInformationThreadFunction orig_fn = reinterpret_cast< + NtSetInformationThreadFunction>(g_originals[SET_INFORMATION_THREAD_ID]); + return TargetNtSetInformationThread(orig_fn, thread, thread_info_class, + thread_information, + thread_information_bytes); +} + +NTSTATUS WINAPI TargetNtOpenThreadToken64( + HANDLE thread, ACCESS_MASK desired_access, BOOLEAN open_as_self, + PHANDLE token) { + NtOpenThreadTokenFunction orig_fn = reinterpret_cast< + NtOpenThreadTokenFunction>(g_originals[OPEN_THREAD_TOKEN_ID]); + return TargetNtOpenThreadToken(orig_fn, thread, desired_access, open_as_self, + token); +} + +NTSTATUS WINAPI TargetNtOpenThreadTokenEx64( + HANDLE thread, ACCESS_MASK desired_access, BOOLEAN open_as_self, + ULONG handle_attributes, PHANDLE token) { + NtOpenThreadTokenExFunction orig_fn = reinterpret_cast< + NtOpenThreadTokenExFunction>(g_originals[OPEN_THREAD_TOKEN_EX_ID]); + return TargetNtOpenThreadTokenEx(orig_fn, thread, desired_access, + open_as_self, handle_attributes, token); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateFile64( + PHANDLE file, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, ULONG file_attributes, ULONG sharing, + ULONG disposition, ULONG options, PVOID ea_buffer, ULONG ea_length) { + NtCreateFileFunction orig_fn = reinterpret_cast< + NtCreateFileFunction>(g_originals[CREATE_FILE_ID]); + return TargetNtCreateFile(orig_fn, file, desired_access, object_attributes, + io_status, allocation_size, file_attributes, + sharing, disposition, options, ea_buffer, + ea_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenFile64( + PHANDLE file, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, PIO_STATUS_BLOCK io_status, + ULONG sharing, ULONG options) { + NtOpenFileFunction orig_fn = reinterpret_cast< + NtOpenFileFunction>(g_originals[OPEN_FILE_ID]); + return TargetNtOpenFile(orig_fn, file, desired_access, object_attributes, + io_status, sharing, options); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryAttributesFile64( + POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes) { + NtQueryAttributesFileFunction orig_fn = reinterpret_cast< + NtQueryAttributesFileFunction>(g_originals[QUERY_ATTRIB_FILE_ID]); + return TargetNtQueryAttributesFile(orig_fn, object_attributes, + file_attributes); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryFullAttributesFile64( + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes) { + NtQueryFullAttributesFileFunction orig_fn = reinterpret_cast< + NtQueryFullAttributesFileFunction>( + g_originals[QUERY_FULL_ATTRIB_FILE_ID]); + return TargetNtQueryFullAttributesFile(orig_fn, object_attributes, + file_attributes); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtSetInformationFile64( + HANDLE file, PIO_STATUS_BLOCK io_status, PVOID file_information, + ULONG length, FILE_INFORMATION_CLASS file_information_class) { + NtSetInformationFileFunction orig_fn = reinterpret_cast< + NtSetInformationFileFunction>(g_originals[SET_INFO_FILE_ID]); + return TargetNtSetInformationFile(orig_fn, file, io_status, file_information, + length, file_information_class); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT HANDLE WINAPI TargetCreateNamedPipeW64( + LPCWSTR pipe_name, DWORD open_mode, DWORD pipe_mode, DWORD max_instance, + DWORD out_buffer_size, DWORD in_buffer_size, DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes) { + CreateNamedPipeWFunction orig_fn = reinterpret_cast< + CreateNamedPipeWFunction>(g_originals[CREATE_NAMED_PIPE_ID]); + return TargetCreateNamedPipeW(orig_fn, pipe_name, open_mode, pipe_mode, + max_instance, out_buffer_size, in_buffer_size, + default_timeout, security_attributes); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThread64( + PHANDLE thread, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, PCLIENT_ID client_id) { + NtOpenThreadFunction orig_fn = reinterpret_cast< + NtOpenThreadFunction>(g_originals[OPEN_TREAD_ID]); + return TargetNtOpenThread(orig_fn, thread, desired_access, object_attributes, + client_id); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenProcess64( + PHANDLE process, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, PCLIENT_ID client_id) { + NtOpenProcessFunction orig_fn = reinterpret_cast< + NtOpenProcessFunction>(g_originals[OPEN_PROCESS_ID]); + return TargetNtOpenProcess(orig_fn, process, desired_access, + object_attributes, client_id); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenProcessToken64( + HANDLE process, ACCESS_MASK desired_access, PHANDLE token) { + NtOpenProcessTokenFunction orig_fn = reinterpret_cast< + NtOpenProcessTokenFunction>(g_originals[OPEN_PROCESS_TOKEN_ID]); + return TargetNtOpenProcessToken(orig_fn, process, desired_access, token); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenProcessTokenEx64( + HANDLE process, ACCESS_MASK desired_access, ULONG handle_attributes, + PHANDLE token) { + NtOpenProcessTokenExFunction orig_fn = reinterpret_cast< + NtOpenProcessTokenExFunction>(g_originals[OPEN_PROCESS_TOKEN_EX_ID]); + return TargetNtOpenProcessTokenEx(orig_fn, process, desired_access, + handle_attributes, token); +} + +SANDBOX_INTERCEPT BOOL WINAPI TargetCreateProcessW64( + LPCWSTR application_name, LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, BOOL inherit_handles, DWORD flags, + LPVOID environment, LPCWSTR current_directory, LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information) { + CreateProcessWFunction orig_fn = reinterpret_cast< + CreateProcessWFunction>(g_originals[CREATE_PROCESSW_ID]); + return TargetCreateProcessW(orig_fn, application_name, command_line, + process_attributes, thread_attributes, + inherit_handles, flags, environment, + current_directory, startup_info, + process_information); +} + +SANDBOX_INTERCEPT BOOL WINAPI TargetCreateProcessA64( + LPCSTR application_name, LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, BOOL inherit_handles, DWORD flags, + LPVOID environment, LPCSTR current_directory, LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information) { + CreateProcessAFunction orig_fn = reinterpret_cast< + CreateProcessAFunction>(g_originals[CREATE_PROCESSA_ID]); + return TargetCreateProcessA(orig_fn, application_name, command_line, + process_attributes, thread_attributes, + inherit_handles, flags, environment, + current_directory, startup_info, + process_information); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateKey64( + PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, ULONG title_index, + PUNICODE_STRING class_name, ULONG create_options, PULONG disposition) { + NtCreateKeyFunction orig_fn = reinterpret_cast< + NtCreateKeyFunction>(g_originals[CREATE_KEY_ID]); + return TargetNtCreateKey(orig_fn, key, desired_access, object_attributes, + title_index, class_name, create_options, + disposition); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenKey64( + PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + NtOpenKeyFunction orig_fn = reinterpret_cast< + NtOpenKeyFunction>(g_originals[OPEN_KEY_ID]); + return TargetNtOpenKey(orig_fn, key, desired_access, object_attributes); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenKeyEx64( + PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, ULONG open_options) { + NtOpenKeyExFunction orig_fn = reinterpret_cast< + NtOpenKeyExFunction>(g_originals[OPEN_KEY_EX_ID]); + return TargetNtOpenKeyEx(orig_fn, key, desired_access, object_attributes, + open_options); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateEvent64( + PHANDLE event_handle, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, EVENT_TYPE event_type, + BOOLEAN initial_state) { + NtCreateEventFunction orig_fn = reinterpret_cast< + NtCreateEventFunction>(g_originals[CREATE_EVENT_ID]); + return TargetNtCreateEvent(orig_fn, event_handle, desired_access, + object_attributes, event_type, initial_state); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenEvent64( + PHANDLE event_handle, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + NtOpenEventFunction orig_fn = reinterpret_cast< + NtOpenEventFunction>(g_originals[OPEN_EVENT_ID]); + return TargetNtOpenEvent(orig_fn, event_handle, desired_access, + object_attributes); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT BOOL WINAPI TargetGdiDllInitialize64( + HANDLE dll, + DWORD reason) { + GdiDllInitializeFunction orig_fn = reinterpret_cast< + GdiDllInitializeFunction>(g_originals[GDIINITIALIZE_ID]); + return TargetGdiDllInitialize(orig_fn, dll, reason); +} + +SANDBOX_INTERCEPT HGDIOBJ WINAPI TargetGetStockObject64(int object) { + GetStockObjectFunction orig_fn = reinterpret_cast< + GetStockObjectFunction>(g_originals[GETSTOCKOBJECT_ID]); + return TargetGetStockObject(orig_fn, object); +} + +SANDBOX_INTERCEPT ATOM WINAPI TargetRegisterClassW64( + const WNDCLASS* wnd_class) { + RegisterClassWFunction orig_fn = reinterpret_cast< + RegisterClassWFunction>(g_originals[REGISTERCLASSW_ID]); + return TargetRegisterClassW(orig_fn, wnd_class); +} + +} // namespace sandbox diff --git a/sandbox/win/src/interceptors_64.h b/sandbox/win/src/interceptors_64.h new file mode 100644 index 0000000000..7368ceb845 --- /dev/null +++ b/sandbox/win/src/interceptors_64.h @@ -0,0 +1,175 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_INTERCEPTORS_64_H_ +#define SANDBOX_SRC_INTERCEPTORS_64_H_ + +namespace sandbox { + +extern "C" { + +// Interception of NtMapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being loaded, so we can patch them if needed. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtMapViewOfSection64( + HANDLE section, HANDLE process, PVOID *base, ULONG_PTR zero_bits, + SIZE_T commit_size, PLARGE_INTEGER offset, PSIZE_T view_size, + SECTION_INHERIT inherit, ULONG allocation_type, ULONG protect); + +// Interception of NtUnmapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being unloaded, so we can clean up our interceptions. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtUnmapViewOfSection64(HANDLE process, + PVOID base); + +// ----------------------------------------------------------------------- +// Interceptors without IPC. + +// Interception of NtSetInformationThread on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtSetInformationThread64( + HANDLE thread, NT_THREAD_INFORMATION_CLASS thread_info_class, + PVOID thread_information, ULONG thread_information_bytes); + +// Interception of NtOpenThreadToken on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThreadToken64( + HANDLE thread, ACCESS_MASK desired_access, BOOLEAN open_as_self, + PHANDLE token); + +// Interception of NtOpenThreadTokenEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThreadTokenEx64( + HANDLE thread, ACCESS_MASK desired_access, BOOLEAN open_as_self, + ULONG handle_attributes, PHANDLE token); + +// ----------------------------------------------------------------------- +// Interceptors handled by the file system dispatcher. + +// Interception of NtCreateFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateFile64( + PHANDLE file, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, ULONG file_attributes, ULONG sharing, + ULONG disposition, ULONG options, PVOID ea_buffer, ULONG ea_length); + +// Interception of NtOpenFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenFile64( + PHANDLE file, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, PIO_STATUS_BLOCK io_status, + ULONG sharing, ULONG options); + +// Interception of NtQueryAtttributesFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryAttributesFile64( + POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes); + +// Interception of NtQueryFullAtttributesFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryFullAttributesFile64( + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes); + +// Interception of NtSetInformationFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtSetInformationFile64( + HANDLE file, PIO_STATUS_BLOCK io_status, PVOID file_information, + ULONG length, FILE_INFORMATION_CLASS file_information_class); + +// ----------------------------------------------------------------------- +// Interceptors handled by the named pipe dispatcher. + +// Interception of CreateNamedPipeW in kernel32.dll +SANDBOX_INTERCEPT HANDLE WINAPI TargetCreateNamedPipeW64( + LPCWSTR pipe_name, DWORD open_mode, DWORD pipe_mode, DWORD max_instance, + DWORD out_buffer_size, DWORD in_buffer_size, DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes); + +// ----------------------------------------------------------------------- +// Interceptors handled by the process-thread dispatcher. + +// Interception of NtOpenThread on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThread64( + PHANDLE thread, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, PCLIENT_ID client_id); + +// Interception of NtOpenProcess on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenProcess64( + PHANDLE process, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, PCLIENT_ID client_id); + +// Interception of NtOpenProcessToken on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenProcessToken64( + HANDLE process, ACCESS_MASK desired_access, PHANDLE token); + +// Interception of NtOpenProcessTokenEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenProcessTokenEx64( + HANDLE process, ACCESS_MASK desired_access, ULONG handle_attributes, + PHANDLE token); + +// Interception of CreateProcessW in kernel32.dll. +SANDBOX_INTERCEPT BOOL WINAPI TargetCreateProcessW64( + LPCWSTR application_name, LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, BOOL inherit_handles, DWORD flags, + LPVOID environment, LPCWSTR current_directory, LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information); + +// Interception of CreateProcessA in kernel32.dll. +SANDBOX_INTERCEPT BOOL WINAPI TargetCreateProcessA64( + LPCSTR application_name, LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, BOOL inherit_handles, DWORD flags, + LPVOID environment, LPCSTR current_directory, LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information); + +// ----------------------------------------------------------------------- +// Interceptors handled by the registry dispatcher. + +// Interception of NtCreateKey on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateKey64( + PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, ULONG title_index, + PUNICODE_STRING class_name, ULONG create_options, PULONG disposition); + +// Interception of NtOpenKey on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenKey64( + PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +// Interception of NtOpenKeyEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenKeyEx64( + PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, ULONG open_options); + +// ----------------------------------------------------------------------- +// Interceptors handled by the sync dispatcher. + +// Interception of NtCreateEvent/NtOpenEvent on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateEvent64( + PHANDLE event_handle, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, EVENT_TYPE event_type, + BOOLEAN initial_state); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenEvent64( + PHANDLE event_handle, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +// ----------------------------------------------------------------------- +// Interceptors handled by the process mitigations win32k lockdown code. + +// Interceptor for the GdiDllInitialize function. +SANDBOX_INTERCEPT BOOL WINAPI TargetGdiDllInitialize64( + HANDLE dll, + DWORD reason); + +// Interceptor for the GetStockObject function. +SANDBOX_INTERCEPT HGDIOBJ WINAPI TargetGetStockObject64(int object); + +// Interceptor for the RegisterClassW function. +SANDBOX_INTERCEPT ATOM WINAPI TargetRegisterClassW64(const WNDCLASS* wnd_class); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTORS_64_H_ diff --git a/sandbox/win/src/internal_types.h b/sandbox/win/src/internal_types.h new file mode 100644 index 0000000000..026bedb064 --- /dev/null +++ b/sandbox/win/src/internal_types.h @@ -0,0 +1,76 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_INTERNAL_TYPES_H_ +#define SANDBOX_WIN_SRC_INTERNAL_TYPES_H_ + +namespace sandbox { + +const wchar_t kNtdllName[] = L"ntdll.dll"; +const wchar_t kKerneldllName[] = L"kernel32.dll"; +const wchar_t kKernelBasedllName[] = L"kernelbase.dll"; + +// Defines the supported C++ types encoding to numeric id. Like a poor's man +// RTTI. Note that true C++ RTTI will not work because the types are not +// polymorphic anyway. +enum ArgType { + INVALID_TYPE = 0, + WCHAR_TYPE, + UINT32_TYPE, + UNISTR_TYPE, + VOIDPTR_TYPE, + INPTR_TYPE, + INOUTPTR_TYPE, + LAST_TYPE +}; + +// Encapsulates a pointer to a buffer and the size of the buffer. +class CountedBuffer { + public: + CountedBuffer(void* buffer, uint32 size) : size_(size), buffer_(buffer) {} + + uint32 Size() const { + return size_; + } + + void* Buffer() const { + return buffer_; + } + + private: + uint32 size_; + void* buffer_; +}; + +// Helper class to convert void-pointer packed ints for both +// 32 and 64 bit builds. This construct is non-portable. +class IPCInt { + public: + explicit IPCInt(void* buffer) { + buffer_.vp = buffer; + } + + explicit IPCInt(unsigned __int32 i32) { + buffer_.vp = NULL; + buffer_.i32 = i32; + } + + unsigned __int32 As32Bit() const { + return buffer_.i32; + } + + void* AsVoidPtr() const { + return buffer_.vp; + } + + private: + union U { + void* vp; + unsigned __int32 i32; + } buffer_; +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_INTERNAL_TYPES_H_ diff --git a/sandbox/win/src/ipc_ping_test.cc b/sandbox/win/src/ipc_ping_test.cc new file mode 100644 index 0000000000..64e3de6c54 --- /dev/null +++ b/sandbox/win/src/ipc_ping_test.cc @@ -0,0 +1,58 @@ +// Copyright (c) 2006-2008 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 "testing/gtest/include/gtest/gtest.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/tests/common/controller.h" + +namespace sandbox { + +// Tests that the IPC is working by issuing a special IPC that is not exposed +// in the public API. +SBOX_TESTS_COMMAND int IPC_Ping(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED; + + TargetServices* ts = SandboxFactory::GetTargetServices(); + if (NULL == ts) + return SBOX_TEST_FAILED; + + // Downcast because we have internal knowledge of the object returned. + TargetServicesBase* ts_base = reinterpret_cast<TargetServicesBase*>(ts); + + int version = 0; + if (L'1' == argv[0][0]) + version = 1; + else + version = 2; + + if (!ts_base->TestIPCPing(version)) + return SBOX_TEST_FAILED; + + ::Sleep(1); + if (!ts_base->TestIPCPing(version)) + return SBOX_TEST_FAILED; + + return SBOX_TEST_SUCCEEDED; +} + +// The IPC ping test should work before and after the token drop. +TEST(IPCTest, IPCPingTestSimple) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"IPC_Ping 1")); +} + +TEST(IPCTest, IPCPingTestWithOutput) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"IPC_Ping 2")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"IPC_Ping 2")); +} + +} // namespace sandbox diff --git a/sandbox/win/src/ipc_tags.h b/sandbox/win/src/ipc_tags.h new file mode 100644 index 0000000000..d680411dac --- /dev/null +++ b/sandbox/win/src/ipc_tags.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_IPC_TAGS_H__ +#define SANDBOX_SRC_IPC_TAGS_H__ + +namespace sandbox { + +enum { + IPC_UNUSED_TAG = 0, + IPC_PING1_TAG, // Takes a cookie in parameters and returns the cookie + // multiplied by 2 and the tick_count. Used for testing only. + IPC_PING2_TAG, // Takes an in/out cookie in parameters and modify the cookie + // to be multiplied by 3. Used for testing only. + IPC_NTCREATEFILE_TAG, + IPC_NTOPENFILE_TAG, + IPC_NTQUERYATTRIBUTESFILE_TAG, + IPC_NTQUERYFULLATTRIBUTESFILE_TAG, + IPC_NTSETINFO_RENAME_TAG, + IPC_CREATENAMEDPIPEW_TAG, + IPC_NTOPENTHREAD_TAG, + IPC_NTOPENPROCESS_TAG, + IPC_NTOPENPROCESSTOKEN_TAG, + IPC_NTOPENPROCESSTOKENEX_TAG, + IPC_CREATEPROCESSW_TAG, + IPC_CREATEEVENT_TAG, + IPC_OPENEVENT_TAG, + IPC_NTCREATEKEY_TAG, + IPC_NTOPENKEY_TAG, + IPC_DUPLICATEHANDLEPROXY_TAG, + IPC_GDI_GDIDLLINITIALIZE_TAG, + IPC_GDI_GETSTOCKOBJECT_TAG, + IPC_USER_REGISTERCLASSW_TAG, + IPC_LAST_TAG +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_IPC_TAGS_H__ diff --git a/sandbox/win/src/ipc_unittest.cc b/sandbox/win/src/ipc_unittest.cc new file mode 100644 index 0000000000..b1e74abcfd --- /dev/null +++ b/sandbox/win/src/ipc_unittest.cc @@ -0,0 +1,639 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/sharedmem_ipc_server.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Helper function to make the fake shared memory with some +// basic elements initialized. +IPCControl* MakeChannels(size_t channel_size, size_t total_shared_size, + size_t* base_start) { + // Allocate memory + char* mem = new char[total_shared_size]; + memset(mem, 0, total_shared_size); + // Calculate how many channels we can fit in the shared memory. + total_shared_size -= offsetof(IPCControl, channels); + size_t channel_count = + total_shared_size / (sizeof(ChannelControl) + channel_size); + // Calculate the start of the first channel. + *base_start = (sizeof(ChannelControl)* channel_count) + + offsetof(IPCControl, channels); + // Setup client structure. + IPCControl* client_control = reinterpret_cast<IPCControl*>(mem); + client_control->channels_count = channel_count; + return client_control; +} + +enum TestFixMode { + FIX_NO_EVENTS, + FIX_PONG_READY, + FIX_PONG_NOT_READY +}; + +void FixChannels(IPCControl* client_control, size_t base_start, + size_t channel_size, TestFixMode mode) { + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + channel.channel_base = base_start; + channel.state = kFreeChannel; + if (mode != FIX_NO_EVENTS) { + BOOL signaled = (FIX_PONG_READY == mode)? TRUE : FALSE; + channel.ping_event = ::CreateEventW(NULL, FALSE, FALSE, NULL); + channel.pong_event = ::CreateEventW(NULL, FALSE, signaled, NULL); + } + base_start += channel_size; + } +} + +void CloseChannelEvents(IPCControl* client_control) { + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + ::CloseHandle(channel.ping_event); + ::CloseHandle(channel.pong_event); + } +} + +TEST(IPCTest, ChannelMaker) { + // Test that our testing rig is computing offsets properly. We should have + // 5 channnels and the offset to the first channel is 108 bytes in 32 bits + // and 216 in 64 bits. + size_t channel_start = 0; + IPCControl* client_control = MakeChannels(12 * 64, 4096, &channel_start); + ASSERT_TRUE(NULL != client_control); + EXPECT_EQ(5, client_control->channels_count); +#if defined(_WIN64) + EXPECT_EQ(216, channel_start); +#else + EXPECT_EQ(108, channel_start); +#endif + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, ClientLockUnlock) { + // Make 7 channels of kIPCChannelSize (1kb) each. Test that we lock and + // unlock channels properly. + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 2, &base_start); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_NO_EVENTS); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + // Test that we lock the first 3 channels in sequence. + void* buff0 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[0].channel_base == buff0); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff1 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff2 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[2].channel_base == buff2); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + // Test that we unlock and re-lock the right channel. + client.FreeBuffer(buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff2b = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff2b); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + client.FreeBuffer(buff0); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, CrossCallStrPacking) { + // This test tries the CrossCall object with null and non-null string + // combination of parameters, integer types and verifies that the unpacker + // can read them properly. + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 4, &base_start); + client_control->server_alive = HANDLE(1); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + CrossCallReturn answer; + uint32 tag1 = 666; + const wchar_t *text = L"98765 - 43210"; + base::string16 copied_text; + CrossCallParamsEx* actual_params; + + CrossCall(client, tag1, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + EXPECT_STREQ(text, copied_text.c_str()); + + // Check with an empty string. + uint32 tag2 = 777; + const wchar_t* null_text = NULL; + CrossCall(client, tag2, null_text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1, actual_params->GetParamsCount()); + EXPECT_EQ(tag2, actual_params->GetTag()); + uint32 param_size = 1; + ArgType type = INVALID_TYPE; + void* param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + EXPECT_TRUE(NULL != param_addr); + EXPECT_EQ(0, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + + uint32 tag3 = 888; + param_size = 1; + copied_text.clear(); + + // Check with an empty string and a non-empty string. + CrossCall(client, tag3, null_text, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(2, actual_params->GetParamsCount()); + EXPECT_EQ(tag3, actual_params->GetTag()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + EXPECT_TRUE(NULL != param_addr); + EXPECT_EQ(0, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + EXPECT_TRUE(actual_params->GetParameterStr(1, &copied_text)); + EXPECT_STREQ(text, copied_text.c_str()); + + param_size = 1; + base::string16 copied_text_p0, copied_text_p2; + + const wchar_t *text2 = L"AeFG"; + CrossCall(client, tag1, text2, null_text, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(3, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text_p0)); + EXPECT_STREQ(text2, copied_text_p0.c_str()); + EXPECT_TRUE(actual_params->GetParameterStr(2, &copied_text_p2)); + EXPECT_STREQ(text, copied_text_p2.c_str()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); + EXPECT_TRUE(NULL != param_addr); + EXPECT_EQ(0, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + + CloseChannelEvents(client_control); + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, CrossCallIntPacking) { + // Check handling for regular 32 bit integers used in Windows. + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 4, &base_start); + client_control->server_alive = HANDLE(1); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); + + uint32 tag1 = 999; + uint32 tag2 = 111; + const wchar_t *text = L"godzilla"; + CrossCallParamsEx* actual_params; + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + CrossCallReturn answer; + DWORD dw = 0xE6578; + CrossCall(client, tag2, dw, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1, actual_params->GetParamsCount()); + EXPECT_EQ(tag2, actual_params->GetTag()); + ArgType type = INVALID_TYPE; + uint32 param_size = 1; + void* param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + ASSERT_EQ(sizeof(dw), param_size); + EXPECT_EQ(UINT32_TYPE, type); + ASSERT_TRUE(NULL != param_addr); + EXPECT_EQ(0, memcmp(&dw, param_addr, param_size)); + + // Check handling for windows HANDLES. + HANDLE h = HANDLE(0x70000500); + CrossCall(client, tag1, text, h, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(2, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); + ASSERT_EQ(sizeof(h), param_size); + EXPECT_EQ(VOIDPTR_TYPE, type); + ASSERT_TRUE(NULL != param_addr); + EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); + + // Check combination of 32 and 64 bits. + CrossCall(client, tag2, h, dw, h, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(3, actual_params->GetParamsCount()); + EXPECT_EQ(tag2, actual_params->GetTag()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + ASSERT_EQ(sizeof(h), param_size); + EXPECT_EQ(VOIDPTR_TYPE, type); + ASSERT_TRUE(NULL != param_addr); + EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); + ASSERT_EQ(sizeof(dw), param_size); + EXPECT_EQ(UINT32_TYPE, type); + ASSERT_TRUE(NULL != param_addr); + EXPECT_EQ(0, memcmp(&dw, param_addr, param_size)); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(2, ¶m_size, &type); + ASSERT_EQ(sizeof(h), param_size); + EXPECT_EQ(VOIDPTR_TYPE, type); + ASSERT_TRUE(NULL != param_addr); + EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); + + CloseChannelEvents(client_control); + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, CrossCallValidation) { + // First a sanity test with a well formed parameter object. + unsigned long value = 124816; + const uint32 kTag = 33; + const uint32 kBufferSize = 256; + ActualCallParams<1, kBufferSize> params_1(kTag); + params_1.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + void* buffer = const_cast<void*>(params_1.GetBuffer()); + + uint32 out_size = 0; + CrossCallParamsEx* ccp = 0; + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, params_1.GetSize(), + &out_size); + ASSERT_TRUE(NULL != ccp); + EXPECT_TRUE(ccp->GetBuffer() != buffer); + EXPECT_EQ(kTag, ccp->GetTag()); + EXPECT_EQ(1, ccp->GetParamsCount()); + delete[] (reinterpret_cast<char*>(ccp)); + + // Test that we handle integer overflow on the number of params + // correctly. We use a test-only ctor for ActualCallParams that + // allows to create malformed cross-call buffers. + const int32 kPtrDiffSz = sizeof(ptrdiff_t); + for (int32 ix = -1; ix != 3; ++ix) { + uint32 fake_num_params = (kuint32max / kPtrDiffSz) + ix; + ActualCallParams<1, kBufferSize> params_2(kTag, fake_num_params); + params_2.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + buffer = const_cast<void*>(params_2.GetBuffer()); + + EXPECT_TRUE(NULL != buffer); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, params_1.GetSize(), + &out_size); + // If the buffer is malformed the return is NULL. + EXPECT_TRUE(NULL == ccp); + } + + ActualCallParams<1, kBufferSize> params_3(kTag, 1); + params_3.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + buffer = const_cast<void*>(params_3.GetBuffer()); + EXPECT_TRUE(NULL != buffer); + + uint32 correct_size = params_3.OverrideSize(1); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(NULL == ccp); + + // The correct_size is 8 bytes aligned. + params_3.OverrideSize(correct_size - 7); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(NULL == ccp); + + params_3.OverrideSize(correct_size); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(NULL != ccp); + + // Make sure that two parameters work as expected. + ActualCallParams<2, kBufferSize> params_4(kTag, 2); + params_4.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + params_4.CopyParamIn(1, buffer, sizeof(buffer), false, VOIDPTR_TYPE); + buffer = const_cast<void*>(params_4.GetBuffer()); + EXPECT_TRUE(NULL != buffer); + + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(NULL != ccp); + +#if defined(_WIN64) + correct_size = params_4.OverrideSize(1); + params_4.OverrideSize(correct_size - 1); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(NULL == ccp); +#endif +} + +// This structure is passed to the mock server threads to simulate +// the server side IPC so it has the required kernel objects. +struct ServerEvents { + HANDLE ping; + HANDLE pong; + volatile LONG* state; + HANDLE mutex; +}; + +// This is the server thread that quicky answers an IPC and exits. +DWORD WINAPI QuickResponseServer(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->ping, INFINITE); + ::InterlockedExchange(events->state, kAckChannel); + ::SetEvent(events->pong); + return wait_result; +} + +class CrossCallParamsMock : public CrossCallParams { + public: + CrossCallParamsMock(uint32 tag, uint32 params_count) + : CrossCallParams(tag, params_count) { + } + private: + void* params[4]; +}; + +void FakeOkAnswerInChannel(void* channel) { + CrossCallReturn* answer = reinterpret_cast<CrossCallReturn*>(channel); + answer->call_outcome = SBOX_ALL_OK; +} + +// Create two threads that will quickly answer IPCs; the first one +// using channel 1 (channel 0 is busy) and one using channel 0. No time-out +// should occur. +TEST(IPCTest, ClientFastServer) { + const size_t channel_size = kIPCChannelSize; + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(channel_size, 4096 * 2, &base_start); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_NOT_READY); + client_control->server_alive = ::CreateMutex(NULL, FALSE, NULL); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + ServerEvents events = {0}; + events.ping = client_control->channels[1].ping_event; + events.pong = client_control->channels[1].pong_event; + events.state = &client_control->channels[1].state; + + HANDLE t1 = ::CreateThread(NULL, 0, QuickResponseServer, &events, 0, NULL); + ASSERT_TRUE(NULL != t1); + ::CloseHandle(t1); + + void* buff0 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[0].channel_base == buff0); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + void* buff1 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + EXPECT_EQ(0, client_control->channels[1].ipc_tag); + + uint32 tag = 7654; + CrossCallReturn answer; + CrossCallParamsMock* params1 = new(buff1) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff1); + + ResultCode result = client.DoCall(params1, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff1); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[1].ipc_tag); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + HANDLE t2 = ::CreateThread(NULL, 0, QuickResponseServer, &events, 0, NULL); + ASSERT_TRUE(NULL != t2); + ::CloseHandle(t2); + + client.FreeBuffer(buff0); + events.ping = client_control->channels[0].ping_event; + events.pong = client_control->channels[0].pong_event; + events.state = &client_control->channels[0].state; + + tag = 4567; + CrossCallParamsMock* params2 = new(buff0) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff0); + + result = client.DoCall(params2, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff0); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[0].ipc_tag); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + CloseChannelEvents(client_control); + ::CloseHandle(client_control->server_alive); + + delete[] reinterpret_cast<char*>(client_control); +} + +// This is the server thread that very slowly answers an IPC and exits. Note +// that the pong event needs to be signaled twice. +DWORD WINAPI SlowResponseServer(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->ping, INFINITE); + ::Sleep(kIPCWaitTimeOut1 + kIPCWaitTimeOut2 + 200); + ::InterlockedExchange(events->state, kAckChannel); + ::SetEvent(events->pong); + return wait_result; +} + +// This thread's job is to keep the mutex locked. +DWORD WINAPI MainServerThread(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->mutex, INFINITE); + Sleep(kIPCWaitTimeOut1 * 20); + return wait_result; +} + +// Creates a server thread that answers the IPC so slow that is guaranteed to +// trigger the time-out code path in the client. A second thread is created +// to hold locked the server_alive mutex: this signals the client that the +// server is not dead and it retries the wait. +TEST(IPCTest, ClientSlowServer) { + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096*2, &base_start); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_NOT_READY); + client_control->server_alive = ::CreateMutex(NULL, FALSE, NULL); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + ServerEvents events = {0}; + events.ping = client_control->channels[0].ping_event; + events.pong = client_control->channels[0].pong_event; + events.state = &client_control->channels[0].state; + + HANDLE t1 = ::CreateThread(NULL, 0, SlowResponseServer, &events, 0, NULL); + ASSERT_TRUE(NULL != t1); + ::CloseHandle(t1); + + ServerEvents events2 = {0}; + events2.pong = events.pong; + events2.mutex = client_control->server_alive; + + HANDLE t2 = ::CreateThread(NULL, 0, MainServerThread, &events2, 0, NULL); + ASSERT_TRUE(NULL != t2); + ::CloseHandle(t2); + + ::Sleep(1); + + void* buff0 = client.GetBuffer(); + uint32 tag = 4321; + CrossCallReturn answer; + CrossCallParamsMock* params1 = new(buff0) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff0); + + ResultCode result = client.DoCall(params1, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff0); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[0].ipc_tag); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + + CloseChannelEvents(client_control); + ::CloseHandle(client_control->server_alive); + delete[] reinterpret_cast<char*>(client_control); +} + +// This test-only IPC dispatcher has two handlers with the same signature +// but only CallOneHandler should be used. +class UnitTestIPCDispatcher : public Dispatcher { + public: + enum { + CALL_ONE_TAG = 78, + CALL_TWO_TAG = 87 + }; + + UnitTestIPCDispatcher(); + ~UnitTestIPCDispatcher() override{}; + + bool SetupService(InterceptionManager* manager, int service) override { + return true; + } + + private: + bool CallOneHandler(IPCInfo* ipc, HANDLE p1, uint32 p2) { + ipc->return_info.extended[0].handle = p1; + ipc->return_info.extended[1].unsigned_int = p2; + return true; + } + + bool CallTwoHandler(IPCInfo* ipc, HANDLE p1, uint32 p2) { + return true; + } +}; + +UnitTestIPCDispatcher::UnitTestIPCDispatcher() { + static const IPCCall call_one = { + {CALL_ONE_TAG, VOIDPTR_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>( + &UnitTestIPCDispatcher::CallOneHandler) + }; + static const IPCCall call_two = { + {CALL_TWO_TAG, VOIDPTR_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>( + &UnitTestIPCDispatcher::CallTwoHandler) + }; + ipc_calls_.push_back(call_one); + ipc_calls_.push_back(call_two); +} + +// This test does most of the shared memory IPC client-server roundtrip +// and tests the packing, unpacking and call dispatching. +TEST(IPCTest, SharedMemServerTests) { + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096, &base_start); + client_control->server_alive = HANDLE(1); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + CrossCallReturn answer; + HANDLE bar = HANDLE(191919); + DWORD foo = 6767676; + CrossCall(client, UnitTestIPCDispatcher::CALL_ONE_TAG, bar, foo, &answer); + void* buff = client.GetBuffer(); + ASSERT_TRUE(NULL != buff); + + UnitTestIPCDispatcher dispatcher; + // Since we are directly calling InvokeCallback, most of this structure + // can be set to NULL. + sandbox::SharedMemIPCServer::ServerControl srv_control = { + NULL, NULL, kIPCChannelSize, NULL, + reinterpret_cast<char*>(client_control), + NULL, &dispatcher, {0} }; + + sandbox::CrossCallReturn call_return = {0}; + EXPECT_TRUE(SharedMemIPCServer::InvokeCallback(&srv_control, buff, + &call_return)); + EXPECT_EQ(SBOX_ALL_OK, call_return.call_outcome); + EXPECT_TRUE(bar == call_return.extended[0].handle); + EXPECT_EQ(foo, call_return.extended[1].unsigned_int); + + CloseChannelEvents(client_control); + delete[] reinterpret_cast<char*>(client_control); +} + +} // namespace sandbox diff --git a/sandbox/win/src/job.cc b/sandbox/win/src/job.cc new file mode 100644 index 0000000000..8852ab0c72 --- /dev/null +++ b/sandbox/win/src/job.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/job.h" + +#include "base/win/windows_version.h" +#include "sandbox/win/src/restricted_token.h" + +namespace sandbox { + +Job::~Job() { + if (job_handle_) + ::CloseHandle(job_handle_); +}; + +DWORD Job::Init(JobLevel security_level, + const wchar_t* job_name, + DWORD ui_exceptions, + size_t memory_limit) { + if (job_handle_) + return ERROR_ALREADY_INITIALIZED; + + job_handle_ = ::CreateJobObject(NULL, // No security attribute + job_name); + if (!job_handle_) + return ::GetLastError(); + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0}; + JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {0}; + + // Set the settings for the different security levels. Note: The higher levels + // inherit from the lower levels. + switch (security_level) { + case JOB_LOCKDOWN: { + jeli.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION; + } + case JOB_RESTRICTED: { + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_WRITECLIPBOARD; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_READCLIPBOARD; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_GLOBALATOMS; + } + case JOB_LIMITED_USER: { + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_DISPLAYSETTINGS; + jeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS; + jeli.BasicLimitInformation.ActiveProcessLimit = 1; + } + case JOB_INTERACTIVE: { + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_DESKTOP; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS; + } + case JOB_UNPROTECTED: { + if (memory_limit) { + jeli.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_PROCESS_MEMORY; + jeli.ProcessMemoryLimit = memory_limit; + } + + jeli.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + break; + } + default: { + return ERROR_BAD_ARGUMENTS; + } + } + + if (FALSE == ::SetInformationJobObject(job_handle_, + JobObjectExtendedLimitInformation, + &jeli, + sizeof(jeli))) { + return ::GetLastError(); + } + + jbur.UIRestrictionsClass = jbur.UIRestrictionsClass & (~ui_exceptions); + if (FALSE == ::SetInformationJobObject(job_handle_, + JobObjectBasicUIRestrictions, + &jbur, + sizeof(jbur))) { + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +DWORD Job::UserHandleGrantAccess(HANDLE handle) { + if (!job_handle_) + return ERROR_NO_DATA; + + if (!::UserHandleGrantAccess(handle, + job_handle_, + TRUE)) { // Access allowed. + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +HANDLE Job::Detach() { + HANDLE handle_temp = job_handle_; + job_handle_ = NULL; + return handle_temp; +} + +DWORD Job::AssignProcessToJob(HANDLE process_handle) { + if (!job_handle_) + return ERROR_NO_DATA; + + if (FALSE == ::AssignProcessToJobObject(job_handle_, process_handle)) + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/sandbox/win/src/job.h b/sandbox/win/src/job.h new file mode 100644 index 0000000000..60dc3146b7 --- /dev/null +++ b/sandbox/win/src/job.h @@ -0,0 +1,65 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_JOB_H_ +#define SANDBOX_SRC_JOB_H_ + +#include "base/basictypes.h" +#include "sandbox/win/src/restricted_token_utils.h" + +namespace sandbox { + +// Handles the creation of job objects based on a security profile. +// Sample usage: +// Job job; +// job.Init(JOB_LOCKDOWN, NULL); //no job name +// job.AssignProcessToJob(process_handle); +class Job { + public: + Job() : job_handle_(NULL) { } + + ~Job(); + + // Initializes and creates the job object. The security of the job is based + // on the security_level parameter. + // job_name can be NULL if the job is unnamed. + // If the chosen profile has too many ui restrictions, you can disable some + // by specifying them in the ui_exceptions parameters. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD Init(JobLevel security_level, + const wchar_t* job_name, + DWORD ui_exceptions, + size_t memory_limit); + + // Assigns the process referenced by process_handle to the job. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AssignProcessToJob(HANDLE process_handle); + + // Grants access to "handle" to the job. All processes in the job can + // subsequently recognize and use the handle. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD UserHandleGrantAccess(HANDLE handle); + + // Revokes ownership to the job handle and returns it. The destructor of the + // class won't close the handle when called. + // If the object is not yet initialized, it returns 0. + HANDLE Detach(); + + private: + // Handle to the job referenced by the object. + HANDLE job_handle_; + + DISALLOW_COPY_AND_ASSIGN(Job); +}; + +} // namespace sandbox + + +#endif // SANDBOX_SRC_JOB_H_ diff --git a/sandbox/win/src/job_unittest.cc b/sandbox/win/src/job_unittest.cc new file mode 100644 index 0000000000..a1b7acafe3 --- /dev/null +++ b/sandbox/win/src/job_unittest.cc @@ -0,0 +1,195 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for the job object. + +#include "base/win/scoped_process_information.h" +#include "sandbox/win/src/job.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Tests the creation and destruction of the job. +TEST(JobTest, TestCreation) { + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + + // check if the job exists. + HANDLE job_handle = ::OpenJobObjectW(GENERIC_ALL, FALSE, + L"my_test_job_name"); + ASSERT_TRUE(job_handle != NULL); + + if (job_handle) + CloseHandle(job_handle); + } + + // Check if the job is destroyed when the object goes out of scope. + HANDLE job_handle = ::OpenJobObjectW(GENERIC_ALL, FALSE, L"my_test_job_name"); + ASSERT_TRUE(job_handle == NULL); + ASSERT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError()); +} + +// Tests the method "Detach". +TEST(JobTest, TestDetach) { + HANDLE job_handle; + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + + job_handle = job.Detach(); + ASSERT_TRUE(job_handle != NULL); + } + + // Check to be sure that the job is still alive even after the object is gone + // out of scope. + HANDLE job_handle_dup = ::OpenJobObjectW(GENERIC_ALL, FALSE, + L"my_test_job_name"); + ASSERT_TRUE(job_handle_dup != NULL); + + // Remove all references. + if (job_handle_dup) + ::CloseHandle(job_handle_dup); + + if (job_handle) + ::CloseHandle(job_handle); + + // Check if the jbo is really dead. + job_handle = ::OpenJobObjectW(GENERIC_ALL, FALSE, L"my_test_job_name"); + ASSERT_TRUE(job_handle == NULL); + ASSERT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError()); +} + +// Tests the ui exceptions +TEST(JobTest, TestExceptions) { + HANDLE job_handle; + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", + JOB_OBJECT_UILIMIT_READCLIPBOARD, 0)); + + job_handle = job.Detach(); + ASSERT_TRUE(job_handle != NULL); + + JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {0}; + DWORD size = sizeof(jbur); + BOOL result = ::QueryInformationJobObject(job_handle, + JobObjectBasicUIRestrictions, + &jbur, size, &size); + ASSERT_TRUE(result); + + ASSERT_EQ(jbur.UIRestrictionsClass & JOB_OBJECT_UILIMIT_READCLIPBOARD, 0); + ::CloseHandle(job_handle); + } + + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + + job_handle = job.Detach(); + ASSERT_TRUE(job_handle != NULL); + + JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {0}; + DWORD size = sizeof(jbur); + BOOL result = ::QueryInformationJobObject(job_handle, + JobObjectBasicUIRestrictions, + &jbur, size, &size); + ASSERT_TRUE(result); + + ASSERT_EQ(jbur.UIRestrictionsClass & JOB_OBJECT_UILIMIT_READCLIPBOARD, + JOB_OBJECT_UILIMIT_READCLIPBOARD); + ::CloseHandle(job_handle); + } +} + +// Tests the error case when the job is initialized twice. +TEST(JobTest, DoubleInit) { + // Create the job. + Job job; + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + ASSERT_EQ(ERROR_ALREADY_INITIALIZED, job.Init(JOB_LOCKDOWN, L"test", 0, 0)); +} + +// Tests the error case when we use a method and the object is not yet +// initialized. +TEST(JobTest, NoInit) { + Job job; + ASSERT_EQ(ERROR_NO_DATA, job.UserHandleGrantAccess(NULL)); + ASSERT_EQ(ERROR_NO_DATA, job.AssignProcessToJob(NULL)); + ASSERT_TRUE(job.Detach() == NULL); +} + +// Tests the initialization of the job with different security level. +TEST(JobTest, SecurityLevel) { + Job job1; + ASSERT_EQ(ERROR_SUCCESS, job1.Init(JOB_LOCKDOWN, L"job1", 0, 0)); + + Job job2; + ASSERT_EQ(ERROR_SUCCESS, job2.Init(JOB_RESTRICTED, L"job2", 0, 0)); + + Job job3; + ASSERT_EQ(ERROR_SUCCESS, job3.Init(JOB_LIMITED_USER, L"job3", 0, 0)); + + Job job4; + ASSERT_EQ(ERROR_SUCCESS, job4.Init(JOB_INTERACTIVE, L"job4", 0, 0)); + + Job job5; + ASSERT_EQ(ERROR_SUCCESS, job5.Init(JOB_UNPROTECTED, L"job5", 0, 0)); + + // JOB_NONE means we run without a job object so Init should fail. + Job job6; + ASSERT_EQ(ERROR_BAD_ARGUMENTS, job6.Init(JOB_NONE, L"job6", 0, 0)); + + Job job7; + ASSERT_EQ(ERROR_BAD_ARGUMENTS, job7.Init( + static_cast<JobLevel>(JOB_NONE+1), L"job7", 0, 0)); +} + +// Tests the method "AssignProcessToJob". +TEST(JobTest, ProcessInJob) { + // Create the job. + Job job; + ASSERT_EQ(ERROR_SUCCESS, job.Init(JOB_UNPROTECTED, L"job_test_process", 0, + 0)); + + BOOL result = FALSE; + + wchar_t notepad[] = L"notepad"; + STARTUPINFO si = { sizeof(si) }; + PROCESS_INFORMATION temp_process_info = {}; + result = ::CreateProcess(NULL, notepad, NULL, NULL, FALSE, 0, NULL, NULL, &si, + &temp_process_info); + ASSERT_TRUE(result); + base::win::ScopedProcessInformation pi(temp_process_info); + ASSERT_EQ(ERROR_SUCCESS, job.AssignProcessToJob(pi.process_handle())); + + // Get the job handle. + HANDLE job_handle = job.Detach(); + + // Check if the process is in the job. + JOBOBJECT_BASIC_PROCESS_ID_LIST jbpidl = {0}; + DWORD size = sizeof(jbpidl); + result = ::QueryInformationJobObject(job_handle, + JobObjectBasicProcessIdList, + &jbpidl, size, &size); + EXPECT_TRUE(result); + + EXPECT_EQ(1, jbpidl.NumberOfAssignedProcesses); + EXPECT_EQ(1, jbpidl.NumberOfProcessIdsInList); + EXPECT_EQ(pi.process_id(), jbpidl.ProcessIdList[0]); + + EXPECT_TRUE(::TerminateProcess(pi.process_handle(), 0)); + + EXPECT_TRUE(::CloseHandle(job_handle)); +} + +} // namespace sandbox diff --git a/sandbox/win/src/named_pipe_dispatcher.cc b/sandbox/win/src/named_pipe_dispatcher.cc new file mode 100644 index 0000000000..5527319833 --- /dev/null +++ b/sandbox/win/src/named_pipe_dispatcher.cc @@ -0,0 +1,98 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/named_pipe_dispatcher.h" + +#include "base/basictypes.h" +#include "base/strings/string_split.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/named_pipe_interception.h" +#include "sandbox/win/src/named_pipe_policy.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" + + +namespace sandbox { + +NamedPipeDispatcher::NamedPipeDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IPC_CREATENAMEDPIPEW_TAG, WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE, + UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>(&NamedPipeDispatcher::CreateNamedPipe) + }; + + ipc_calls_.push_back(create_params); +} + +bool NamedPipeDispatcher::SetupService(InterceptionManager* manager, + int service) { + if (IPC_CREATENAMEDPIPEW_TAG == service) + return INTERCEPT_EAT(manager, kKerneldllName, CreateNamedPipeW, + CREATE_NAMED_PIPE_ID, 36); + + return false; +} + +bool NamedPipeDispatcher::CreateNamedPipe(IPCInfo* ipc, + base::string16* name, + uint32 open_mode, + uint32 pipe_mode, + uint32 max_instances, + uint32 out_buffer_size, + uint32 in_buffer_size, + uint32 default_timeout) { + ipc->return_info.win32_result = ERROR_ACCESS_DENIED; + ipc->return_info.handle = INVALID_HANDLE_VALUE; + + std::vector<base::string16> paths; + std::vector<base::string16> innerpaths; + base::SplitString(*name, '/', &paths); + + for (std::vector<base::string16>::const_iterator iter = paths.begin(); + iter != paths.end(); ++iter) { + base::SplitString(*iter, '\\', &innerpaths); + for (std::vector<base::string16>::const_iterator iter2 = innerpaths.begin(); + iter2 != innerpaths.end(); ++iter2) { + if (*iter2 == L"..") + return true; + } + } + + const wchar_t* pipe_name = name->c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(pipe_name); + + EvalResult eval = policy_base_->EvalPolicy(IPC_CREATENAMEDPIPEW_TAG, + params.GetBase()); + + // "For file I/O, the "\\?\" prefix to a path string tells the Windows APIs to + // disable all string parsing and to send the string that follows it straight + // to the file system." + // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx + // This ensures even if there is a path traversal in the pipe name, and it is + // able to get past the checks above, it will still not be allowed to escape + // our whitelisted namespace. + if (name->compare(0, 4, L"\\\\.\\") == 0) + name->replace(0, 4, L"\\\\\?\\"); + + HANDLE pipe; + DWORD ret = NamedPipePolicy::CreateNamedPipeAction(eval, *ipc->client_info, + *name, open_mode, + pipe_mode, max_instances, + out_buffer_size, + in_buffer_size, + default_timeout, &pipe); + + ipc->return_info.win32_result = ret; + ipc->return_info.handle = pipe; + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/named_pipe_dispatcher.h b/sandbox/win/src/named_pipe_dispatcher.h new file mode 100644 index 0000000000..1c02199784 --- /dev/null +++ b/sandbox/win/src/named_pipe_dispatcher.h @@ -0,0 +1,42 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_NAMED_PIPE_DISPATCHER_H__ +#define SANDBOX_SRC_NAMED_PIPE_DISPATCHER_H__ + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles named pipe related IPC calls. +class NamedPipeDispatcher : public Dispatcher { + public: + explicit NamedPipeDispatcher(PolicyBase* policy_base); + ~NamedPipeDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, int service) override; + + private: + // Processes IPC requests coming from calls to CreateNamedPipeW() in the + // target. + bool CreateNamedPipe(IPCInfo* ipc, + base::string16* name, + uint32 open_mode, + uint32 pipe_mode, + uint32 max_instances, + uint32 out_buffer_size, + uint32 in_buffer_size, + uint32 default_timeout); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(NamedPipeDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_NAMED_PIPE_DISPATCHER_H__ diff --git a/sandbox/win/src/named_pipe_interception.cc b/sandbox/win/src/named_pipe_interception.cc new file mode 100644 index 0000000000..c62d0931d7 --- /dev/null +++ b/sandbox/win/src/named_pipe_interception.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/named_pipe_interception.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +HANDLE WINAPI TargetCreateNamedPipeW( + CreateNamedPipeWFunction orig_CreateNamedPipeW, LPCWSTR pipe_name, + DWORD open_mode, DWORD pipe_mode, DWORD max_instance, DWORD out_buffer_size, + DWORD in_buffer_size, DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes) { + HANDLE pipe = orig_CreateNamedPipeW(pipe_name, open_mode, pipe_mode, + max_instance, out_buffer_size, + in_buffer_size, default_timeout, + security_attributes); + if (INVALID_HANDLE_VALUE != pipe) + return pipe; + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return INVALID_HANDLE_VALUE; + + DWORD original_error = ::GetLastError(); + + // We don't support specific Security Attributes. + if (security_attributes) + return INVALID_HANDLE_VALUE; + + do { + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(pipe_name); + + if (!QueryBroker(IPC_CREATENAMEDPIPEW_TAG, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_CREATENAMEDPIPEW_TAG, pipe_name, + open_mode, pipe_mode, max_instance, + out_buffer_size, in_buffer_size, + default_timeout, &answer); + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + + if (ERROR_SUCCESS != answer.win32_result) + return INVALID_HANDLE_VALUE; + + return answer.handle; + } while (false); + + ::SetLastError(original_error); + return INVALID_HANDLE_VALUE; +} + +} // namespace sandbox diff --git a/sandbox/win/src/named_pipe_interception.h b/sandbox/win/src/named_pipe_interception.h new file mode 100644 index 0000000000..fdbee19766 --- /dev/null +++ b/sandbox/win/src/named_pipe_interception.h @@ -0,0 +1,36 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_NAMED_PIPE_INTERCEPTION_H__ +#define SANDBOX_SRC_NAMED_PIPE_INTERCEPTION_H__ + +namespace sandbox { + +extern "C" { + +typedef HANDLE (WINAPI *CreateNamedPipeWFunction) ( + LPCWSTR lpName, + DWORD dwOpenMode, + DWORD dwPipeMode, + DWORD nMaxInstances, + DWORD nOutBufferSize, + DWORD nInBufferSize, + DWORD nDefaultTimeOut, + LPSECURITY_ATTRIBUTES lpSecurityAttributes); + +// Interception of CreateNamedPipeW in kernel32.dll +SANDBOX_INTERCEPT HANDLE WINAPI TargetCreateNamedPipeW( + CreateNamedPipeWFunction orig_CreateNamedPipeW, LPCWSTR pipe_name, + DWORD open_mode, DWORD pipe_mode, DWORD max_instance, DWORD out_buffer_size, + DWORD in_buffer_size, DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_NAMED_PIPE_INTERCEPTION_H__ diff --git a/sandbox/win/src/named_pipe_policy.cc b/sandbox/win/src/named_pipe_policy.cc new file mode 100644 index 0000000000..eee719e50a --- /dev/null +++ b/sandbox/win/src/named_pipe_policy.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/named_pipe_policy.h" + +#include <string> + +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace { + +// Creates a named pipe and duplicates the handle to 'target_process'. The +// remaining parameters are the same as CreateNamedPipeW(). +HANDLE CreateNamedPipeHelper(HANDLE target_process, LPCWSTR pipe_name, + DWORD open_mode, DWORD pipe_mode, + DWORD max_instances, DWORD out_buffer_size, + DWORD in_buffer_size, DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes) { + HANDLE pipe = ::CreateNamedPipeW(pipe_name, open_mode, pipe_mode, + max_instances, out_buffer_size, + in_buffer_size, default_timeout, + security_attributes); + if (INVALID_HANDLE_VALUE == pipe) + return pipe; + + HANDLE new_pipe; + if (!::DuplicateHandle(::GetCurrentProcess(), pipe, + target_process, &new_pipe, + 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return INVALID_HANDLE_VALUE; + } + + return new_pipe; +} + +} // namespace + +namespace sandbox { + +bool NamedPipePolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + if (TargetPolicy::NAMEDPIPES_ALLOW_ANY != semantics) { + return false; + } + PolicyRule pipe(ASK_BROKER); + if (!pipe.AddStringMatch(IF, NameBased::NAME, name, CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IPC_CREATENAMEDPIPEW_TAG, &pipe)) { + return false; + } + return true; +} + +DWORD NamedPipePolicy::CreateNamedPipeAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &name, + DWORD open_mode, DWORD pipe_mode, + DWORD max_instances, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + HANDLE* pipe) { + // The only action supported is ASK_BROKER which means create the pipe. + if (ASK_BROKER != eval_result) { + return ERROR_ACCESS_DENIED; + } + + *pipe = CreateNamedPipeHelper(client_info.process, name.c_str(), + open_mode, pipe_mode, max_instances, + out_buffer_size, in_buffer_size, + default_timeout, NULL); + + if (INVALID_HANDLE_VALUE == *pipe) + return ERROR_ACCESS_DENIED; + + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/sandbox/win/src/named_pipe_policy.h b/sandbox/win/src/named_pipe_policy.h new file mode 100644 index 0000000000..c904aa3618 --- /dev/null +++ b/sandbox/win/src/named_pipe_policy.h @@ -0,0 +1,46 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_NAMED_PIPE_POLICY_H__ +#define SANDBOX_SRC_NAMED_PIPE_POLICY_H__ + +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum EvalResult; + +// This class centralizes most of the knowledge related to named pipe creation. +class NamedPipePolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level. + // policy rule for named pipe creation + // 'name' is the named pipe to be created + // 'semantics' is the desired semantics. + // 'policy' is the policy generator to which the rules are going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Processes a 'CreateNamedPipeW()' request from the target. + static DWORD CreateNamedPipeAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &name, + DWORD open_mode, DWORD pipe_mode, + DWORD max_instances, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, HANDLE* pipe); +}; + +} // namespace sandbox + + +#endif // SANDBOX_SRC_NAMED_PIPE_POLICY_H__ diff --git a/sandbox/win/src/named_pipe_policy_test.cc b/sandbox/win/src/named_pipe_policy_test.cc new file mode 100644 index 0000000000..813cf1f464 --- /dev/null +++ b/sandbox/win/src/named_pipe_policy_test.cc @@ -0,0 +1,140 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/windows_version.h" +#include "sandbox/win/src/handle_closer.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + + +SBOX_TESTS_COMMAND int NamedPipe_Create(int argc, wchar_t **argv) { + if (argc < 1 || argc > 2) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + if ((NULL == argv) || (NULL == argv[0])) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + HANDLE pipe = ::CreateNamedPipeW(argv[0], + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 4096, + 4096, 2000, NULL); + if (INVALID_HANDLE_VALUE == pipe) + return SBOX_TEST_DENIED; + + // The second parameter allows us to enforce a whitelist for where the + // pipe should be in the object namespace after creation. + if (argc == 2) { + base::string16 handle_name; + if (GetHandleName(pipe, &handle_name)) { + if (handle_name.compare(0, wcslen(argv[1]), argv[1]) != 0) + return SBOX_TEST_FAILED; + } else { + return SBOX_TEST_FAILED; + } + } + + OVERLAPPED overlapped = {0}; + overlapped.hEvent = ::CreateEvent(NULL, TRUE, TRUE, NULL); + BOOL result = ::ConnectNamedPipe(pipe, &overlapped); + + if (!result) { + DWORD error = ::GetLastError(); + if (ERROR_PIPE_CONNECTED != error && + ERROR_IO_PENDING != error) { + return SBOX_TEST_FAILED; + } + } + + if (!::CloseHandle(pipe)) + return SBOX_TEST_FAILED; + + ::CloseHandle(overlapped.hEvent); + return SBOX_TEST_SUCCEEDED; +} + +// Tests if we can create a pipe in the sandbox. +TEST(NamedPipePolicyTest, CreatePipe) { + TestRunner runner; + // TODO(nsylvain): This policy is wrong because "*" is a valid char in a + // namedpipe name. Here we apply it like a wildcard. http://b/893603 + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_NAMED_PIPES, + TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\test*")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\testbleh")); + + // On XP, the sandbox can create a pipe without any help but it fails on + // Vista+, this is why we do not test the "denied" case. + if (base::win::OSInfo::GetInstance()->version() >= base::win::VERSION_VISTA) { + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\bleh")); + } +} + +// Tests if we can create a pipe with a path traversal in the sandbox. +TEST(NamedPipePolicyTest, CreatePipeTraversal) { + TestRunner runner; + // TODO(nsylvain): This policy is wrong because "*" is a valid char in a + // namedpipe name. Here we apply it like a wildcard. http://b/893603 + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_NAMED_PIPES, + TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\test*")); + + // On XP, the sandbox can create a pipe without any help but it fails on + // Vista+, this is why we do not test the "denied" case. + if (base::win::OSInfo::GetInstance()->version() >= base::win::VERSION_VISTA) { + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test\\..\\bleh")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test/../bleh")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test\\../bleh")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test/..\\bleh")); + } +} + +// This tests that path canonicalization is actually disabled if we use \\?\ +// syntax. +TEST(NamedPipePolicyTest, CreatePipeCanonicalization) { + // "For file I/O, the "\\?\" prefix to a path string tells the Windows APIs to + // disable all string parsing and to send the string that follows it straight + // to the file system." + // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx + const wchar_t* argv[2] = { L"\\\\?\\pipe\\test\\..\\bleh", + L"\\Device\\NamedPipe\\test" }; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + NamedPipe_Create(2, const_cast<wchar_t**>(argv))); +} + +// The same test as CreatePipe but this time using strict interceptions. +TEST(NamedPipePolicyTest, CreatePipeStrictInterceptions) { + TestRunner runner; + runner.GetPolicy()->SetStrictInterceptions(); + + // TODO(nsylvain): This policy is wrong because "*" is a valid char in a + // namedpipe name. Here we apply it like a wildcard. http://b/893603 + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_NAMED_PIPES, + TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\test*")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\testbleh")); + + // On XP, the sandbox can create a pipe without any help but it fails on + // Vista+, this is why we do not test the "denied" case. + if (base::win::OSInfo::GetInstance()->version() >= base::win::VERSION_VISTA) { + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\bleh")); + } +} + +} // namespace sandbox diff --git a/sandbox/win/src/nt_internals.h b/sandbox/win/src/nt_internals.h new file mode 100644 index 0000000000..40b29c6beb --- /dev/null +++ b/sandbox/win/src/nt_internals.h @@ -0,0 +1,690 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file holds definitions related to the ntdll API. + +#ifndef SANDBOX_WIN_SRC_NT_INTERNALS_H__ +#define SANDBOX_WIN_SRC_NT_INTERNALS_H__ + +#include <windows.h> + +typedef LONG NTSTATUS; +#define NT_SUCCESS(st) (st >= 0) + +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) +#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002L) +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) +#ifndef STATUS_INVALID_PARAMETER +// It is now defined in Windows 2008 SDK. +#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000DL) +#endif +#define STATUS_CONFLICTING_ADDRESSES ((NTSTATUS)0xC0000018L) +#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L) +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L) +#define STATUS_OBJECT_NAME_COLLISION ((NTSTATUS)0xC0000035L) +#define STATUS_PROCEDURE_NOT_FOUND ((NTSTATUS)0xC000007AL) +#define STATUS_INVALID_IMAGE_FORMAT ((NTSTATUS)0xC000007BL) +#define STATUS_NO_TOKEN ((NTSTATUS)0xC000007CL) + +#define CURRENT_PROCESS ((HANDLE) -1) +#define CURRENT_THREAD ((HANDLE) -2) +#define NtCurrentProcess CURRENT_PROCESS + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING; +typedef UNICODE_STRING *PUNICODE_STRING; +typedef const UNICODE_STRING *PCUNICODE_STRING; + +typedef struct _STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} STRING; +typedef STRING *PSTRING; + +typedef STRING ANSI_STRING; +typedef PSTRING PANSI_STRING; +typedef CONST PSTRING PCANSI_STRING; + +typedef STRING OEM_STRING; +typedef PSTRING POEM_STRING; +typedef CONST STRING* PCOEM_STRING; + +#define OBJ_CASE_INSENSITIVE 0x00000040L +#define OBJ_OPENIF 0x00000080L + +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES; +typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES; + +#define InitializeObjectAttributes(p, n, a, r, s) { \ + (p)->Length = sizeof(OBJECT_ATTRIBUTES);\ + (p)->RootDirectory = r;\ + (p)->Attributes = a;\ + (p)->ObjectName = n;\ + (p)->SecurityDescriptor = s;\ + (p)->SecurityQualityOfService = NULL;\ +} + +typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +// ----------------------------------------------------------------------- +// File IO + +// Create disposition values. + +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 + +// Create/open option flags. + +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 + +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 + +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 + +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 + +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 + +// Create/open result values. These are the disposition values returned on the +// io status information. +#define FILE_SUPERSEDED 0x00000000 +#define FILE_OPENED 0x00000001 +#define FILE_CREATED 0x00000002 +#define FILE_OVERWRITTEN 0x00000003 +#define FILE_EXISTS 0x00000004 +#define FILE_DOES_NOT_EXIST 0x00000005 + +typedef NTSTATUS (WINAPI *NtCreateFileFunction)( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PLARGE_INTEGER AllocationSize OPTIONAL, + IN ULONG FileAttributes, + IN ULONG ShareAccess, + IN ULONG CreateDisposition, + IN ULONG CreateOptions, + IN PVOID EaBuffer OPTIONAL, + IN ULONG EaLength); + +typedef NTSTATUS (WINAPI *NtOpenFileFunction)( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG ShareAccess, + IN ULONG OpenOptions); + +typedef NTSTATUS (WINAPI *NtCloseFunction)( + IN HANDLE Handle); + +typedef enum _FILE_INFORMATION_CLASS { + FileRenameInformation = 10 +} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; + +typedef struct _FILE_RENAME_INFORMATION { + BOOLEAN ReplaceIfExists; + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; + +typedef NTSTATUS (WINAPI *NtSetInformationFileFunction)( + IN HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PVOID FileInformation, + IN ULONG Length, + IN FILE_INFORMATION_CLASS FileInformationClass); + +typedef struct FILE_BASIC_INFORMATION { + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + ULONG FileAttributes; +} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION; + +typedef NTSTATUS (WINAPI *NtQueryAttributesFileFunction)( + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PFILE_BASIC_INFORMATION FileAttributes); + +typedef struct _FILE_NETWORK_OPEN_INFORMATION { + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG FileAttributes; +} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION; + +typedef NTSTATUS (WINAPI *NtQueryFullAttributesFileFunction)( + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PFILE_NETWORK_OPEN_INFORMATION FileAttributes); + +// ----------------------------------------------------------------------- +// Sections + +typedef NTSTATUS (WINAPI *NtCreateSectionFunction)( + OUT PHANDLE SectionHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, + IN PLARGE_INTEGER MaximumSize OPTIONAL, + IN ULONG SectionPageProtection, + IN ULONG AllocationAttributes, + IN HANDLE FileHandle OPTIONAL); + +typedef ULONG SECTION_INHERIT; +#define ViewShare 1 +#define ViewUnmap 2 + +typedef NTSTATUS (WINAPI *NtMapViewOfSectionFunction)( + IN HANDLE SectionHandle, + IN HANDLE ProcessHandle, + IN OUT PVOID *BaseAddress, + IN ULONG_PTR ZeroBits, + IN SIZE_T CommitSize, + IN OUT PLARGE_INTEGER SectionOffset OPTIONAL, + IN OUT PSIZE_T ViewSize, + IN SECTION_INHERIT InheritDisposition, + IN ULONG AllocationType, + IN ULONG Win32Protect); + +typedef NTSTATUS (WINAPI *NtUnmapViewOfSectionFunction)( + IN HANDLE ProcessHandle, + IN PVOID BaseAddress); + +typedef enum _SECTION_INFORMATION_CLASS { + SectionBasicInformation = 0, + SectionImageInformation +} SECTION_INFORMATION_CLASS; + +typedef struct _SECTION_BASIC_INFORMATION { + PVOID BaseAddress; + ULONG Attributes; + LARGE_INTEGER Size; +} SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION; + +typedef NTSTATUS (WINAPI *NtQuerySectionFunction)( + IN HANDLE SectionHandle, + IN SECTION_INFORMATION_CLASS SectionInformationClass, + OUT PVOID SectionInformation, + IN SIZE_T SectionInformationLength, + OUT PSIZE_T ReturnLength OPTIONAL); + +// ----------------------------------------------------------------------- +// Process and Thread + +typedef struct _CLIENT_ID { + PVOID UniqueProcess; + PVOID UniqueThread; +} CLIENT_ID, *PCLIENT_ID; + +typedef NTSTATUS (WINAPI *NtOpenThreadFunction) ( + OUT PHANDLE ThreadHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + IN PCLIENT_ID ClientId); + +typedef NTSTATUS (WINAPI *NtOpenProcessFunction) ( + OUT PHANDLE ProcessHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + IN PCLIENT_ID ClientId); + +typedef enum _NT_THREAD_INFORMATION_CLASS { + ThreadBasicInformation, + ThreadTimes, + ThreadPriority, + ThreadBasePriority, + ThreadAffinityMask, + ThreadImpersonationToken, + ThreadDescriptorTableEntry, + ThreadEnableAlignmentFaultFixup, + ThreadEventPair, + ThreadQuerySetWin32StartAddress, + ThreadZeroTlsCell, + ThreadPerformanceCount, + ThreadAmILastThread, + ThreadIdealProcessor, + ThreadPriorityBoost, + ThreadSetTlsArrayAddress, + ThreadIsIoPending, + ThreadHideFromDebugger +} NT_THREAD_INFORMATION_CLASS, *PNT_THREAD_INFORMATION_CLASS; + +typedef NTSTATUS (WINAPI *NtSetInformationThreadFunction) ( + IN HANDLE ThreadHandle, + IN NT_THREAD_INFORMATION_CLASS ThreadInformationClass, + IN PVOID ThreadInformation, + IN ULONG ThreadInformationLength); + +// Partial definition only: +typedef enum _PROCESSINFOCLASS { + ProcessBasicInformation = 0, + ProcessExecuteFlags = 0x22 +} PROCESSINFOCLASS; + +typedef PVOID PPEB; +typedef PVOID KPRIORITY; + +typedef struct _PROCESS_BASIC_INFORMATION { + NTSTATUS ExitStatus; + PPEB PebBaseAddress; + KAFFINITY AffinityMask; + KPRIORITY BasePriority; + ULONG UniqueProcessId; + ULONG InheritedFromUniqueProcessId; +} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; + +typedef NTSTATUS (WINAPI *NtQueryInformationProcessFunction)( + IN HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength OPTIONAL); + +typedef NTSTATUS (WINAPI *NtSetInformationProcessFunction)( + HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + IN PVOID ProcessInformation, + IN ULONG ProcessInformationLength); + +typedef NTSTATUS (WINAPI *NtOpenThreadTokenFunction) ( + IN HANDLE ThreadHandle, + IN ACCESS_MASK DesiredAccess, + IN BOOLEAN OpenAsSelf, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS (WINAPI *NtOpenThreadTokenExFunction) ( + IN HANDLE ThreadHandle, + IN ACCESS_MASK DesiredAccess, + IN BOOLEAN OpenAsSelf, + IN ULONG HandleAttributes, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS (WINAPI *NtOpenProcessTokenFunction) ( + IN HANDLE ProcessHandle, + IN ACCESS_MASK DesiredAccess, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS (WINAPI *NtOpenProcessTokenExFunction) ( + IN HANDLE ProcessHandle, + IN ACCESS_MASK DesiredAccess, + IN ULONG HandleAttributes, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS (WINAPI * RtlCreateUserThreadFunction)( + IN HANDLE Process, + IN PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, + IN BOOLEAN CreateSuspended, + IN ULONG ZeroBits, + IN SIZE_T MaximumStackSize, + IN SIZE_T CommittedStackSize, + IN LPTHREAD_START_ROUTINE StartAddress, + IN PVOID Parameter, + OUT PHANDLE Thread, + OUT PCLIENT_ID ClientId); + +// ----------------------------------------------------------------------- +// Registry + +typedef NTSTATUS (WINAPI *NtCreateKeyFunction)( + OUT PHANDLE KeyHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + IN ULONG TitleIndex, + IN PUNICODE_STRING Class OPTIONAL, + IN ULONG CreateOptions, + OUT PULONG Disposition OPTIONAL); + +typedef NTSTATUS (WINAPI *NtOpenKeyFunction)( + OUT PHANDLE KeyHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI *NtOpenKeyExFunction)( + OUT PHANDLE KeyHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + IN DWORD open_options); + +typedef NTSTATUS (WINAPI *NtDeleteKeyFunction)( + IN HANDLE KeyHandle); + +// ----------------------------------------------------------------------- +// Memory + +// Don't really need this structure right now. +typedef PVOID PRTL_HEAP_PARAMETERS; + +typedef PVOID (WINAPI *RtlCreateHeapFunction)( + IN ULONG Flags, + IN PVOID HeapBase OPTIONAL, + IN SIZE_T ReserveSize OPTIONAL, + IN SIZE_T CommitSize OPTIONAL, + IN PVOID Lock OPTIONAL, + IN PRTL_HEAP_PARAMETERS Parameters OPTIONAL); + +typedef PVOID (WINAPI *RtlDestroyHeapFunction)( + IN PVOID HeapHandle); + +typedef PVOID (WINAPI *RtlAllocateHeapFunction)( + IN PVOID HeapHandle, + IN ULONG Flags, + IN SIZE_T Size); + +typedef BOOLEAN (WINAPI *RtlFreeHeapFunction)( + IN PVOID HeapHandle, + IN ULONG Flags, + IN PVOID HeapBase); + +typedef NTSTATUS (WINAPI *NtAllocateVirtualMemoryFunction) ( + IN HANDLE ProcessHandle, + IN OUT PVOID *BaseAddress, + IN ULONG_PTR ZeroBits, + IN OUT PSIZE_T RegionSize, + IN ULONG AllocationType, + IN ULONG Protect); + +typedef NTSTATUS (WINAPI *NtFreeVirtualMemoryFunction) ( + IN HANDLE ProcessHandle, + IN OUT PVOID *BaseAddress, + IN OUT PSIZE_T RegionSize, + IN ULONG FreeType); + +typedef enum _MEMORY_INFORMATION_CLASS { + MemoryBasicInformation = 0, + MemoryWorkingSetList, + MemorySectionName, + MemoryBasicVlmInformation +} MEMORY_INFORMATION_CLASS; + +typedef struct _MEMORY_SECTION_NAME { // Information Class 2 + UNICODE_STRING SectionFileName; +} MEMORY_SECTION_NAME, *PMEMORY_SECTION_NAME; + +typedef NTSTATUS (WINAPI *NtQueryVirtualMemoryFunction)( + IN HANDLE ProcessHandle, + IN PVOID BaseAddress, + IN MEMORY_INFORMATION_CLASS MemoryInformationClass, + OUT PVOID MemoryInformation, + IN SIZE_T MemoryInformationLength, + OUT PSIZE_T ReturnLength OPTIONAL); + +typedef NTSTATUS (WINAPI *NtProtectVirtualMemoryFunction)( + IN HANDLE ProcessHandle, + IN OUT PVOID* BaseAddress, + IN OUT PSIZE_T ProtectSize, + IN ULONG NewProtect, + OUT PULONG OldProtect); + +// ----------------------------------------------------------------------- +// Objects + +typedef enum _OBJECT_INFORMATION_CLASS { + ObjectBasicInformation, + ObjectNameInformation, + ObjectTypeInformation, + ObjectAllInformation, + ObjectDataInformation +} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS; + +typedef struct _OBJDIR_INFORMATION { + UNICODE_STRING ObjectName; + UNICODE_STRING ObjectTypeName; + BYTE Data[1]; +} OBJDIR_INFORMATION; + +typedef struct _PUBLIC_OBJECT_BASIC_INFORMATION { + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + ULONG Reserved[10]; // reserved for internal use +} PUBLIC_OBJECT_BASIC_INFORMATION, *PPUBLIC_OBJECT_BASIC_INFORMATION; + +typedef struct __PUBLIC_OBJECT_TYPE_INFORMATION { + UNICODE_STRING TypeName; + ULONG Reserved[22]; // reserved for internal use +} PUBLIC_OBJECT_TYPE_INFORMATION, *PPUBLIC_OBJECT_TYPE_INFORMATION; + +typedef enum _POOL_TYPE { + NonPagedPool, + PagedPool, + NonPagedPoolMustSucceed, + ReservedType, + NonPagedPoolCacheAligned, + PagedPoolCacheAligned, + NonPagedPoolCacheAlignedMustS +} POOL_TYPE; + +typedef struct _OBJECT_BASIC_INFORMATION { + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + ULONG PagedPoolUsage; + ULONG NonPagedPoolUsage; + ULONG Reserved[3]; + ULONG NameInformationLength; + ULONG TypeInformationLength; + ULONG SecurityDescriptorLength; + LARGE_INTEGER CreateTime; +} OBJECT_BASIC_INFORMATION, *POBJECT_BASIC_INFORMATION; + +typedef struct _OBJECT_TYPE_INFORMATION { + UNICODE_STRING Name; + ULONG TotalNumberOfObjects; + ULONG TotalNumberOfHandles; + ULONG TotalPagedPoolUsage; + ULONG TotalNonPagedPoolUsage; + ULONG TotalNamePoolUsage; + ULONG TotalHandleTableUsage; + ULONG HighWaterNumberOfObjects; + ULONG HighWaterNumberOfHandles; + ULONG HighWaterPagedPoolUsage; + ULONG HighWaterNonPagedPoolUsage; + ULONG HighWaterNamePoolUsage; + ULONG HighWaterHandleTableUsage; + ULONG InvalidAttributes; + GENERIC_MAPPING GenericMapping; + ULONG ValidAccess; + BOOLEAN SecurityRequired; + BOOLEAN MaintainHandleCount; + USHORT MaintainTypeList; + POOL_TYPE PoolType; + ULONG PagedPoolUsage; + ULONG NonPagedPoolUsage; +} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; + +typedef enum _SYSTEM_INFORMATION_CLASS { + SystemHandleInformation = 16 +} SYSTEM_INFORMATION_CLASS; + +typedef struct _SYSTEM_HANDLE_INFORMATION { + USHORT ProcessId; + USHORT CreatorBackTraceIndex; + UCHAR ObjectTypeNumber; + UCHAR Flags; + USHORT Handle; + PVOID Object; + ACCESS_MASK GrantedAccess; +} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; + +typedef struct _SYSTEM_HANDLE_INFORMATION_EX { + ULONG NumberOfHandles; + SYSTEM_HANDLE_INFORMATION Information[1]; +} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; + +typedef struct _OBJECT_NAME_INFORMATION { + UNICODE_STRING ObjectName; +} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; + +typedef NTSTATUS (WINAPI *NtQueryObjectFunction)( + IN HANDLE Handle, + IN OBJECT_INFORMATION_CLASS ObjectInformationClass, + OUT PVOID ObjectInformation OPTIONAL, + IN ULONG ObjectInformationLength, + OUT PULONG ReturnLength OPTIONAL); + +typedef NTSTATUS (WINAPI *NtDuplicateObjectFunction)( + IN HANDLE SourceProcess, + IN HANDLE SourceHandle, + IN HANDLE TargetProcess, + OUT PHANDLE TargetHandle, + IN ACCESS_MASK DesiredAccess, + IN ULONG Attributes, + IN ULONG Options); + +typedef NTSTATUS (WINAPI *NtSignalAndWaitForSingleObjectFunction)( + IN HANDLE HandleToSignal, + IN HANDLE HandleToWait, + IN BOOLEAN Alertable, + IN PLARGE_INTEGER Timeout OPTIONAL); + +typedef NTSTATUS (WINAPI *NtQuerySystemInformation)( + IN SYSTEM_INFORMATION_CLASS SystemInformationClass, + OUT PVOID SystemInformation, + IN ULONG SystemInformationLength, + OUT PULONG ReturnLength); + +typedef NTSTATUS (WINAPI *NtQueryObject)( + IN HANDLE Handle, + IN OBJECT_INFORMATION_CLASS ObjectInformationClass, + OUT PVOID ObjectInformation, + IN ULONG ObjectInformationLength, + OUT PULONG ReturnLength); + +// ----------------------------------------------------------------------- +// Strings + +typedef int (__cdecl *_strnicmpFunction)( + IN const char* _Str1, + IN const char* _Str2, + IN size_t _MaxCount); + +typedef size_t (__cdecl *strlenFunction)( + IN const char * _Str); + +typedef size_t (__cdecl *wcslenFunction)( + IN const wchar_t* _Str); + +typedef void* (__cdecl *memcpyFunction)( + IN void* dest, + IN const void* src, + IN size_t count); + +typedef NTSTATUS (WINAPI *RtlAnsiStringToUnicodeStringFunction)( + IN OUT PUNICODE_STRING DestinationString, + IN PANSI_STRING SourceString, + IN BOOLEAN AllocateDestinationString); + +typedef LONG (WINAPI *RtlCompareUnicodeStringFunction)( + IN PCUNICODE_STRING String1, + IN PCUNICODE_STRING String2, + IN BOOLEAN CaseInSensitive); + +typedef VOID (WINAPI *RtlInitUnicodeStringFunction) ( + IN OUT PUNICODE_STRING DestinationString, + IN PCWSTR SourceString); + +typedef enum _EVENT_TYPE { + NotificationEvent, + SynchronizationEvent +} EVENT_TYPE, *PEVENT_TYPE; + +typedef NTSTATUS (WINAPI* NtCreateDirectoryObjectFunction) ( + PHANDLE DirectoryHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NtOpenDirectoryObjectFunction) ( + PHANDLE DirectoryHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NtQuerySymbolicLinkObjectFunction) ( + HANDLE LinkHandle, + PUNICODE_STRING LinkTarget, + PULONG ReturnedLength); + +typedef NTSTATUS (WINAPI* NtOpenSymbolicLinkObjectFunction) ( + PHANDLE LinkHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +#define DIRECTORY_QUERY 0x0001 +#define DIRECTORY_TRAVERSE 0x0002 +#define DIRECTORY_CREATE_OBJECT 0x0004 +#define DIRECTORY_CREATE_SUBDIRECTORY 0x0008 +#define DIRECTORY_ALL_ACCESS 0x000F + +typedef NTSTATUS (WINAPI* NtCreateLowBoxToken)( + OUT PHANDLE token, + IN HANDLE original_handle, + IN ACCESS_MASK access, + IN POBJECT_ATTRIBUTES object_attribute, + IN PSID appcontainer_sid, + IN DWORD capabilityCount, + IN PSID_AND_ATTRIBUTES capabilities, + IN DWORD handle_count, + IN PHANDLE handles); + +typedef NTSTATUS(WINAPI *NtSetInformationProcess)( + IN HANDLE process_handle, + IN ULONG info_class, + IN PVOID process_information, + IN ULONG information_length); + +struct PROCESS_ACCESS_TOKEN { + HANDLE token; + HANDLE thread; +}; + +const unsigned int NtProcessInformationAccessToken = 9; + +#endif // SANDBOX_WIN_SRC_NT_INTERNALS_H__ + diff --git a/sandbox/win/src/policy_broker.cc b/sandbox/win/src/policy_broker.cc new file mode 100644 index 0000000000..dc5e18c28b --- /dev/null +++ b/sandbox/win/src/policy_broker.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <map> + +#include "sandbox/win/src/policy_broker.h" + +#include "base/logging.h" +#include "base/win/pe_image.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/target_process.h" + +// This code executes on the broker side, as a callback from the policy on the +// target side (the child). + +namespace sandbox { + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +#define INIT_GLOBAL_NT(member) \ + g_nt.member = reinterpret_cast<Nt##member##Function>( \ + ntdll_image.GetProcAddress("Nt" #member)); \ + if (NULL == g_nt.member) \ + return false + +#define INIT_GLOBAL_RTL(member) \ + g_nt.member = reinterpret_cast<member##Function>( \ + ntdll_image.GetProcAddress(#member)); \ + if (NULL == g_nt.member) \ + return false + +bool SetupNtdllImports(TargetProcess *child) { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + base::win::PEImage ntdll_image(ntdll); + + // Bypass purify's interception. + wchar_t* loader_get = reinterpret_cast<wchar_t*>( + ntdll_image.GetProcAddress("LdrGetDllHandle")); + if (loader_get) { + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + loader_get, &ntdll); + } + + INIT_GLOBAL_NT(AllocateVirtualMemory); + INIT_GLOBAL_NT(Close); + INIT_GLOBAL_NT(DuplicateObject); + INIT_GLOBAL_NT(FreeVirtualMemory); + INIT_GLOBAL_NT(MapViewOfSection); + INIT_GLOBAL_NT(ProtectVirtualMemory); + INIT_GLOBAL_NT(QueryInformationProcess); + INIT_GLOBAL_NT(QueryObject); + INIT_GLOBAL_NT(QuerySection); + INIT_GLOBAL_NT(QueryVirtualMemory); + INIT_GLOBAL_NT(UnmapViewOfSection); + + INIT_GLOBAL_RTL(RtlAllocateHeap); + INIT_GLOBAL_RTL(RtlAnsiStringToUnicodeString); + INIT_GLOBAL_RTL(RtlCompareUnicodeString); + INIT_GLOBAL_RTL(RtlCreateHeap); + INIT_GLOBAL_RTL(RtlCreateUserThread); + INIT_GLOBAL_RTL(RtlDestroyHeap); + INIT_GLOBAL_RTL(RtlFreeHeap); + INIT_GLOBAL_RTL(_strnicmp); + INIT_GLOBAL_RTL(strlen); + INIT_GLOBAL_RTL(wcslen); + INIT_GLOBAL_RTL(memcpy); + +#ifndef NDEBUG + // Verify that the structure is fully initialized. + for (size_t i = 0; i < sizeof(g_nt)/sizeof(void*); i++) + DCHECK(reinterpret_cast<char**>(&g_nt)[i]); +#endif + return (SBOX_ALL_OK == child->TransferVariable("g_nt", &g_nt, sizeof(g_nt))); +} + +#undef INIT_GLOBAL_NT +#undef INIT_GLOBAL_RTL + +bool SetupBasicInterceptions(InterceptionManager* manager) { + // Interceptions provided by process_thread_policy, without actual policy. + if (!INTERCEPT_NT(manager, NtOpenThread, OPEN_TREAD_ID, 20) || + !INTERCEPT_NT(manager, NtOpenProcess, OPEN_PROCESS_ID, 20) || + !INTERCEPT_NT(manager, NtOpenProcessToken, OPEN_PROCESS_TOKEN_ID, 16)) + return false; + + // Interceptions with neither policy nor IPC. + if (!INTERCEPT_NT(manager, NtSetInformationThread, SET_INFORMATION_THREAD_ID, + 20) || + !INTERCEPT_NT(manager, NtOpenThreadToken, OPEN_THREAD_TOKEN_ID, 20)) + return false; + + if (base::win::GetVersion() >= base::win::VERSION_XP) { + // Bug 27218: We don't have dispatch for some x64 syscalls. + // This one is also provided by process_thread_policy. + if (!INTERCEPT_NT(manager, NtOpenProcessTokenEx, OPEN_PROCESS_TOKEN_EX_ID, + 20)) + return false; + + return INTERCEPT_NT(manager, NtOpenThreadTokenEx, OPEN_THREAD_TOKEN_EX_ID, + 24); + } + + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/policy_broker.h b/sandbox/win/src/policy_broker.h new file mode 100644 index 0000000000..1c5cc26c23 --- /dev/null +++ b/sandbox/win/src/policy_broker.h @@ -0,0 +1,23 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_POLICY_BROKER_H_ +#define SANDBOX_SRC_POLICY_BROKER_H_ + +#include "sandbox/win/src/interception.h" + +namespace sandbox { + +class TargetProcess; + +// Sets up interceptions not controlled by explicit policies. +bool SetupBasicInterceptions(InterceptionManager* manager); + +// Sets up imports from NTDLL for the given target process so the interceptions +// can work. +bool SetupNtdllImports(TargetProcess *child); + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_BROKER_H_ diff --git a/sandbox/win/src/policy_engine_opcodes.cc b/sandbox/win/src/policy_engine_opcodes.cc new file mode 100644 index 0000000000..24ba119195 --- /dev/null +++ b/sandbox/win/src/policy_engine_opcodes.cc @@ -0,0 +1,454 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_engine_opcodes.h" + +#include "base/basictypes.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace { +const unsigned short kMaxUniStrSize = 0xfffc; + +bool InitStringUnicode(const wchar_t* source, size_t length, + UNICODE_STRING* ustring) { + ustring->Buffer = const_cast<wchar_t*>(source); + ustring->Length = static_cast<USHORT>(length) * sizeof(wchar_t); + if (length > kMaxUniStrSize) { + return false; + } + ustring->MaximumLength = (NULL != source) ? + ustring->Length + sizeof(wchar_t) : 0; + return true; +} + +} // namespace + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +// Note: The opcodes are implemented as functions (as opposed to classes derived +// from PolicyOpcode) because you should not add more member variables to the +// PolicyOpcode class since it would cause object slicing on the target. So to +// enforce that (instead of just trusting the developer) the opcodes became +// just functions. +// +// In the code that follows I have keep the evaluation function and the factory +// function together to stress the close relationship between both. For example, +// only the factory method and the evaluation function know the stored argument +// order and meaning. + +template <int> +EvalResult OpcodeEval(PolicyOpcode* opcode, const ParameterSet* pp, + MatchContext* match); + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpAlwaysFalse: +// Does not require input parameter. + +PolicyOpcode* OpcodeFactory::MakeOpAlwaysFalse(uint32 options) { + return MakeBase(OP_ALWAYS_FALSE, options, -1); +} + +template <> +EvalResult OpcodeEval<OP_ALWAYS_FALSE>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + UNREFERENCED_PARAMETER(opcode); + UNREFERENCED_PARAMETER(param); + UNREFERENCED_PARAMETER(context); + return EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpAlwaysTrue: +// Does not require input parameter. + +PolicyOpcode* OpcodeFactory::MakeOpAlwaysTrue(uint32 options) { + return MakeBase(OP_ALWAYS_TRUE, options, -1); +} + +template <> +EvalResult OpcodeEval<OP_ALWAYS_TRUE>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + UNREFERENCED_PARAMETER(opcode); + UNREFERENCED_PARAMETER(param); + UNREFERENCED_PARAMETER(context); + return EVAL_TRUE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpAction: +// Does not require input parameter. +// Argument 0 contains the actual action to return. + +PolicyOpcode* OpcodeFactory::MakeOpAction(EvalResult action, + uint32 options) { + PolicyOpcode* opcode = MakeBase(OP_ACTION, options, 0); + if (NULL == opcode) return NULL; + opcode->SetArgument(0, action); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_ACTION>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + UNREFERENCED_PARAMETER(param); + UNREFERENCED_PARAMETER(context); + int action = 0; + opcode->GetArgument(0, &action); + return static_cast<EvalResult>(action); +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpNumberMatch: +// Requires a uint32 or void* in selected_param +// Argument 0 is the stored number to match. +// Argument 1 is the C++ type of the 0th argument. + +PolicyOpcode* OpcodeFactory::MakeOpNumberMatch(int16 selected_param, + uint32 match, + uint32 options) { + PolicyOpcode* opcode = MakeBase(OP_NUMBER_MATCH, options, selected_param); + if (NULL == opcode) return NULL; + opcode->SetArgument(0, match); + opcode->SetArgument(1, UINT32_TYPE); + return opcode; +} + +PolicyOpcode* OpcodeFactory::MakeOpVoidPtrMatch(int16 selected_param, + const void* match, + uint32 options) { + PolicyOpcode* opcode = MakeBase(OP_NUMBER_MATCH, options, selected_param); + if (NULL == opcode) return NULL; + opcode->SetArgument(0, match); + opcode->SetArgument(1, VOIDPTR_TYPE); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_NUMBER_MATCH>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + UNREFERENCED_PARAMETER(context); + uint32 value_uint32 = 0; + if (param->Get(&value_uint32)) { + uint32 match_uint32 = 0; + opcode->GetArgument(0, &match_uint32); + return (match_uint32 != value_uint32)? EVAL_FALSE : EVAL_TRUE; + } else { + const void* value_ptr = NULL; + if (param->Get(&value_ptr)) { + const void* match_ptr = NULL; + opcode->GetArgument(0, &match_ptr); + return (match_ptr != value_ptr)? EVAL_FALSE : EVAL_TRUE; + } + } + return EVAL_ERROR; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpNumberMatchRange +// Requires a uint32 in selected_param. +// Argument 0 is the stored lower bound to match. +// Argument 1 is the stored upper bound to match. + +PolicyOpcode* OpcodeFactory::MakeOpNumberMatchRange(int16 selected_param, + uint32 lower_bound, + uint32 upper_bound, + uint32 options) { + if (lower_bound > upper_bound) { + return NULL; + } + PolicyOpcode* opcode = MakeBase(OP_NUMBER_MATCH_RANGE, options, + selected_param); + if (NULL == opcode) return NULL; + opcode->SetArgument(0, lower_bound); + opcode->SetArgument(1, upper_bound); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_NUMBER_MATCH_RANGE>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + UNREFERENCED_PARAMETER(context); + uint32 value = 0; + if (!param->Get(&value)) return EVAL_ERROR; + + uint32 lower_bound = 0; + uint32 upper_bound = 0; + opcode->GetArgument(0, &lower_bound); + opcode->GetArgument(1, &upper_bound); + return((lower_bound <= value) && (upper_bound >= value))? + EVAL_TRUE : EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpNumberAndMatch: +// Requires a uint32 in selected_param. +// Argument 0 is the stored number to match. + +PolicyOpcode* OpcodeFactory::MakeOpNumberAndMatch(int16 selected_param, + uint32 match, + uint32 options) { + PolicyOpcode* opcode = MakeBase(OP_NUMBER_AND_MATCH, options, selected_param); + if (NULL == opcode) return NULL; + opcode->SetArgument(0, match); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_NUMBER_AND_MATCH>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + UNREFERENCED_PARAMETER(context); + uint32 value = 0; + if (!param->Get(&value)) return EVAL_ERROR; + + uint32 number = 0; + opcode->GetArgument(0, &number); + return (number & value)? EVAL_TRUE : EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpWStringMatch: +// Requires a wchar_t* in selected_param. +// Argument 0 is the byte displacement of the stored string. +// Argument 1 is the lenght in chars of the stored string. +// Argument 2 is the offset to apply on the input string. It has special values. +// as noted in the header file. +// Argument 3 is the string matching options. + +PolicyOpcode* OpcodeFactory::MakeOpWStringMatch(int16 selected_param, + const wchar_t* match_str, + int start_position, + StringMatchOptions match_opts, + uint32 options) { + if (NULL == match_str) { + return NULL; + } + if ('\0' == match_str[0]) { + return NULL; + } + + int lenght = lstrlenW(match_str); + + PolicyOpcode* opcode = MakeBase(OP_WSTRING_MATCH, options, selected_param); + if (NULL == opcode) { + return NULL; + } + ptrdiff_t delta_str = AllocRelative(opcode, match_str, wcslen(match_str)+1); + if (0 == delta_str) { + return NULL; + } + opcode->SetArgument(0, delta_str); + opcode->SetArgument(1, lenght); + opcode->SetArgument(2, start_position); + opcode->SetArgument(3, match_opts); + return opcode; +} + +template<> +EvalResult OpcodeEval<OP_WSTRING_MATCH>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + if (NULL == context) { + return EVAL_ERROR; + } + const wchar_t* source_str = NULL; + if (!param->Get(&source_str)) return EVAL_ERROR; + + int start_position = 0; + int match_len = 0; + unsigned int match_opts = 0; + opcode->GetArgument(1, &match_len); + opcode->GetArgument(2, &start_position); + opcode->GetArgument(3, &match_opts); + + const wchar_t* match_str = opcode->GetRelativeString(0); + // Advance the source string to the last successfully evaluated position + // according to the match context. + source_str = &source_str[context->position]; + int source_len = static_cast<int>(g_nt.wcslen(source_str)); + + if (0 == source_len) { + // If we reached the end of the source string there is nothing we can + // match against. + return EVAL_FALSE; + } + if (match_len > source_len) { + // There can't be a positive match when the target string is bigger than + // the source string + return EVAL_FALSE; + } + + BOOLEAN case_sensitive = (match_opts & CASE_INSENSITIVE) ? TRUE : FALSE; + + // We have three cases, depending on the value of start_pos: + // Case 1. We skip N characters and compare once. + // Case 2: We skip to the end and compare once. + // Case 3: We match the first substring (if we find any). + if (start_position >= 0) { + if (kSeekToEnd == start_position) { + start_position = source_len - match_len; + } else if (match_opts & EXACT_LENGHT) { + // A sub-case of case 3 is when the EXACT_LENGHT flag is on + // the match needs to be not just substring but full match. + if ((match_len + start_position) != source_len) { + return EVAL_FALSE; + } + } + + // Advance start_pos characters. Warning! this does not consider + // utf16 encodings (surrogate pairs) or other Unicode 'features'. + source_str += start_position; + + // Since we skipped, lets reevaluate just the lengths again. + if ((match_len + start_position) > source_len) { + return EVAL_FALSE; + } + + UNICODE_STRING match_ustr; + InitStringUnicode(match_str, match_len, &match_ustr); + UNICODE_STRING source_ustr; + InitStringUnicode(source_str, match_len, &source_ustr); + + if (0 == g_nt.RtlCompareUnicodeString(&match_ustr, &source_ustr, + case_sensitive)) { + // Match! update the match context. + context->position += start_position + match_len; + return EVAL_TRUE; + } else { + return EVAL_FALSE; + } + } else if (start_position < 0) { + UNICODE_STRING match_ustr; + InitStringUnicode(match_str, match_len, &match_ustr); + UNICODE_STRING source_ustr; + InitStringUnicode(source_str, match_len, &source_ustr); + + do { + if (0 == g_nt.RtlCompareUnicodeString(&match_ustr, &source_ustr, + case_sensitive)) { + // Match! update the match context. + context->position += (source_ustr.Buffer - source_str) + match_len; + return EVAL_TRUE; + } + ++source_ustr.Buffer; + --source_len; + } while (source_len >= match_len); + } + return EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// OpcodeMaker (other member functions). + +PolicyOpcode* OpcodeFactory::MakeBase(OpcodeID opcode_id, + uint32 options, + int16 selected_param) { + if (memory_size() < sizeof(PolicyOpcode)) { + return NULL; + } + + // Create opcode using placement-new on the buffer memory. + PolicyOpcode* opcode = new(memory_top_) PolicyOpcode(); + + // Fill in the standard fields, that every opcode has. + memory_top_ += sizeof(PolicyOpcode); + opcode->opcode_id_ = opcode_id; + opcode->SetOptions(options); + opcode->parameter_ = selected_param; + return opcode; +} + +ptrdiff_t OpcodeFactory::AllocRelative(void* start, const wchar_t* str, + size_t lenght) { + size_t bytes = lenght * sizeof(wchar_t); + if (memory_size() < bytes) { + return 0; + } + memory_bottom_ -= bytes; + if (reinterpret_cast<UINT_PTR>(memory_bottom_) & 1) { + // TODO(cpu) replace this for something better. + ::DebugBreak(); + } + memcpy(memory_bottom_, str, bytes); + ptrdiff_t delta = memory_bottom_ - reinterpret_cast<char*>(start); + return delta; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode evaluation dispatchers. + +// This function is the one and only entry for evaluating any opcode. It is +// in charge of applying any relevant opcode options and calling EvaluateInner +// were the actual dispatch-by-id is made. It would seem at first glance that +// the dispatch should be done by virtual function (vtable) calls but you have +// to remember that the opcodes are made in the broker process and copied as +// raw memory to the target process. + +EvalResult PolicyOpcode::Evaluate(const ParameterSet* call_params, + size_t param_count, MatchContext* match) { + if (NULL == call_params) { + return EVAL_ERROR; + } + const ParameterSet* selected_param = NULL; + if (parameter_ >= 0) { + if (static_cast<size_t>(parameter_) >= param_count) { + return EVAL_ERROR; + } + selected_param = &call_params[parameter_]; + } + EvalResult result = EvaluateHelper(selected_param, match); + + // Apply the general options regardless of the particular type of opcode. + if (kPolNone == options_) { + return result; + } + + if (options_ & kPolNegateEval) { + if (EVAL_TRUE == result) { + result = EVAL_FALSE; + } else if (EVAL_FALSE == result) { + result = EVAL_TRUE; + } else if (EVAL_ERROR != result) { + result = EVAL_ERROR; + } + } + if (NULL != match) { + if (options_ & kPolClearContext) { + match->Clear(); + } + if (options_ & kPolUseOREval) { + match->options = kPolUseOREval; + } + } + return result; +} + +#define OPCODE_EVAL(op, x, y, z) case op: return OpcodeEval<op>(x, y, z) + +EvalResult PolicyOpcode::EvaluateHelper(const ParameterSet* parameters, + MatchContext* match) { + switch (opcode_id_) { + OPCODE_EVAL(OP_ALWAYS_FALSE, this, parameters, match); + OPCODE_EVAL(OP_ALWAYS_TRUE, this, parameters, match); + OPCODE_EVAL(OP_NUMBER_MATCH, this, parameters, match); + OPCODE_EVAL(OP_NUMBER_MATCH_RANGE, this, parameters, match); + OPCODE_EVAL(OP_NUMBER_AND_MATCH, this, parameters, match); + OPCODE_EVAL(OP_WSTRING_MATCH, this, parameters, match); + OPCODE_EVAL(OP_ACTION, this, parameters, match); + default: + return EVAL_ERROR; + } +} + +#undef OPCODE_EVAL + +} // namespace sandbox diff --git a/sandbox/win/src/policy_engine_opcodes.h b/sandbox/win/src/policy_engine_opcodes.h new file mode 100644 index 0000000000..17d1764b1c --- /dev/null +++ b/sandbox/win/src/policy_engine_opcodes.h @@ -0,0 +1,386 @@ +// Copyright (c) 2010 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_WIN_SRC_POLICY_ENGINE_OPCODES_H_ +#define SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ + +#include "base/basictypes.h" +#include "base/numerics/safe_conversions.h" +#include "sandbox/win/src/policy_engine_params.h" + +// The low-level policy is implemented using the concept of policy 'opcodes'. +// An opcode is a structure that contains enough information to perform one +// comparison against one single input parameter. For example, an opcode can +// encode just one of the following comparison: +// +// - Is input parameter 3 not equal to NULL? +// - Does input parameter 2 start with L"c:\\"? +// - Is input parameter 5, bit 3 is equal 1? +// +// Each opcode is in fact equivalent to a function invocation where all +// the parameters are known by the opcode except one. So say you have a +// function of this form: +// bool fn(a, b, c, d) with 4 arguments +// +// Then an opcode is: +// op(fn, b, c, d) +// Which stores the function to call and its 3 last arguments +// +// Then and opcode evaluation is: +// op.eval(a) ------------------------> fn(a,b,c,d) +// internally calls +// +// The idea is that complex policy rules can be split into streams of +// opcodes which are evaluated in sequence. The evaluation is done in +// groups of opcodes that have N comparison opcodes plus 1 action opcode: +// +// [comparison 1][comparison 2]...[comparison N][action][comparison 1]... +// ----- evaluation order-----------> +// +// Each opcode group encodes one high-level policy rule. The rule applies +// only if all the conditions on the group evaluate to true. The action +// opcode contains the policy outcome for that particular rule. +// +// Note that this header contains the main building blocks of low-level policy +// but not the low level policy class. +namespace sandbox { + +// These are the possible policy outcomes. Note that some of them might +// not apply and can be removed. Also note that The following values only +// specify what to do, not how to do it and it is acceptable given specific +// cases to ignore the policy outcome. +enum EvalResult { + // Comparison opcode values: + EVAL_TRUE, // Opcode condition evaluated true. + EVAL_FALSE, // Opcode condition evaluated false. + EVAL_ERROR, // Opcode condition generated an error while evaluating. + // Action opcode values: + ASK_BROKER, // The target must generate an IPC to the broker. On the broker + // side, this means grant access to the resource. + DENY_ACCESS, // No access granted to the resource. + GIVE_READONLY, // Give readonly access to the resource. + GIVE_ALLACCESS, // Give full access to the resource. + GIVE_CACHED, // IPC is not required. Target can return a cached handle. + GIVE_FIRST, // TODO(cpu) + SIGNAL_ALARM, // Unusual activity. Generate an alarm. + FAKE_SUCCESS, // Do not call original function. Just return 'success'. + FAKE_ACCESS_DENIED, // Do not call original function. Just return 'denied' + // and do not do IPC. + TERMINATE_PROCESS, // Destroy target process. Do IPC as well. +}; + +// The following are the implemented opcodes. +enum OpcodeID { + OP_ALWAYS_FALSE, // Evaluates to false (EVAL_FALSE). + OP_ALWAYS_TRUE, // Evaluates to true (EVAL_TRUE). + OP_NUMBER_MATCH, // Match a 32-bit integer as n == a. + OP_NUMBER_MATCH_RANGE, // Match a 32-bit integer as a <= n <= b. + OP_NUMBER_AND_MATCH, // Match using bitwise AND; as in: n & a != 0. + OP_WSTRING_MATCH, // Match a string for equality. + OP_ACTION // Evaluates to an action opcode. +}; + +// Options that apply to every opcode. They are specified when creating +// each opcode using OpcodeFactory::MakeOpXXXXX() family of functions +// Do nothing special. +const uint32 kPolNone = 0; + +// Convert EVAL_TRUE into EVAL_FALSE and vice-versa. This allows to express +// negated conditions such as if ( a && !b). +const uint32 kPolNegateEval = 1; + +// Zero the MatchContext context structure. This happens after the opcode +// is evaluated. +const uint32 kPolClearContext = 2; + +// Use OR when evaluating this set of opcodes. The policy evaluator by default +// uses AND when evaluating. Very helpful when +// used with kPolNegateEval. For example if you have a condition best expressed +// as if(! (a && b && c)), the use of this flags allows it to be expressed as +// if ((!a) || (!b) || (!c)). +const uint32 kPolUseOREval = 4; + +// Keeps the evaluation state between opcode evaluations. This is used +// for string matching where the next opcode needs to continue matching +// from the last character position from the current opcode. The match +// context is preserved across opcode evaluation unless an opcode specifies +// as an option kPolClearContext. +struct MatchContext { + size_t position; + uint32 options; + + MatchContext() { + Clear(); + } + + void Clear() { + position = 0; + options = 0; + } +}; + +// Models a policy opcode; that is a condition evaluation were all the +// arguments but one are stored in objects of this class. Use OpcodeFactory +// to create objects of this type. +// This class is just an implementation artifact and not exposed to the +// API clients or visible in the intercepted service. Internally, an +// opcode is just: +// - An integer that identifies the actual opcode. +// - An index to indicate which one is the input argument +// - An array of arguments. +// While an OO hierarchy of objects would have been a natural choice, the fact +// that 1) this code can execute before the CRT is loaded, presents serious +// problems in terms of guarantees about the actual state of the vtables and +// 2) because the opcode objects are generated in the broker process, we need to +// use plain objects. To preserve some minimal type safety templates are used +// when possible. +class PolicyOpcode { + friend class OpcodeFactory; + public: + // Evaluates the opcode. For a typical comparison opcode the return value + // is EVAL_TRUE or EVAL_FALSE. If there was an error in the evaluation the + // the return is EVAL_ERROR. If the opcode is an action opcode then the + // return can take other values such as ASK_BROKER. + // parameters: An array of all input parameters. This argument is normally + // created by the macros POLPARAMS_BEGIN() POLPARAMS_END. + // count: The number of parameters passed as first argument. + // match: The match context that is persisted across the opcode evaluation + // sequence. + EvalResult Evaluate(const ParameterSet* parameters, size_t count, + MatchContext* match); + + // Retrieves a stored argument by index. Valid index values are + // from 0 to < kArgumentCount. + template <typename T> + void GetArgument(size_t index, T* argument) const { + static_assert(sizeof(T) <= sizeof(arguments_[0]), "invalid size"); + *argument = *reinterpret_cast<const T*>(&arguments_[index].mem); + } + + // Sets a stored argument by index. Valid index values are + // from 0 to < kArgumentCount. + template <typename T> + void SetArgument(size_t index, const T& argument) { + static_assert(sizeof(T) <= sizeof(arguments_[0]), "invalid size"); + *reinterpret_cast<T*>(&arguments_[index].mem) = argument; + } + + // Retrieves the actual address of an string argument. When using + // GetArgument() to retrieve an index that contains a string, the returned + // value is just an offset to the actual string. + // index: the stored string index. Valid values are from 0 + // to < kArgumentCount. + const wchar_t* GetRelativeString(size_t index) const { + ptrdiff_t str_delta = 0; + GetArgument(index, &str_delta); + const char* delta = reinterpret_cast<const char*>(this) + str_delta; + return reinterpret_cast<const wchar_t*>(delta); + } + + // Returns true if this opcode is an action opcode without actually + // evaluating it. Used to do a quick scan forward to the next opcode group. + bool IsAction() const { + return (OP_ACTION == opcode_id_); + }; + + // Returns the opcode type. + OpcodeID GetID() const { + return opcode_id_; + } + + // Returns the stored options such as kPolNegateEval and others. + uint32 GetOptions() const { + return options_; + } + + // Sets the stored options such as kPolNegateEval. + void SetOptions(uint32 options) { + options_ = base::checked_cast<uint16>(options); + } + + private: + + static const size_t kArgumentCount = 4; // The number of supported argument. + + struct OpcodeArgument { + UINT_PTR mem; + }; + + // Better define placement new in the class instead of relying on the + // global definition which seems to be fubared. + void* operator new(size_t, void* location) { + return location; + } + + // Helper function to evaluate the opcode. The parameters have the same + // meaning that in Evaluate(). + EvalResult EvaluateHelper(const ParameterSet* parameters, + MatchContext* match); + OpcodeID opcode_id_; + int16 parameter_; + // TODO(cpu): Making |options_| a uint32 would avoid casting, but causes test + // failures. Somewhere code is relying on the size of this struct. + // http://crbug.com/420296 + uint16 options_; + OpcodeArgument arguments_[PolicyOpcode::kArgumentCount]; +}; + +enum StringMatchOptions { + CASE_SENSITIVE = 0, // Pay or Not attention to the case as defined by + CASE_INSENSITIVE = 1, // RtlCompareUnicodeString windows API. + EXACT_LENGHT = 2 // Don't do substring match. Do full string match. +}; + +// Opcodes that do string comparisons take a parameter that is the starting +// position to perform the comparison so we can do substring matching. There +// are two special values: +// +// Start from the current position and compare strings advancing forward until +// a match is found if any. Similar to CRT strstr(). +const int kSeekForward = -1; +// Perform a match with the end of the string. It only does a single comparison. +const int kSeekToEnd = 0xfffff; + + +// A PolicyBuffer is a variable size structure that contains all the opcodes +// that are to be created or evaluated in sequence. +struct PolicyBuffer { + size_t opcode_count; + PolicyOpcode opcodes[1]; +}; + +// Helper class to create any opcode sequence. This class is normally invoked +// only by the high level policy module or when you need to handcraft a special +// policy. +// The factory works by creating the opcodes using a chunk of memory given +// in the constructor. The opcodes themselves are allocated from the beginning +// (top) of the memory, while any string that an opcode needs is allocated from +// the end (bottom) of the memory. +// +// In essence: +// +// low address ---> [opcode 1] +// [opcode 2] +// [opcode 3] +// | | <--- memory_top_ +// | free | +// | | +// | | <--- memory_bottom_ +// [string 1] +// high address --> [string 2] +// +// Note that this class does not keep track of the number of opcodes made and +// it is designed to be a building block for low-level policy. +// +// Note that any of the MakeOpXXXXX member functions below can return NULL on +// failure. When that happens opcode sequence creation must be aborted. +class OpcodeFactory { + public: + // memory: base pointer to a chunk of memory where the opcodes are created. + // memory_size: the size in bytes of the memory chunk. + OpcodeFactory(char* memory, size_t memory_size) + : memory_top_(memory) { + memory_bottom_ = &memory_top_[memory_size]; + } + + // policy: contains the raw memory where the opcodes are created. + // memory_size: contains the actual size of the policy argument. + OpcodeFactory(PolicyBuffer* policy, size_t memory_size) { + memory_top_ = reinterpret_cast<char*>(&policy->opcodes[0]); + memory_bottom_ = &memory_top_[memory_size]; + } + + // Returns the available memory to make opcodes. + size_t memory_size() const { + return memory_bottom_ - memory_top_; + } + + // Creates an OpAlwaysFalse opcode. + PolicyOpcode* MakeOpAlwaysFalse(uint32 options); + + // Creates an OpAlwaysFalse opcode. + PolicyOpcode* MakeOpAlwaysTrue(uint32 options); + + // Creates an OpAction opcode. + // action: The action to return when Evaluate() is called. + PolicyOpcode* MakeOpAction(EvalResult action, uint32 options); + + // Creates an OpNumberMatch opcode. + // selected_param: index of the input argument. It must be a uint32 or the + // evaluation result will generate a EVAL_ERROR. + // match: the number to compare against the selected_param. + PolicyOpcode* MakeOpNumberMatch(int16 selected_param, + uint32 match, + uint32 options); + + // Creates an OpNumberMatch opcode (void pointers are cast to numbers). + // selected_param: index of the input argument. It must be an void* or the + // evaluation result will generate a EVAL_ERROR. + // match: the pointer numeric value to compare against selected_param. + PolicyOpcode* MakeOpVoidPtrMatch(int16 selected_param, + const void* match, + uint32 options); + + // Creates an OpNumberMatchRange opcode using the memory passed in the ctor. + // selected_param: index of the input argument. It must be a uint32 or the + // evaluation result will generate a EVAL_ERROR. + // lower_bound, upper_bound: the range to compare against selected_param. + PolicyOpcode* MakeOpNumberMatchRange(int16 selected_param, + uint32 lower_bound, + uint32 upper_bound, + uint32 options); + + // Creates an OpWStringMatch opcode using the raw memory passed in the ctor. + // selected_param: index of the input argument. It must be a wide string + // pointer or the evaluation result will generate a EVAL_ERROR. + // match_str: string to compare against selected_param. + // start_position: when its value is from 0 to < 0x7fff it indicates an + // offset from the selected_param string where to perform the comparison. If + // the value is SeekForward then a substring search is performed. If the + // value is SeekToEnd the comparison is performed against the last part of + // the selected_param string. + // Note that the range in the position (0 to 0x7fff) is dictated by the + // current implementation. + // match_opts: Indicates additional matching flags. Currently CaseInsensitive + // is supported. + PolicyOpcode* MakeOpWStringMatch(int16 selected_param, + const wchar_t* match_str, + int start_position, + StringMatchOptions match_opts, + uint32 options); + + // Creates an OpNumberAndMatch opcode using the raw memory passed in the ctor. + // selected_param: index of the input argument. It must be uint32 or the + // evaluation result will generate a EVAL_ERROR. + // match: the value to bitwise AND against selected_param. + PolicyOpcode* MakeOpNumberAndMatch(int16 selected_param, + uint32 match, + uint32 options); + + private: + // Constructs the common part of every opcode. selected_param is the index + // of the input param to use when evaluating the opcode. Pass -1 in + // selected_param to indicate that no input parameter is required. + PolicyOpcode* MakeBase(OpcodeID opcode_id, uint32 options, + int16 selected_param); + + // Allocates (and copies) a string (of size length) inside the buffer and + // returns the displacement with respect to start. + ptrdiff_t AllocRelative(void* start, const wchar_t* str, size_t lenght); + + // Points to the lowest currently available address of the memory + // used to make the opcodes. This pointer increments as opcodes are made. + char* memory_top_; + + // Points to the highest currently available address of the memory + // used to make the opcodes. This pointer decrements as opcode strings are + // allocated. + char* memory_bottom_; + + DISALLOW_COPY_AND_ASSIGN(OpcodeFactory); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ diff --git a/sandbox/win/src/policy_engine_params.h b/sandbox/win/src/policy_engine_params.h new file mode 100644 index 0000000000..5b3c5ef11c --- /dev/null +++ b/sandbox/win/src/policy_engine_params.h @@ -0,0 +1,202 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_POLICY_ENGINE_PARAMS_H__ +#define SANDBOX_SRC_POLICY_ENGINE_PARAMS_H__ + +#include "base/basictypes.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +// This header defines the classes that allow the low level policy to select +// the input parameters. In order to better make sense of this header is +// recommended that you check policy_engine_opcodes.h first. + +namespace sandbox { + +// Models the set of interesting parameters of an intercepted system call +// normally you don't create objects of this class directly, instead you +// use the POLPARAMS_XXX macros. +// For example, if an intercepted function has the following signature: +// +// NTSTATUS NtOpenFileFunction (PHANDLE FileHandle, +// ACCESS_MASK DesiredAccess, +// POBJECT_ATTRIBUTES ObjectAttributes, +// PIO_STATUS_BLOCK IoStatusBlock, +// ULONG ShareAccess, +// ULONG OpenOptions); +// +// You could say that the following parameters are of interest to policy: +// +// POLPARAMS_BEGIN(open_params) +// POLPARAM(DESIRED_ACCESS) +// POLPARAM(OBJECT_NAME) +// POLPARAM(SECURITY_DESCRIPTOR) +// POLPARAM(IO_STATUS) +// POLPARAM(OPEN_OPTIONS) +// POLPARAMS_END; +// +// and the actual code will use this for defining the parameters: +// +// CountedParameterSet<open_params> p; +// p[open_params::DESIRED_ACCESS] = ParamPickerMake(DesiredAccess); +// p[open_params::OBJECT_NAME] = +// ParamPickerMake(ObjectAttributes->ObjectName); +// p[open_params::SECURITY_DESCRIPTOR] = +// ParamPickerMake(ObjectAttributes->SecurityDescriptor); +// p[open_params::IO_STATUS] = ParamPickerMake(IoStatusBlock); +// p[open_params::OPEN_OPTIONS] = ParamPickerMake(OpenOptions); +// +// These will create an stack-allocated array of ParameterSet objects which +// have each 1) the address of the parameter 2) a numeric id that encodes the +// original C++ type. This allows the policy to treat any set of supported +// argument types uniformily and with some type safety. +// +// TODO(cpu): support not fully implemented yet for unicode string and will +// probably add other types as well. +class ParameterSet { + public: + ParameterSet() : real_type_(INVALID_TYPE), address_(NULL) {} + + // Retrieve the stored parameter. If the type does not match ulong fail. + bool Get(uint32* destination) const { + if (real_type_ != UINT32_TYPE) { + return false; + } + *destination = Void2TypePointerCopy<uint32>(); + return true; + } + + // Retrieve the stored parameter. If the type does not match void* fail. + bool Get(const void** destination) const { + if (real_type_ != VOIDPTR_TYPE) { + return false; + } + *destination = Void2TypePointerCopy<void*>(); + return true; + } + + // Retrieve the stored parameter. If the type does not match wchar_t* fail. + bool Get(const wchar_t** destination) const { + if (real_type_ != WCHAR_TYPE) { + return false; + } + *destination = Void2TypePointerCopy<const wchar_t*>(); + return true; + } + + // False if the parameter is not properly initialized. + bool IsValid() const { + return real_type_ != INVALID_TYPE; + } + + protected: + // The constructor can only be called by derived types, which should + // safely provide the real_type and the address of the argument. + ParameterSet(ArgType real_type, const void* address) + : real_type_(real_type), address_(address) { + } + + private: + // This template provides the same functionality as bits_cast but + // it works with pointer while the former works only with references. + template <typename T> + T Void2TypePointerCopy() const { + return *(reinterpret_cast<const T*>(address_)); + } + + ArgType real_type_; + const void* address_; +}; + +// To safely infer the type, we use a set of template specializations +// in ParameterSetEx with a template function ParamPickerMake to do the +// parameter type deduction. + +// Base template class. Not implemented so using unsupported types should +// fail to compile. +template <typename T> +class ParameterSetEx : public ParameterSet { + public: + ParameterSetEx(const void* address); +}; + +template<> +class ParameterSetEx<void const*> : public ParameterSet { + public: + ParameterSetEx(const void* address) + : ParameterSet(VOIDPTR_TYPE, address) {} +}; + +template<> +class ParameterSetEx<void*> : public ParameterSet { + public: + ParameterSetEx(const void* address) + : ParameterSet(VOIDPTR_TYPE, address) {} +}; + + +template<> +class ParameterSetEx<wchar_t*> : public ParameterSet { + public: + ParameterSetEx(const void* address) + : ParameterSet(WCHAR_TYPE, address) {} +}; + +template<> +class ParameterSetEx<wchar_t const*> : public ParameterSet { + public: + ParameterSetEx(const void* address) + : ParameterSet(WCHAR_TYPE, address) {} +}; + + +template<> +class ParameterSetEx<uint32> : public ParameterSet { + public: + ParameterSetEx(const void* address) + : ParameterSet(UINT32_TYPE, address) {} +}; + +template<> +class ParameterSetEx<UNICODE_STRING> : public ParameterSet { + public: + ParameterSetEx(const void* address) + : ParameterSet(UNISTR_TYPE, address) {} +}; + +template <typename T> +ParameterSet ParamPickerMake(T& parameter) { + return ParameterSetEx<T>(¶meter); +}; + +struct CountedParameterSetBase { + int count; + ParameterSet parameters[1]; +}; + +// This template defines the actual list of policy parameters for a given +// interception. +// Warning: This template stores the address to the actual variables, in +// other words, the values are not copied. +template <typename T> +struct CountedParameterSet { + CountedParameterSet() : count(T::PolParamLast) {} + + ParameterSet& operator[](typename T::Args n) { + return parameters[n]; + } + + CountedParameterSetBase* GetBase() { + return reinterpret_cast<CountedParameterSetBase*>(this); + } + + int count; + ParameterSet parameters[T::PolParamLast]; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_ENGINE_PARAMS_H__ diff --git a/sandbox/win/src/policy_engine_processor.cc b/sandbox/win/src/policy_engine_processor.cc new file mode 100644 index 0000000000..7ca25b2ed2 --- /dev/null +++ b/sandbox/win/src/policy_engine_processor.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_engine_processor.h" + +namespace sandbox { + +void PolicyProcessor::SetInternalState(size_t index, EvalResult result) { + state_.current_index_ = index; + state_.current_result_ = result; +} + +EvalResult PolicyProcessor::GetAction() const { + return state_.current_result_; +} + +// Decides if an opcode can be skipped (not evaluated) or not. The function +// takes as inputs the opcode and the current evaluation context and returns +// true if the opcode should be skipped or not and also can set keep_skipping +// to false to signal that the current instruction should be skipped but not +// the next after the current one. +bool SkipOpcode(const PolicyOpcode& opcode, MatchContext* context, + bool* keep_skipping) { + if (opcode.IsAction()) { + uint32 options = context->options; + context->Clear(); + *keep_skipping = false; + return (kPolUseOREval != options); + } + *keep_skipping = true; + return true; +} + +PolicyResult PolicyProcessor::Evaluate(uint32 options, + ParameterSet* parameters, + size_t param_count) { + if (NULL == policy_) { + return NO_POLICY_MATCH; + } + if (0 == policy_->opcode_count) { + return NO_POLICY_MATCH; + } + if (!(kShortEval & options)) { + return POLICY_ERROR; + } + + MatchContext context; + bool evaluation = false; + bool skip_group = false; + SetInternalState(0, EVAL_FALSE); + size_t count = policy_->opcode_count; + + // Loop over all the opcodes Evaluating in sequence. Since we only support + // short circuit evaluation, we stop as soon as we find an 'action' opcode + // and the current evaluation is true. + // + // Skipping opcodes can happen when we are in AND mode (!kPolUseOREval) and + // have got EVAL_FALSE or when we are in OR mode (kPolUseOREval) and got + // EVAL_TRUE. Skipping will stop at the next action opcode or at the opcode + // after the action depending on kPolUseOREval. + + for (size_t ix = 0; ix != count; ++ix) { + PolicyOpcode& opcode = policy_->opcodes[ix]; + // Skipping block. + if (skip_group) { + if (SkipOpcode(opcode, &context, &skip_group)) { + continue; + } + } + // Evaluation block. + EvalResult result = opcode.Evaluate(parameters, param_count, &context); + switch (result) { + case EVAL_FALSE: + evaluation = false; + if (kPolUseOREval != context.options) { + skip_group = true; + } + break; + case EVAL_ERROR: + if (kStopOnErrors & options) { + return POLICY_ERROR; + } + break; + case EVAL_TRUE: + evaluation = true; + if (kPolUseOREval == context.options) { + skip_group = true; + } + break; + default: + // We have evaluated an action. + SetInternalState(ix, result); + return POLICY_MATCH; + } + } + + if (evaluation) { + // Reaching the end of the policy with a positive evaluation is probably + // an error: we did not find a final action opcode? + return POLICY_ERROR; + } + return NO_POLICY_MATCH; +} + + +} // namespace sandbox diff --git a/sandbox/win/src/policy_engine_processor.h b/sandbox/win/src/policy_engine_processor.h new file mode 100644 index 0000000000..9e416bd35f --- /dev/null +++ b/sandbox/win/src/policy_engine_processor.h @@ -0,0 +1,145 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_POLICY_ENGINE_PROCESSOR_H__ +#define SANDBOX_SRC_POLICY_ENGINE_PROCESSOR_H__ + +#include "base/basictypes.h" +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/policy_engine_opcodes.h" + +namespace sandbox { + +// This header contains the core policy evaluator. In its simplest form +// it evaluates a stream of opcodes assuming that they are laid out in +// memory as opcode groups. +// +// An opcode group has N comparison opcodes plus 1 action opcode. For +// example here we have 3 opcode groups (A, B,C): +// +// [comparison 1] <-- group A start +// [comparison 2] +// [comparison 3] +// [action A ] +// [comparison 1] <-- group B start +// [action B ] +// [comparison 1] <-- group C start +// [comparison 2] +// [action C ] +// +// The opcode evaluator proceeds from the top, evaluating each opcode in +// sequence. An opcode group is evaluated until the first comparison that +// returns false. At that point the rest of the group is skipped and evaluation +// resumes with the first comparison of the next group. When all the comparisons +// in a group have evaluated to true and the action is reached. The group is +// considered a matching group. +// +// In the 'ShortEval' mode evaluation stops when it reaches the end or the first +// matching group. The action opcode from this group is the resulting policy +// action. +// +// In the 'RankedEval' mode evaluation stops only when it reaches the end of the +// the opcode stream. In the process all matching groups are saved and at the +// end the 'best' group is selected (what makes the best is TBD) and the action +// from this group is the resulting policy action. +// +// As explained above, the policy evaluation of a group is a logical AND of +// the evaluation of each opcode. However an opcode can request kPolUseOREval +// which makes the evaluation to use logical OR. Given that each opcode can +// request its evaluation result to be negated with kPolNegateEval you can +// achieve the negation of the total group evaluation. This means that if you +// need to express: +// if (!(c1 && c2 && c3)) +// You can do it by: +// if ((!c1) || (!c2) || (!c3)) +// + +// Possible outcomes of policy evaluation. +enum PolicyResult { + NO_POLICY_MATCH, + POLICY_MATCH, + POLICY_ERROR +}; + +// Policy evaluation flags +// TODO(cpu): implement the options kStopOnErrors & kRankedEval. +// +// Stop evaluating as soon as an error is encountered. +const uint32 kStopOnErrors = 1; +// Ignore all non fatal opcode evaluation errors. +const uint32 kIgnoreErrors = 2; +// Short-circuit evaluation: Only evaluate until opcode group that +// evaluated to true has been found. +const uint32 kShortEval = 4; +// Discussed briefly at the policy design meeting. It will evaluate +// all rules and then return the 'best' rule that evaluated true. +const uint32 kRankedEval = 8; + +// This class evaluates a policy-opcode stream given the memory where the +// opcodes are and an input 'parameter set'. +// +// This class is designed to be callable from interception points +// as low as the NtXXXX service level (it is not currently safe, but +// it is designed to be made safe). +// +// Its usage in an interception is: +// +// POLPARAMS_BEGIN(eval_params) +// POLPARAM(param1) +// POLPARAM(param2) +// POLPARAM(param3) +// POLPARAM(param4) +// POLPARAM(param5) +// POLPARAMS_END; +// +// PolicyProcessor pol_evaluator(policy_memory); +// PolicyResult pr = pol_evaluator.Evaluate(ShortEval, eval_params, +// _countof(eval_params)); +// if (NO_POLICY_MATCH == pr) { +// EvalResult policy_action = pol_evaluator.GetAction(); +// // apply policy here... +// } +// +// Where the POLPARAM() arguments are derived from the intercepted function +// arguments, and represent all the 'interesting' policy inputs, and +// policy_memory is a memory buffer containing the opcode stream that is the +// relevant policy for this intercept. +class PolicyProcessor { + public: + // policy_buffer contains opcodes made with OpcodeFactory. They are usually + // created in the broker process and evaluated in the target process. + + // This constructor is just a variant of the previous constructor. + explicit PolicyProcessor(PolicyBuffer* policy) + : policy_(policy) { + SetInternalState(0, EVAL_FALSE); + } + + // Evaluates a policy-opcode stream. See the comments at the top of this + // class for more info. Returns POLICY_MATCH if a rule set was found that + // matches an active policy. + PolicyResult Evaluate(uint32 options, + ParameterSet* parameters, + size_t parameter_count); + + // If the result of Evaluate() was POLICY_MATCH, calling this function returns + // the recommended policy action. + EvalResult GetAction() const; + + private: + struct { + size_t current_index_; + EvalResult current_result_; + } state_; + + // Sets the currently matching action result. + void SetInternalState(size_t index, EvalResult result); + + PolicyBuffer* policy_; + DISALLOW_COPY_AND_ASSIGN(PolicyProcessor); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_ENGINE_PROCESSOR_H__ diff --git a/sandbox/win/src/policy_engine_unittest.cc b/sandbox/win/src/policy_engine_unittest.cc new file mode 100644 index 0000000000..325a101dac --- /dev/null +++ b/sandbox/win/src/policy_engine_unittest.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define POLPARAMS_BEGIN(x) sandbox::ParameterSet x[] = { +#define POLPARAM(p) sandbox::ParamPickerMake(p), +#define POLPARAMS_END } + +namespace sandbox { + +bool SetupNtdllImports(); + +TEST(PolicyEngineTest, Rules1) { + SetupNtdllImports(); + + // Construct two policy rules that say: + // + // #1 + // If the path is c:\\documents and settings\\* AND + // If the creation mode is 'open existing' AND + // If the security descriptor is null THEN + // Ask the broker. + // + // #2 + // If the security descriptor is null AND + // If the path ends with *.txt AND + // If the creation mode is not 'create new' THEN + // return Access Denied. + + enum FileCreateArgs { + FileNameArg, + CreationDispositionArg, + FlagsAndAttributesArg, + SecurityAttributes + }; + + const size_t policy_sz = 1024; + PolicyBuffer* policy = reinterpret_cast<PolicyBuffer*>(new char[policy_sz]); + OpcodeFactory opcode_maker(policy, policy_sz - 0x40); + + // Add rule set #1 + opcode_maker.MakeOpWStringMatch(FileNameArg, + L"c:\\documents and settings\\", + 0, CASE_INSENSITIVE, kPolNone); + opcode_maker.MakeOpNumberMatch(CreationDispositionArg, OPEN_EXISTING, + kPolNone); + opcode_maker.MakeOpVoidPtrMatch(SecurityAttributes, (void*)NULL, + kPolNone); + opcode_maker.MakeOpAction(ASK_BROKER, kPolNone); + + // Add rule set #2 + opcode_maker.MakeOpWStringMatch(FileNameArg, L".TXT", + kSeekToEnd, CASE_INSENSITIVE, kPolNone); + opcode_maker.MakeOpNumberMatch(CreationDispositionArg, CREATE_NEW, + kPolNegateEval); + opcode_maker.MakeOpAction(FAKE_ACCESS_DENIED, kPolNone); + policy->opcode_count = 7; + + const wchar_t* filename = L"c:\\Documents and Settings\\Microsoft\\BLAH.txt"; + uint32 creation_mode = OPEN_EXISTING; + uint32 flags = FILE_ATTRIBUTE_NORMAL; + void* security_descriptor = NULL; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) + POLPARAM(creation_mode) + POLPARAM(flags) + POLPARAM(security_descriptor) + POLPARAMS_END; + + PolicyResult pr; + PolicyProcessor pol_ev(policy); + + // Test should match the first rule set. + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, pr); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + // Test should still match the first rule set. + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, pr); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + // Changing creation_mode such that evaluation should not match any rule. + creation_mode = CREATE_NEW; + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, pr); + + // Changing creation_mode such that evaluation should match rule #2. + creation_mode = OPEN_ALWAYS; + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, pr); + EXPECT_EQ(FAKE_ACCESS_DENIED, pol_ev.GetAction()); + + delete [] reinterpret_cast<char*>(policy); +} + +} // namespace sandbox diff --git a/sandbox/win/src/policy_low_level.cc b/sandbox/win/src/policy_low_level.cc new file mode 100644 index 0000000000..739321ca07 --- /dev/null +++ b/sandbox/win/src/policy_low_level.cc @@ -0,0 +1,356 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_low_level.h" + +#include <string> +#include <map> + +#include "base/basictypes.h" + +namespace { + + // A single rule can use at most this amount of memory. + const size_t kRuleBufferSize = 1024*4; + + // The possible states of the string matching opcode generator. + enum { + PENDING_NONE, + PENDING_ASTERISK, // Have seen an '*' but have not generated an opcode. + PENDING_QMARK, // Have seen an '?' but have not generated an opcode. + }; + + // The category of the last character seen by the string matching opcode + // generator. + const uint32 kLastCharIsNone = 0; + const uint32 kLastCharIsAlpha = 1; + const uint32 kLastCharIsWild = 2; + const uint32 kLastCharIsAsterisk = kLastCharIsWild + 4; + const uint32 kLastCharIsQuestionM = kLastCharIsWild + 8; +} + +namespace sandbox { + +LowLevelPolicy::LowLevelPolicy(PolicyGlobal* policy_store) + : policy_store_(policy_store) { +} + +// Adding a rule is nothing more than pushing it into an stl container. Done() +// is called for the rule in case the code that made the rule in the first +// place has not done it. +bool LowLevelPolicy::AddRule(int service, PolicyRule* rule) { + if (!rule->Done()) { + return false; + } + + PolicyRule* local_rule = new PolicyRule(*rule); + RuleNode node = {local_rule, service}; + rules_.push_back(node); + return true; +} + +LowLevelPolicy::~LowLevelPolicy() { + // Delete all the rules. + typedef std::list<RuleNode> RuleNodes; + for (RuleNodes::iterator it = rules_.begin(); it != rules_.end(); ++it) { + delete it->rule; + } +} + +// Here is where the heavy byte shuffling is done. We take all the rules and +// 'compile' them into a single memory region. Now, the rules are in random +// order so the first step is to reorganize them into a stl map that is keyed +// by the service id and as a value contains a list with all the rules that +// belong to that service. Then we enter the big for-loop where we carve a +// memory zone for the opcodes and the data and call RebindCopy on each rule +// so they all end up nicely packed in the policy_store_. +bool LowLevelPolicy::Done() { + typedef std::list<RuleNode> RuleNodes; + typedef std::list<const PolicyRule*> RuleList; + typedef std::map<uint32, RuleList> Mmap; + Mmap mmap; + + for (RuleNodes::iterator it = rules_.begin(); it != rules_.end(); ++it) { + mmap[it->service].push_back(it->rule); + } + + PolicyBuffer* current_buffer = &policy_store_->data[0]; + char* buffer_end = reinterpret_cast<char*>(current_buffer) + + policy_store_->data_size; + size_t avail_size = policy_store_->data_size; + + for (Mmap::iterator it = mmap.begin(); it != mmap.end(); ++it) { + uint32 service = (*it).first; + if (service >= kMaxServiceCount) { + return false; + } + policy_store_->entry[service] = current_buffer; + + RuleList::iterator rules_it = (*it).second.begin(); + RuleList::iterator rules_it_end = (*it).second.end(); + + size_t svc_opcode_count = 0; + + for (; rules_it != rules_it_end; ++rules_it) { + const PolicyRule* rule = (*rules_it); + size_t op_count = rule->GetOpcodeCount(); + + size_t opcodes_size = op_count * sizeof(PolicyOpcode); + if (avail_size < opcodes_size) { + return false; + } + size_t data_size = avail_size - opcodes_size; + PolicyOpcode* opcodes_start = ¤t_buffer->opcodes[svc_opcode_count]; + if (!rule->RebindCopy(opcodes_start, opcodes_size, + buffer_end, &data_size)) { + return false; + } + size_t used = avail_size - data_size; + buffer_end -= used; + avail_size -= used; + svc_opcode_count += op_count; + } + + current_buffer->opcode_count += svc_opcode_count; + size_t policy_byte_count = (svc_opcode_count * sizeof(PolicyOpcode)) + / sizeof(current_buffer[0]); + current_buffer = ¤t_buffer[policy_byte_count + 1]; + } + + return true; +} + +PolicyRule::PolicyRule(EvalResult action) + : action_(action), done_(false) { + char* memory = new char[sizeof(PolicyBuffer) + kRuleBufferSize]; + buffer_ = reinterpret_cast<PolicyBuffer*>(memory); + buffer_->opcode_count = 0; + opcode_factory_ = new OpcodeFactory(buffer_, + kRuleBufferSize + sizeof(PolicyOpcode)); +} + +PolicyRule::PolicyRule(const PolicyRule& other) { + if (this == &other) + return; + action_ = other.action_; + done_ = other.done_; + size_t buffer_size = sizeof(PolicyBuffer) + kRuleBufferSize; + char* memory = new char[buffer_size]; + buffer_ = reinterpret_cast<PolicyBuffer*>(memory); + memcpy(buffer_, other.buffer_, buffer_size); + + char* opcode_buffer = reinterpret_cast<char*>(&buffer_->opcodes[0]); + char* next_opcode = &opcode_buffer[GetOpcodeCount() * sizeof(PolicyOpcode)]; + opcode_factory_ = + new OpcodeFactory(next_opcode, other.opcode_factory_->memory_size()); +} + +// This function get called from a simple state machine implemented in +// AddStringMatch() which passes the current state (in state) and it passes +// true in last_call if AddStringMatch() has finished processing the input +// pattern string and this would be the last call to generate any pending +// opcode. The skip_count is the currently accumulated number of '?' seen so +// far and once the associated opcode is generated this function sets it back +// to zero. +bool PolicyRule::GenStringOpcode(RuleType rule_type, + StringMatchOptions match_opts, + uint16 parameter, int state, bool last_call, + int* skip_count, base::string16* fragment) { + + // The last opcode must: + // 1) Always clear the context. + // 2) Preserve the negation. + // 3) Remove the 'OR' mode flag. + uint32 options = kPolNone; + if (last_call) { + if (IF_NOT == rule_type) { + options = kPolClearContext | kPolNegateEval; + } else { + options = kPolClearContext; + } + } else if (IF_NOT == rule_type) { + options = kPolUseOREval | kPolNegateEval; + } + + PolicyOpcode* op = NULL; + + // The fragment string contains the accumulated characters to match with, it + // never contains wildcards (unless they have been escaped) and while there + // is no fragment there is no new string match opcode to generate. + if (fragment->empty()) { + // There is no new opcode to generate but in the last call we have to fix + // the previous opcode because it was really the last but we did not know + // it at that time. + if (last_call && (buffer_->opcode_count > 0)) { + op = &buffer_->opcodes[buffer_->opcode_count - 1]; + op->SetOptions(options); + } + return true; + } + + if (PENDING_ASTERISK == state) { + if (last_call) { + op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), + kSeekToEnd, match_opts, + options); + } else { + op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), + kSeekForward, match_opts, + options); + } + + } else if (PENDING_QMARK == state) { + op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), + *skip_count, match_opts, options); + *skip_count = 0; + } else { + if (last_call) { + match_opts = static_cast<StringMatchOptions>(EXACT_LENGHT | match_opts); + } + op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), 0, + match_opts, options); + } + if (NULL == op) { + return false; + } + ++buffer_->opcode_count; + fragment->clear(); + return true; +} + +bool PolicyRule::AddStringMatch(RuleType rule_type, int16 parameter, + const wchar_t* string, + StringMatchOptions match_opts) { + if (done_) { + // Do not allow to add more rules after generating the action opcode. + return false; + } + + const wchar_t* current_char = string; + uint32 last_char = kLastCharIsNone; + int state = PENDING_NONE; + int skip_count = 0; // counts how many '?' we have seen in a row. + base::string16 fragment; // accumulates the non-wildcard part. + + while (L'\0' != *current_char) { + switch (*current_char) { + case L'*': + if (kLastCharIsWild & last_char) { + // '**' and '&*' is an error. + return false; + } + if (!GenStringOpcode(rule_type, match_opts, parameter, + state, false, &skip_count, &fragment)) { + return false; + } + last_char = kLastCharIsAsterisk; + state = PENDING_ASTERISK; + break; + case L'?': + if (kLastCharIsAsterisk == last_char) { + // '*?' is an error. + return false; + } + if (!GenStringOpcode(rule_type, match_opts, parameter, + state, false, &skip_count, &fragment)) { + return false; + } + ++skip_count; + last_char = kLastCharIsQuestionM; + state = PENDING_QMARK; + break; + case L'/': + // Note: "/?" is an escaped '?'. Eat the slash and fall through. + if (L'?' == current_char[1]) { + ++current_char; + } + default: + fragment += *current_char; + last_char = kLastCharIsAlpha; + } + ++current_char; + } + + if (!GenStringOpcode(rule_type, match_opts, parameter, + state, true, &skip_count, &fragment)) { + return false; + } + return true; +} + +bool PolicyRule::AddNumberMatch(RuleType rule_type, + int16 parameter, + uint32 number, + RuleOp comparison_op) { + if (done_) { + // Do not allow to add more rules after generating the action opcode. + return false; + } + uint32 opts = (rule_type == IF_NOT)? kPolNegateEval : kPolNone; + + if (EQUAL == comparison_op) { + if (NULL == opcode_factory_->MakeOpNumberMatch(parameter, number, opts)) { + return false; + } + } else if (AND == comparison_op) { + if (NULL == opcode_factory_->MakeOpNumberAndMatch(parameter, number, + opts)) { + return false; + } + } + ++buffer_->opcode_count; + return true; +} + +bool PolicyRule::Done() { + if (done_) { + return true; + } + if (NULL == opcode_factory_->MakeOpAction(action_, kPolNone)) { + return false; + } + ++buffer_->opcode_count; + done_ = true; + return true; +} + +bool PolicyRule::RebindCopy(PolicyOpcode* opcode_start, size_t opcode_size, + char* data_start, size_t* data_size) const { + size_t count = buffer_->opcode_count; + for (size_t ix = 0; ix != count; ++ix) { + if (opcode_size < sizeof(PolicyOpcode)) { + return false; + } + PolicyOpcode& opcode = buffer_->opcodes[ix]; + *opcode_start = opcode; + if (OP_WSTRING_MATCH == opcode.GetID()) { + // For this opcode argument 0 is a delta to the string and argument 1 + // is the length (in chars) of the string. + const wchar_t* str = opcode.GetRelativeString(0); + size_t str_len; + opcode.GetArgument(1, &str_len); + str_len = str_len * sizeof(wchar_t); + if ((*data_size) < str_len) { + return false; + } + *data_size -= str_len; + data_start -= str_len; + memcpy(data_start, str, str_len); + // Recompute the string displacement + ptrdiff_t delta = data_start - reinterpret_cast<char*>(opcode_start); + opcode_start->SetArgument(0, delta); + } + ++opcode_start; + opcode_size -= sizeof(PolicyOpcode); + } + + return true; +} + +PolicyRule::~PolicyRule() { + delete [] reinterpret_cast<char*>(buffer_); + delete opcode_factory_; +} + +} // namespace sandbox diff --git a/sandbox/win/src/policy_low_level.h b/sandbox/win/src/policy_low_level.h new file mode 100644 index 0000000000..6a62631311 --- /dev/null +++ b/sandbox/win/src/policy_low_level.h @@ -0,0 +1,184 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_POLICY_LOW_LEVEL_H__ +#define SANDBOX_SRC_POLICY_LOW_LEVEL_H__ + +#include <list> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/policy_engine_opcodes.h" + +// Low level policy classes. +// Built on top of the PolicyOpcode and OpcodeFatory, the low level policy +// provides a way to define rules on strings and numbers but it is unaware +// of Windows specific details or how the Interceptions must be set up. +// To use these classes you construct one or more rules and add them to the +// LowLevelPolicy object like this: +// +// PolicyRule rule1(ASK_BROKER); +// rule1.AddStringMatch(IF, 0, L"\\\\/?/?\\c:\\*Microsoft*\\*.exe", true); +// rule1.AddNumberMatch(IF_NOT, 1, CREATE_ALWAYS, EQUAL); +// rule1.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL); +// +// PolicyRule rule2(FAKE_SUCCESS); +// rule2.AddStringMatch(IF, 0, L"\\\\/?/?\\Pipe\\Chrome.*", false)); +// rule2.AddNumberMatch(IF, 1, OPEN_EXISTING, EQUAL)); +// +// LowLevelPolicy policyGen(*policy_memory); +// policyGen.AddRule(kNtCreateFileSvc, &rule1); +// policyGen.AddRule(kNtCreateFileSvc, &rule2); +// policyGen.Done(); +// +// At this point (error checking omitted) the policy_memory can be copied +// to the target process where it can be evaluated. + +namespace sandbox { + +// TODO(cpu): Move this constant to crosscall_client.h. +const size_t kMaxServiceCount = 32; +static_assert(IPC_LAST_TAG <= kMaxServiceCount, + "kMaxServiceCount is too low"); + +// Defines the memory layout of the policy. This memory is filled by +// LowLevelPolicy object. +// For example: +// +// [Service 0] --points to---\ +// [Service 1] --------------|-----\ +// ...... | | +// [Service N] | | +// [data_size] | | +// [Policy Buffer 0] <-------/ | +// [opcodes of] | +// ....... | +// [Policy Buffer 1] <-------------/ +// [opcodes] +// ....... +// ....... +// [Policy Buffer N] +// [opcodes] +// ....... +// <possibly unused space here> +// ....... +// [opcode string ] +// [opcode string ] +// ....... +// [opcode string ] +struct PolicyGlobal { + PolicyBuffer* entry[kMaxServiceCount]; + size_t data_size; + PolicyBuffer data[1]; +}; + +class PolicyRule; + +// Provides the means to collect rules into a policy store (memory) +class LowLevelPolicy { + public: + // policy_store: must contain allocated memory and the internal + // size fields set to correct values. + explicit LowLevelPolicy(PolicyGlobal* policy_store); + + // Destroys all the policy rules. + ~LowLevelPolicy(); + + // Adds a rule to be generated when Done() is called. + // service: The id of the service that this rule is associated with, + // for example the 'Open Thread' service or the "Create File" service. + // returns false on error. + bool AddRule(int service, PolicyRule* rule); + + // Generates all the rules added with AddRule() into the memory area + // passed on the constructor. Returns false on error. + bool Done(); + + private: + struct RuleNode { + const PolicyRule* rule; + int service; + }; + std::list<RuleNode> rules_; + PolicyGlobal* policy_store_; + DISALLOW_IMPLICIT_CONSTRUCTORS(LowLevelPolicy); +}; + +// There are 'if' rules and 'if not' comparisons +enum RuleType { + IF = 0, + IF_NOT = 1, +}; + +// Possible comparisons for numbers +enum RuleOp { + EQUAL, + AND, + RANGE // TODO(cpu): Implement this option. +}; + +// Provides the means to collect a set of comparisons into a single +// rule and its associated action. +class PolicyRule { + friend class LowLevelPolicy; + + public: + explicit PolicyRule(EvalResult action); + PolicyRule(const PolicyRule& other); + ~PolicyRule(); + + // Adds a string comparison to the rule. + // rule_type: possible values are IF and IF_NOT. + // parameter: the expected index of the argument for this rule. For example + // in a 'create file' service the file name argument can be at index 0. + // string: is the desired matching pattern. + // match_opts: if the pattern matching is case sensitive or not. + bool AddStringMatch(RuleType rule_type, int16 parameter, + const wchar_t* string, StringMatchOptions match_opts); + + // Adds a number match comparison to the rule. + // rule_type: possible values are IF and IF_NOT. + // parameter: the expected index of the argument for this rule. + // number: the value to compare the input to. + // comparison_op: the comparison kind (equal, logical and, etc). + bool AddNumberMatch(RuleType rule_type, + int16 parameter, + uint32 number, + RuleOp comparison_op); + + // Returns the number of opcodes generated so far. + size_t GetOpcodeCount() const { + return buffer_->opcode_count; + } + + // Called when there is no more comparisons to add. Internally it generates + // the last opcode (the action opcode). Returns false if this operation fails. + bool Done(); + + private: + void operator=(const PolicyRule&); + // Called in a loop from AddStringMatch to generate the required string + // match opcodes. rule_type, match_opts and parameter are the same as + // in AddStringMatch. + bool GenStringOpcode(RuleType rule_type, StringMatchOptions match_opts, + uint16 parameter, int state, bool last_call, + int* skip_count, base::string16* fragment); + + // Loop over all generated opcodes and copy them to increasing memory + // addresses from opcode_start and copy the extra data (strings usually) into + // decreasing addresses from data_start. Extra data is only present in the + // string evaluation opcodes. + bool RebindCopy(PolicyOpcode* opcode_start, size_t opcode_size, + char* data_start, size_t* data_size) const; + PolicyBuffer* buffer_; + OpcodeFactory* opcode_factory_; + EvalResult action_; + bool done_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_LOW_LEVEL_H__ diff --git a/sandbox/win/src/policy_low_level_unittest.cc b/sandbox/win/src/policy_low_level_unittest.cc new file mode 100644 index 0000000000..4081a5885d --- /dev/null +++ b/sandbox/win/src/policy_low_level_unittest.cc @@ -0,0 +1,618 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "sandbox/win/src/policy_low_level.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define POLPARAMS_BEGIN(x) sandbox::ParameterSet x[] = { +#define POLPARAM(p) sandbox::ParamPickerMake(p), +#define POLPARAMS_END } + +namespace sandbox { + +bool SetupNtdllImports(); + +// Testing that we allow opcode generation on valid string patterns. +TEST(PolicyEngineTest, StringPatternsOK) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"c:\\adobe\\ver??\\", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"*.tmp", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"c:\\*.doc", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"c:\\windows\\*", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"d:\\adobe\\acrobat.exe", + CASE_SENSITIVE)); +} + +// Testing that we signal invalid string patterns. +TEST(PolicyEngineTest, StringPatternsBAD) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"one**two", CASE_SENSITIVE)); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"**three", CASE_SENSITIVE)); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"five?six*?seven", CASE_SENSITIVE)); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"eight?*nine", CASE_SENSITIVE)); +} + +// Helper function to allocate space (on the heap) for policy. +PolicyGlobal* MakePolicyMemory() { + const size_t kTotalPolicySz = 4096*8; + char* mem = new char[kTotalPolicySz]; + memset(mem, 0, kTotalPolicySz); + PolicyGlobal* policy = reinterpret_cast<PolicyGlobal*>(mem); + policy->data_size = kTotalPolicySz - sizeof(PolicyGlobal); + return policy; +} + +// The simplest test using LowLevelPolicy it should test a single opcode which +// does a exact string comparison. +TEST(PolicyEngineTest, SimpleStrMatch) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"z:\\Directory\\domo.txt", + CASE_INSENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const uint32 kFakeService = 2; + + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = L"Z:\\Directory\\domo.txt"; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[kFakeService]); + + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"Z:\\Directory\\domo.txt.tmp"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete [] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, SimpleIfNotStrMatch) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\", + CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const uint32 kFakeService = 2; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = NULL; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[kFakeService]); + + filename = L"c:\\Microsoft\\"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Microsoft\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + delete [] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, SimpleIfNotStrMatchWild1) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\*", + CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const uint32 kFakeService = 3; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = NULL; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[kFakeService]); + + filename = L"c:\\Microsoft\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + delete [] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, SimpleIfNotStrMatchWild2) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\*.txt", + CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const uint32 kFakeService = 3; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = NULL; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[kFakeService]); + + filename = L"c:\\Microsoft\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Microsoft\\domo.bmp"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + delete [] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, IfNotStrMatchTwoRulesWild1) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\*", + CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddNumberMatch(IF, 1, 24, EQUAL)); + + PolicyGlobal* policy = MakePolicyMemory(); + const uint32 kFakeService = 3; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = NULL; + uint32 access = 0; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAM(access) // Argument 1 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[kFakeService]); + + filename = L"c:\\Microsoft\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Microsoft\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Micronesia\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete [] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, IfNotStrMatchTwoRulesWild2) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddNumberMatch(IF, 1, 24, EQUAL)); + EXPECT_TRUE(pr.AddStringMatch(IF_NOT, 0, L"c:\\GoogleV?\\*.txt", + CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddNumberMatch(IF, 2, 66, EQUAL)); + + PolicyGlobal* policy = MakePolicyMemory(); + const uint32 kFakeService = 3; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = NULL; + uint32 access = 0; + uint32 sharing = 66; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAM(access) // Argument 1 + POLPARAM(sharing) // Argument 2 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[kFakeService]); + + filename = L"c:\\GoogleV2\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\GoogleV2\\domo.bmp"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\GoogleV23\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + + filename = L"c:\\GoogleV2\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Google\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Micronesia\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\GoogleV2\\domo.bmp"; + access = 24; + sharing = 0; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete [] reinterpret_cast<char*>(policy); +} + +// Testing one single rule in one single service. The service is made to +// resemble NtCreateFile. +TEST(PolicyEngineTest, OneRuleTest) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"c:\\*Microsoft*\\*.txt", + CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddNumberMatch(IF_NOT, 1, CREATE_ALWAYS, EQUAL)); + EXPECT_TRUE(pr.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + PolicyGlobal* policy = MakePolicyMemory(); + + const uint32 kNtFakeCreateFile = 7; + + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = L"c:\\Documents and Settings\\Microsoft\\BLAH.txt"; + uint32 creation_mode = OPEN_EXISTING; + uint32 flags = FILE_ATTRIBUTE_NORMAL; + void* security_descriptor = NULL; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAM(creation_mode) // Argument 1 + POLPARAM(flags) // Argument 2 + POLPARAM(security_descriptor) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[kNtFakeCreateFile]); + + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + creation_mode = CREATE_ALWAYS; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + creation_mode = OPEN_EXISTING; + filename = L"c:\\Other\\Path\\Microsoft\\Another file.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Other\\Path\\Microsoft\\Another file.txt.tmp"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + flags = FILE_ATTRIBUTE_DEVICE; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Other\\Macrosoft\\Another file.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Microsoft\\1.txt"; + flags = FILE_ATTRIBUTE_NORMAL; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Microsoft\\1.ttt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete [] reinterpret_cast<char*>(policy); +} + +// Testing 3 rules in 3 services. Two of the services resemble File services. +TEST(PolicyEngineTest, ThreeRulesTest) { + SetupNtdllImports(); + PolicyRule pr_pipe(FAKE_SUCCESS); + EXPECT_TRUE(pr_pipe.AddStringMatch(IF, 0, L"\\\\/?/?\\Pipe\\Chrome.*", + CASE_INSENSITIVE)); + EXPECT_TRUE(pr_pipe.AddNumberMatch(IF, 1, OPEN_EXISTING, EQUAL)); + EXPECT_TRUE(pr_pipe.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc1 = pr_pipe.GetOpcodeCount(); + EXPECT_EQ(3, opc1); + + PolicyRule pr_dump(ASK_BROKER); + EXPECT_TRUE(pr_dump.AddStringMatch(IF, 0, L"\\\\/?/?\\*\\Crash Reports\\*", + CASE_INSENSITIVE)); + EXPECT_TRUE(pr_dump.AddNumberMatch(IF, 1, CREATE_ALWAYS, EQUAL)); + EXPECT_TRUE(pr_dump.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc2 = pr_dump.GetOpcodeCount(); + EXPECT_EQ(4, opc2); + + PolicyRule pr_winexe(SIGNAL_ALARM); + EXPECT_TRUE(pr_winexe.AddStringMatch(IF, 0, L"\\\\/?/?\\C:\\Windows\\*.exe", + CASE_INSENSITIVE)); + EXPECT_TRUE(pr_winexe.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc3 = pr_winexe.GetOpcodeCount(); + EXPECT_EQ(3, opc3); + + PolicyRule pr_adobe(GIVE_CACHED); + EXPECT_TRUE(pr_adobe.AddStringMatch(IF, 0, L"c:\\adobe\\ver?.?\\", + CASE_SENSITIVE)); + EXPECT_TRUE(pr_adobe.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc4 = pr_adobe.GetOpcodeCount(); + EXPECT_EQ(4, opc4); + + PolicyRule pr_none(GIVE_FIRST); + EXPECT_TRUE(pr_none.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_READONLY, AND)); + EXPECT_TRUE(pr_none.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_SYSTEM, AND)); + + size_t opc5 = pr_none.GetOpcodeCount(); + EXPECT_EQ(2, opc5); + + PolicyGlobal* policy = MakePolicyMemory(); + + const uint32 kNtFakeNone = 4; + const uint32 kNtFakeCreateFile = 5; + const uint32 kNtFakeOpenFile = 6; + + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr_pipe)); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr_dump)); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr_winexe)); + + EXPECT_TRUE(policyGen.AddRule(kNtFakeOpenFile, &pr_adobe)); + EXPECT_TRUE(policyGen.AddRule(kNtFakeOpenFile, &pr_pipe)); + + EXPECT_TRUE(policyGen.AddRule(kNtFakeNone, &pr_none)); + + EXPECT_TRUE(policyGen.Done()); + + // Inspect the policy structure manually. + EXPECT_TRUE(NULL == policy->entry[0]); + EXPECT_TRUE(NULL == policy->entry[1]); + EXPECT_TRUE(NULL == policy->entry[2]); + EXPECT_TRUE(NULL == policy->entry[3]); + EXPECT_TRUE(NULL != policy->entry[4]); // kNtFakeNone. + EXPECT_TRUE(NULL != policy->entry[5]); // kNtFakeCreateFile. + EXPECT_TRUE(NULL != policy->entry[6]); // kNtFakeOpenFile. + EXPECT_TRUE(NULL == policy->entry[7]); + + // The total per service opcode counts now must take in account one + // extra opcode (action opcode) per rule. + ++opc1; + ++opc2; + ++opc3; + ++opc4; + ++opc5; + + size_t tc1 = policy->entry[kNtFakeNone]->opcode_count; + size_t tc2 = policy->entry[kNtFakeCreateFile]->opcode_count; + size_t tc3 = policy->entry[kNtFakeOpenFile]->opcode_count; + + EXPECT_EQ(opc5, tc1); + EXPECT_EQ((opc1 + opc2 + opc3), tc2); + EXPECT_EQ((opc1 + opc4), tc3); + + // Check the type of the first and last opcode of each service. + + EXPECT_EQ(OP_NUMBER_AND_MATCH, + policy->entry[kNtFakeNone]->opcodes[0].GetID()); + EXPECT_EQ(OP_ACTION, policy->entry[kNtFakeNone]->opcodes[tc1-1].GetID()); + EXPECT_EQ(OP_WSTRING_MATCH, + policy->entry[kNtFakeCreateFile]->opcodes[0].GetID()); + EXPECT_EQ(OP_ACTION, + policy->entry[kNtFakeCreateFile]->opcodes[tc2-1].GetID()); + EXPECT_EQ(OP_WSTRING_MATCH, + policy->entry[kNtFakeOpenFile]->opcodes[0].GetID()); + EXPECT_EQ(OP_ACTION, policy->entry[kNtFakeOpenFile]->opcodes[tc3-1].GetID()); + + // Test the policy evaluation. + + const wchar_t* filename = L""; + uint32 creation_mode = OPEN_EXISTING; + uint32 flags = FILE_ATTRIBUTE_NORMAL; + void* security_descriptor = NULL; + + POLPARAMS_BEGIN(params) + POLPARAM(filename) // Argument 0 + POLPARAM(creation_mode) // Argument 1 + POLPARAM(flags) // Argument 2 + POLPARAM(security_descriptor) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor eval_CreateFile(policy->entry[kNtFakeCreateFile]); + PolicyProcessor eval_OpenFile(policy->entry[kNtFakeOpenFile]); + PolicyProcessor eval_None(policy->entry[kNtFakeNone]); + + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"\\\\??\\c:\\Windows\\System32\\calc.exe"; + flags = FILE_ATTRIBUTE_SYSTEM; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + flags += FILE_ATTRIBUTE_READONLY; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(GIVE_FIRST, eval_None.GetAction()); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + flags = FILE_ATTRIBUTE_NORMAL; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(SIGNAL_ALARM, eval_CreateFile.GetAction()); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\adobe\\ver3.2\\temp"; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(GIVE_CACHED, eval_OpenFile.GetAction()); + + filename = L"c:\\adobe\\ver3.22\\temp"; + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"\\\\??\\c:\\some path\\other path\\crash reports\\some path"; + creation_mode = CREATE_ALWAYS; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, eval_CreateFile.GetAction()); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"\\\\??\\Pipe\\Chrome.12345"; + creation_mode = OPEN_EXISTING; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(FAKE_SUCCESS, eval_CreateFile.GetAction()); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(FAKE_SUCCESS, eval_OpenFile.GetAction()); + + delete [] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, PolicyRuleCopyConstructorTwoStrings) { + SetupNtdllImports(); + // Both pr_orig and pr_copy should allow hello.* but not *.txt files. + PolicyRule pr_orig(ASK_BROKER); + EXPECT_TRUE(pr_orig.AddStringMatch(IF, 0, L"hello.*", CASE_SENSITIVE)); + + PolicyRule pr_copy(pr_orig); + EXPECT_TRUE(pr_orig.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + EXPECT_TRUE(pr_copy.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(1, &pr_orig)); + EXPECT_TRUE(policyGen.AddRule(2, &pr_copy)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* name = NULL; + POLPARAMS_BEGIN(eval_params) + POLPARAM(name) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev_orig(policy->entry[1]); + name = L"domo.txt"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + name = L"hello.bmp"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev_orig.GetAction()); + + PolicyProcessor pol_ev_copy(policy->entry[2]); + name = L"domo.txt"; + result = pol_ev_copy.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + name = L"hello.bmp"; + result = pol_ev_copy.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev_copy.GetAction()); +} +} // namespace sandbox diff --git a/sandbox/win/src/policy_opcodes_unittest.cc b/sandbox/win/src/policy_opcodes_unittest.cc new file mode 100644 index 0000000000..c999348fa8 --- /dev/null +++ b/sandbox/win/src/policy_opcodes_unittest.cc @@ -0,0 +1,369 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "testing/gtest/include/gtest/gtest.h" + + +#define INIT_GLOBAL_RTL(member) \ + g_nt.member = reinterpret_cast<member##Function>( \ + ::GetProcAddress(ntdll, #member)); \ + if (NULL == g_nt.member) \ + return false + +namespace sandbox { + +const size_t kOpcodeMemory = 1024; + +SANDBOX_INTERCEPT NtExports g_nt; + +bool SetupNtdllImports() { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + + INIT_GLOBAL_RTL(RtlAllocateHeap); + INIT_GLOBAL_RTL(RtlAnsiStringToUnicodeString); + INIT_GLOBAL_RTL(RtlCompareUnicodeString); + INIT_GLOBAL_RTL(RtlCreateHeap); + INIT_GLOBAL_RTL(RtlDestroyHeap); + INIT_GLOBAL_RTL(RtlFreeHeap); + INIT_GLOBAL_RTL(_strnicmp); + INIT_GLOBAL_RTL(strlen); + INIT_GLOBAL_RTL(wcslen); + + return true; +} + +TEST(PolicyEngineTest, ParameterSetTest) { + void* pv1 = reinterpret_cast<void*>(0x477EAA5); + const void* pv2 = reinterpret_cast<void*>(0x987654); + ParameterSet pset1 = ParamPickerMake(pv1); + ParameterSet pset2 = ParamPickerMake(pv2); + + // Test that we can store and retrieve a void pointer: + const void* result1 =0; + uint32 result2 = 0; + EXPECT_TRUE(pset1.Get(&result1)); + EXPECT_TRUE(pv1 == result1); + EXPECT_FALSE(pset1.Get(&result2)); + EXPECT_TRUE(pset2.Get(&result1)); + EXPECT_TRUE(pv2 == result1); + EXPECT_FALSE(pset2.Get(&result2)); + + // Test that we can store and retrieve a uint32: + uint32 number = 12747; + ParameterSet pset3 = ParamPickerMake(number); + EXPECT_FALSE(pset3.Get(&result1)); + EXPECT_TRUE(pset3.Get(&result2)); + EXPECT_EQ(number, result2); + + // Test that we can store and retrieve a string: + const wchar_t* txt = L"S231L"; + ParameterSet pset4 = ParamPickerMake(txt); + const wchar_t* result3 = NULL; + EXPECT_TRUE(pset4.Get(&result3)); + EXPECT_EQ(0, wcscmp(txt, result3)); +} + +TEST(PolicyEngineTest, OpcodeConstraints) { + // Test that PolicyOpcode has no virtual functions + // because these objects are copied over to other processes + // so they cannot have vtables. + EXPECT_FALSE(__is_polymorphic(PolicyOpcode)); + // Keep developers from adding smarts to the opcodes which should + // be pretty much a bag of bytes with a OO interface. + EXPECT_TRUE(__has_trivial_destructor(PolicyOpcode)); + EXPECT_TRUE(__has_trivial_constructor(PolicyOpcode)); + EXPECT_TRUE(__has_trivial_copy(PolicyOpcode)); +} + +TEST(PolicyEngineTest, TrueFalseOpcodes) { + void* dummy = NULL; + ParameterSet ppb1 = ParamPickerMake(dummy); + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + // This opcode always evaluates to true. + PolicyOpcode* op1 = opcode_maker.MakeOpAlwaysFalse(kPolNone); + ASSERT_NE(nullptr, op1); + EXPECT_EQ(EVAL_FALSE, op1->Evaluate(&ppb1, 1, NULL)); + EXPECT_FALSE(op1->IsAction()); + + // This opcode always evaluates to false. + PolicyOpcode* op2 = opcode_maker.MakeOpAlwaysTrue(kPolNone); + ASSERT_NE(nullptr, op2); + EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 1, NULL)); + + // Nulls not allowed on the params. + EXPECT_EQ(EVAL_ERROR, op2->Evaluate(NULL, 0, NULL)); + EXPECT_EQ(EVAL_ERROR, op2->Evaluate(NULL, 1, NULL)); + + // True and False opcodes do not 'require' a number of parameters + EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 0, NULL)); + EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 1, NULL)); + + // Test Inverting the logic. Note that inversion is done outside + // any particular opcode evaluation so no need to repeat for all + // opcodes. + PolicyOpcode* op3 = opcode_maker.MakeOpAlwaysFalse(kPolNegateEval); + ASSERT_NE(nullptr, op3); + EXPECT_EQ(EVAL_TRUE, op3->Evaluate(&ppb1, 1, NULL)); + PolicyOpcode* op4 = opcode_maker.MakeOpAlwaysTrue(kPolNegateEval); + ASSERT_NE(nullptr, op4); + EXPECT_EQ(EVAL_FALSE, op4->Evaluate(&ppb1, 1, NULL)); + + // Test that we clear the match context + PolicyOpcode* op5 = opcode_maker.MakeOpAlwaysTrue(kPolClearContext); + ASSERT_NE(nullptr, op5); + MatchContext context; + context.position = 1; + context.options = kPolUseOREval; + EXPECT_EQ(EVAL_TRUE, op5->Evaluate(&ppb1, 1, &context)); + EXPECT_EQ(0, context.position); + MatchContext context2; + EXPECT_EQ(context2.options, context.options); +} + +TEST(PolicyEngineTest, OpcodeMakerCase1) { + // Testing that the opcode maker does not overrun the + // supplied buffer. It should only be able to make 'count' opcodes. + void* dummy = NULL; + ParameterSet ppb1 = ParamPickerMake(dummy); + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + size_t count = sizeof(memory) / sizeof(PolicyOpcode); + + for (size_t ix =0; ix != count; ++ix) { + PolicyOpcode* op = opcode_maker.MakeOpAlwaysFalse(kPolNone); + ASSERT_NE(nullptr, op); + EXPECT_EQ(EVAL_FALSE, op->Evaluate(&ppb1, 1, NULL)); + } + // There should be no room more another opcode: + PolicyOpcode* op1 = opcode_maker.MakeOpAlwaysFalse(kPolNone); + ASSERT_EQ(nullptr, op1); +} + +TEST(PolicyEngineTest, OpcodeMakerCase2) { + SetupNtdllImports(); + // Testing that the opcode maker does not overrun the + // supplied buffer. It should only be able to make 'count' opcodes. + // The difference with the previous test is that this opcodes allocate + // the string 'txt2' inside the same buffer. + const wchar_t* txt1 = L"1234"; + const wchar_t txt2[] = L"123"; + + ParameterSet ppb1 = ParamPickerMake(txt1); + MatchContext mc1; + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + size_t count = sizeof(memory) / (sizeof(PolicyOpcode) + sizeof(txt2)); + + // Test that it does not overrun the buffer. + for (size_t ix =0; ix != count; ++ix) { + PolicyOpcode* op = opcode_maker.MakeOpWStringMatch(0, txt2, 0, + CASE_SENSITIVE, + kPolClearContext); + ASSERT_NE(nullptr, op); + EXPECT_EQ(EVAL_TRUE, op->Evaluate(&ppb1, 1, &mc1)); + } + + // There should be no room more another opcode: + PolicyOpcode* op1 = opcode_maker.MakeOpWStringMatch(0, txt2, 0, + CASE_SENSITIVE, + kPolNone); + ASSERT_EQ(nullptr, op1); +} + +TEST(PolicyEngineTest, IntegerOpcodes) { + const wchar_t* txt = L"abcdef"; + uint32 num1 = 42; + uint32 num2 = 113377; + + ParameterSet pp_wrong1 = ParamPickerMake(txt); + ParameterSet pp_num1 = ParamPickerMake(num1); + ParameterSet pp_num2 = ParamPickerMake(num2); + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + // Test basic match for uint32s 42 == 42 and 42 != 113377. + PolicyOpcode* op_m42 = opcode_maker.MakeOpNumberMatch(0, 42UL, kPolNone); + ASSERT_NE(nullptr, op_m42); + EXPECT_EQ(EVAL_TRUE, op_m42->Evaluate(&pp_num1, 1, NULL)); + EXPECT_EQ(EVAL_FALSE, op_m42->Evaluate(&pp_num2, 1, NULL)); + EXPECT_EQ(EVAL_ERROR, op_m42->Evaluate(&pp_wrong1, 1, NULL)); + + // Test basic match for void pointers. + const void* vp = NULL; + ParameterSet pp_num3 = ParamPickerMake(vp); + PolicyOpcode* op_vp_null = opcode_maker.MakeOpVoidPtrMatch(0, NULL, + kPolNone); + ASSERT_NE(nullptr, op_vp_null); + EXPECT_EQ(EVAL_TRUE, op_vp_null->Evaluate(&pp_num3, 1, NULL)); + EXPECT_EQ(EVAL_FALSE, op_vp_null->Evaluate(&pp_num1, 1, NULL)); + EXPECT_EQ(EVAL_ERROR, op_vp_null->Evaluate(&pp_wrong1, 1, NULL)); + + // Basic range test [41 43] (inclusive). + PolicyOpcode* op_range1 = + opcode_maker.MakeOpNumberMatchRange(0, 41, 43, kPolNone); + ASSERT_NE(nullptr, op_range1); + EXPECT_EQ(EVAL_TRUE, op_range1->Evaluate(&pp_num1, 1, NULL)); + EXPECT_EQ(EVAL_FALSE, op_range1->Evaluate(&pp_num2, 1, NULL)); + EXPECT_EQ(EVAL_ERROR, op_range1->Evaluate(&pp_wrong1, 1, NULL)); +} + +TEST(PolicyEngineTest, LogicalOpcodes) { + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + uint32 num1 = 0x10100702; + ParameterSet pp_num1 = ParamPickerMake(num1); + + PolicyOpcode* op_and1 = + opcode_maker.MakeOpNumberAndMatch(0, 0x00100000, kPolNone); + ASSERT_NE(nullptr, op_and1); + EXPECT_EQ(EVAL_TRUE, op_and1->Evaluate(&pp_num1, 1, NULL)); + PolicyOpcode* op_and2 = + opcode_maker.MakeOpNumberAndMatch(0, 0x00000001, kPolNone); + ASSERT_NE(nullptr, op_and2); + EXPECT_EQ(EVAL_FALSE, op_and2->Evaluate(&pp_num1, 1, NULL)); +} + +TEST(PolicyEngineTest, WCharOpcodes1) { + SetupNtdllImports(); + + const wchar_t* txt1 = L"the quick fox jumps over the lazy dog"; + const wchar_t txt2[] = L"the quick"; + const wchar_t txt3[] = L" fox jumps"; + const wchar_t txt4[] = L"the lazy dog"; + const wchar_t txt5[] = L"jumps over"; + const wchar_t txt6[] = L"g"; + + ParameterSet pp_tc1 = ParamPickerMake(txt1); + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + PolicyOpcode* op1 = opcode_maker.MakeOpWStringMatch(0, txt2, 0, + CASE_SENSITIVE, + kPolNone); + ASSERT_NE(nullptr, op1); + + // Simplest substring match from pos 0. It should be a successful match + // and the match context should be updated. + MatchContext mc1; + EXPECT_EQ(EVAL_TRUE, op1->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_TRUE(_countof(txt2) == mc1.position + 1); + + // Matching again should fail and the context should be unmodified. + EXPECT_EQ(EVAL_FALSE, op1->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_TRUE(_countof(txt2) == mc1.position + 1); + + // Using the same match context we should continue where we left + // in the previous successful match, + PolicyOpcode* op3 = opcode_maker.MakeOpWStringMatch(0, txt3, 0, + CASE_SENSITIVE, + kPolNone); + ASSERT_NE(nullptr, op3); + EXPECT_EQ(EVAL_TRUE, op3->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_TRUE(_countof(txt3) + _countof(txt2) == mc1.position + 2); + + // We now keep on matching but now we skip 6 characters which means + // we skip the string ' over '. And we zero the match context. This is + // the primitive that we use to build '??'. + PolicyOpcode* op4 = opcode_maker.MakeOpWStringMatch(0, txt4, 6, + CASE_SENSITIVE, + kPolClearContext); + ASSERT_NE(nullptr, op4); + EXPECT_EQ(EVAL_TRUE, op4->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(0, mc1.position); + + // Test that we can properly match the last part of the string + PolicyOpcode* op4b = opcode_maker.MakeOpWStringMatch(0, txt4, kSeekToEnd, + CASE_SENSITIVE, + kPolClearContext); + ASSERT_NE(nullptr, op4b); + EXPECT_EQ(EVAL_TRUE, op4b->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(0, mc1.position); + + // Test matching 'jumps over' over the entire string. This is the + // primitive we build '*' from. + PolicyOpcode* op5 = opcode_maker.MakeOpWStringMatch(0, txt5, kSeekForward, + CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op5); + EXPECT_EQ(EVAL_TRUE, op5->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(24, mc1.position); + + // Test that we don't match because it is not at the end of the string + PolicyOpcode* op5b = opcode_maker.MakeOpWStringMatch(0, txt5, kSeekToEnd, + CASE_SENSITIVE, + kPolNone); + ASSERT_NE(nullptr, op5b); + EXPECT_EQ(EVAL_FALSE, op5b->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(24, mc1.position); + + // Test that we function if the string does not fit. In this case we + // try to match 'the lazy dog' against 'he lazy dog'. + PolicyOpcode* op6 = opcode_maker.MakeOpWStringMatch(0, txt4, 2, + CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op6); + EXPECT_EQ(EVAL_FALSE, op6->Evaluate(&pp_tc1, 1, &mc1)); + + // Testing matching against 'g' which should be the last char. + MatchContext mc2; + PolicyOpcode* op7 = opcode_maker.MakeOpWStringMatch(0, txt6, kSeekForward, + CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op7); + EXPECT_EQ(EVAL_TRUE, op7->Evaluate(&pp_tc1, 1, &mc2)); + EXPECT_EQ(37, mc2.position); + + // Trying to match again should fail since we are in the last char. + // This also covers a couple of boundary conditions. + EXPECT_EQ(EVAL_FALSE, op7->Evaluate(&pp_tc1, 1, &mc2)); + EXPECT_EQ(37, mc2.position); +} + +TEST(PolicyEngineTest, WCharOpcodes2) { + SetupNtdllImports(); + + const wchar_t* path1 = L"c:\\documents and settings\\Microsoft\\BLAH.txt"; + const wchar_t txt1[] = L"Settings\\microsoft"; + ParameterSet pp_tc1 = ParamPickerMake(path1); + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + MatchContext mc1; + + // Testing case-insensitive does not buy us much since it this option + // is just passed to the Microsoft API that we use normally, but just for + // coverage, here it is: + PolicyOpcode* op1s = opcode_maker.MakeOpWStringMatch(0, txt1, kSeekForward, + CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op1s); + PolicyOpcode* op1i = opcode_maker.MakeOpWStringMatch(0, txt1, kSeekForward, + CASE_INSENSITIVE, + kPolNone); + ASSERT_NE(nullptr, op1i); + EXPECT_EQ(EVAL_FALSE, op1s->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(EVAL_TRUE, op1i->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(35, mc1.position); +} + +TEST(PolicyEngineTest, ActionOpcodes) { + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + MatchContext mc1; + void* dummy = NULL; + ParameterSet ppb1 = ParamPickerMake(dummy); + + PolicyOpcode* op1 = opcode_maker.MakeOpAction(ASK_BROKER, kPolNone); + ASSERT_NE(nullptr, op1); + EXPECT_TRUE(op1->IsAction()); + EXPECT_EQ(ASK_BROKER, op1->Evaluate(&ppb1, 1, &mc1)); +} + +} // namespace sandbox diff --git a/sandbox/win/src/policy_params.h b/sandbox/win/src/policy_params.h new file mode 100644 index 0000000000..e051d2b59d --- /dev/null +++ b/sandbox/win/src/policy_params.h @@ -0,0 +1,67 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_POLICY_PARAMS_H__ +#define SANDBOX_SRC_POLICY_PARAMS_H__ + +#include "sandbox/win/src/policy_engine_params.h" + +namespace sandbox { + +class ParameterSet; + +// Warning: The following macros store the address to the actual variables, in +// other words, the values are not copied. +#define POLPARAMS_BEGIN(type) class type { public: enum Args { +#define POLPARAM(arg) arg, +#define POLPARAMS_END(type) PolParamLast }; }; \ + typedef sandbox::ParameterSet type##Array [type::PolParamLast]; + +// Policy parameters for file open / create. +POLPARAMS_BEGIN(OpenFile) + POLPARAM(NAME) + POLPARAM(BROKER) // TRUE if called from the broker. + POLPARAM(ACCESS) + POLPARAM(DISPOSITION) + POLPARAM(OPTIONS) +POLPARAMS_END(OpenFile) + +// Policy parameter for name-based policies. +POLPARAMS_BEGIN(FileName) + POLPARAM(NAME) + POLPARAM(BROKER) // TRUE if called from the broker. +POLPARAMS_END(FileName) + +static_assert(OpenFile::NAME == static_cast<int>(FileName::NAME), + "to simplify fs policies"); +static_assert(OpenFile::BROKER == static_cast<int>(FileName::BROKER), + "to simplify fs policies"); + +// Policy parameter for name-based policies. +POLPARAMS_BEGIN(NameBased) + POLPARAM(NAME) +POLPARAMS_END(NameBased) + +// Policy parameters for open event. +POLPARAMS_BEGIN(OpenEventParams) + POLPARAM(NAME) + POLPARAM(ACCESS) +POLPARAMS_END(OpenEventParams) + +// Policy Parameters for reg open / create. +POLPARAMS_BEGIN(OpenKey) + POLPARAM(NAME) + POLPARAM(ACCESS) +POLPARAMS_END(OpenKey) + +// Policy parameter for name-based policies. +POLPARAMS_BEGIN(HandleTarget) + POLPARAM(NAME) + POLPARAM(TARGET) +POLPARAMS_END(HandleTarget) + + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_PARAMS_H__ diff --git a/sandbox/win/src/policy_target.cc b/sandbox/win/src/policy_target.cc new file mode 100644 index 0000000000..fb464cc0cb --- /dev/null +++ b/sandbox/win/src/policy_target.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_target.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +// Handle for our private heap. +extern void* g_heap; + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +// Policy data. +extern void* volatile g_shared_policy_memory; +SANDBOX_INTERCEPT size_t g_shared_policy_size; + +bool QueryBroker(int ipc_id, CountedParameterSetBase* params) { + DCHECK_NT(static_cast<size_t>(ipc_id) < kMaxServiceCount); + DCHECK_NT(g_shared_policy_memory); + DCHECK_NT(g_shared_policy_size > 0); + + if (static_cast<size_t>(ipc_id) >= kMaxServiceCount) + return false; + + PolicyGlobal* global_policy = + reinterpret_cast<PolicyGlobal*>(g_shared_policy_memory); + + if (!global_policy->entry[ipc_id]) + return false; + + PolicyBuffer* policy = reinterpret_cast<PolicyBuffer*>( + reinterpret_cast<char*>(g_shared_policy_memory) + + reinterpret_cast<size_t>(global_policy->entry[ipc_id])); + + if ((reinterpret_cast<size_t>(global_policy->entry[ipc_id]) > + global_policy->data_size) || + (g_shared_policy_size < global_policy->data_size)) { + NOTREACHED_NT(); + return false; + } + + for (int i = 0; i < params->count; i++) { + if (!params->parameters[i].IsValid()) { + NOTREACHED_NT(); + return false; + } + } + + PolicyProcessor processor(policy); + PolicyResult result = processor.Evaluate(kShortEval, params->parameters, + params->count); + DCHECK_NT(POLICY_ERROR != result); + + return POLICY_MATCH == result && ASK_BROKER == processor.GetAction(); +} + +// ----------------------------------------------------------------------- + +// Hooks NtSetInformationThread to block RevertToSelf from being +// called before the actual call to LowerToken. +NTSTATUS WINAPI TargetNtSetInformationThread( + NtSetInformationThreadFunction orig_SetInformationThread, HANDLE thread, + NT_THREAD_INFORMATION_CLASS thread_info_class, PVOID thread_information, + ULONG thread_information_bytes) { + do { + if (SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) + break; + if (ThreadImpersonationToken != thread_info_class) + break; + // This is a revert to self. + return STATUS_SUCCESS; + } while (false); + + return orig_SetInformationThread(thread, thread_info_class, + thread_information, + thread_information_bytes); +} + +// Hooks NtOpenThreadToken to force the open_as_self parameter to be set to +// FALSE if we are still running with the impersonation token. open_as_self set +// to TRUE means that the token will be open using the process token instead of +// the impersonation token. This is bad because the process token does not have +// access to open the thread token. +NTSTATUS WINAPI TargetNtOpenThreadToken( + NtOpenThreadTokenFunction orig_OpenThreadToken, HANDLE thread, + ACCESS_MASK desired_access, BOOLEAN open_as_self, PHANDLE token) { + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) + open_as_self = FALSE; + + return orig_OpenThreadToken(thread, desired_access, open_as_self, token); +} + +// See comment for TargetNtOpenThreadToken +NTSTATUS WINAPI TargetNtOpenThreadTokenEx( + NtOpenThreadTokenExFunction orig_OpenThreadTokenEx, HANDLE thread, + ACCESS_MASK desired_access, BOOLEAN open_as_self, ULONG handle_attributes, + PHANDLE token) { + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) + open_as_self = FALSE; + + return orig_OpenThreadTokenEx(thread, desired_access, open_as_self, + handle_attributes, token); +} + +} // namespace sandbox diff --git a/sandbox/win/src/policy_target.h b/sandbox/win/src/policy_target.h new file mode 100644 index 0000000000..8a2b437095 --- /dev/null +++ b/sandbox/win/src/policy_target.h @@ -0,0 +1,45 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_POLICY_TARGET_H__ +#define SANDBOX_SRC_POLICY_TARGET_H__ + +namespace sandbox { + +struct CountedParameterSetBase; + +// Performs a policy lookup and returns true if the request should be passed to +// the broker process. +bool QueryBroker(int ipc_id, CountedParameterSetBase* params); + +extern "C" { + +// Interception of NtSetInformationThread on the child process. +// It should never be called directly. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtSetInformationThread( + NtSetInformationThreadFunction orig_SetInformationThread, HANDLE thread, + NT_THREAD_INFORMATION_CLASS thread_info_class, PVOID thread_information, + ULONG thread_information_bytes); + +// Interception of NtOpenThreadToken on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThreadToken( + NtOpenThreadTokenFunction orig_OpenThreadToken, HANDLE thread, + ACCESS_MASK desired_access, BOOLEAN open_as_self, PHANDLE token); + +// Interception of NtOpenThreadTokenEx on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThreadTokenEx( + NtOpenThreadTokenExFunction orig_OpenThreadTokenEx, HANDLE thread, + ACCESS_MASK desired_access, BOOLEAN open_as_self, ULONG handle_attributes, + PHANDLE token); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_TARGET_H__ diff --git a/sandbox/win/src/policy_target_test.cc b/sandbox/win/src/policy_target_test.cc new file mode 100644 index 0000000000..4395a4f0d2 --- /dev/null +++ b/sandbox/win/src/policy_target_test.cc @@ -0,0 +1,412 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/shared_memory.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +#define BINDNTDLL(name) \ + name ## Function name = reinterpret_cast<name ## Function>( \ + ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name)) + +// Reverts to self and verify that SetInformationToken was faked. Returns +// SBOX_TEST_SUCCEEDED if faked and SBOX_TEST_FAILED if not faked. +SBOX_TESTS_COMMAND int PolicyTargetTest_token(int argc, wchar_t **argv) { + HANDLE thread_token; + // Get the thread token, using impersonation. + if (!::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | + TOKEN_DUPLICATE, FALSE, &thread_token)) + return ::GetLastError(); + + ::RevertToSelf(); + ::CloseHandle(thread_token); + + int ret = SBOX_TEST_FAILED; + if (::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE, + FALSE, &thread_token)) { + ret = SBOX_TEST_SUCCEEDED; + ::CloseHandle(thread_token); + } + return ret; +} + +// Stores the high privilege token on a static variable, change impersonation +// again to that one and verify that we are not interfering anymore with +// RevertToSelf. +SBOX_TESTS_COMMAND int PolicyTargetTest_steal(int argc, wchar_t **argv) { + static HANDLE thread_token; + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) { + if (!::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | + TOKEN_DUPLICATE, FALSE, &thread_token)) + return ::GetLastError(); + } else { + if (!::SetThreadToken(NULL, thread_token)) + return ::GetLastError(); + + // See if we fake the call again. + int ret = PolicyTargetTest_token(argc, argv); + ::CloseHandle(thread_token); + return ret; + } + return 0; +} + +// Opens the thread token with and without impersonation. +SBOX_TESTS_COMMAND int PolicyTargetTest_token2(int argc, wchar_t **argv) { + HANDLE thread_token; + // Get the thread token, using impersonation. + if (!::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | + TOKEN_DUPLICATE, FALSE, &thread_token)) + return ::GetLastError(); + ::CloseHandle(thread_token); + + // Get the thread token, without impersonation. + if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE, + TRUE, &thread_token)) + return ::GetLastError(); + ::CloseHandle(thread_token); + return SBOX_TEST_SUCCEEDED; +} + +// Opens the thread token with and without impersonation, using +// NtOpenThreadTokenEX. +SBOX_TESTS_COMMAND int PolicyTargetTest_token3(int argc, wchar_t **argv) { + BINDNTDLL(NtOpenThreadTokenEx); + if (!NtOpenThreadTokenEx) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + HANDLE thread_token; + // Get the thread token, using impersonation. + NTSTATUS status = NtOpenThreadTokenEx(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, + FALSE, 0, &thread_token); + if (status == STATUS_NO_TOKEN) + return ERROR_NO_TOKEN; + if (!NT_SUCCESS(status)) + return SBOX_TEST_FAILED; + + ::CloseHandle(thread_token); + + // Get the thread token, without impersonation. + status = NtOpenThreadTokenEx(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, TRUE, 0, + &thread_token); + if (!NT_SUCCESS(status)) + return SBOX_TEST_FAILED; + + ::CloseHandle(thread_token); + return SBOX_TEST_SUCCEEDED; +} + +// Tests that we can open the current thread. +SBOX_TESTS_COMMAND int PolicyTargetTest_thread(int argc, wchar_t **argv) { + DWORD thread_id = ::GetCurrentThreadId(); + HANDLE thread = ::OpenThread(SYNCHRONIZE, FALSE, thread_id); + if (!thread) + return ::GetLastError(); + if (!::CloseHandle(thread)) + return ::GetLastError(); + + return SBOX_TEST_SUCCEEDED; +} + +// New thread entry point: do nothing. +DWORD WINAPI PolicyTargetTest_thread_main(void* param) { + ::Sleep(INFINITE); + return 0; +} + +// Tests that we can create a new thread, and open it. +SBOX_TESTS_COMMAND int PolicyTargetTest_thread2(int argc, wchar_t **argv) { + // Use default values to create a new thread. + DWORD thread_id; + HANDLE thread = ::CreateThread(NULL, 0, &PolicyTargetTest_thread_main, 0, 0, + &thread_id); + if (!thread) + return ::GetLastError(); + if (!::CloseHandle(thread)) + return ::GetLastError(); + + thread = ::OpenThread(SYNCHRONIZE, FALSE, thread_id); + if (!thread) + return ::GetLastError(); + + if (!::CloseHandle(thread)) + return ::GetLastError(); + + return SBOX_TEST_SUCCEEDED; +} + +// Tests that we can call CreateProcess. +SBOX_TESTS_COMMAND int PolicyTargetTest_process(int argc, wchar_t **argv) { + // Use default values to create a new process. + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + PROCESS_INFORMATION temp_process_info = {}; + // Note: CreateProcessW() can write to its lpCommandLine, don't pass a + // raw string literal. + base::string16 writable_cmdline_str(L"foo.exe"); + if (!::CreateProcessW(L"foo.exe", &writable_cmdline_str[0], NULL, NULL, FALSE, + 0, NULL, NULL, &startup_info, &temp_process_info)) + return SBOX_TEST_SUCCEEDED; + base::win::ScopedProcessInformation process_info(temp_process_info); + return SBOX_TEST_FAILED; +} + +TEST(PolicyTargetTest, SetInformationThread) { + TestRunner runner; + if (base::win::GetVersion() >= base::win::VERSION_XP) { + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token")); + } + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token")); + + runner.SetTestState(EVERY_STATE); + if (base::win::GetVersion() >= base::win::VERSION_XP) + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"PolicyTargetTest_steal")); +} + +TEST(PolicyTargetTest, OpenThreadToken) { + TestRunner runner; + if (base::win::GetVersion() >= base::win::VERSION_XP) { + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token2")); + } + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token2")); +} + +TEST(PolicyTargetTest, OpenThreadTokenEx) { + TestRunner runner; + if (base::win::GetVersion() < base::win::VERSION_XP) + return; + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token3")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token3")); +} + +TEST(PolicyTargetTest, OpenThread) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread")) << + "Opens the current thread"; + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread2")) << + "Creates a new thread and opens it"; +} + +TEST(PolicyTargetTest, OpenProcess) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_process")) << + "Opens a process"; +} + +// Launches the app in the sandbox and ask it to wait in an +// infinite loop. Waits for 2 seconds and then check if the +// desktop associated with the app thread is not the same as the +// current desktop. +TEST(PolicyTargetTest, DesktopPolicy) { + BrokerServices* broker = GetBroker(); + + // Precreate the desktop. + TargetPolicy* temp_policy = broker->CreatePolicy(); + temp_policy->CreateAlternateDesktop(false); + temp_policy->Release(); + + ASSERT_TRUE(broker != NULL); + + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(NULL, prog_name, MAX_PATH); + + base::string16 arguments(L"\""); + arguments += prog_name; + arguments += L"\" -child 0 wait"; // Don't care about the "state" argument. + + // Launch the app. + ResultCode result = SBOX_ALL_OK; + base::win::ScopedProcessInformation target; + + TargetPolicy* policy = broker->CreatePolicy(); + policy->SetAlternateDesktop(false); + policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + PROCESS_INFORMATION temp_process_info = {}; + result = broker->SpawnTarget(prog_name, arguments.c_str(), policy, + &temp_process_info); + base::string16 desktop_name = policy->GetAlternateDesktop(); + policy->Release(); + + EXPECT_EQ(SBOX_ALL_OK, result); + if (result == SBOX_ALL_OK) + target.Set(temp_process_info); + + EXPECT_EQ(1, ::ResumeThread(target.thread_handle())); + + EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(target.process_handle(), 2000)); + + EXPECT_NE(::GetThreadDesktop(target.thread_id()), + ::GetThreadDesktop(::GetCurrentThreadId())); + + HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, FALSE, DESKTOP_ENUMERATE); + EXPECT_TRUE(NULL != desk); + EXPECT_TRUE(::CloseDesktop(desk)); + EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); + + ::WaitForSingleObject(target.process_handle(), INFINITE); + + // Close the desktop handle. + temp_policy = broker->CreatePolicy(); + temp_policy->DestroyAlternateDesktop(); + temp_policy->Release(); + + // Make sure the desktop does not exist anymore. + desk = ::OpenDesktop(desktop_name.c_str(), 0, FALSE, DESKTOP_ENUMERATE); + EXPECT_TRUE(NULL == desk); +} + +// Launches the app in the sandbox and ask it to wait in an +// infinite loop. Waits for 2 seconds and then check if the +// winstation associated with the app thread is not the same as the +// current desktop. +TEST(PolicyTargetTest, WinstaPolicy) { + BrokerServices* broker = GetBroker(); + + // Precreate the desktop. + TargetPolicy* temp_policy = broker->CreatePolicy(); + temp_policy->CreateAlternateDesktop(true); + temp_policy->Release(); + + ASSERT_TRUE(broker != NULL); + + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(NULL, prog_name, MAX_PATH); + + base::string16 arguments(L"\""); + arguments += prog_name; + arguments += L"\" -child 0 wait"; // Don't care about the "state" argument. + + // Launch the app. + ResultCode result = SBOX_ALL_OK; + base::win::ScopedProcessInformation target; + + TargetPolicy* policy = broker->CreatePolicy(); + policy->SetAlternateDesktop(true); + policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + PROCESS_INFORMATION temp_process_info = {}; + result = broker->SpawnTarget(prog_name, arguments.c_str(), policy, + &temp_process_info); + base::string16 desktop_name = policy->GetAlternateDesktop(); + policy->Release(); + + EXPECT_EQ(SBOX_ALL_OK, result); + if (result == SBOX_ALL_OK) + target.Set(temp_process_info); + + EXPECT_EQ(1, ::ResumeThread(target.thread_handle())); + + EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(target.process_handle(), 2000)); + + EXPECT_NE(::GetThreadDesktop(target.thread_id()), + ::GetThreadDesktop(::GetCurrentThreadId())); + + ASSERT_FALSE(desktop_name.empty()); + + // Make sure there is a backslash, for the window station name. + EXPECT_NE(desktop_name.find_first_of(L'\\'), base::string16::npos); + + // Isolate the desktop name. + desktop_name = desktop_name.substr(desktop_name.find_first_of(L'\\') + 1); + + HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, FALSE, DESKTOP_ENUMERATE); + // This should fail if the desktop is really on another window station. + EXPECT_FALSE(NULL != desk); + EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); + + ::WaitForSingleObject(target.process_handle(), INFINITE); + + // Close the desktop handle. + temp_policy = broker->CreatePolicy(); + temp_policy->DestroyAlternateDesktop(); + temp_policy->Release(); +} + +// Launches the app in the sandbox and share a handle with it. The app should +// be able to use the handle. +TEST(PolicyTargetTest, ShareHandleTest) { + // The way we share handles via STARTUPINFOEX does not work on XP. + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return; + + BrokerServices* broker = GetBroker(); + ASSERT_TRUE(broker != NULL); + + base::StringPiece contents = "Hello World"; + std::string name = "TestSharedMemory"; + base::SharedMemoryCreateOptions options; + options.size = contents.size(); + options.share_read_only = true; + options.name_deprecated = &name; + base::SharedMemory writable_shmem; + ASSERT_TRUE(writable_shmem.Create(options)); + ASSERT_TRUE(writable_shmem.Map(options.size)); + memcpy(writable_shmem.memory(), contents.data(), contents.size()); + + base::SharedMemory read_only_view; + ASSERT_TRUE(read_only_view.Open(name, true)); + + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(NULL, prog_name, MAX_PATH); + + TargetPolicy* policy = broker->CreatePolicy(); + void* shared_handle = policy->AddHandleToShare( + read_only_view.handle()); + + base::string16 arguments(L"\""); + arguments += prog_name; + arguments += L"\" -child 0 shared_memory_handle "; + arguments += base::UintToString16( + reinterpret_cast<unsigned int>(shared_handle)); + + // Launch the app. + ResultCode result = SBOX_ALL_OK; + base::win::ScopedProcessInformation target; + + policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + PROCESS_INFORMATION temp_process_info = {}; + result = broker->SpawnTarget(prog_name, arguments.c_str(), policy, + &temp_process_info); + policy->Release(); + + EXPECT_EQ(SBOX_ALL_OK, result); + if (result == SBOX_ALL_OK) + target.Set(temp_process_info); + + EXPECT_EQ(1, ::ResumeThread(target.thread_handle())); + + EXPECT_EQ(WAIT_TIMEOUT, + ::WaitForSingleObject(target.process_handle(), 2000)); + + EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); + + ::WaitForSingleObject(target.process_handle(), INFINITE); +} + +} // namespace sandbox diff --git a/sandbox/win/src/process_mitigations.cc b/sandbox/win/src/process_mitigations.cc new file mode 100644 index 0000000000..d187c55e3e --- /dev/null +++ b/sandbox/win/src/process_mitigations.cc @@ -0,0 +1,331 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_mitigations.h" + +#include <algorithm> + +#include "base/win/windows_version.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// Functions for enabling policies. +typedef BOOL (WINAPI *SetProcessDEPPolicyFunction)(DWORD dwFlags); + +typedef BOOL (WINAPI *SetProcessMitigationPolicyFunction)( + PROCESS_MITIGATION_POLICY mitigation_policy, + PVOID buffer, + SIZE_T length); + +typedef BOOL (WINAPI *SetDefaultDllDirectoriesFunction)( + DWORD DirectoryFlags); + +} // namespace + +namespace sandbox { + +bool ApplyProcessMitigationsToCurrentProcess(MitigationFlags flags) { + if (!CanSetProcessMitigationsPostStartup(flags)) + return false; + + base::win::Version version = base::win::GetVersion(); + HMODULE module = ::GetModuleHandleA("kernel32.dll"); + + if (version >= base::win::VERSION_VISTA && + (flags & MITIGATION_DLL_SEARCH_ORDER)) { + SetDefaultDllDirectoriesFunction set_default_dll_directories = + reinterpret_cast<SetDefaultDllDirectoriesFunction>( + ::GetProcAddress(module, "SetDefaultDllDirectories")); + + // Check for SetDefaultDllDirectories since it requires KB2533623. + if (set_default_dll_directories) { + if (!set_default_dll_directories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + } + + // Set the heap to terminate on corruption + if (version >= base::win::VERSION_VISTA && + (flags & MITIGATION_HEAP_TERMINATE)) { + if (!::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, + NULL, 0) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (version >= base::win::VERSION_WIN7 && + (flags & MITIGATION_HARDEN_TOKEN_IL_POLICY)) { + DWORD error = HardenProcessIntegrityLevelPolicy(); + if ((error != ERROR_SUCCESS) && (error != ERROR_ACCESS_DENIED)) + return false; + } + +#if !defined(_WIN64) // DEP is always enabled on 64-bit. + if (flags & MITIGATION_DEP) { + DWORD dep_flags = PROCESS_DEP_ENABLE; + // DEP support is quirky on XP, so don't force a failure in that case. + const bool return_on_fail = version >= base::win::VERSION_VISTA; + + if (flags & MITIGATION_DEP_NO_ATL_THUNK) + dep_flags |= PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION; + + SetProcessDEPPolicyFunction set_process_dep_policy = + reinterpret_cast<SetProcessDEPPolicyFunction>( + ::GetProcAddress(module, "SetProcessDEPPolicy")); + if (set_process_dep_policy) { + if (!set_process_dep_policy(dep_flags) && + ERROR_ACCESS_DENIED != ::GetLastError() && return_on_fail) { + return false; + } + } else { + // We're on XP sp2, so use the less standard approach. + // For reference: http://www.uninformed.org/?v=2&a=4 + static const int MEM_EXECUTE_OPTION_ENABLE = 1; + static const int MEM_EXECUTE_OPTION_DISABLE = 2; + static const int MEM_EXECUTE_OPTION_ATL7_THUNK_EMULATION = 4; + static const int MEM_EXECUTE_OPTION_PERMANENT = 8; + + NtSetInformationProcessFunction set_information_process = NULL; + ResolveNTFunctionPtr("NtSetInformationProcess", + &set_information_process); + if (!set_information_process) + return false; + ULONG dep = MEM_EXECUTE_OPTION_DISABLE | MEM_EXECUTE_OPTION_PERMANENT; + if (!(dep_flags & PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION)) + dep |= MEM_EXECUTE_OPTION_ATL7_THUNK_EMULATION; + if (!SUCCEEDED(set_information_process(GetCurrentProcess(), + ProcessExecuteFlags, + &dep, sizeof(dep))) && + ERROR_ACCESS_DENIED != ::GetLastError() && return_on_fail) { + return false; + } + } + } +#endif + + // This is all we can do in Win7 and below. + if (version < base::win::VERSION_WIN8) + return true; + + SetProcessMitigationPolicyFunction set_process_mitigation_policy = + reinterpret_cast<SetProcessMitigationPolicyFunction>( + ::GetProcAddress(module, "SetProcessMitigationPolicy")); + if (!set_process_mitigation_policy) + return false; + + // Enable ASLR policies. + if (flags & MITIGATION_RELOCATE_IMAGE) { + PROCESS_MITIGATION_ASLR_POLICY policy = { 0 }; + policy.EnableForceRelocateImages = true; + policy.DisallowStrippedImages = (flags & + MITIGATION_RELOCATE_IMAGE_REQUIRED) == + MITIGATION_RELOCATE_IMAGE_REQUIRED; + + if (!set_process_mitigation_policy(ProcessASLRPolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable strict handle policies. + if (flags & MITIGATION_STRICT_HANDLE_CHECKS) { + PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY policy = { 0 }; + policy.HandleExceptionsPermanentlyEnabled = + policy.RaiseExceptionOnInvalidHandleReference = true; + + if (!set_process_mitigation_policy(ProcessStrictHandleCheckPolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable system call policies. + if (flags & MITIGATION_WIN32K_DISABLE) { + PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY policy = { 0 }; + policy.DisallowWin32kSystemCalls = true; + + if (!set_process_mitigation_policy(ProcessSystemCallDisablePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable system call policies. + if (flags & MITIGATION_EXTENSION_DLL_DISABLE) { + PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY policy = { 0 }; + policy.DisableExtensionPoints = true; + + if (!set_process_mitigation_policy(ProcessExtensionPointDisablePolicy, + &policy, sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + return true; +} + +void ConvertProcessMitigationsToPolicy(MitigationFlags flags, + DWORD64* policy_flags, size_t* size) { + base::win::Version version = base::win::GetVersion(); + + *policy_flags = 0; +#if defined(_WIN64) + *size = sizeof(*policy_flags); +#elif defined(_M_IX86) + // A 64-bit flags attribute is illegal on 32-bit Win 7 and below. + if (version < base::win::VERSION_WIN8) + *size = sizeof(DWORD); + else + *size = sizeof(*policy_flags); +#else +#error This platform is not supported. +#endif + + // Nothing for Win XP or Vista. + if (version <= base::win::VERSION_VISTA) + return; + + // DEP and SEHOP are not valid for 64-bit Windows +#if !defined(_WIN64) + if (flags & MITIGATION_DEP) { + *policy_flags |= PROCESS_CREATION_MITIGATION_POLICY_DEP_ENABLE; + if (!(flags & MITIGATION_DEP_NO_ATL_THUNK)) + *policy_flags |= PROCESS_CREATION_MITIGATION_POLICY_DEP_ATL_THUNK_ENABLE; + } + + if (flags & MITIGATION_SEHOP) + *policy_flags |= PROCESS_CREATION_MITIGATION_POLICY_SEHOP_ENABLE; +#endif + + // Win 7 + if (version < base::win::VERSION_WIN8) + return; + + if (flags & MITIGATION_RELOCATE_IMAGE) { + *policy_flags |= + PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON; + if (flags & MITIGATION_RELOCATE_IMAGE_REQUIRED) { + *policy_flags |= + PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON_REQ_RELOCS; + } + } + + if (flags & MITIGATION_HEAP_TERMINATE) { + *policy_flags |= + PROCESS_CREATION_MITIGATION_POLICY_HEAP_TERMINATE_ALWAYS_ON; + } + + if (flags & MITIGATION_BOTTOM_UP_ASLR) { + *policy_flags |= + PROCESS_CREATION_MITIGATION_POLICY_BOTTOM_UP_ASLR_ALWAYS_ON; + } + + if (flags & MITIGATION_HIGH_ENTROPY_ASLR) { + *policy_flags |= + PROCESS_CREATION_MITIGATION_POLICY_HIGH_ENTROPY_ASLR_ALWAYS_ON; + } + + if (flags & MITIGATION_STRICT_HANDLE_CHECKS) { + *policy_flags |= + PROCESS_CREATION_MITIGATION_POLICY_STRICT_HANDLE_CHECKS_ALWAYS_ON; + } + + if (flags & MITIGATION_WIN32K_DISABLE) { + *policy_flags |= + PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON; + } + + if (flags & MITIGATION_EXTENSION_DLL_DISABLE) { + *policy_flags |= + PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON; + } +} + +MitigationFlags FilterPostStartupProcessMitigations(MitigationFlags flags) { + base::win::Version version = base::win::GetVersion(); + + // Windows XP SP2+. + if (version < base::win::VERSION_VISTA) { + return flags & (MITIGATION_DEP | + MITIGATION_DEP_NO_ATL_THUNK); + } + + // Windows Vista + if (version < base::win::VERSION_WIN7) { + return flags & (MITIGATION_BOTTOM_UP_ASLR | + MITIGATION_DLL_SEARCH_ORDER | + MITIGATION_HEAP_TERMINATE); + } + + // Windows 7. + if (version < base::win::VERSION_WIN8) { + return flags & (MITIGATION_BOTTOM_UP_ASLR | + MITIGATION_DLL_SEARCH_ORDER | + MITIGATION_HEAP_TERMINATE); + } + + // Windows 8 and above. + return flags & (MITIGATION_BOTTOM_UP_ASLR | + MITIGATION_DLL_SEARCH_ORDER); +} + +bool ApplyProcessMitigationsToSuspendedProcess(HANDLE process, + MitigationFlags flags) { +// This is a hack to fake a weak bottom-up ASLR on 32-bit Windows. +#if !defined(_WIN64) + if (flags & MITIGATION_BOTTOM_UP_ASLR) { + unsigned int limit; + rand_s(&limit); + char* ptr = 0; + const size_t kMask64k = 0xFFFF; + // Random range (512k-16.5mb) in 64k steps. + const char* end = ptr + ((((limit % 16384) + 512) * 1024) & ~kMask64k); + while (ptr < end) { + MEMORY_BASIC_INFORMATION memory_info; + if (!::VirtualQueryEx(process, ptr, &memory_info, sizeof(memory_info))) + break; + size_t size = std::min((memory_info.RegionSize + kMask64k) & ~kMask64k, + static_cast<SIZE_T>(end - ptr)); + if (ptr && memory_info.State == MEM_FREE) + ::VirtualAllocEx(process, ptr, size, MEM_RESERVE, PAGE_NOACCESS); + ptr += size; + } + } +#endif + + return true; +} + +bool CanSetProcessMitigationsPostStartup(MitigationFlags flags) { + // All of these mitigations can be enabled after startup. + return !(flags & ~(MITIGATION_HEAP_TERMINATE | + MITIGATION_DEP | + MITIGATION_DEP_NO_ATL_THUNK | + MITIGATION_RELOCATE_IMAGE | + MITIGATION_RELOCATE_IMAGE_REQUIRED | + MITIGATION_BOTTOM_UP_ASLR | + MITIGATION_STRICT_HANDLE_CHECKS | + MITIGATION_EXTENSION_DLL_DISABLE | + MITIGATION_DLL_SEARCH_ORDER | + MITIGATION_HARDEN_TOKEN_IL_POLICY)); +} + +bool CanSetProcessMitigationsPreStartup(MitigationFlags flags) { + // These mitigations cannot be enabled prior to startup. + return !(flags & (MITIGATION_STRICT_HANDLE_CHECKS | + MITIGATION_DLL_SEARCH_ORDER)); +} + +} // namespace sandbox + diff --git a/sandbox/win/src/process_mitigations.h b/sandbox/win/src/process_mitigations.h new file mode 100644 index 0000000000..9039ad677b --- /dev/null +++ b/sandbox/win/src/process_mitigations.h @@ -0,0 +1,44 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_WIN_PROCESS_MITIGATIONS_H_ +#define SANDBOX_SRC_WIN_PROCESS_MITIGATIONS_H_ + +#include <windows.h> + +#include "base/basictypes.h" +#include "sandbox/win/src/security_level.h" + +namespace sandbox { + +// Sets the mitigation policy for the current process, ignoring any settings +// that are invalid for the current version of Windows. +bool ApplyProcessMitigationsToCurrentProcess(MitigationFlags flags); + +// Returns the flags that must be enforced after startup for the current OS +// version. +MitigationFlags FilterPostStartupProcessMitigations(MitigationFlags flags); + +// Converts sandbox flags to the PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES +// policy flags used by UpdateProcThreadAttribute(). The size field varies +// between a 32-bit and a 64-bit type based on the exact build and version of +// Windows, so the returned size must be passed to UpdateProcThreadAttribute(). +void ConvertProcessMitigationsToPolicy(MitigationFlags flags, + DWORD64* policy_flags, size_t* size); + +// Adds mitigations that need to be performed on the suspended target process +// before execution begins. +bool ApplyProcessMitigationsToSuspendedProcess(HANDLE process, + MitigationFlags flags); + +// Returns true if all the supplied flags can be set after a process starts. +bool CanSetProcessMitigationsPostStartup(MitigationFlags flags); + +// Returns true if all the supplied flags can be set before a process starts. +bool CanSetProcessMitigationsPreStartup(MitigationFlags flags); + +} // namespace sandbox + +#endif // SANDBOX_SRC_WIN_PROCESS_MITIGATIONS_H_ + diff --git a/sandbox/win/src/process_mitigations_test.cc b/sandbox/win/src/process_mitigations_test.cc new file mode 100644 index 0000000000..4d2e9c6e95 --- /dev/null +++ b/sandbox/win/src/process_mitigations_test.cc @@ -0,0 +1,248 @@ +// Copyright (c) 2011 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 "base/strings/stringprintf.h" +#include "base/win/scoped_handle.h" + +#include "base/win/windows_version.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +typedef BOOL (WINAPI *GetProcessDEPPolicyFunction)( + HANDLE process, + LPDWORD flags, + PBOOL permanent); + +typedef BOOL (WINAPI *GetProcessMitigationPolicyFunction)( + HANDLE process, + PROCESS_MITIGATION_POLICY mitigation_policy, + PVOID buffer, + SIZE_T length); + +GetProcessMitigationPolicyFunction get_process_mitigation_policy; + +bool CheckWin8DepPolicy() { + PROCESS_MITIGATION_DEP_POLICY policy; + if (!get_process_mitigation_policy(::GetCurrentProcess(), ProcessDEPPolicy, + &policy, sizeof(policy))) { + return false; + } + return policy.Enable && policy.Permanent; +} + +bool CheckWin8AslrPolicy() { + PROCESS_MITIGATION_ASLR_POLICY policy; + if (!get_process_mitigation_policy(::GetCurrentProcess(), ProcessASLRPolicy, + &policy, sizeof(policy))) { + return false; + } + return policy.EnableForceRelocateImages && policy.DisallowStrippedImages; +} + +bool CheckWin8StrictHandlePolicy() { + PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY policy; + if (!get_process_mitigation_policy(::GetCurrentProcess(), + ProcessStrictHandleCheckPolicy, + &policy, sizeof(policy))) { + return false; + } + return policy.RaiseExceptionOnInvalidHandleReference && + policy.HandleExceptionsPermanentlyEnabled; +} + +bool CheckWin8Win32CallPolicy() { + PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY policy; + if (!get_process_mitigation_policy(::GetCurrentProcess(), + ProcessSystemCallDisablePolicy, + &policy, sizeof(policy))) { + return false; + } + return policy.DisallowWin32kSystemCalls; +} + +bool CheckWin8DllExtensionPolicy() { + PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY policy; + if (!get_process_mitigation_policy(::GetCurrentProcess(), + ProcessExtensionPointDisablePolicy, + &policy, sizeof(policy))) { + return false; + } + return policy.DisableExtensionPoints; +} + +} // namespace + +namespace sandbox { + +SBOX_TESTS_COMMAND int CheckWin8(int argc, wchar_t **argv) { + get_process_mitigation_policy = + reinterpret_cast<GetProcessMitigationPolicyFunction>( + ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), + "GetProcessMitigationPolicy")); + if (!get_process_mitigation_policy) + return SBOX_TEST_NOT_FOUND; + + if (!CheckWin8DepPolicy()) + return SBOX_TEST_FIRST_ERROR; + +#if defined(NDEBUG) // ASLR cannot be forced in debug builds. + if (!CheckWin8AslrPolicy()) + return SBOX_TEST_SECOND_ERROR; +#endif + + if (!CheckWin8StrictHandlePolicy()) + return SBOX_TEST_THIRD_ERROR; + + if (!CheckWin8DllExtensionPolicy()) + return SBOX_TEST_FIFTH_ERROR; + + return SBOX_TEST_SUCCEEDED; +} + +TEST(ProcessMitigationsTest, CheckWin8) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + TestRunner runner; + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + sandbox::MitigationFlags mitigations = MITIGATION_DEP | + MITIGATION_DEP_NO_ATL_THUNK | + MITIGATION_EXTENSION_DLL_DISABLE; +#if defined(NDEBUG) // ASLR cannot be forced in debug builds. + mitigations |= MITIGATION_RELOCATE_IMAGE | + MITIGATION_RELOCATE_IMAGE_REQUIRED; +#endif + + EXPECT_EQ(policy->SetProcessMitigations(mitigations), SBOX_ALL_OK); + + mitigations |= MITIGATION_STRICT_HANDLE_CHECKS; + + EXPECT_EQ(policy->SetDelayedProcessMitigations(mitigations), SBOX_ALL_OK); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckWin8")); +} + + +SBOX_TESTS_COMMAND int CheckDep(int argc, wchar_t **argv) { + GetProcessDEPPolicyFunction get_process_dep_policy = + reinterpret_cast<GetProcessDEPPolicyFunction>( + ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), + "GetProcessDEPPolicy")); + if (get_process_dep_policy) { + BOOL is_permanent = FALSE; + DWORD dep_flags = 0; + + if (!get_process_dep_policy(::GetCurrentProcess(), &dep_flags, + &is_permanent)) { + return SBOX_TEST_FIRST_ERROR; + } + + if (!(dep_flags & PROCESS_DEP_ENABLE) || !is_permanent) + return SBOX_TEST_SECOND_ERROR; + + } else { + NtQueryInformationProcessFunction query_information_process = NULL; + ResolveNTFunctionPtr("NtQueryInformationProcess", + &query_information_process); + if (!query_information_process) + return SBOX_TEST_NOT_FOUND; + + ULONG size = 0; + ULONG dep_flags = 0; + if (!SUCCEEDED(query_information_process(::GetCurrentProcess(), + ProcessExecuteFlags, &dep_flags, + sizeof(dep_flags), &size))) { + return SBOX_TEST_THIRD_ERROR; + } + + static const int MEM_EXECUTE_OPTION_ENABLE = 1; + static const int MEM_EXECUTE_OPTION_DISABLE = 2; + static const int MEM_EXECUTE_OPTION_ATL7_THUNK_EMULATION = 4; + static const int MEM_EXECUTE_OPTION_PERMANENT = 8; + dep_flags &= 0xff; + + if (dep_flags != (MEM_EXECUTE_OPTION_DISABLE | + MEM_EXECUTE_OPTION_PERMANENT)) { + return SBOX_TEST_FOURTH_ERROR; + } + } + + return SBOX_TEST_SUCCEEDED; +} + +#if !defined(_WIN64) // DEP is always enabled on 64-bit. +TEST(ProcessMitigationsTest, CheckDep) { + if (base::win::GetVersion() > base::win::VERSION_WIN7) + return; + + TestRunner runner; + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + EXPECT_EQ(policy->SetProcessMitigations( + MITIGATION_DEP | + MITIGATION_DEP_NO_ATL_THUNK | + MITIGATION_SEHOP), + SBOX_ALL_OK); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckDep")); +} +#endif + +SBOX_TESTS_COMMAND int CheckWin8Lockdown(int argc, wchar_t **argv) { + get_process_mitigation_policy = + reinterpret_cast<GetProcessMitigationPolicyFunction>( + ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), + "GetProcessMitigationPolicy")); + if (!get_process_mitigation_policy) + return SBOX_TEST_NOT_FOUND; + + if (!CheckWin8Win32CallPolicy()) + return SBOX_TEST_FIRST_ERROR; + return SBOX_TEST_SUCCEEDED; +} + +// This test validates that setting the MITIGATION_WIN32K_DISABLE mitigation on +// the target process causes the launch to fail in process initialization. +// The test process itself links against user32/gdi32. +TEST(ProcessMitigationsTest, CheckWin8Win32KLockDownFailure) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + TestRunner runner; + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + EXPECT_EQ(policy->SetProcessMitigations(MITIGATION_WIN32K_DISABLE), + SBOX_ALL_OK); + EXPECT_NE(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckWin8Lockdown")); +} + +// This test validates that setting the MITIGATION_WIN32K_DISABLE mitigation +// along with the policy to fake user32 and gdi32 initialization successfully +// launches the target process. +// The test process itself links against user32/gdi32. +TEST(ProcessMitigationsTest, CheckWin8Win32KLockDownSuccess) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + TestRunner runner; + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + EXPECT_EQ(policy->SetProcessMitigations(MITIGATION_WIN32K_DISABLE), + SBOX_ALL_OK); + EXPECT_EQ(policy->AddRule(sandbox::TargetPolicy::SUBSYS_WIN32K_LOCKDOWN, + sandbox::TargetPolicy::FAKE_USER_GDI_INIT, NULL), + sandbox::SBOX_ALL_OK); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckWin8Lockdown")); +} + +} // namespace sandbox + diff --git a/sandbox/win/src/process_mitigations_win32k_dispatcher.cc b/sandbox/win/src/process_mitigations_win32k_dispatcher.cc new file mode 100644 index 0000000000..e426084f86 --- /dev/null +++ b/sandbox/win/src/process_mitigations_win32k_dispatcher.cc @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_mitigations_win32k_dispatcher.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/process_mitigations_win32k_interception.h" + +namespace sandbox { + +ProcessMitigationsWin32KDispatcher::ProcessMitigationsWin32KDispatcher( + PolicyBase* policy_base) + : policy_base_(policy_base) { +} + +bool ProcessMitigationsWin32KDispatcher::SetupService( + InterceptionManager* manager, int service) { + if (!(policy_base_->GetProcessMitigations() & + sandbox::MITIGATION_WIN32K_DISABLE)) { + return false; + } + + switch (service) { + case IPC_GDI_GDIDLLINITIALIZE_TAG: { + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GdiDllInitialize, + GDIINITIALIZE_ID, 12)) { + return false; + } + return true; + } + + case IPC_GDI_GETSTOCKOBJECT_TAG: { + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetStockObject, + GETSTOCKOBJECT_ID, 8)) { + return false; + } + return true; + } + + case IPC_USER_REGISTERCLASSW_TAG: { + if (!INTERCEPT_EAT(manager, L"user32.dll", RegisterClassW, + REGISTERCLASSW_ID, 8)) { + return false; + } + return true; + } + + default: + break; + } + return false; +} + +} // namespace sandbox + diff --git a/sandbox/win/src/process_mitigations_win32k_dispatcher.h b/sandbox/win/src/process_mitigations_win32k_dispatcher.h new file mode 100644 index 0000000000..2e1e1a802f --- /dev/null +++ b/sandbox/win/src/process_mitigations_win32k_dispatcher.h @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ + +#include "base/basictypes.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class sets up intercepts for the Win32K lockdown policy which is set +// on Windows 8 and beyond. +class ProcessMitigationsWin32KDispatcher : public Dispatcher { + public: + explicit ProcessMitigationsWin32KDispatcher(PolicyBase* policy_base); + ~ProcessMitigationsWin32KDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, int service) override; + + private: + PolicyBase* policy_base_; + + DISALLOW_COPY_AND_ASSIGN(ProcessMitigationsWin32KDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ diff --git a/sandbox/win/src/process_mitigations_win32k_interception.cc b/sandbox/win/src/process_mitigations_win32k_interception.cc new file mode 100644 index 0000000000..ee24fbf434 --- /dev/null +++ b/sandbox/win/src/process_mitigations_win32k_interception.cc @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_mitigations_win32k_interception.h" + +namespace sandbox { + +BOOL WINAPI TargetGdiDllInitialize( + GdiDllInitializeFunction orig_gdi_dll_initialize, + HANDLE dll, + DWORD reason) { + return TRUE; +} + +HGDIOBJ WINAPI TargetGetStockObject( + GetStockObjectFunction orig_get_stock_object, + int object) { + return reinterpret_cast<HGDIOBJ>(NULL); +} + +ATOM WINAPI TargetRegisterClassW( + RegisterClassWFunction orig_register_class_function, + const WNDCLASS* wnd_class) { + return TRUE; +} + +} // namespace sandbox + diff --git a/sandbox/win/src/process_mitigations_win32k_interception.h b/sandbox/win/src/process_mitigations_win32k_interception.h new file mode 100644 index 0000000000..bf7b551227 --- /dev/null +++ b/sandbox/win/src/process_mitigations_win32k_interception.h @@ -0,0 +1,46 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ + +#include <windows.h> +#include "base/basictypes.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +typedef BOOL (WINAPI* GdiDllInitializeFunction) ( + HANDLE dll, + DWORD reason, + LPVOID reserved); + +typedef HGDIOBJ (WINAPI *GetStockObjectFunction) (int object); + +typedef ATOM (WINAPI *RegisterClassWFunction) (const WNDCLASS* wnd_class); + +// Interceptor for the GdiDllInitialize function. +SANDBOX_INTERCEPT BOOL WINAPI TargetGdiDllInitialize( + GdiDllInitializeFunction orig_gdi_dll_initialize, + HANDLE dll, + DWORD reason); + +// Interceptor for the GetStockObject function. +SANDBOX_INTERCEPT HGDIOBJ WINAPI TargetGetStockObject( + GetStockObjectFunction orig_get_stock_object, + int object); + +// Interceptor for the RegisterClassW function. +SANDBOX_INTERCEPT ATOM WINAPI TargetRegisterClassW( + RegisterClassWFunction orig_register_class_function, + const WNDCLASS* wnd_class); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ + diff --git a/sandbox/win/src/process_mitigations_win32k_policy.cc b/sandbox/win/src/process_mitigations_win32k_policy.cc new file mode 100644 index 0000000000..af18c5413c --- /dev/null +++ b/sandbox/win/src/process_mitigations_win32k_policy.cc @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_mitigations_win32k_policy.h" + +namespace sandbox { + +bool ProcessMitigationsWin32KLockdownPolicy::GenerateRules( + const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + PolicyRule rule(FAKE_SUCCESS); + if (!policy->AddRule(IPC_GDI_GDIDLLINITIALIZE_TAG, &rule)) + return false; + if (!policy->AddRule(IPC_GDI_GETSTOCKOBJECT_TAG, &rule)) + return false; + if (!policy->AddRule(IPC_USER_REGISTERCLASSW_TAG, &rule)) + return false; + return true; +} + +} // namespace sandbox + diff --git a/sandbox/win/src/process_mitigations_win32k_policy.h b/sandbox/win/src/process_mitigations_win32k_policy.h new file mode 100644 index 0000000000..078ed2bee1 --- /dev/null +++ b/sandbox/win/src/process_mitigations_win32k_policy.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ + +#include "base/basictypes.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum EvalResult; + +// This class centralizes most of the knowledge related to the process +// mitigations Win32K lockdown policy. +class ProcessMitigationsWin32KLockdownPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for the Win32K process mitigation policy. + // name is the object name, semantics is the desired semantics for the + // open or create and policy is the policy generator to which the rules are + // going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ + + diff --git a/sandbox/win/src/process_policy_test.cc b/sandbox/win/src/process_policy_test.cc new file mode 100644 index 0000000000..44effa3635 --- /dev/null +++ b/sandbox/win/src/process_policy_test.cc @@ -0,0 +1,385 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <memory> +#include <string> + +#include "base/strings/string16.h" +#include "base/strings/sys_string_conversions.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// While the shell API provides better calls than this home brew function +// we use GetSystemWindowsDirectoryW which does not query the registry so +// it is safe to use after revert. +base::string16 MakeFullPathToSystem32(const wchar_t* name) { + wchar_t windows_path[MAX_PATH] = {0}; + ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH); + base::string16 full_path(windows_path); + if (full_path.empty()) { + return full_path; + } + full_path += L"\\system32\\"; + full_path += name; + return full_path; +} + +// Creates a process with the |exe| and |command| parameter using the +// unicode and ascii version of the api. +sandbox::SboxTestResult CreateProcessHelper(const base::string16& exe, + const base::string16& command) { + base::win::ScopedProcessInformation pi; + STARTUPINFOW si = {sizeof(si)}; + + const wchar_t *exe_name = NULL; + if (!exe.empty()) + exe_name = exe.c_str(); + + base::string16 writable_command = command; + + // Create the process with the unicode version of the API. + sandbox::SboxTestResult ret1 = sandbox::SBOX_TEST_FAILED; + PROCESS_INFORMATION temp_process_info = {}; + if (::CreateProcessW(exe_name, + command.empty() ? NULL : &writable_command[0], + NULL, + NULL, + FALSE, + 0, + NULL, + NULL, + &si, + &temp_process_info)) { + pi.Set(temp_process_info); + ret1 = sandbox::SBOX_TEST_SUCCEEDED; + } else { + DWORD last_error = GetLastError(); + if ((ERROR_NOT_ENOUGH_QUOTA == last_error) || + (ERROR_ACCESS_DENIED == last_error) || + (ERROR_FILE_NOT_FOUND == last_error)) { + ret1 = sandbox::SBOX_TEST_DENIED; + } else { + ret1 = sandbox::SBOX_TEST_FAILED; + } + } + + pi.Close(); + + // Do the same with the ansi version of the api + STARTUPINFOA sia = {sizeof(sia)}; + sandbox::SboxTestResult ret2 = sandbox::SBOX_TEST_FAILED; + + std::string narrow_cmd_line = + base::SysWideToMultiByte(command.c_str(), CP_UTF8); + if (::CreateProcessA( + exe_name ? base::SysWideToMultiByte(exe_name, CP_UTF8).c_str() : NULL, + command.empty() ? NULL : &narrow_cmd_line[0], + NULL, NULL, FALSE, 0, NULL, NULL, &sia, &temp_process_info)) { + pi.Set(temp_process_info); + ret2 = sandbox::SBOX_TEST_SUCCEEDED; + } else { + DWORD last_error = GetLastError(); + if ((ERROR_NOT_ENOUGH_QUOTA == last_error) || + (ERROR_ACCESS_DENIED == last_error) || + (ERROR_FILE_NOT_FOUND == last_error)) { + ret2 = sandbox::SBOX_TEST_DENIED; + } else { + ret2 = sandbox::SBOX_TEST_FAILED; + } + } + + if (ret1 == ret2) + return ret1; + + return sandbox::SBOX_TEST_FAILED; +} + +} // namespace + +namespace sandbox { + +SBOX_TESTS_COMMAND int Process_RunApp1(int argc, wchar_t **argv) { + if (argc != 1) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + if ((NULL == argv) || (NULL == argv[0])) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + base::string16 path = MakeFullPathToSystem32(argv[0]); + + // TEST 1: Try with the path in the app_name. + return CreateProcessHelper(path, base::string16()); +} + +SBOX_TESTS_COMMAND int Process_RunApp2(int argc, wchar_t **argv) { + if (argc != 1) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + if ((NULL == argv) || (NULL == argv[0])) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + base::string16 path = MakeFullPathToSystem32(argv[0]); + + // TEST 2: Try with the path in the cmd_line. + base::string16 cmd_line = L"\""; + cmd_line += path; + cmd_line += L"\""; + return CreateProcessHelper(base::string16(), cmd_line); +} + +SBOX_TESTS_COMMAND int Process_RunApp3(int argc, wchar_t **argv) { + if (argc != 1) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + if ((NULL == argv) || (NULL == argv[0])) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + // TEST 3: Try file name in the cmd_line. + return CreateProcessHelper(base::string16(), argv[0]); +} + +SBOX_TESTS_COMMAND int Process_RunApp4(int argc, wchar_t **argv) { + if (argc != 1) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + if ((NULL == argv) || (NULL == argv[0])) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + // TEST 4: Try file name in the app_name and current directory sets correctly. + base::string16 system32 = MakeFullPathToSystem32(L""); + wchar_t current_directory[MAX_PATH + 1]; + DWORD ret = ::GetCurrentDirectory(MAX_PATH, current_directory); + if (!ret) + return SBOX_TEST_FIRST_ERROR; + if (ret >= MAX_PATH) + return SBOX_TEST_FAILED; + + current_directory[ret] = L'\\'; + current_directory[ret+1] = L'\0'; + if (!::SetCurrentDirectory(system32.c_str())) { + return SBOX_TEST_SECOND_ERROR; + } + + const int result4 = CreateProcessHelper(argv[0], base::string16()); + return ::SetCurrentDirectory(current_directory) ? result4 : SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int Process_RunApp5(int argc, wchar_t **argv) { + if (argc != 1) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + if ((NULL == argv) || (NULL == argv[0])) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + base::string16 path = MakeFullPathToSystem32(argv[0]); + + // TEST 5: Try with the path in the cmd_line and arguments. + base::string16 cmd_line = L"\""; + cmd_line += path; + cmd_line += L"\" /I"; + return CreateProcessHelper(base::string16(), cmd_line); +} + +SBOX_TESTS_COMMAND int Process_RunApp6(int argc, wchar_t **argv) { + if (argc != 1) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + if ((NULL == argv) || (NULL == argv[0])) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + // TEST 6: Try with the file_name in the cmd_line and arguments. + base::string16 cmd_line = argv[0]; + cmd_line += L" /I"; + return CreateProcessHelper(base::string16(), cmd_line); +} + +// Creates a process and checks if it's possible to get a handle to it's token. +SBOX_TESTS_COMMAND int Process_GetChildProcessToken(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if ((NULL == argv) || (NULL == argv[0])) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + base::string16 path = MakeFullPathToSystem32(argv[0]); + + STARTUPINFOW si = {sizeof(si)}; + + PROCESS_INFORMATION temp_process_info = {}; + if (!::CreateProcessW(path.c_str(), NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, + NULL, NULL, &si, &temp_process_info)) { + return SBOX_TEST_FAILED; + } + base::win::ScopedProcessInformation pi(temp_process_info); + + HANDLE token = NULL; + BOOL result = + ::OpenProcessToken(pi.process_handle(), TOKEN_IMPERSONATE, &token); + DWORD error = ::GetLastError(); + + base::win::ScopedHandle token_handle(token); + + if (!::TerminateProcess(pi.process_handle(), 0)) + return SBOX_TEST_FAILED; + + if (result && token) + return SBOX_TEST_SUCCEEDED; + + if (ERROR_ACCESS_DENIED == error) + return SBOX_TEST_DENIED; + + return SBOX_TEST_FAILED; +} + + +SBOX_TESTS_COMMAND int Process_OpenToken(int argc, wchar_t **argv) { + HANDLE token; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) { + if (ERROR_ACCESS_DENIED == ::GetLastError()) { + return SBOX_TEST_DENIED; + } + } else { + ::CloseHandle(token); + return SBOX_TEST_SUCCEEDED; + } + + return SBOX_TEST_FAILED; +} + +TEST(ProcessPolicyTest, TestAllAccess) { + // Check if the "all access" rule fails to be added when the token is too + // powerful. + TestRunner runner; + + // Check the failing case. + runner.GetPolicy()->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + EXPECT_EQ(SBOX_ERROR_UNSUPPORTED, + runner.GetPolicy()->AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, + L"this is not important")); + + // Check the working case. + runner.GetPolicy()->SetTokenLevel(USER_INTERACTIVE, USER_INTERACTIVE); + + EXPECT_EQ(SBOX_ALL_OK, + runner.GetPolicy()->AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, + L"this is not important")); +} + +TEST(ProcessPolicyTest, CreateProcessAW) { + TestRunner runner; + base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); + base::string16 system32 = MakeFullPathToSystem32(L""); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, + exe_path.c_str())); + + // Need to add directory rules for the directories that we use in + // SetCurrentDirectory. + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_DIR_ANY, + system32.c_str())); + + wchar_t current_directory[MAX_PATH]; + DWORD ret = ::GetCurrentDirectory(MAX_PATH, current_directory); + ASSERT_TRUE(0 != ret && ret < MAX_PATH); + + wcscat_s(current_directory, MAX_PATH, L"\\"); + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_DIR_ANY, + current_directory)); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp1 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp2 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp3 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp5 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp6 calc.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp1 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp2 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp3 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp5 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp6 findstr.exe")); + +#if !defined(_WIN64) + if (base::win::OSInfo::GetInstance()->version() >= base::win::VERSION_VISTA) { + // WinXP results are not reliable. + EXPECT_EQ(SBOX_TEST_SECOND_ERROR, + runner.RunTest(L"Process_RunApp4 calc.exe")); + EXPECT_EQ(SBOX_TEST_SECOND_ERROR, + runner.RunTest(L"Process_RunApp4 findstr.exe")); + } +#endif +} + +TEST(ProcessPolicyTest, OpenToken) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Process_OpenToken")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMinAccess) { + TestRunner runner; + base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, + exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccess) { + TestRunner runner(JOB_UNPROTECTED, USER_INTERACTIVE, USER_INTERACTIVE); + base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, + exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMinAccessNoJob) { + TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, + exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccessNoJob) { + TestRunner runner(JOB_NONE, USER_INTERACTIVE, USER_INTERACTIVE); + base::string16 exe_path = MakeFullPathToSystem32(L"findstr.exe"); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, + exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +} // namespace sandbox diff --git a/sandbox/win/src/process_thread_dispatcher.cc b/sandbox/win/src/process_thread_dispatcher.cc new file mode 100644 index 0000000000..ca17d4920a --- /dev/null +++ b/sandbox/win/src/process_thread_dispatcher.cc @@ -0,0 +1,249 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_thread_dispatcher.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/process_thread_policy.h" +#include "sandbox/win/src/sandbox.h" + +namespace { + +// Extracts the application name from a command line. +// +// The application name is the first element of the command line. If +// there is no quotes, the first element is delimited by the first space. +// If there are quotes, the first element is delimited by the quotes. +// +// The create process call is smarter than us. It tries really hard to launch +// the process even if the command line is wrong. For example: +// "c:\program files\test param" will first try to launch c:\program.exe then +// c:\program files\test.exe. We don't do that, we stop after at the first +// space when there is no quotes. +base::string16 GetPathFromCmdLine(const base::string16 &cmd_line) { + base::string16 exe_name; + // Check if it starts with '"'. + if (cmd_line[0] == L'\"') { + // Find the position of the second '"', this terminates the path. + base::string16::size_type pos = cmd_line.find(L'\"', 1); + if (base::string16::npos == pos) + return cmd_line; + exe_name = cmd_line.substr(1, pos - 1); + } else { + // There is no '"', that means that the appname is terminated at the + // first space. + base::string16::size_type pos = cmd_line.find(L' '); + if (base::string16::npos == pos) { + // There is no space, the cmd_line contains only the app_name + exe_name = cmd_line; + } else { + exe_name = cmd_line.substr(0, pos); + } + } + + return exe_name; +} + +// Returns true is the path in parameter is relative. False if it's +// absolute. +bool IsPathRelative(const base::string16 &path) { + // A path is Relative if it's not a UNC path beginnning with \\ or a + // path beginning with a drive. (i.e. X:\) + if (path.find(L"\\\\") == 0 || path.find(L":\\") == 1) + return false; + return true; +} + +// Converts a relative path to an absolute path. +bool ConvertToAbsolutePath(const base::string16& child_current_directory, + bool use_env_path, base::string16 *path) { + wchar_t file_buffer[MAX_PATH]; + wchar_t *file_part = NULL; + + // Here we should start by looking at the path where the child application was + // started. We don't have this information yet. + DWORD result = 0; + if (use_env_path) { + // Try with the complete path + result = ::SearchPath(NULL, path->c_str(), NULL, MAX_PATH, file_buffer, + &file_part); + } + + if (0 == result) { + // Try with the current directory of the child + result = ::SearchPath(child_current_directory.c_str(), path->c_str(), NULL, + MAX_PATH, file_buffer, &file_part); + } + + if (0 == result || result >= MAX_PATH) + return false; + + *path = file_buffer; + return true; +} + +} // namespace +namespace sandbox { + +ThreadProcessDispatcher::ThreadProcessDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall open_thread = { + {IPC_NTOPENTHREAD_TAG, UINT32_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenThread) + }; + + static const IPCCall open_process = { + {IPC_NTOPENPROCESS_TAG, UINT32_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenProcess) + }; + + static const IPCCall process_token = { + {IPC_NTOPENPROCESSTOKEN_TAG, VOIDPTR_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenProcessToken) + }; + + static const IPCCall process_tokenex = { + {IPC_NTOPENPROCESSTOKENEX_TAG, VOIDPTR_TYPE, UINT32_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenProcessTokenEx) + }; + + static const IPCCall create_params = { + {IPC_CREATEPROCESSW_TAG, WCHAR_TYPE, WCHAR_TYPE, WCHAR_TYPE, INOUTPTR_TYPE}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::CreateProcessW) + }; + + ipc_calls_.push_back(open_thread); + ipc_calls_.push_back(open_process); + ipc_calls_.push_back(process_token); + ipc_calls_.push_back(process_tokenex); + ipc_calls_.push_back(create_params); +} + +bool ThreadProcessDispatcher::SetupService(InterceptionManager* manager, + int service) { + switch (service) { + case IPC_NTOPENTHREAD_TAG: + case IPC_NTOPENPROCESS_TAG: + case IPC_NTOPENPROCESSTOKEN_TAG: + case IPC_NTOPENPROCESSTOKENEX_TAG: + // There is no explicit policy for these services. + NOTREACHED(); + return false; + + case IPC_CREATEPROCESSW_TAG: + return INTERCEPT_EAT(manager, kKerneldllName, CreateProcessW, + CREATE_PROCESSW_ID, 44) && + INTERCEPT_EAT(manager, L"kernel32.dll", CreateProcessA, + CREATE_PROCESSA_ID, 44); + + default: + return false; + } +} + +bool ThreadProcessDispatcher::NtOpenThread(IPCInfo* ipc, + uint32 desired_access, + uint32 thread_id) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenThreadAction(*ipc->client_info, + desired_access, thread_id, + &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::NtOpenProcess(IPCInfo* ipc, + uint32 desired_access, + uint32 process_id) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenProcessAction(*ipc->client_info, + desired_access, process_id, + &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::NtOpenProcessToken(IPCInfo* ipc, + HANDLE process, + uint32 desired_access) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenProcessTokenAction(*ipc->client_info, + process, desired_access, + &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::NtOpenProcessTokenEx(IPCInfo* ipc, + HANDLE process, + uint32 desired_access, + uint32 attributes) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenProcessTokenExAction(*ipc->client_info, + process, + desired_access, + attributes, &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::CreateProcessW(IPCInfo* ipc, base::string16* name, + base::string16* cmd_line, + base::string16* cur_dir, + CountedBuffer* info) { + if (sizeof(PROCESS_INFORMATION) != info->Size()) + return false; + + // Check if there is an application name. + base::string16 exe_name; + if (!name->empty()) + exe_name = *name; + else + exe_name = GetPathFromCmdLine(*cmd_line); + + if (IsPathRelative(exe_name)) { + if (!ConvertToAbsolutePath(*cur_dir, name->empty(), &exe_name)) { + // Cannot find the path. Maybe the file does not exist. + ipc->return_info.win32_result = ERROR_FILE_NOT_FOUND; + return true; + } + } + + const wchar_t* const_exe_name = exe_name.c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(const_exe_name); + + EvalResult eval = policy_base_->EvalPolicy(IPC_CREATEPROCESSW_TAG, + params.GetBase()); + + PROCESS_INFORMATION* proc_info = + reinterpret_cast<PROCESS_INFORMATION*>(info->Buffer()); + // Here we force the app_name to be the one we used for the policy lookup. + // If our logic was wrong, at least we wont allow create a random process. + DWORD ret = ProcessPolicy::CreateProcessWAction(eval, *ipc->client_info, + exe_name, *cmd_line, + proc_info); + + ipc->return_info.win32_result = ret; + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/process_thread_dispatcher.h b/sandbox/win/src/process_thread_dispatcher.h new file mode 100644 index 0000000000..2bb3b6ea6f --- /dev/null +++ b/sandbox/win/src/process_thread_dispatcher.h @@ -0,0 +1,53 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_PROCESS_THREAD_DISPATCHER_H_ +#define SANDBOX_SRC_PROCESS_THREAD_DISPATCHER_H_ + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles process and thread-related IPC calls. +class ThreadProcessDispatcher : public Dispatcher { + public: + explicit ThreadProcessDispatcher(PolicyBase* policy_base); + ~ThreadProcessDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, int service) override; + + private: + // Processes IPC requests coming from calls to NtOpenThread() in the target. + bool NtOpenThread(IPCInfo* ipc, uint32 desired_access, uint32 thread_id); + + // Processes IPC requests coming from calls to NtOpenProcess() in the target. + bool NtOpenProcess(IPCInfo* ipc, uint32 desired_access, uint32 process_id); + + // Processes IPC requests from calls to NtOpenProcessToken() in the target. + bool NtOpenProcessToken(IPCInfo* ipc, HANDLE process, uint32 desired_access); + + // Processes IPC requests from calls to NtOpenProcessTokenEx() in the target. + bool NtOpenProcessTokenEx(IPCInfo* ipc, + HANDLE process, + uint32 desired_access, + uint32 attributes); + + // Processes IPC requests coming from calls to CreateProcessW() in the target. + bool CreateProcessW(IPCInfo* ipc, + base::string16* name, + base::string16* cmd_line, + base::string16* cur_dir, + CountedBuffer* info); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(ThreadProcessDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_THREAD_DISPATCHER_H_ diff --git a/sandbox/win/src/process_thread_interception.cc b/sandbox/win/src/process_thread_interception.cc new file mode 100644 index 0000000000..45926bc5f6 --- /dev/null +++ b/sandbox/win/src/process_thread_interception.cc @@ -0,0 +1,403 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_thread_interception.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +// Hooks NtOpenThread and proxy the call to the broker if it's trying to +// open a thread in the same process. +NTSTATUS WINAPI TargetNtOpenThread(NtOpenThreadFunction orig_OpenThread, + PHANDLE thread, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id) { + NTSTATUS status = orig_OpenThread(thread, desired_access, object_attributes, + client_id); + if (NT_SUCCESS(status)) + return status; + + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + if (!client_id) + break; + + uint32 thread_id = 0; + bool should_break = false; + __try { + // We support only the calls for the current process + if (NULL != client_id->UniqueProcess) + should_break = true; + + // Object attributes should be NULL or empty. + if (!should_break && NULL != object_attributes) { + if (0 != object_attributes->Attributes || + NULL != object_attributes->ObjectName || + NULL != object_attributes->RootDirectory || + NULL != object_attributes->SecurityDescriptor || + NULL != object_attributes->SecurityQualityOfService) { + should_break = true; + } + } + + thread_id = static_cast<uint32>( + reinterpret_cast<ULONG_PTR>(client_id->UniqueThread)); + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + + if (should_break) + break; + + if (!ValidParameter(thread, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_NTOPENTHREAD_TAG, desired_access, + thread_id, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + // The nt_status here is most likely STATUS_INVALID_CID because + // in the broker we set the process id in the CID (client ID) param + // to be the current process. If you try to open a thread from another + // process you will get this INVALID_CID error. On the other hand, if you + // try to open a thread in your own process, it should return success. + // We don't want to return STATUS_INVALID_CID here, so we return the + // return of the original open thread status, which is most likely + // STATUS_ACCESS_DENIED. + break; + + __try { + // Write the output parameters. + *thread = answer.handle; + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + + return answer.nt_status; + } while (false); + + return status; +} + +// Hooks NtOpenProcess and proxy the call to the broker if it's trying to +// open the current process. +NTSTATUS WINAPI TargetNtOpenProcess(NtOpenProcessFunction orig_OpenProcess, + PHANDLE process, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id) { + NTSTATUS status = orig_OpenProcess(process, desired_access, object_attributes, + client_id); + if (NT_SUCCESS(status)) + return status; + + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + if (!client_id) + break; + + uint32 process_id = 0; + bool should_break = false; + __try { + // Object attributes should be NULL or empty. + if (!should_break && NULL != object_attributes) { + if (0 != object_attributes->Attributes || + NULL != object_attributes->ObjectName || + NULL != object_attributes->RootDirectory || + NULL != object_attributes->SecurityDescriptor || + NULL != object_attributes->SecurityQualityOfService) { + should_break = true; + } + } + + process_id = static_cast<uint32>( + reinterpret_cast<ULONG_PTR>(client_id->UniqueProcess)); + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + + if (should_break) + break; + + if (!ValidParameter(process, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_NTOPENPROCESS_TAG, desired_access, + process_id, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + return answer.nt_status; + + __try { + // Write the output parameters. + *process = answer.handle; + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + + return answer.nt_status; + } while (false); + + return status; +} + + +NTSTATUS WINAPI TargetNtOpenProcessToken( + NtOpenProcessTokenFunction orig_OpenProcessToken, HANDLE process, + ACCESS_MASK desired_access, PHANDLE token) { + NTSTATUS status = orig_OpenProcessToken(process, desired_access, token); + if (NT_SUCCESS(status)) + return status; + + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + + if (CURRENT_PROCESS != process) + break; + + if (!ValidParameter(token, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_NTOPENPROCESSTOKEN_TAG, process, + desired_access, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + return answer.nt_status; + + __try { + // Write the output parameters. + *token = answer.handle; + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + + return answer.nt_status; + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtOpenProcessTokenEx( + NtOpenProcessTokenExFunction orig_OpenProcessTokenEx, HANDLE process, + ACCESS_MASK desired_access, ULONG handle_attributes, PHANDLE token) { + NTSTATUS status = orig_OpenProcessTokenEx(process, desired_access, + handle_attributes, token); + if (NT_SUCCESS(status)) + return status; + + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + + if (CURRENT_PROCESS != process) + break; + + if (!ValidParameter(token, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_NTOPENPROCESSTOKENEX_TAG, process, + desired_access, handle_attributes, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + return answer.nt_status; + + __try { + // Write the output parameters. + *token = answer.handle; + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + + return answer.nt_status; + } while (false); + + return status; +} + +BOOL WINAPI TargetCreateProcessW(CreateProcessWFunction orig_CreateProcessW, + LPCWSTR application_name, LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, DWORD flags, + LPVOID environment, LPCWSTR current_directory, + LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information) { + if (orig_CreateProcessW(application_name, command_line, process_attributes, + thread_attributes, inherit_handles, flags, + environment, current_directory, startup_info, + process_information)) { + return TRUE; + } + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return FALSE; + + DWORD original_error = ::GetLastError(); + + do { + if (!ValidParameter(process_information, sizeof(PROCESS_INFORMATION), + WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + const wchar_t* cur_dir = NULL; + + wchar_t current_directory[MAX_PATH]; + DWORD result = ::GetCurrentDirectory(MAX_PATH, current_directory); + if (0 != result && result < MAX_PATH) + cur_dir = current_directory; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + InOutCountedBuffer proc_info(process_information, + sizeof(PROCESS_INFORMATION)); + + ResultCode code = CrossCall(ipc, IPC_CREATEPROCESSW_TAG, application_name, + command_line, cur_dir, proc_info, &answer); + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + if (ERROR_SUCCESS != answer.win32_result) + return FALSE; + + return TRUE; + } while (false); + + ::SetLastError(original_error); + return FALSE; +} + +BOOL WINAPI TargetCreateProcessA(CreateProcessAFunction orig_CreateProcessA, + LPCSTR application_name, LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, DWORD flags, + LPVOID environment, LPCSTR current_directory, + LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information) { + if (orig_CreateProcessA(application_name, command_line, process_attributes, + thread_attributes, inherit_handles, flags, + environment, current_directory, startup_info, + process_information)) { + return TRUE; + } + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return FALSE; + + DWORD original_error = ::GetLastError(); + + do { + if (!ValidParameter(process_information, sizeof(PROCESS_INFORMATION), + WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + // Convert the input params to unicode. + UNICODE_STRING *cmd_unicode = NULL; + UNICODE_STRING *app_unicode = NULL; + if (command_line) { + cmd_unicode = AnsiToUnicode(command_line); + if (!cmd_unicode) + break; + } + + if (application_name) { + app_unicode = AnsiToUnicode(application_name); + if (!app_unicode) { + operator delete(cmd_unicode, NT_ALLOC); + break; + } + } + + const wchar_t* cmd_line = cmd_unicode ? cmd_unicode->Buffer : NULL; + const wchar_t* app_name = app_unicode ? app_unicode->Buffer : NULL; + const wchar_t* cur_dir = NULL; + + wchar_t current_directory[MAX_PATH]; + DWORD result = ::GetCurrentDirectory(MAX_PATH, current_directory); + if (0 != result && result < MAX_PATH) + cur_dir = current_directory; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + InOutCountedBuffer proc_info(process_information, + sizeof(PROCESS_INFORMATION)); + + ResultCode code = CrossCall(ipc, IPC_CREATEPROCESSW_TAG, app_name, + cmd_line, cur_dir, proc_info, &answer); + + operator delete(cmd_unicode, NT_ALLOC); + operator delete(app_unicode, NT_ALLOC); + + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + if (ERROR_SUCCESS != answer.win32_result) + return FALSE; + + return TRUE; + } while (false); + + ::SetLastError(original_error); + return FALSE; +} + +} // namespace sandbox diff --git a/sandbox/win/src/process_thread_interception.h b/sandbox/win/src/process_thread_interception.h new file mode 100644 index 0000000000..31dc231543 --- /dev/null +++ b/sandbox/win/src/process_thread_interception.h @@ -0,0 +1,90 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_PROCESS_THREAD_INTERCEPTION_H__ +#define SANDBOX_SRC_PROCESS_THREAD_INTERCEPTION_H__ + +namespace sandbox { + +extern "C" { + +typedef BOOL (WINAPI *CreateProcessWFunction)( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation); + +typedef BOOL (WINAPI *CreateProcessAFunction)( + LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation); + +typedef HANDLE (WINAPI *CreateThreadFunction)( + LPSECURITY_ATTRIBUTES lpThreadAttributes, + SIZE_T dwStackSize, + LPTHREAD_START_ROUTINE lpStartAddress, + PVOID lpParameter, + DWORD dwCreationFlags, + LPDWORD lpThreadId); + +typedef LCID (WINAPI *GetUserDefaultLCIDFunction)(); + +// Interception of NtOpenThread on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThread( + NtOpenThreadFunction orig_OpenThread, PHANDLE thread, + ACCESS_MASK desired_access, POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id); + +// Interception of NtOpenProcess on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenProcess( + NtOpenProcessFunction orig_OpenProcess, PHANDLE process, + ACCESS_MASK desired_access, POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id); + +// Interception of NtOpenProcessToken on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenProcessToken( + NtOpenProcessTokenFunction orig_OpenProcessToken, HANDLE process, + ACCESS_MASK desired_access, PHANDLE token); + +// Interception of NtOpenProcessTokenEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenProcessTokenEx( + NtOpenProcessTokenExFunction orig_OpenProcessTokenEx, HANDLE process, + ACCESS_MASK desired_access, ULONG handle_attributes, PHANDLE token); + +// Interception of CreateProcessW and A in kernel32.dll. +SANDBOX_INTERCEPT BOOL WINAPI TargetCreateProcessW( + CreateProcessWFunction orig_CreateProcessW, LPCWSTR application_name, + LPWSTR command_line, LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, BOOL inherit_handles, DWORD flags, + LPVOID environment, LPCWSTR current_directory, LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information); + +SANDBOX_INTERCEPT BOOL WINAPI TargetCreateProcessA( + CreateProcessAFunction orig_CreateProcessA, LPCSTR application_name, + LPSTR command_line, LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, BOOL inherit_handles, DWORD flags, + LPVOID environment, LPCSTR current_directory, LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_THREAD_INTERCEPTION_H__ diff --git a/sandbox/win/src/process_thread_policy.cc b/sandbox/win/src/process_thread_policy.cc new file mode 100644 index 0000000000..065359161f --- /dev/null +++ b/sandbox/win/src/process_thread_policy.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_thread_policy.h" + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// These are the only safe rights that can be given to a sandboxed +// process for the process created by the broker. All others are potential +// vectors of privilege elevation. +const DWORD kProcessRights = SYNCHRONIZE | + PROCESS_QUERY_INFORMATION | + PROCESS_QUERY_LIMITED_INFORMATION | + PROCESS_TERMINATE | + PROCESS_SUSPEND_RESUME; + +const DWORD kThreadRights = SYNCHRONIZE | + THREAD_TERMINATE | + THREAD_SUSPEND_RESUME | + THREAD_QUERY_INFORMATION | + THREAD_QUERY_LIMITED_INFORMATION | + THREAD_SET_LIMITED_INFORMATION; + +// Creates a child process and duplicates the handles to 'target_process'. The +// remaining parameters are the same as CreateProcess(). +BOOL CreateProcessExWHelper(HANDLE target_process, BOOL give_full_access, + LPCWSTR lpApplicationName, LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + BOOL bInheritHandles, DWORD dwCreationFlags, + LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) { + if (!::CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, + lpThreadAttributes, bInheritHandles, dwCreationFlags, + lpEnvironment, lpCurrentDirectory, lpStartupInfo, + lpProcessInformation)) { + return FALSE; + } + + DWORD process_access = kProcessRights; + DWORD thread_access = kThreadRights; + if (give_full_access) { + process_access = PROCESS_ALL_ACCESS; + thread_access = THREAD_ALL_ACCESS; + } + if (!::DuplicateHandle(::GetCurrentProcess(), lpProcessInformation->hProcess, + target_process, &lpProcessInformation->hProcess, + process_access, FALSE, DUPLICATE_CLOSE_SOURCE)) { + ::CloseHandle(lpProcessInformation->hThread); + return FALSE; + } + if (!::DuplicateHandle(::GetCurrentProcess(), lpProcessInformation->hThread, + target_process, &lpProcessInformation->hThread, + thread_access, FALSE, DUPLICATE_CLOSE_SOURCE)) { + return FALSE; + } + return TRUE; +} + +} + +namespace sandbox { + +bool ProcessPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + scoped_ptr<PolicyRule> process; + switch (semantics) { + case TargetPolicy::PROCESS_MIN_EXEC: { + process.reset(new PolicyRule(GIVE_READONLY)); + break; + }; + case TargetPolicy::PROCESS_ALL_EXEC: { + process.reset(new PolicyRule(GIVE_ALLACCESS)); + break; + }; + default: { + return false; + }; + } + + if (!process->AddStringMatch(IF, NameBased::NAME, name, CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IPC_CREATEPROCESSW_TAG, process.get())) { + return false; + } + return true; +} + +NTSTATUS ProcessPolicy::OpenThreadAction(const ClientInfo& client_info, + uint32 desired_access, + uint32 thread_id, + HANDLE* handle) { + *handle = NULL; + + NtOpenThreadFunction NtOpenThread = NULL; + ResolveNTFunctionPtr("NtOpenThread", &NtOpenThread); + + OBJECT_ATTRIBUTES attributes = {0}; + attributes.Length = sizeof(attributes); + CLIENT_ID client_id = {0}; + client_id.UniqueProcess = reinterpret_cast<PVOID>( + static_cast<ULONG_PTR>(client_info.process_id)); + client_id.UniqueThread = + reinterpret_cast<PVOID>(static_cast<ULONG_PTR>(thread_id)); + + HANDLE local_handle; + NTSTATUS status = NtOpenThread(&local_handle, desired_access, &attributes, + &client_id); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + + return status; +} + +NTSTATUS ProcessPolicy::OpenProcessAction(const ClientInfo& client_info, + uint32 desired_access, + uint32 process_id, + HANDLE* handle) { + *handle = NULL; + + NtOpenProcessFunction NtOpenProcess = NULL; + ResolveNTFunctionPtr("NtOpenProcess", &NtOpenProcess); + + if (client_info.process_id != process_id) + return STATUS_ACCESS_DENIED; + + OBJECT_ATTRIBUTES attributes = {0}; + attributes.Length = sizeof(attributes); + CLIENT_ID client_id = {0}; + client_id.UniqueProcess = reinterpret_cast<PVOID>( + static_cast<ULONG_PTR>(client_info.process_id)); + HANDLE local_handle; + NTSTATUS status = NtOpenProcess(&local_handle, desired_access, &attributes, + &client_id); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + + return status; +} + +NTSTATUS ProcessPolicy::OpenProcessTokenAction(const ClientInfo& client_info, + HANDLE process, + uint32 desired_access, + HANDLE* handle) { + *handle = NULL; + NtOpenProcessTokenFunction NtOpenProcessToken = NULL; + ResolveNTFunctionPtr("NtOpenProcessToken", &NtOpenProcessToken); + + if (CURRENT_PROCESS != process) + return STATUS_ACCESS_DENIED; + + HANDLE local_handle; + NTSTATUS status = NtOpenProcessToken(client_info.process, desired_access, + &local_handle); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + return status; +} + +NTSTATUS ProcessPolicy::OpenProcessTokenExAction(const ClientInfo& client_info, + HANDLE process, + uint32 desired_access, + uint32 attributes, + HANDLE* handle) { + *handle = NULL; + NtOpenProcessTokenExFunction NtOpenProcessTokenEx = NULL; + ResolveNTFunctionPtr("NtOpenProcessTokenEx", &NtOpenProcessTokenEx); + + if (CURRENT_PROCESS != process) + return STATUS_ACCESS_DENIED; + + HANDLE local_handle; + NTSTATUS status = NtOpenProcessTokenEx(client_info.process, desired_access, + attributes, &local_handle); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + return status; +} + +DWORD ProcessPolicy::CreateProcessWAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &app_name, + const base::string16 &command_line, + PROCESS_INFORMATION* process_info) { + // The only action supported is ASK_BROKER which means create the process. + if (GIVE_ALLACCESS != eval_result && GIVE_READONLY != eval_result) { + return ERROR_ACCESS_DENIED; + } + + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + scoped_ptr<wchar_t, base::FreeDeleter> + cmd_line(_wcsdup(command_line.c_str())); + + BOOL should_give_full_access = (GIVE_ALLACCESS == eval_result); + if (!CreateProcessExWHelper(client_info.process, should_give_full_access, + app_name.c_str(), cmd_line.get(), NULL, NULL, + FALSE, 0, NULL, NULL, &startup_info, + process_info)) { + return ERROR_ACCESS_DENIED; + } + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/sandbox/win/src/process_thread_policy.h b/sandbox/win/src/process_thread_policy.h new file mode 100644 index 0000000000..2871dcaa27 --- /dev/null +++ b/sandbox/win/src/process_thread_policy.h @@ -0,0 +1,83 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_PROCESS_THREAD_POLICY_H_ +#define SANDBOX_SRC_PROCESS_THREAD_POLICY_H_ + +#include <string> + +#include "sandbox/win/src/policy_low_level.h" + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum EvalResult; + +// This class centralizes most of the knowledge related to process execution. +class ProcessPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level. + // policy rule for process creation + // 'name' is the executable to be spawn. + // 'semantics' is the desired semantics. + // 'policy' is the policy generator to which the rules are going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Opens a thread from the child process and returns the handle. + // client_info contains the information about the child process, + // desired_access is the access requested by the child and thread_id + // is the thread_id to be opened. + // The function returns the return value of NtOpenThread. + static NTSTATUS OpenThreadAction(const ClientInfo& client_info, + uint32 desired_access, + uint32 thread_id, + HANDLE* handle); + + // Opens the process id passed in and returns the duplicated handle to + // the child. We only allow the child processes to open themselves. Any other + // pid open is denied. + static NTSTATUS OpenProcessAction(const ClientInfo& client_info, + uint32 desired_access, + uint32 process_id, + HANDLE* handle); + + // Opens the token associated with the process and returns the duplicated + // handle to the child. We only allow the child processes to open his own + // token (using ::GetCurrentProcess()). + static NTSTATUS OpenProcessTokenAction(const ClientInfo& client_info, + HANDLE process, + uint32 desired_access, + HANDLE* handle); + + // Opens the token associated with the process and returns the duplicated + // handle to the child. We only allow the child processes to open his own + // token (using ::GetCurrentProcess()). + static NTSTATUS OpenProcessTokenExAction(const ClientInfo& client_info, + HANDLE process, + uint32 desired_access, + uint32 attributes, + HANDLE* handle); + + // Processes a 'CreateProcessW()' request from the target. + // 'client_info' : the target process that is making the request. + // 'eval_result' : The desired policy action to accomplish. + // 'app_name' : The full path of the process to be created. + // 'command_line' : The command line passed to the created process. + static DWORD CreateProcessWAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &app_name, + const base::string16 &command_line, + PROCESS_INFORMATION* process_info); +}; + +} // namespace sandbox + + +#endif // SANDBOX_SRC_PROCESS_THREAD_POLICY_H_ diff --git a/sandbox/win/src/registry_dispatcher.cc b/sandbox/win/src/registry_dispatcher.cc new file mode 100644 index 0000000000..967fe652ad --- /dev/null +++ b/sandbox/win/src/registry_dispatcher.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/registry_dispatcher.h" + +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/registry_interception.h" +#include "sandbox/win/src/registry_policy.h" + +namespace { + +// Builds a path using the root directory and the name. +bool GetCompletePath(HANDLE root, const base::string16& name, + base::string16* complete_name) { + if (root) { + if (!sandbox::GetPathFromHandle(root, complete_name)) + return false; + + *complete_name += L"\\"; + *complete_name += name; + } else { + *complete_name = name; + } + + return true; +} + +} + +namespace sandbox { + +RegistryDispatcher::RegistryDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IPC_NTCREATEKEY_TAG, WCHAR_TYPE, UINT32_TYPE, VOIDPTR_TYPE, UINT32_TYPE, + UINT32_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>(&RegistryDispatcher::NtCreateKey) + }; + + static const IPCCall open_params = { + {IPC_NTOPENKEY_TAG, WCHAR_TYPE, UINT32_TYPE, VOIDPTR_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>(&RegistryDispatcher::NtOpenKey) + }; + + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(open_params); +} + +bool RegistryDispatcher::SetupService(InterceptionManager* manager, + int service) { + if (IPC_NTCREATEKEY_TAG == service) + return INTERCEPT_NT(manager, NtCreateKey, CREATE_KEY_ID, 32); + + if (IPC_NTOPENKEY_TAG == service) { + bool result = INTERCEPT_NT(manager, NtOpenKey, OPEN_KEY_ID, 16); + if (base::win::GetVersion() >= base::win::VERSION_WIN7 || + (base::win::GetVersion() == base::win::VERSION_VISTA && + base::win::OSInfo::GetInstance()->version_type() == + base::win::SUITE_SERVER)) + result &= INTERCEPT_NT(manager, NtOpenKeyEx, OPEN_KEY_EX_ID, 20); + return result; + } + + return false; +} + +bool RegistryDispatcher::NtCreateKey(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + HANDLE root, + uint32 desired_access, + uint32 title_index, + uint32 create_options) { + base::win::ScopedHandle root_handle; + base::string16 real_path = *name; + + // If there is a root directory, we need to duplicate the handle to make + // it valid in this process. + if (root) { + if (!::DuplicateHandle(ipc->client_info->process, root, + ::GetCurrentProcess(), &root, 0, FALSE, + DUPLICATE_SAME_ACCESS)) + return false; + + root_handle.Set(root); + } + + if (!GetCompletePath(root, *name, &real_path)) + return false; + + const wchar_t* regname = real_path.c_str(); + CountedParameterSet<OpenKey> params; + params[OpenKey::NAME] = ParamPickerMake(regname); + params[OpenKey::ACCESS] = ParamPickerMake(desired_access); + + EvalResult result = policy_base_->EvalPolicy(IPC_NTCREATEKEY_TAG, + params.GetBase()); + + HANDLE handle; + NTSTATUS nt_status; + ULONG disposition = 0; + if (!RegistryPolicy::CreateKeyAction(result, *ipc->client_info, *name, + attributes, root, desired_access, + title_index, create_options, &handle, + &nt_status, &disposition)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.extended[0].unsigned_int = disposition; + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +bool RegistryDispatcher::NtOpenKey(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + HANDLE root, + uint32 desired_access) { + base::win::ScopedHandle root_handle; + base::string16 real_path = *name; + + // If there is a root directory, we need to duplicate the handle to make + // it valid in this process. + if (root) { + if (!::DuplicateHandle(ipc->client_info->process, root, + ::GetCurrentProcess(), &root, 0, FALSE, + DUPLICATE_SAME_ACCESS)) + return false; + root_handle.Set(root); + } + + if (!GetCompletePath(root, *name, &real_path)) + return false; + + const wchar_t* regname = real_path.c_str(); + CountedParameterSet<OpenKey> params; + params[OpenKey::NAME] = ParamPickerMake(regname); + params[OpenKey::ACCESS] = ParamPickerMake(desired_access); + + EvalResult result = policy_base_->EvalPolicy(IPC_NTOPENKEY_TAG, + params.GetBase()); + HANDLE handle; + NTSTATUS nt_status; + if (!RegistryPolicy::OpenKeyAction(result, *ipc->client_info, *name, + attributes, root, desired_access, &handle, + &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/registry_dispatcher.h b/sandbox/win/src/registry_dispatcher.h new file mode 100644 index 0000000000..83811a9cd6 --- /dev/null +++ b/sandbox/win/src/registry_dispatcher.h @@ -0,0 +1,47 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_REGISTRY_DISPATCHER_H_ +#define SANDBOX_SRC_REGISTRY_DISPATCHER_H_ + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles registry-related IPC calls. +class RegistryDispatcher : public Dispatcher { + public: + explicit RegistryDispatcher(PolicyBase* policy_base); + ~RegistryDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, int service) override; + + private: + // Processes IPC requests coming from calls to NtCreateKey in the target. + bool NtCreateKey(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + HANDLE root, + uint32 desired_access, + uint32 title_index, + uint32 create_options); + + // Processes IPC requests coming from calls to NtOpenKey in the target. + bool NtOpenKey(IPCInfo* ipc, + base::string16* name, + uint32 attributes, + HANDLE root, + uint32 desired_access); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(RegistryDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_REGISTRY_DISPATCHER_H_ diff --git a/sandbox/win/src/registry_interception.cc b/sandbox/win/src/registry_interception.cc new file mode 100644 index 0000000000..4a1a846984 --- /dev/null +++ b/sandbox/win/src/registry_interception.cc @@ -0,0 +1,224 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/registry_interception.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +NTSTATUS WINAPI TargetNtCreateKey(NtCreateKeyFunction orig_CreateKey, + PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG title_index, PUNICODE_STRING class_name, + ULONG create_options, PULONG disposition) { + // Check if the process can create it first. + NTSTATUS status = orig_CreateKey(key, desired_access, object_attributes, + title_index, class_name, create_options, + disposition); + if (NT_SUCCESS(status)) + return status; + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(key, sizeof(HANDLE), WRITE)) + break; + + if (disposition && !ValidParameter(disposition, sizeof(ULONG), WRITE)) + break; + + // At this point we don't support class_name. + if (class_name && class_name->Buffer && class_name->Length) + break; + + // We don't support creating link keys, volatile keys and backup/restore. + if (create_options) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + wchar_t* name; + uint32 attributes = 0; + HANDLE root_directory = 0; + NTSTATUS ret = AllocAndCopyName(object_attributes, &name, &attributes, + &root_directory); + if (!NT_SUCCESS(ret) || NULL == name) + break; + + uint32 desired_access_uint32 = desired_access; + CountedParameterSet<OpenKey> params; + params[OpenKey::ACCESS] = ParamPickerMake(desired_access_uint32); + + wchar_t* full_name = NULL; + + if (root_directory) { + ret = sandbox::AllocAndGetFullPath(root_directory, name, &full_name); + if (!NT_SUCCESS(ret) || NULL == full_name) + break; + params[OpenKey::NAME] = ParamPickerMake(full_name); + } else { + params[OpenKey::NAME] = ParamPickerMake(name); + } + + bool query_broker = QueryBroker(IPC_NTCREATEKEY_TAG, params.GetBase()); + + if (full_name != NULL) + operator delete(full_name, NT_ALLOC); + + if (!query_broker) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + ResultCode code = CrossCall(ipc, IPC_NTCREATEKEY_TAG, name, attributes, + root_directory, desired_access, title_index, + create_options, &answer); + + operator delete(name, NT_ALLOC); + + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + // TODO(nsylvain): We should return answer.nt_status here instead + // of status. We can do this only after we checked the policy. + // otherwise we will returns ACCESS_DENIED for all paths + // that are not specified by a policy, even though your token allows + // access to that path, and the original call had a more meaningful + // error. Bug 4369 + break; + + __try { + *key = answer.handle; + + if (disposition) + *disposition = answer.extended[0].unsigned_int; + + status = answer.nt_status; + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + } while (false); + + return status; +} + +NTSTATUS WINAPI CommonNtOpenKey(NTSTATUS status, PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(key, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + break; + + wchar_t* name; + uint32 attributes; + HANDLE root_directory; + NTSTATUS ret = AllocAndCopyName(object_attributes, &name, &attributes, + &root_directory); + if (!NT_SUCCESS(ret) || NULL == name) + break; + + uint32 desired_access_uint32 = desired_access; + CountedParameterSet<OpenKey> params; + params[OpenKey::ACCESS] = ParamPickerMake(desired_access_uint32); + + wchar_t* full_name = NULL; + + if (root_directory) { + ret = sandbox::AllocAndGetFullPath(root_directory, name, &full_name); + if (!NT_SUCCESS(ret) || NULL == full_name) + break; + params[OpenKey::NAME] = ParamPickerMake(full_name); + } else { + params[OpenKey::NAME] = ParamPickerMake(name); + } + + bool query_broker = QueryBroker(IPC_NTOPENKEY_TAG, params.GetBase()); + + if (full_name != NULL) + operator delete(full_name, NT_ALLOC); + + if (!query_broker) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IPC_NTOPENKEY_TAG, name, attributes, + root_directory, desired_access, &answer); + + operator delete(name, NT_ALLOC); + + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + // TODO(nsylvain): We should return answer.nt_status here instead + // of status. We can do this only after we checked the policy. + // otherwise we will returns ACCESS_DENIED for all paths + // that are not specified by a policy, even though your token allows + // access to that path, and the original call had a more meaningful + // error. Bug 4369 + break; + + __try { + *key = answer.handle; + status = answer.nt_status; + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtOpenKey(NtOpenKeyFunction orig_OpenKey, PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + // Check if the process can open it first. + NTSTATUS status = orig_OpenKey(key, desired_access, object_attributes); + if (NT_SUCCESS(status)) + return status; + + return CommonNtOpenKey(status, key, desired_access, object_attributes); +} + +NTSTATUS WINAPI TargetNtOpenKeyEx(NtOpenKeyExFunction orig_OpenKeyEx, + PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG open_options) { + // Check if the process can open it first. + NTSTATUS status = orig_OpenKeyEx(key, desired_access, object_attributes, + open_options); + + // We do not support open_options at this time. The 2 current known values + // are REG_OPTION_CREATE_LINK, to open a symbolic link, and + // REG_OPTION_BACKUP_RESTORE to open the key with special privileges. + if (NT_SUCCESS(status) || open_options != 0) + return status; + + return CommonNtOpenKey(status, key, desired_access, object_attributes); +} + +} // namespace sandbox diff --git a/sandbox/win/src/registry_interception.h b/sandbox/win/src/registry_interception.h new file mode 100644 index 0000000000..c3cbde02e7 --- /dev/null +++ b/sandbox/win/src/registry_interception.h @@ -0,0 +1,38 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_REGISTRY_INTERCEPTION_H__ +#define SANDBOX_SRC_REGISTRY_INTERCEPTION_H__ + +namespace sandbox { + +extern "C" { + +// Interception of NtCreateKey on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateKey( + NtCreateKeyFunction orig_CreateKey, PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, ULONG title_index, + PUNICODE_STRING class_name, ULONG create_options, PULONG disposition); + +// Interception of NtOpenKey on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenKey( + NtOpenKeyFunction orig_OpenKey, PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +// Interception of NtOpenKeyEx on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenKeyEx( + NtOpenKeyExFunction orig_OpenKeyEx, PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, ULONG open_options); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_REGISTRY_INTERCEPTION_H__ diff --git a/sandbox/win/src/registry_policy.cc b/sandbox/win/src/registry_policy.cc new file mode 100644 index 0000000000..b0f24a79ee --- /dev/null +++ b/sandbox/win/src/registry_policy.cc @@ -0,0 +1,225 @@ +// Copyright (c) 2006-2008 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 <string> + +#include "sandbox/win/src/registry_policy.h" + +#include "base/logging.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +static const uint32 kAllowedRegFlags = + KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_READ | + GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL; + +// Opens the key referenced by |obj_attributes| with |access| and +// checks what permission was given. Remove the WRITE flags and update +// |access| with the new value. +NTSTATUS TranslateMaximumAllowed(OBJECT_ATTRIBUTES* obj_attributes, + DWORD* access) { + NtOpenKeyFunction NtOpenKey = NULL; + ResolveNTFunctionPtr("NtOpenKey", &NtOpenKey); + + NtCloseFunction NtClose = NULL; + ResolveNTFunctionPtr("NtClose", &NtClose); + + NtQueryObjectFunction NtQueryObject = NULL; + ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); + + // Open the key. + HANDLE handle; + NTSTATUS status = NtOpenKey(&handle, *access, obj_attributes); + if (!NT_SUCCESS(status)) + return status; + + OBJECT_BASIC_INFORMATION info = {0}; + status = NtQueryObject(handle, ObjectBasicInformation, &info, sizeof(info), + NULL); + NtClose(handle); + if (!NT_SUCCESS(status)) + return status; + + *access = info.GrantedAccess & kAllowedRegFlags; + return STATUS_SUCCESS; +} + +NTSTATUS NtCreateKeyInTarget(HANDLE* target_key_handle, + ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES* obj_attributes, + ULONG title_index, + UNICODE_STRING* class_name, + ULONG create_options, + ULONG* disposition, + HANDLE target_process) { + NtCreateKeyFunction NtCreateKey = NULL; + ResolveNTFunctionPtr("NtCreateKey", &NtCreateKey); + + if (MAXIMUM_ALLOWED & desired_access) { + NTSTATUS status = TranslateMaximumAllowed(obj_attributes, &desired_access); + if (!NT_SUCCESS(status)) + return STATUS_ACCESS_DENIED; + } + + HANDLE local_handle = INVALID_HANDLE_VALUE; + NTSTATUS status = NtCreateKey(&local_handle, desired_access, obj_attributes, + title_index, class_name, create_options, + disposition); + if (!NT_SUCCESS(status)) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + target_process, target_key_handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return STATUS_SUCCESS; +} + +NTSTATUS NtOpenKeyInTarget(HANDLE* target_key_handle, + ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES* obj_attributes, + HANDLE target_process) { + NtOpenKeyFunction NtOpenKey = NULL; + ResolveNTFunctionPtr("NtOpenKey", &NtOpenKey); + + if (MAXIMUM_ALLOWED & desired_access) { + NTSTATUS status = TranslateMaximumAllowed(obj_attributes, &desired_access); + if (!NT_SUCCESS(status)) + return STATUS_ACCESS_DENIED; + } + + HANDLE local_handle = INVALID_HANDLE_VALUE; + NTSTATUS status = NtOpenKey(&local_handle, desired_access, obj_attributes); + + if (!NT_SUCCESS(status)) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + target_process, target_key_handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return STATUS_SUCCESS; +} + +} + +namespace sandbox { + +bool RegistryPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + base::string16 resovled_name(name); + if (resovled_name.empty()) { + return false; + } + + if (!ResolveRegistryName(resovled_name, &resovled_name)) + return false; + + name = resovled_name.c_str(); + + EvalResult result = ASK_BROKER; + + PolicyRule open(result); + PolicyRule create(result); + + switch (semantics) { + case TargetPolicy::REG_ALLOW_READONLY: { + // We consider all flags that are not known to be readonly as potentially + // used for write. Here we also support MAXIMUM_ALLOWED, but we are going + // to expand it to read-only before the call. + uint32 restricted_flags = ~(kAllowedRegFlags | MAXIMUM_ALLOWED); + open.AddNumberMatch(IF_NOT, OpenKey::ACCESS, restricted_flags, AND); + create.AddNumberMatch(IF_NOT, OpenKey::ACCESS, restricted_flags, AND); + break; + } + case TargetPolicy::REG_ALLOW_ANY: { + break; + } + default: { + NOTREACHED(); + return false; + } + } + + if (!create.AddStringMatch(IF, OpenKey::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IPC_NTCREATEKEY_TAG, &create)) { + return false; + } + + if (!open.AddStringMatch(IF, OpenKey::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IPC_NTOPENKEY_TAG, &open)) { + return false; + } + + return true; +} + +bool RegistryPolicy::CreateKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &key, + uint32 attributes, + HANDLE root_directory, + uint32 desired_access, + uint32 title_index, + uint32 create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG* disposition) { + // The only action supported is ASK_BROKER which means create the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + // We don't support creating link keys, volatile keys or backup/restore. + if (create_options) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + InitObjectAttribs(key, attributes, root_directory, &obj_attributes, + &uni_name, NULL); + *nt_status = NtCreateKeyInTarget(handle, desired_access, &obj_attributes, + title_index, NULL, create_options, + disposition, client_info.process); + return true; +} + +bool RegistryPolicy::OpenKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &key, + uint32 attributes, + HANDLE root_directory, + uint32 desired_access, + HANDLE* handle, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means open the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return true; + } + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + InitObjectAttribs(key, attributes, root_directory, &obj_attributes, + &uni_name, NULL); + *nt_status = NtOpenKeyInTarget(handle, desired_access, &obj_attributes, + client_info.process); + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/registry_policy.h b/sandbox/win/src/registry_policy.h new file mode 100644 index 0000000000..69af8415d2 --- /dev/null +++ b/sandbox/win/src/registry_policy.h @@ -0,0 +1,58 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_REGISTRY_POLICY_H__ +#define SANDBOX_SRC_REGISTRY_POLICY_H__ + +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum EvalResult; + +// This class centralizes most of the knowledge related to registry policy +class RegistryPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for registry IO, in particular open or create actions. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Performs the desired policy action on a create request with an + // API that is compatible with the IPC-received parameters. + static bool CreateKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &key, + uint32 attributes, + HANDLE root_directory, + uint32 desired_access, + uint32 title_index, + uint32 create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG* disposition); + + // Performs the desired policy action on an open request with an + // API that is compatible with the IPC-received parameters. + static bool OpenKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &key, + uint32 attributes, + HANDLE root_directory, + uint32 desired_access, + HANDLE* handle, + NTSTATUS* nt_status); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_REGISTRY_POLICY_H__ diff --git a/sandbox/win/src/registry_policy_test.cc b/sandbox/win/src/registry_policy_test.cc new file mode 100644 index 0000000000..d8ee34b06d --- /dev/null +++ b/sandbox/win/src/registry_policy_test.cc @@ -0,0 +1,289 @@ +// Copyright (c) 2006-2010 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 <shlobj.h> + +#include "testing/gtest/include/gtest/gtest.h" +#include "sandbox/win/src/registry_policy.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" + +namespace { + +static const DWORD kAllowedRegFlags = KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | + KEY_NOTIFY | KEY_READ | GENERIC_READ | + GENERIC_EXECUTE | READ_CONTROL; + +#define BINDNTDLL(name) \ + name ## Function name = reinterpret_cast<name ## Function>( \ + ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name)) + +bool IsKeyOpenForRead(HKEY handle) { + BINDNTDLL(NtQueryObject); + + OBJECT_BASIC_INFORMATION info = {0}; + NTSTATUS status = NtQueryObject(handle, ObjectBasicInformation, &info, + sizeof(info), NULL); + + if (!NT_SUCCESS(status)) + return false; + + if ((info.GrantedAccess & (~kAllowedRegFlags)) != 0) + return false; + return true; +} + +} + +namespace sandbox { + +SBOX_TESTS_COMMAND int Reg_OpenKey(int argc, wchar_t **argv) { + if (argc != 4) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + REGSAM desired_access = 0; + ULONG options = 0; + if (wcscmp(argv[1], L"read") == 0) { + desired_access = KEY_READ; + } else if (wcscmp(argv[1], L"write") == 0) { + desired_access = KEY_ALL_ACCESS; + } else if (wcscmp(argv[1], L"link") == 0) { + options = REG_OPTION_CREATE_LINK; + desired_access = KEY_ALL_ACCESS; + } else { + desired_access = MAXIMUM_ALLOWED; + } + + HKEY root = GetReservedKeyFromName(argv[2]); + HKEY key; + LRESULT result = 0; + + if (wcscmp(argv[0], L"create") == 0) + result = ::RegCreateKeyEx(root, argv[3], 0, NULL, options, desired_access, + NULL, &key, NULL); + else + result = ::RegOpenKeyEx(root, argv[3], 0, desired_access, &key); + + if (ERROR_SUCCESS == result) { + if (MAXIMUM_ALLOWED == desired_access) { + if (!IsKeyOpenForRead(key)) { + ::RegCloseKey(key); + return SBOX_TEST_FAILED; + } + } + ::RegCloseKey(key); + return SBOX_TEST_SUCCEEDED; + } else if (ERROR_ACCESS_DENIED == result) { + return SBOX_TEST_DENIED; + } + + return SBOX_TEST_FAILED; +} + +TEST(RegistryPolicyTest, TestKeyAnyAccess) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Microsoft")); + + // Tests read access on key allowed for read-write. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest( + L"Reg_OpenKey create read HKEY_LOCAL_MACHINE software\\microsoft")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest( + L"Reg_OpenKey open read HKEY_LOCAL_MACHINE software\\microsoft")); + + if (::IsUserAnAdmin()) { + // Tests write access on key allowed for read-write. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest( + L"Reg_OpenKey create write HKEY_LOCAL_MACHINE software\\microsoft")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest( + L"Reg_OpenKey open write HKEY_LOCAL_MACHINE software\\microsoft")); + } + + // Tests subdirectory access on keys where we don't have subdirectory acess. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Reg_OpenKey create read " + L"HKEY_LOCAL_MACHINE software\\microsoft\\Windows")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Reg_OpenKey open read " + L"HKEY_LOCAL_MACHINE software\\microsoft\\windows")); + + // Tests to see if we can create keys where we dont have subdirectory access. + // This is denied. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Reg_OpenKey create write " + L"HKEY_LOCAL_MACHINE software\\Microsoft\\google_unit_tests")); + + RegDeleteKey(HKEY_LOCAL_MACHINE, L"software\\Microsoft\\google_unit_tests"); + + // Tests if we need to handle differently the "\\" at the end. + // This is denied. We need to add both rules. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest( + L"Reg_OpenKey create read HKEY_LOCAL_MACHINE software\\microsoft\\")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest( + L"Reg_OpenKey open read HKEY_LOCAL_MACHINE software\\microsoft\\")); +} + +TEST(RegistryPolicyTest, TestKeyNoAccess) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + // Tests read access where we don't have access at all. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest( + L"Reg_OpenKey create read HKEY_LOCAL_MACHINE software")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest( + L"Reg_OpenKey open read HKEY_LOCAL_MACHINE software")); +} + +TEST(RegistryPolicyTest, TestKeyReadOnlyAccess) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies\\*")); + + // Tests subdirectory acess on keys where we have subdirectory acess. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Reg_OpenKey create read " + L"HKEY_LOCAL_MACHINE software\\Policies\\microsoft")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Reg_OpenKey open read " + L"HKEY_LOCAL_MACHINE software\\Policies\\microsoft")); + + // Tests to see if we can create keys where we have subdirectory access. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Reg_OpenKey create write " + L"HKEY_LOCAL_MACHINE software\\Policies\\google_unit_tests")); + + RegDeleteKey(HKEY_LOCAL_MACHINE, L"software\\Policies\\google_unit_tests"); +} + +TEST(RegistryPolicyTest, TestKeyAllAccessSubDir) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies\\*")); + + if (::IsUserAnAdmin()) { + // Tests to see if we can create keys where we have subdirectory access. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Reg_OpenKey create write " + L"HKEY_LOCAL_MACHINE software\\Policies\\google_unit_tests")); + + RegDeleteKey(HKEY_LOCAL_MACHINE, L"software\\Policies\\google_unit_tests"); + } +} + +TEST(RegistryPolicyTest, TestKeyCreateLink) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies\\*")); + + // Tests to see if we can create a registry link key. + // NOTE: In theory here we should make sure to check for SBOX_TEST_DENIED + // instead of !SBOX_TEST_SUCCEEDED, but unfortunately the result is not + // access denied. Internally RegCreateKeyEx (At least on Vista 64) tries to + // create the link, and we return successfully access denied, then, it + // decides to try to break the path in multiple chunks, and create the links + // one by one. In this scenario, it tries to create "HKLM\Software" as a + // link key, which obviously fail with STATUS_OBJECT_NAME_COLLISION, and + // this is what is returned to the user. + EXPECT_NE(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Reg_OpenKey create link " + L"HKEY_LOCAL_MACHINE software\\Policies\\google_unit_tests")); + + // In case our code fails, and the call works, we need to delete the new + // link. There is no api for this, so we need to use the NT call. + HKEY key = NULL; + LRESULT result = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"software\\Policies\\google_unit_tests", + REG_OPTION_OPEN_LINK, MAXIMUM_ALLOWED, + &key); + + if (!result) { + HMODULE ntdll = GetModuleHandle(L"ntdll.dll"); + NtDeleteKeyFunction NtDeleteKey = + reinterpret_cast<NtDeleteKeyFunction>(GetProcAddress(ntdll, + "NtDeleteKey")); + NtDeleteKey(key); + } +} + +TEST(RegistryPolicyTest, TestKeyReadOnlyHKCU) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER\\Software")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_USERS\\.default")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_USERS\\.default\\software")); + + // Tests read access where we only have read-only access. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest( + L"Reg_OpenKey create read HKEY_CURRENT_USER software")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest( + L"Reg_OpenKey open read HKEY_CURRENT_USER software")); + + // Tests write access where we only have read-only acess. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest( + L"Reg_OpenKey create write HKEY_CURRENT_USER software")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest( + L"Reg_OpenKey open write HKEY_CURRENT_USER software")); + + // Tests maximum allowed access where we only have read-only access. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest( + L"Reg_OpenKey create maximum_allowed HKEY_CURRENT_USER software")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest( + L"Reg_OpenKey open maximum_allowed HKEY_CURRENT_USER software")); +} + +} // namespace sandbox diff --git a/sandbox/win/src/resolver.cc b/sandbox/win/src/resolver.cc new file mode 100644 index 0000000000..6616fa52c3 --- /dev/null +++ b/sandbox/win/src/resolver.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/resolver.h" + +#include "base/win/pe_image.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +NTSTATUS ResolverThunk::Init(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes) { + if (NULL == thunk_storage || 0 == storage_bytes || + NULL == target_module || NULL == target_name) + return STATUS_INVALID_PARAMETER; + + if (storage_bytes < GetThunkSize()) + return STATUS_BUFFER_TOO_SMALL; + + NTSTATUS ret = STATUS_SUCCESS; + if (NULL == interceptor_entry_point) { + ret = ResolveInterceptor(interceptor_module, interceptor_name, + &interceptor_entry_point); + if (!NT_SUCCESS(ret)) + return ret; + } + + ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + interceptor_ = interceptor_entry_point; + + return ret; +} + +NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module, + const char* interceptor_name, + const void** address) { + DCHECK_NT(address); + if (!interceptor_module) + return STATUS_INVALID_PARAMETER; + + base::win::PEImage pe(interceptor_module); + if (!pe.VerifyMagic()) + return STATUS_INVALID_IMAGE_FORMAT; + + *address = pe.GetProcAddress(interceptor_name); + + if (!(*address)) + return STATUS_PROCEDURE_NOT_FOUND; + + return STATUS_SUCCESS; +} + +} // namespace sandbox diff --git a/sandbox/win/src/resolver.h b/sandbox/win/src/resolver.h new file mode 100644 index 0000000000..85f1e91990 --- /dev/null +++ b/sandbox/win/src/resolver.h @@ -0,0 +1,105 @@ +// Copyright (c) 2010 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. + +// Defines ResolverThunk, the interface for classes that perform interceptions. +// For more details see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#include "base/basictypes.h" +#include "sandbox/win/src/nt_internals.h" + +#ifndef SANDBOX_SRC_RESOLVER_H__ +#define SANDBOX_SRC_RESOLVER_H__ + +namespace sandbox { + +// A resolver is the object in charge of performing the actual interception of +// a function. There should be a concrete implementation of a resolver roughly +// per type of interception. +class ResolverThunk { + public: + ResolverThunk() {} + virtual ~ResolverThunk() {} + + // Performs the actual interception of a function. + // target_name is an exported function from the module loaded at + // target_module, and must be replaced by interceptor_name, exported from + // interceptor_module. interceptor_entry_point can be provided instead of + // interceptor_name / interceptor_module. + // thunk_storage must point to a buffer on the child's address space, to hold + // the patch thunk, and related data. If provided, storage_used will receive + // the number of bytes used from thunk_storage. + // + // Example: (without error checking) + // + // size_t size = resolver.GetThunkSize(); + // char* buffer = ::VirtualAllocEx(child_process, NULL, size, + // MEM_COMMIT, PAGE_READWRITE); + // resolver.Setup(ntdll_module, NULL, L"NtCreateFile", NULL, + // &MyReplacementFunction, buffer, size, NULL); + // + // In general, the idea is to allocate a single big buffer for all + // interceptions on the same dll, and call Setup n times. + // WARNING: This means that any data member that is specific to a single + // interception must be reset within this method. + virtual NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) = 0; + + // Gets the address of function_name inside module (main exe). + virtual NTSTATUS ResolveInterceptor(const void* module, + const char* function_name, + const void** address); + + // Gets the address of an exported function_name inside module. + virtual NTSTATUS ResolveTarget(const void* module, + const char* function_name, + void** address); + + // Gets the required buffer size for this type of thunk. + virtual size_t GetThunkSize() const = 0; + + protected: + // Performs basic initialization on behalf of a concrete instance of a + // resolver. That is, parameter validation and resolution of the target + // and the interceptor into the member variables. + // + // target_name is an exported function from the module loaded at + // target_module, and must be replaced by interceptor_name, exported from + // interceptor_module. interceptor_entry_point can be provided instead of + // interceptor_name / interceptor_module. + // thunk_storage must point to a buffer on the child's address space, to hold + // the patch thunk, and related data. + virtual NTSTATUS Init(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes); + + // Gets the required buffer size for the internal part of the thunk. + size_t GetInternalThunkSize() const; + + // Initializes the internal part of the thunk. + // interceptor is the function to be called instead of original_function. + bool SetInternalThunk(void* storage, size_t storage_bytes, + const void* original_function, const void* interceptor); + + // Holds the resolved interception target. + void* target_; + // Holds the resolved interception interceptor. + const void* interceptor_; + + DISALLOW_COPY_AND_ASSIGN(ResolverThunk); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_RESOLVER_H__ diff --git a/sandbox/win/src/resolver_32.cc b/sandbox/win/src/resolver_32.cc new file mode 100644 index 0000000000..a591a8b1dd --- /dev/null +++ b/sandbox/win/src/resolver_32.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/resolver.h" + +// For placement new. This file must not depend on the CRT at runtime, but +// placement operator new is inline. +#include <new> + +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +#pragma pack(push, 1) +struct InternalThunk { + // This struct contains roughly the following code: + // sub esp, 8 // Create working space + // push edx // Save register + // mov edx, [esp + 0xc] // Get return adddress + // mov [esp + 8], edx // Store return address + // mov dword ptr [esp + 0xc], 0x7c401200 // Store extra argument + // mov dword ptr [esp + 4], 0x40010203 // Store address to jump to + // pop edx // Restore register + // ret // Jump to interceptor + // + // This code only modifies esp and eip so it must work with to normal calling + // convention. It is assembled as: + // + // 00 83ec08 sub esp,8 + // 03 52 push edx + // 04 8b54240c mov edx,dword ptr [esp + 0Ch] + // 08 89542408 mov dword ptr [esp + 8], edx + // 0c c744240c0012407c mov dword ptr [esp + 0Ch], 7C401200h + // 14 c744240403020140 mov dword ptr [esp + 4], 40010203h + // 1c 5a pop edx + // 1d c3 ret + InternalThunk() { + opcodes_1 = 0x5208ec83; + opcodes_2 = 0x0c24548b; + opcodes_3 = 0x08245489; + opcodes_4 = 0x0c2444c7; + opcodes_5 = 0x042444c7; + opcodes_6 = 0xc35a; + extra_argument = 0; + interceptor_function = 0; + }; + ULONG opcodes_1; // = 0x5208ec83 + ULONG opcodes_2; // = 0x0c24548b + ULONG opcodes_3; // = 0x08245489 + ULONG opcodes_4; // = 0x0c2444c7 + ULONG extra_argument; + ULONG opcodes_5; // = 0x042444c7 + ULONG interceptor_function; + USHORT opcodes_6; // = 0xc35a +}; +#pragma pack(pop) + +}; // namespace + +namespace sandbox { + +bool ResolverThunk::SetInternalThunk(void* storage, size_t storage_bytes, + const void* original_function, + const void* interceptor) { + if (storage_bytes < sizeof(InternalThunk)) + return false; + + InternalThunk* thunk = new(storage) InternalThunk; + +#pragma warning(push) +#pragma warning(disable: 4311) + // These casts generate warnings because they are 32 bit specific. + thunk->interceptor_function = reinterpret_cast<ULONG>(interceptor); + thunk->extra_argument = reinterpret_cast<ULONG>(original_function); +#pragma warning(pop) + + return true; +} + +size_t ResolverThunk::GetInternalThunkSize() const { + return sizeof(InternalThunk); +} + +NTSTATUS ResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + const void** casted = const_cast<const void**>(address); + return ResolverThunk::ResolveInterceptor(module, function_name, casted); +} + +} // namespace sandbox diff --git a/sandbox/win/src/resolver_64.cc b/sandbox/win/src/resolver_64.cc new file mode 100644 index 0000000000..8b2cc53c97 --- /dev/null +++ b/sandbox/win/src/resolver_64.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/resolver.h" + +// For placement new. This file must not depend on the CRT at runtime, but +// placement operator new is inline. +#include <new> + +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +const BYTE kPushRax = 0x50; +const USHORT kMovRax = 0xB848; +const ULONG kMovRspRax = 0x24048948; +const BYTE kRetNp = 0xC3; + +#pragma pack(push, 1) +struct InternalThunk { + // This struct contains roughly the following code: + // 00 50 push rax + // 01 48b8f0debc9a78563412 mov rax,123456789ABCDEF0h + // 0b 48890424 mov qword ptr [rsp],rax + // 0f c3 ret + // + // The code modifies rax, but that should not be an issue for the common + // calling conventions. + + InternalThunk() { + push_rax = kPushRax; + mov_rax = kMovRax; + interceptor_function = 0; + mov_rsp_rax = kMovRspRax; + ret = kRetNp; + }; + BYTE push_rax; // = 50 + USHORT mov_rax; // = 48 B8 + ULONG_PTR interceptor_function; + ULONG mov_rsp_rax; // = 48 89 04 24 + BYTE ret; // = C3 +}; +#pragma pack(pop) + +} // namespace. + +namespace sandbox { + +size_t ResolverThunk::GetInternalThunkSize() const { + return sizeof(InternalThunk); +} + +bool ResolverThunk::SetInternalThunk(void* storage, size_t storage_bytes, + const void* original_function, + const void* interceptor) { + if (storage_bytes < sizeof(InternalThunk)) + return false; + + InternalThunk* thunk = new(storage) InternalThunk; + thunk->interceptor_function = reinterpret_cast<ULONG_PTR>(interceptor); + + return true; +} + +NTSTATUS ResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + // We don't support sidestep & co. + return STATUS_NOT_IMPLEMENTED; +} + +} // namespace sandbox diff --git a/sandbox/win/src/restricted_token.cc b/sandbox/win/src/restricted_token.cc new file mode 100644 index 0000000000..7ebef3de9a --- /dev/null +++ b/sandbox/win/src/restricted_token.cc @@ -0,0 +1,481 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/restricted_token.h" + +#include <vector> + +#include "base/logging.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +RestrictedToken::RestrictedToken() + : init_(false), + effective_token_(NULL), + integrity_level_(INTEGRITY_LEVEL_LAST) { +} + +RestrictedToken::~RestrictedToken() { + if (effective_token_) + CloseHandle(effective_token_); +} + +unsigned RestrictedToken::Init(const HANDLE effective_token) { + if (init_) + return ERROR_ALREADY_INITIALIZED; + + if (effective_token) { + // We duplicate the handle to be able to use it even if the original handle + // is closed. + HANDLE effective_token_dup; + if (::DuplicateHandle(::GetCurrentProcess(), + effective_token, + ::GetCurrentProcess(), + &effective_token_dup, + 0, + FALSE, + DUPLICATE_SAME_ACCESS)) { + effective_token_ = effective_token_dup; + } else { + return ::GetLastError(); + } + } else { + if (!::OpenProcessToken(::GetCurrentProcess(), + TOKEN_ALL_ACCESS, + &effective_token_)) + return ::GetLastError(); + } + + init_ = true; + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::GetRestrictedTokenHandle(HANDLE *token_handle) const { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + size_t deny_size = sids_for_deny_only_.size(); + size_t restrict_size = sids_to_restrict_.size(); + size_t privileges_size = privileges_to_disable_.size(); + + SID_AND_ATTRIBUTES *deny_only_array = NULL; + if (deny_size) { + deny_only_array = new SID_AND_ATTRIBUTES[deny_size]; + + for (unsigned int i = 0; i < sids_for_deny_only_.size() ; ++i) { + deny_only_array[i].Attributes = SE_GROUP_USE_FOR_DENY_ONLY; + deny_only_array[i].Sid = + const_cast<SID*>(sids_for_deny_only_[i].GetPSID()); + } + } + + SID_AND_ATTRIBUTES *sids_to_restrict_array = NULL; + if (restrict_size) { + sids_to_restrict_array = new SID_AND_ATTRIBUTES[restrict_size]; + + for (unsigned int i = 0; i < restrict_size; ++i) { + sids_to_restrict_array[i].Attributes = 0; + sids_to_restrict_array[i].Sid = + const_cast<SID*>(sids_to_restrict_[i].GetPSID()); + } + } + + LUID_AND_ATTRIBUTES *privileges_to_disable_array = NULL; + if (privileges_size) { + privileges_to_disable_array = new LUID_AND_ATTRIBUTES[privileges_size]; + + for (unsigned int i = 0; i < privileges_size; ++i) { + privileges_to_disable_array[i].Attributes = 0; + privileges_to_disable_array[i].Luid = privileges_to_disable_[i]; + } + } + + BOOL result = TRUE; + HANDLE new_token = NULL; + // The SANDBOX_INERT flag did nothing in XP and it was just a way to tell + // if a token has ben restricted given the limiations of IsTokenRestricted() + // but it appears that in Windows 7 it hints the AppLocker subsystem to + // leave us alone. + if (deny_size || restrict_size || privileges_size) { + result = ::CreateRestrictedToken(effective_token_, + SANDBOX_INERT, + static_cast<DWORD>(deny_size), + deny_only_array, + static_cast<DWORD>(privileges_size), + privileges_to_disable_array, + static_cast<DWORD>(restrict_size), + sids_to_restrict_array, + &new_token); + } else { + // Duplicate the token even if it's not modified at this point + // because any subsequent changes to this token would also affect the + // current process. + result = ::DuplicateTokenEx(effective_token_, TOKEN_ALL_ACCESS, NULL, + SecurityIdentification, TokenPrimary, + &new_token); + } + + if (deny_only_array) + delete[] deny_only_array; + + if (sids_to_restrict_array) + delete[] sids_to_restrict_array; + + if (privileges_to_disable_array) + delete[] privileges_to_disable_array; + + if (!result) + return ::GetLastError(); + + // Modify the default dacl on the token to contain Restricted and the user. + if (!AddSidToDefaultDacl(new_token, WinRestrictedCodeSid, GENERIC_ALL)) + return ::GetLastError(); + + if (!AddUserSidToDefaultDacl(new_token, GENERIC_ALL)) + return ::GetLastError(); + + DWORD error = SetTokenIntegrityLevel(new_token, integrity_level_); + if (ERROR_SUCCESS != error) + return error; + + BOOL status = ::DuplicateHandle(::GetCurrentProcess(), + new_token, + ::GetCurrentProcess(), + token_handle, + TOKEN_ALL_ACCESS, + FALSE, // Don't inherit. + 0); + + if (new_token != effective_token_) + ::CloseHandle(new_token); + + if (!status) + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::GetRestrictedTokenHandleForImpersonation( + HANDLE *token_handle) const { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + HANDLE restricted_token_handle; + unsigned err_code = GetRestrictedTokenHandle(&restricted_token_handle); + if (ERROR_SUCCESS != err_code) + return err_code; + + HANDLE impersonation_token; + if (!::DuplicateToken(restricted_token_handle, + SecurityImpersonation, + &impersonation_token)) { + ::CloseHandle(restricted_token_handle); + return ::GetLastError(); + } + + ::CloseHandle(restricted_token_handle); + + BOOL status = ::DuplicateHandle(::GetCurrentProcess(), + impersonation_token, + ::GetCurrentProcess(), + token_handle, + TOKEN_ALL_ACCESS, + FALSE, // Don't inherit. + 0); + + ::CloseHandle(impersonation_token); + + if (!status) + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::AddAllSidsForDenyOnly(std::vector<Sid> *exceptions) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + TOKEN_GROUPS *token_groups = NULL; + DWORD size = 0; + + BOOL result = ::GetTokenInformation(effective_token_, + TokenGroups, + NULL, // No buffer. + 0, // Size is 0. + &size); + if (!size) + return ::GetLastError(); + + token_groups = reinterpret_cast<TOKEN_GROUPS*>(new BYTE[size]); + result = ::GetTokenInformation(effective_token_, + TokenGroups, + token_groups, + size, + &size); + if (!result) { + delete[] reinterpret_cast<BYTE*>(token_groups); + return ::GetLastError(); + } + + // Build the list of the deny only group SIDs + for (unsigned int i = 0; i < token_groups->GroupCount ; ++i) { + if ((token_groups->Groups[i].Attributes & SE_GROUP_INTEGRITY) == 0 && + (token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == 0) { + bool should_ignore = false; + if (exceptions) { + for (unsigned int j = 0; j < exceptions->size(); ++j) { + if (::EqualSid(const_cast<SID*>((*exceptions)[j].GetPSID()), + token_groups->Groups[i].Sid)) { + should_ignore = true; + break; + } + } + } + if (!should_ignore) { + sids_for_deny_only_.push_back( + reinterpret_cast<SID*>(token_groups->Groups[i].Sid)); + } + } + } + + delete[] reinterpret_cast<BYTE*>(token_groups); + + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::AddSidForDenyOnly(const Sid &sid) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + sids_for_deny_only_.push_back(sid); + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::AddUserSidForDenyOnly() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(new BYTE[size]); + + BOOL result = ::GetTokenInformation(effective_token_, + TokenUser, + token_user, + size, + &size); + + if (!result) { + delete[] reinterpret_cast<BYTE*>(token_user); + return ::GetLastError(); + } + + Sid user = reinterpret_cast<SID*>(token_user->User.Sid); + sids_for_deny_only_.push_back(user); + + delete[] reinterpret_cast<BYTE*>(token_user); + + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::DeleteAllPrivileges( + const std::vector<base::string16> *exceptions) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + // Get the list of privileges in the token + TOKEN_PRIVILEGES *token_privileges = NULL; + DWORD size = 0; + + BOOL result = ::GetTokenInformation(effective_token_, + TokenPrivileges, + NULL, // No buffer. + 0, // Size is 0. + &size); + if (!size) + return ::GetLastError(); + + token_privileges = reinterpret_cast<TOKEN_PRIVILEGES*>(new BYTE[size]); + result = ::GetTokenInformation(effective_token_, + TokenPrivileges, + token_privileges, + size, + &size); + if (!result) { + delete[] reinterpret_cast<BYTE *>(token_privileges); + return ::GetLastError(); + } + + + // Build the list of privileges to disable + for (unsigned int i = 0; i < token_privileges->PrivilegeCount; ++i) { + bool should_ignore = false; + if (exceptions) { + for (unsigned int j = 0; j < exceptions->size(); ++j) { + LUID luid = {0}; + ::LookupPrivilegeValue(NULL, (*exceptions)[j].c_str(), &luid); + if (token_privileges->Privileges[i].Luid.HighPart == luid.HighPart && + token_privileges->Privileges[i].Luid.LowPart == luid.LowPart) { + should_ignore = true; + break; + } + } + } + if (!should_ignore) { + privileges_to_disable_.push_back(token_privileges->Privileges[i].Luid); + } + } + + delete[] reinterpret_cast<BYTE *>(token_privileges); + + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::DeletePrivilege(const wchar_t *privilege) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + LUID luid = {0}; + if (LookupPrivilegeValue(NULL, privilege, &luid)) + privileges_to_disable_.push_back(luid); + else + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::AddRestrictingSid(const Sid &sid) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + sids_to_restrict_.push_back(sid); // No attributes + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::AddRestrictingSidLogonSession() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + TOKEN_GROUPS *token_groups = NULL; + DWORD size = 0; + + BOOL result = ::GetTokenInformation(effective_token_, + TokenGroups, + NULL, // No buffer. + 0, // Size is 0. + &size); + if (!size) + return ::GetLastError(); + + token_groups = reinterpret_cast<TOKEN_GROUPS*>(new BYTE[size]); + result = ::GetTokenInformation(effective_token_, + TokenGroups, + token_groups, + size, + &size); + if (!result) { + delete[] reinterpret_cast<BYTE*>(token_groups); + return ::GetLastError(); + } + + SID *logon_sid = NULL; + for (unsigned int i = 0; i < token_groups->GroupCount ; ++i) { + if ((token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) != 0) { + logon_sid = static_cast<SID*>(token_groups->Groups[i].Sid); + break; + } + } + + if (logon_sid) + sids_to_restrict_.push_back(logon_sid); + + delete[] reinterpret_cast<BYTE*>(token_groups); + + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::AddRestrictingSidCurrentUser() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(new BYTE[size]); + + BOOL result = ::GetTokenInformation(effective_token_, + TokenUser, + token_user, + size, + &size); + + if (!result) { + delete[] reinterpret_cast<BYTE*>(token_user); + return ::GetLastError(); + } + + Sid user = reinterpret_cast<SID*>(token_user->User.Sid); + sids_to_restrict_.push_back(user); + + delete[] reinterpret_cast<BYTE*>(token_user); + + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::AddRestrictingSidAllSids() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + // Add the current user to the list. + unsigned error = AddRestrictingSidCurrentUser(); + if (ERROR_SUCCESS != error) + return error; + + TOKEN_GROUPS *token_groups = NULL; + DWORD size = 0; + + // Get the buffer size required. + BOOL result = ::GetTokenInformation(effective_token_, TokenGroups, NULL, 0, + &size); + if (!size) + return ::GetLastError(); + + token_groups = reinterpret_cast<TOKEN_GROUPS*>(new BYTE[size]); + result = ::GetTokenInformation(effective_token_, + TokenGroups, + token_groups, + size, + &size); + if (!result) { + delete[] reinterpret_cast<BYTE*>(token_groups); + return ::GetLastError(); + } + + // Build the list of restricting sids from all groups. + for (unsigned int i = 0; i < token_groups->GroupCount ; ++i) { + if ((token_groups->Groups[i].Attributes & SE_GROUP_INTEGRITY) == 0) + AddRestrictingSid(reinterpret_cast<SID*>(token_groups->Groups[i].Sid)); + } + + delete[] reinterpret_cast<BYTE*>(token_groups); + + return ERROR_SUCCESS; +} + +unsigned RestrictedToken::SetIntegrityLevel(IntegrityLevel integrity_level) { + integrity_level_ = integrity_level; + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/sandbox/win/src/restricted_token.h b/sandbox/win/src/restricted_token.h new file mode 100644 index 0000000000..565880e778 --- /dev/null +++ b/sandbox/win/src/restricted_token.h @@ -0,0 +1,193 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_RESTRICTED_TOKEN_H_ +#define SANDBOX_SRC_RESTRICTED_TOKEN_H_ + +#include <windows.h> +#include <vector> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/security_level.h" +#include "sandbox/win/src/sid.h" + +// Flags present in the Group SID list. These 2 flags are new in Windows Vista +#ifndef SE_GROUP_INTEGRITY +#define SE_GROUP_INTEGRITY (0x00000020L) +#endif +#ifndef SE_GROUP_INTEGRITY_ENABLED +#define SE_GROUP_INTEGRITY_ENABLED (0x00000040L) +#endif + +namespace sandbox { + +// Handles the creation of a restricted token using the effective token or +// any token handle. +// Sample usage: +// RestrictedToken restricted_token; +// unsigned err_code = restricted_token.Init(NULL); // Use the current +// // effective token +// if (ERROR_SUCCESS != err_code) { +// // handle error. +// } +// +// restricted_token.AddRestrictingSid(ATL::Sids::Users().GetPSID()); +// HANDLE token_handle; +// err_code = restricted_token.GetRestrictedTokenHandle(&token_handle); +// if (ERROR_SUCCESS != err_code) { +// // handle error. +// } +// [...] +// CloseHandle(token_handle); +class RestrictedToken { + public: + // Init() has to be called before calling any other method in the class. + RestrictedToken(); + ~RestrictedToken(); + + // Initializes the RestrictedToken object with effective_token. + // If effective_token is NULL, it initializes the RestrictedToken object with + // the effective token of the current process. + unsigned Init(HANDLE effective_token); + + // Creates a restricted token and returns its handle using the token_handle + // output parameter. This handle has to be closed by the caller. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + unsigned GetRestrictedTokenHandle(HANDLE *token_handle) const; + + // Creates a restricted token and uses this new token to create a new token + // for impersonation. Returns the handle of this impersonation token using + // the token_handle output parameter. This handle has to be closed by + // the caller. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // The sample usage is the same as the GetRestrictedTokenHandle function. + unsigned GetRestrictedTokenHandleForImpersonation(HANDLE *token_handle) const; + + // Lists all sids in the token and mark them as Deny Only except for those + // present in the exceptions parameter. If there is no exception needed, + // the caller can pass an empty list or NULL for the exceptions + // parameter. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // Sample usage: + // std::vector<Sid> sid_exceptions; + // sid_exceptions.push_back(ATL::Sids::Users().GetPSID()); + // sid_exceptions.push_back(ATL::Sids::World().GetPSID()); + // restricted_token.AddAllSidsForDenyOnly(&sid_exceptions); + // Note: A Sid marked for Deny Only in a token cannot be used to grant + // access to any resource. It can only be used to deny access. + unsigned AddAllSidsForDenyOnly(std::vector<Sid> *exceptions); + + // Adds a user or group SID for Deny Only in the restricted token. + // Parameter: sid is the SID to add in the Deny Only list. + // The return value is always ERROR_SUCCESS. + // + // Sample Usage: + // restricted_token.AddSidForDenyOnly(ATL::Sids::Admins().GetPSID()); + unsigned AddSidForDenyOnly(const Sid &sid); + + // Adds the user sid of the token for Deny Only in the restricted token. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + unsigned AddUserSidForDenyOnly(); + + // Lists all privileges in the token and add them to the list of privileges + // to remove except for those present in the exceptions parameter. If + // there is no exception needed, the caller can pass an empty list or NULL + // for the exceptions parameter. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // Sample usage: + // std::vector<base::string16> privilege_exceptions; + // privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + // restricted_token.DeleteAllPrivileges(&privilege_exceptions); + unsigned DeleteAllPrivileges( + const std::vector<base::string16> *exceptions); + + // Adds a privilege to the list of privileges to remove in the restricted + // token. + // Parameter: privilege is the privilege name to remove. This is the string + // representing the privilege. (e.g. "SeChangeNotifyPrivilege"). + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // Sample usage: + // restricted_token.DeletePrivilege(SE_LOAD_DRIVER_NAME); + unsigned DeletePrivilege(const wchar_t *privilege); + + // Adds a SID to the list of restricting sids in the restricted token. + // Parameter: sid is the sid to add to the list restricting sids. + // The return value is always ERROR_SUCCESS. + // + // Sample usage: + // restricted_token.AddRestrictingSid(ATL::Sids::Users().GetPSID()); + // Note: The list of restricting is used to force Windows to perform all + // access checks twice. The first time using your user SID and your groups, + // and the second time using your list of restricting sids. The access has + // to be granted in both places to get access to the resource requested. + unsigned AddRestrictingSid(const Sid &sid); + + // Adds the logon sid of the token in the list of restricting sids for the + // restricted token. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + unsigned AddRestrictingSidLogonSession(); + + // Adds the owner sid of the token in the list of restricting sids for the + // restricted token. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + unsigned AddRestrictingSidCurrentUser(); + + // Adds all group sids and the user sid to the restricting sids list. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + unsigned AddRestrictingSidAllSids(); + + // Sets the token integrity level. This is only valid on Vista. The integrity + // level cannot be higher than your current integrity level. + unsigned SetIntegrityLevel(IntegrityLevel integrity_level); + + private: + // The list of restricting sids in the restricted token. + std::vector<Sid> sids_to_restrict_; + // The list of privileges to remove in the restricted token. + std::vector<LUID> privileges_to_disable_; + // The list of sids to mark as Deny Only in the restricted token. + std::vector<Sid> sids_for_deny_only_; + // The token to restrict. Can only be set in a constructor. + HANDLE effective_token_; + // The token integrity level. Only valid on Vista. + IntegrityLevel integrity_level_; + // Tells if the object is initialized or not (if Init() has been called) + bool init_; + + DISALLOW_COPY_AND_ASSIGN(RestrictedToken); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_RESTRICTED_TOKEN_H_ diff --git a/sandbox/win/src/restricted_token_unittest.cc b/sandbox/win/src/restricted_token_unittest.cc new file mode 100644 index 0000000000..8186f9c77c --- /dev/null +++ b/sandbox/win/src/restricted_token_unittest.cc @@ -0,0 +1,588 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for the RestrictedToken. + +#define _ATL_NO_EXCEPTIONS +#include <atlbase.h> +#include <atlsecurity.h> +#include <vector> +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/sid.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Tests the initializatioin with an invalid token handle. +TEST(RestrictedTokenTest, InvalidHandle) { + RestrictedToken token; + ASSERT_EQ(ERROR_INVALID_HANDLE, token.Init(reinterpret_cast<HANDLE>(0x5555))); +} + +// Tests the initialization with NULL as parameter. +TEST(RestrictedTokenTest, DefaultInit) { + // Get the current process token. + HANDLE token_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &token_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, token_handle); + + ATL::CAccessToken access_token; + access_token.Attach(token_handle); + + // Create the token using the current token. + RestrictedToken token_default; + ASSERT_EQ(ERROR_SUCCESS, token_default.Init(NULL)); + + // Get the handle to the restricted token. + + HANDLE restricted_token_handle = NULL; + ASSERT_EQ(ERROR_SUCCESS, + token_default.GetRestrictedTokenHandle(&restricted_token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(restricted_token_handle); + + ATL::CSid sid_user_restricted; + ATL::CSid sid_user_default; + ATL::CSid sid_owner_restricted; + ATL::CSid sid_owner_default; + ASSERT_TRUE(restricted_token.GetUser(&sid_user_restricted)); + ASSERT_TRUE(access_token.GetUser(&sid_user_default)); + ASSERT_TRUE(restricted_token.GetOwner(&sid_owner_restricted)); + ASSERT_TRUE(access_token.GetOwner(&sid_owner_default)); + + // Check if both token have the same owner and user. + ASSERT_EQ(sid_user_restricted, sid_user_default); + ASSERT_EQ(sid_owner_restricted, sid_owner_default); +} + +// Tests the initialization with a custom token as parameter. +TEST(RestrictedTokenTest, CustomInit) { + // Get the current process token. + HANDLE token_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &token_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, token_handle); + + ATL::CAccessToken access_token; + access_token.Attach(token_handle); + + // Change the primary group. + access_token.SetPrimaryGroup(ATL::Sids::World()); + + // Create the token using the current token. + RestrictedToken token; + ASSERT_EQ(ERROR_SUCCESS, token.Init(access_token.GetHandle())); + + // Get the handle to the restricted token. + + HANDLE restricted_token_handle = NULL; + ASSERT_EQ(ERROR_SUCCESS, + token.GetRestrictedTokenHandle(&restricted_token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(restricted_token_handle); + + ATL::CSid sid_restricted; + ATL::CSid sid_default; + ASSERT_TRUE(restricted_token.GetPrimaryGroup(&sid_restricted)); + ASSERT_TRUE(access_token.GetPrimaryGroup(&sid_default)); + + // Check if both token have the same owner. + ASSERT_EQ(sid_restricted, sid_default); +} + +// Verifies that the token created by the object are valid. +TEST(RestrictedTokenTest, ResultToken) { + RestrictedToken token; + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + + ASSERT_EQ(ERROR_SUCCESS, + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + + HANDLE restricted_token; + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&restricted_token)); + + ASSERT_TRUE(::IsTokenRestricted(restricted_token)); + + DWORD length = 0; + TOKEN_TYPE type; + ASSERT_TRUE(::GetTokenInformation(restricted_token, + ::TokenType, + &type, + sizeof(type), + &length)); + + ASSERT_EQ(type, TokenPrimary); + + HANDLE impersonation_token; + ASSERT_EQ(ERROR_SUCCESS, + token.GetRestrictedTokenHandleForImpersonation(&impersonation_token)); + + ASSERT_TRUE(::IsTokenRestricted(impersonation_token)); + + ASSERT_TRUE(::GetTokenInformation(impersonation_token, + ::TokenType, + &type, + sizeof(type), + &length)); + + ASSERT_EQ(type, TokenImpersonation); + + ::CloseHandle(impersonation_token); + ::CloseHandle(restricted_token); +} + +// Verifies that the token created has "Restricted" in its default dacl. +TEST(RestrictedTokenTest, DefaultDacl) { + RestrictedToken token; + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + + ASSERT_EQ(ERROR_SUCCESS, + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + + HANDLE handle; + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(handle); + + ATL::CDacl dacl; + ASSERT_TRUE(restricted_token.GetDefaultDacl(&dacl)); + + bool restricted_found = false; + + unsigned int ace_count = dacl.GetAceCount(); + for (unsigned int i = 0; i < ace_count ; ++i) { + ATL::CSid sid; + ACCESS_MASK mask = 0; + dacl.GetAclEntry(i, &sid, &mask); + if (sid == ATL::Sids::RestrictedCode() && mask == GENERIC_ALL) { + restricted_found = true; + break; + } + } + + ASSERT_TRUE(restricted_found); +} + +// Tests the method "AddSidForDenyOnly". +TEST(RestrictedTokenTest, DenySid) { + RestrictedToken token; + HANDLE token_handle = NULL; + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.AddSidForDenyOnly(Sid(WinWorldSid))); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if (ATL::Sids::World() == sids[i]) { + ASSERT_EQ(SE_GROUP_USE_FOR_DENY_ONLY, + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests the method "AddAllSidsForDenyOnly". +TEST(RestrictedTokenTest, DenySids) { + RestrictedToken token; + HANDLE token_handle = NULL; + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.AddAllSidsForDenyOnly(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + // Verify that all sids are really gone. + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if ((attributes[i] & SE_GROUP_LOGON_ID) == 0 && + (attributes[i] & SE_GROUP_INTEGRITY) == 0) { + ASSERT_EQ(SE_GROUP_USE_FOR_DENY_ONLY, + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests the method "AddAllSidsForDenyOnly" using an exception list. +TEST(RestrictedTokenTest, DenySidsException) { + RestrictedToken token; + HANDLE token_handle = NULL; + + std::vector<Sid> sids_exception; + sids_exception.push_back(Sid(WinWorldSid)); + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.AddAllSidsForDenyOnly(&sids_exception)); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + // Verify that all sids are really gone. + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if ((attributes[i] & SE_GROUP_LOGON_ID) == 0 && + (attributes[i] & SE_GROUP_INTEGRITY) == 0) { + if (ATL::Sids::World() == sids[i]) { + ASSERT_EQ(NULL, attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } else { + ASSERT_EQ(SE_GROUP_USE_FOR_DENY_ONLY, + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } + } +} + +// Tests test method AddOwnerSidForDenyOnly. +TEST(RestrictedTokenTest, DenyOwnerSid) { + RestrictedToken token; + HANDLE token_handle = NULL; + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.AddUserSidForDenyOnly()); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + ATL::CSid user_sid; + ASSERT_TRUE(restricted_token.GetUser(&user_sid)); + + for (unsigned int i = 0; i < sids.GetCount(); ++i) { + if (user_sid == sids[i]) { + ASSERT_EQ(SE_GROUP_USE_FOR_DENY_ONLY, + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests test method AddOwnerSidForDenyOnly with a custom effective token. +TEST(RestrictedTokenTest, DenyOwnerSidCustom) { + // Get the current process token. + HANDLE token_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &token_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, token_handle); + + ATL::CAccessToken access_token; + access_token.Attach(token_handle); + + RestrictedToken token; + ASSERT_EQ(ERROR_SUCCESS, token.Init(access_token.GetHandle())); + ASSERT_EQ(ERROR_SUCCESS, token.AddUserSidForDenyOnly()); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + ATL::CSid user_sid; + ASSERT_TRUE(restricted_token.GetUser(&user_sid)); + + for (unsigned int i = 0; i < sids.GetCount(); ++i) { + if (user_sid == sids[i]) { + ASSERT_EQ(SE_GROUP_USE_FOR_DENY_ONLY, + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests the method DeleteAllPrivileges. +TEST(RestrictedTokenTest, DeleteAllPrivileges) { + RestrictedToken token; + HANDLE token_handle = NULL; + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.DeleteAllPrivileges(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + + ATL::CTokenPrivileges privileges; + ASSERT_TRUE(restricted_token.GetPrivileges(&privileges)); + + ASSERT_EQ(0, privileges.GetCount()); +} + +// Tests the method DeleteAllPrivileges with an exception list. +TEST(RestrictedTokenTest, DeleteAllPrivilegesException) { + RestrictedToken token; + HANDLE token_handle = NULL; + + std::vector<base::string16> exceptions; + exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.DeleteAllPrivileges(&exceptions)); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + + ATL::CTokenPrivileges privileges; + ASSERT_TRUE(restricted_token.GetPrivileges(&privileges)); + + ATL::CTokenPrivileges::CNames privilege_names; + ATL::CTokenPrivileges::CAttributes privilege_name_attributes; + privileges.GetNamesAndAttributes(&privilege_names, + &privilege_name_attributes); + + ASSERT_EQ(1, privileges.GetCount()); + + for (unsigned int i = 0; i < privileges.GetCount(); ++i) { + ASSERT_EQ(privilege_names[i], SE_CHANGE_NOTIFY_NAME); + } +} + +// Tests the method DeletePrivilege. +TEST(RestrictedTokenTest, DeletePrivilege) { + RestrictedToken token; + HANDLE token_handle = NULL; + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.DeletePrivilege(SE_CHANGE_NOTIFY_NAME)); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + + ATL::CTokenPrivileges privileges; + ASSERT_TRUE(restricted_token.GetPrivileges(&privileges)); + + ATL::CTokenPrivileges::CNames privilege_names; + ATL::CTokenPrivileges::CAttributes privilege_name_attributes; + privileges.GetNamesAndAttributes(&privilege_names, + &privilege_name_attributes); + + for (unsigned int i = 0; i < privileges.GetCount(); ++i) { + ASSERT_NE(privilege_names[i], SE_CHANGE_NOTIFY_NAME); + } +} + +// Checks if a sid is in the restricting list of the restricted token. +// Asserts if it's not the case. If count is a positive number, the number of +// elements in the restricting sids list has to be equal. +void CheckRestrictingSid(const ATL::CAccessToken &restricted_token, + ATL::CSid sid, int count) { + DWORD length = 8192; + BYTE *memory = new BYTE[length]; + TOKEN_GROUPS *groups = reinterpret_cast<TOKEN_GROUPS*>(memory); + ASSERT_TRUE(::GetTokenInformation(restricted_token.GetHandle(), + TokenRestrictedSids, + groups, + length, + &length)); + + ATL::CTokenGroups atl_groups(*groups); + delete[] memory; + + if (count >= 0) + ASSERT_EQ(count, atl_groups.GetCount()); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + atl_groups.GetSidsAndAttributes(&sids, &attributes); + + bool present = false; + for (unsigned int i = 0; i < sids.GetCount(); ++i) { + if (sids[i] == sid) { + present = true; + break; + } + } + + ASSERT_TRUE(present); +} + +// Tests the method AddRestrictingSid. +TEST(RestrictedTokenTest, AddRestrictingSid) { + RestrictedToken token; + HANDLE token_handle = NULL; + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + + CheckRestrictingSid(restricted_token, ATL::Sids::World(), 1); +} + +// Tests the method AddRestrictingSidCurrentUser. +TEST(RestrictedTokenTest, AddRestrictingSidCurrentUser) { + RestrictedToken token; + HANDLE token_handle = NULL; + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.AddRestrictingSidCurrentUser()); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + ATL::CSid user; + restricted_token.GetUser(&user); + + CheckRestrictingSid(restricted_token, user, 1); +} + +// Tests the method AddRestrictingSidCurrentUser with a custom effective token. +TEST(RestrictedTokenTest, AddRestrictingSidCurrentUserCustom) { + // Get the current process token. + HANDLE token_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &token_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, token_handle); + + ATL::CAccessToken access_token; + access_token.Attach(token_handle); + + RestrictedToken token; + ASSERT_EQ(ERROR_SUCCESS, token.Init(access_token.GetHandle())); + ASSERT_EQ(ERROR_SUCCESS, token.AddRestrictingSidCurrentUser()); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + ATL::CSid user; + restricted_token.GetUser(&user); + + CheckRestrictingSid(restricted_token, user, 1); +} + +// Tests the method AddRestrictingSidLogonSession. +TEST(RestrictedTokenTest, AddRestrictingSidLogonSession) { + RestrictedToken token; + HANDLE token_handle = NULL; + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.AddRestrictingSidLogonSession()); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + ATL::CSid session; + restricted_token.GetLogonSid(&session); + + CheckRestrictingSid(restricted_token, session, 1); +} + +// Tests adding a lot of restricting sids. +TEST(RestrictedTokenTest, AddMultipleRestrictingSids) { + RestrictedToken token; + HANDLE token_handle = NULL; + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.AddRestrictingSidCurrentUser()); + ASSERT_EQ(ERROR_SUCCESS, token.AddRestrictingSidLogonSession()); + ASSERT_EQ(ERROR_SUCCESS, + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + ATL::CSid session; + restricted_token.GetLogonSid(&session); + + DWORD length = 8192; + BYTE *memory = new BYTE[length]; + TOKEN_GROUPS *groups = reinterpret_cast<TOKEN_GROUPS*>(memory); + ASSERT_TRUE(::GetTokenInformation(restricted_token.GetHandle(), + TokenRestrictedSids, + groups, + length, + &length)); + + ATL::CTokenGroups atl_groups(*groups); + delete[] memory; + + ASSERT_EQ(3, atl_groups.GetCount()); +} + +// Tests the method "AddRestrictingSidAllSids". +TEST(RestrictedTokenTest, AddAllSidToRestrictingSids) { + RestrictedToken token; + HANDLE token_handle = NULL; + + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + ASSERT_EQ(ERROR_SUCCESS, token.AddRestrictingSidAllSids()); + ASSERT_EQ(ERROR_SUCCESS, token.GetRestrictedTokenHandle(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + // Verify that all group sids are in the restricting sid list. + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if ((attributes[i] & SE_GROUP_INTEGRITY) == 0) { + CheckRestrictingSid(restricted_token, sids[i], -1); + } + } + + // Verify that the user is in the restricting sid list. + ATL::CSid user; + restricted_token.GetUser(&user); + CheckRestrictingSid(restricted_token, user, -1); +} + +// Checks the error code when the object is initialized twice. +TEST(RestrictedTokenTest, DoubleInit) { + RestrictedToken token; + ASSERT_EQ(ERROR_SUCCESS, token.Init(NULL)); + + ASSERT_EQ(ERROR_ALREADY_INITIALIZED, token.Init(NULL)); +} + +} // namespace sandbox diff --git a/sandbox/win/src/restricted_token_utils.cc b/sandbox/win/src/restricted_token_utils.cc new file mode 100644 index 0000000000..5e06daa426 --- /dev/null +++ b/sandbox/win/src/restricted_token_utils.cc @@ -0,0 +1,408 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <aclapi.h> +#include <sddl.h> +#include <vector> + +#include "sandbox/win/src/restricted_token_utils.h" + +#include "base/logging.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/job.h" +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/security_level.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +DWORD CreateRestrictedToken(HANDLE *token_handle, + TokenLevel security_level, + IntegrityLevel integrity_level, + TokenType token_type) { + if (!token_handle) + return ERROR_BAD_ARGUMENTS; + + RestrictedToken restricted_token; + restricted_token.Init(NULL); // Initialized with the current process token + + std::vector<base::string16> privilege_exceptions; + std::vector<Sid> sid_exceptions; + + bool deny_sids = true; + bool remove_privileges = true; + + switch (security_level) { + case USER_UNPROTECTED: { + deny_sids = false; + remove_privileges = false; + break; + } + case USER_RESTRICTED_SAME_ACCESS: { + deny_sids = false; + remove_privileges = false; + + unsigned err_code = restricted_token.AddRestrictingSidAllSids(); + if (ERROR_SUCCESS != err_code) + return err_code; + + break; + } + case USER_NON_ADMIN: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + sid_exceptions.push_back(WinAuthenticatedUserSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + break; + } + case USER_INTERACTIVE: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + sid_exceptions.push_back(WinAuthenticatedUserSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + restricted_token.AddRestrictingSid(WinBuiltinUsersSid); + restricted_token.AddRestrictingSid(WinWorldSid); + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + restricted_token.AddRestrictingSidCurrentUser(); + restricted_token.AddRestrictingSidLogonSession(); + break; + } + case USER_LIMITED: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + restricted_token.AddRestrictingSid(WinBuiltinUsersSid); + restricted_token.AddRestrictingSid(WinWorldSid); + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + + // This token has to be able to create objects in BNO. + // Unfortunately, on vista, it needs the current logon sid + // in the token to achieve this. You should also set the process to be + // low integrity level so it can't access object created by other + // processes. + if (base::win::GetVersion() >= base::win::VERSION_VISTA) + restricted_token.AddRestrictingSidLogonSession(); + break; + } + case USER_RESTRICTED: { + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + restricted_token.AddUserSidForDenyOnly(); + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + break; + } + case USER_LOCKDOWN: { + restricted_token.AddUserSidForDenyOnly(); + restricted_token.AddRestrictingSid(WinNullSid); + break; + } + default: { + return ERROR_BAD_ARGUMENTS; + } + } + + DWORD err_code = ERROR_SUCCESS; + if (deny_sids) { + err_code = restricted_token.AddAllSidsForDenyOnly(&sid_exceptions); + if (ERROR_SUCCESS != err_code) + return err_code; + } + + if (remove_privileges) { + err_code = restricted_token.DeleteAllPrivileges(&privilege_exceptions); + if (ERROR_SUCCESS != err_code) + return err_code; + } + + restricted_token.SetIntegrityLevel(integrity_level); + + switch (token_type) { + case PRIMARY: { + err_code = restricted_token.GetRestrictedTokenHandle(token_handle); + break; + } + case IMPERSONATION: { + err_code = restricted_token.GetRestrictedTokenHandleForImpersonation( + token_handle); + break; + } + default: { + err_code = ERROR_BAD_ARGUMENTS; + break; + } + } + + return err_code; +} + +DWORD StartRestrictedProcessInJob(wchar_t *command_line, + TokenLevel primary_level, + TokenLevel impersonation_level, + JobLevel job_level, + HANDLE *const job_handle_ret) { + Job job; + DWORD err_code = job.Init(job_level, NULL, 0, 0); + if (ERROR_SUCCESS != err_code) + return err_code; + + if (JOB_UNPROTECTED != job_level) { + // Share the Desktop handle to be able to use MessageBox() in the sandboxed + // application. + err_code = job.UserHandleGrantAccess(GetDesktopWindow()); + if (ERROR_SUCCESS != err_code) + return err_code; + } + + // Create the primary (restricted) token for the process + HANDLE primary_token_handle = NULL; + err_code = CreateRestrictedToken(&primary_token_handle, + primary_level, + INTEGRITY_LEVEL_LAST, + PRIMARY); + if (ERROR_SUCCESS != err_code) { + return err_code; + } + base::win::ScopedHandle primary_token(primary_token_handle); + + // Create the impersonation token (restricted) to be able to start the + // process. + HANDLE impersonation_token_handle; + err_code = CreateRestrictedToken(&impersonation_token_handle, + impersonation_level, + INTEGRITY_LEVEL_LAST, + IMPERSONATION); + if (ERROR_SUCCESS != err_code) { + return err_code; + } + base::win::ScopedHandle impersonation_token(impersonation_token_handle); + + // Start the process + STARTUPINFO startup_info = {0}; + PROCESS_INFORMATION temp_process_info = {}; + DWORD flags = CREATE_SUSPENDED; + + if (base::win::GetVersion() < base::win::VERSION_WIN8) { + // Windows 8 implements nested jobs, but for older systems we need to + // break out of any job we're in to enforce our restrictions. + flags |= CREATE_BREAKAWAY_FROM_JOB; + } + + if (!::CreateProcessAsUser(primary_token.Get(), + NULL, // No application name. + command_line, + NULL, // No security attribute. + NULL, // No thread attribute. + FALSE, // Do not inherit handles. + flags, + NULL, // Use the environment of the caller. + NULL, // Use current directory of the caller. + &startup_info, + &temp_process_info)) { + return ::GetLastError(); + } + base::win::ScopedProcessInformation process_info(temp_process_info); + + // Change the token of the main thread of the new process for the + // impersonation token with more rights. + { + HANDLE temp_thread = process_info.thread_handle(); + if (!::SetThreadToken(&temp_thread, impersonation_token.Get())) { + ::TerminateProcess(process_info.process_handle(), + 0); // exit code + return ::GetLastError(); + } + } + + err_code = job.AssignProcessToJob(process_info.process_handle()); + if (ERROR_SUCCESS != err_code) { + ::TerminateProcess(process_info.process_handle(), + 0); // exit code + return ::GetLastError(); + } + + // Start the application + ::ResumeThread(process_info.thread_handle()); + + (*job_handle_ret) = job.Detach(); + + return ERROR_SUCCESS; +} + +DWORD SetObjectIntegrityLabel(HANDLE handle, SE_OBJECT_TYPE type, + const wchar_t* ace_access, + const wchar_t* integrity_level_sid) { + // Build the SDDL string for the label. + base::string16 sddl = L"S:("; // SDDL for a SACL. + sddl += SDDL_MANDATORY_LABEL; // Ace Type is "Mandatory Label". + sddl += L";;"; // No Ace Flags. + sddl += ace_access; // Add the ACE access. + sddl += L";;;"; // No ObjectType and Inherited Object Type. + sddl += integrity_level_sid; // Trustee Sid. + sddl += L")"; + + DWORD error = ERROR_SUCCESS; + PSECURITY_DESCRIPTOR sec_desc = NULL; + + PACL sacl = NULL; + BOOL sacl_present = FALSE; + BOOL sacl_defaulted = FALSE; + + if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(), + SDDL_REVISION, + &sec_desc, NULL)) { + if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl, + &sacl_defaulted)) { + error = ::SetSecurityInfo(handle, type, + LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, + sacl); + } else { + error = ::GetLastError(); + } + + ::LocalFree(sec_desc); + } else { + return::GetLastError(); + } + + return error; +} + +const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level) { + switch (integrity_level) { + case INTEGRITY_LEVEL_SYSTEM: + return L"S-1-16-16384"; + case INTEGRITY_LEVEL_HIGH: + return L"S-1-16-12288"; + case INTEGRITY_LEVEL_MEDIUM: + return L"S-1-16-8192"; + case INTEGRITY_LEVEL_MEDIUM_LOW: + return L"S-1-16-6144"; + case INTEGRITY_LEVEL_LOW: + return L"S-1-16-4096"; + case INTEGRITY_LEVEL_BELOW_LOW: + return L"S-1-16-2048"; + case INTEGRITY_LEVEL_UNTRUSTED: + return L"S-1-16-0"; + case INTEGRITY_LEVEL_LAST: + return NULL; + } + + NOTREACHED(); + return NULL; +} +DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) { + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return ERROR_SUCCESS; + + const wchar_t* integrity_level_str = GetIntegrityLevelString(integrity_level); + if (!integrity_level_str) { + // No mandatory level specified, we don't change it. + return ERROR_SUCCESS; + } + + PSID integrity_sid = NULL; + if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid)) + return ::GetLastError(); + + TOKEN_MANDATORY_LABEL label = {0}; + label.Label.Attributes = SE_GROUP_INTEGRITY; + label.Label.Sid = integrity_sid; + + DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid); + BOOL result = ::SetTokenInformation(token, TokenIntegrityLevel, &label, + size); + ::LocalFree(integrity_sid); + + return result ? ERROR_SUCCESS : ::GetLastError(); +} + +DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) { + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return ERROR_SUCCESS; + + // We don't check for an invalid level here because we'll just let it + // fail on the SetTokenIntegrityLevel call later on. + if (integrity_level == INTEGRITY_LEVEL_LAST) { + // No mandatory level specified, we don't change it. + return ERROR_SUCCESS; + } + + HANDLE token_handle; + if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT, + &token_handle)) + return ::GetLastError(); + + base::win::ScopedHandle token(token_handle); + + return SetTokenIntegrityLevel(token.Get(), integrity_level); +} + +DWORD HardenTokenIntegrityLevelPolicy(HANDLE token) { + if (base::win::GetVersion() < base::win::VERSION_WIN7) + return ERROR_SUCCESS; + + DWORD last_error = 0; + DWORD length_needed = 0; + + ::GetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION, + NULL, 0, &length_needed); + + last_error = ::GetLastError(); + if (last_error != ERROR_INSUFFICIENT_BUFFER) + return last_error; + + std::vector<char> security_desc_buffer(length_needed); + PSECURITY_DESCRIPTOR security_desc = + reinterpret_cast<PSECURITY_DESCRIPTOR>(&security_desc_buffer[0]); + + if (!::GetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION, + security_desc, length_needed, + &length_needed)) + return ::GetLastError(); + + PACL sacl = NULL; + BOOL sacl_present = FALSE; + BOOL sacl_defaulted = FALSE; + + if (!::GetSecurityDescriptorSacl(security_desc, &sacl_present, + &sacl, &sacl_defaulted)) + return ::GetLastError(); + + for (DWORD ace_index = 0; ace_index < sacl->AceCount; ++ace_index) { + PSYSTEM_MANDATORY_LABEL_ACE ace; + + if (::GetAce(sacl, ace_index, reinterpret_cast<LPVOID*>(&ace)) + && ace->Header.AceType == SYSTEM_MANDATORY_LABEL_ACE_TYPE) { + ace->Mask |= SYSTEM_MANDATORY_LABEL_NO_READ_UP + | SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP; + break; + } + } + + if (!::SetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION, + security_desc)) + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +DWORD HardenProcessIntegrityLevelPolicy() { + if (base::win::GetVersion() < base::win::VERSION_WIN7) + return ERROR_SUCCESS; + + HANDLE token_handle; + if (!::OpenProcessToken(GetCurrentProcess(), READ_CONTROL | WRITE_OWNER, + &token_handle)) + return ::GetLastError(); + + base::win::ScopedHandle token(token_handle); + + return HardenTokenIntegrityLevelPolicy(token.Get()); +} + +} // namespace sandbox diff --git a/sandbox/win/src/restricted_token_utils.h b/sandbox/win/src/restricted_token_utils.h new file mode 100644 index 0000000000..509feaf74b --- /dev/null +++ b/sandbox/win/src/restricted_token_utils.h @@ -0,0 +1,100 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_RESTRICTED_TOKEN_UTILS_H__ +#define SANDBOX_SRC_RESTRICTED_TOKEN_UTILS_H__ + +#include <accctrl.h> +#include <windows.h> + +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/security_level.h" + +// Contains the utility functions to be able to create restricted tokens based +// on a security profiles. + +namespace sandbox { + +// The type of the token returned by the CreateNakedToken. +enum TokenType { + IMPERSONATION = 0, + PRIMARY +}; + +// Creates a restricted token based on the effective token of the current +// process. The parameter security_level determines how much the token is +// restricted. The token_type determines if the token will be used as a primary +// token or impersonation token. The integrity level of the token is set to +// |integrity level| on Vista only. +// token_handle is the output value containing the handle of the +// newly created restricted token. +// If the function succeeds, the return value is ERROR_SUCCESS. If the +// function fails, the return value is the win32 error code corresponding to +// the error. +DWORD CreateRestrictedToken(HANDLE *token_handle, + TokenLevel security_level, + IntegrityLevel integrity_level, + TokenType token_type); + +// Starts the process described by the input parameter command_line in a job +// with a restricted token. Also set the main thread of this newly created +// process to impersonate a user with more rights so it can initialize +// correctly. +// +// Parameters: primary_level is the security level of the primary token. +// impersonation_level is the security level of the impersonation token used +// to initialize the process. job_level is the security level of the job +// object used to encapsulate the process. +// +// The output parameter job_handle is the handle to the job object. It has +// to be closed with CloseHandle() when not needed. Closing this handle will +// kill the process started. +// +// Note: The process started with this function has to call RevertToSelf() as +// soon as possible to stop using the impersonation token and start being +// secure. +// +// Note: The Unicode version of this function will fail if the command_line +// parameter is a const string. +DWORD StartRestrictedProcessInJob(wchar_t *command_line, + TokenLevel primary_level, + TokenLevel impersonation_level, + JobLevel job_level, + HANDLE *job_handle); + +// Sets the integrity label on a object handle. +DWORD SetObjectIntegrityLabel(HANDLE handle, SE_OBJECT_TYPE type, + const wchar_t* ace_access, + const wchar_t* integrity_level_sid); + +// Sets the integrity level on a token. This is only valid on Vista. It returns +// without failing on XP. If the integrity level that you specify is greater +// than the current integrity level, the function will fail. +DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level); + +// Returns the integrity level SDDL string associated with a given +// IntegrityLevel value. +const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level); + +// Sets the integrity level on the current process on Vista. It returns without +// failing on XP. If the integrity level that you specify is greater than the +// current integrity level, the function will fail. +DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level); + +// Hardens the integrity level policy on a token. This is only valid on Win 7 +// and above. Specifically it sets the policy to block read and execute so +// that a lower privileged process cannot open the token for impersonate or +// duplicate permissions. This should limit potential security holes. +DWORD HardenTokenIntegrityLevelPolicy(HANDLE token); + +// Hardens the integrity level policy on the current process. This is only +// valid on Win 7 and above. Specifically it sets the policy to block read +// and execute so that a lower privileged process cannot open the token for +// impersonate or duplicate permissions. This should limit potential security +// holes. +DWORD HardenProcessIntegrityLevelPolicy(); + +} // namespace sandbox + +#endif // SANDBOX_SRC_RESTRICTED_TOKEN_UTILS_H__ diff --git a/sandbox/win/src/sandbox.cc b/sandbox/win/src/sandbox.cc new file mode 100644 index 0000000000..984dfecec8 --- /dev/null +++ b/sandbox/win/src/sandbox.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdio.h> +#include <windows.h> +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/broker_services.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { +// The section for IPC and policy. +SANDBOX_INTERCEPT HANDLE g_shared_section; +static bool s_is_broker = false; + +// GetBrokerServices: the current implementation relies on a shared section +// that is created by the broker and opened by the target. +BrokerServices* SandboxFactory::GetBrokerServices() { + // Can't be the broker if the shared section is open. + if (NULL != g_shared_section) { + return NULL; + } + // If the shared section does not exist we are the broker, then create + // the broker object. + s_is_broker = true; + return BrokerServicesBase::GetInstance(); +} + +// GetTargetServices implementation must follow the same technique as the +// GetBrokerServices, but in this case the logic is the opposite. +TargetServices* SandboxFactory::GetTargetServices() { + // Can't be the target if the section handle is not valid. + if (NULL == g_shared_section) { + return NULL; + } + // We are the target + s_is_broker = false; + // Creates and returns the target services implementation. + return TargetServicesBase::GetInstance(); +} + +} // namespace sandbox + +// Allows querying for whether the current process has been sandboxed. +extern "C" bool __declspec(dllexport) IsSandboxedProcess() { + return sandbox::g_shared_section != NULL; +} diff --git a/sandbox/win/src/sandbox.h b/sandbox/win/src/sandbox.h new file mode 100644 index 0000000000..e326194403 --- /dev/null +++ b/sandbox/win/src/sandbox.h @@ -0,0 +1,165 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Sandbox is a sandbox library for windows processes. Use when you want a +// 'privileged' process and a 'locked down process' to interact with. +// The privileged process is called the broker and it is started by external +// means (such as the user starting it). The 'sandboxed' process is called the +// target and it is started by the broker. There can be many target processes +// started by a single broker process. This library provides facilities +// for both the broker and the target. +// +// The design rationale and relevant documents can be found at http://go/sbox. +// +// Note: this header does not include the SandboxFactory definitions because +// there are cases where the Sandbox library is linked against the main .exe +// while its API needs to be used in a DLL. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_H_ +#define SANDBOX_WIN_SRC_SANDBOX_H_ + +#include <windows.h> + +#include "base/basictypes.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_types.h" + +// sandbox: Google User-Land Application Sandbox +namespace sandbox { + +class BrokerServices; +class ProcessState; +class TargetPolicy; +class TargetServices; + +// BrokerServices exposes all the broker API. +// The basic use is to start the target(s) and wait for them to end. +// +// This API is intended to be called in the following order +// (error checking omitted): +// BrokerServices* broker = SandboxFactory::GetBrokerServices(); +// broker->Init(); +// PROCESS_INFORMATION target; +// broker->SpawnTarget(target_exe_path, target_args, &target); +// ::ResumeThread(target->hThread); +// // -- later you can call: +// broker->WaitForAllTargets(option); +// +class BrokerServices { + public: + // Initializes the broker. Must be called before any other on this class. + // returns ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode Init() = 0; + + // Returns the interface pointer to a new, empty policy object. Use this + // interface to specify the sandbox policy for new processes created by + // SpawnTarget() + virtual TargetPolicy* CreatePolicy() = 0; + + // Creates a new target (child process) in a suspended state. + // Parameters: + // exe_path: This is the full path to the target binary. This parameter + // can be null and in this case the exe path must be the first argument + // of the command_line. + // command_line: The arguments to be passed as command line to the new + // process. This can be null if the exe_path parameter is not null. + // policy: This is the pointer to the policy object for the sandbox to + // be created. + // target: returns the resulting target process information such as process + // handle and PID just as if CreateProcess() had been called. The caller is + // responsible for closing the handles returned in this structure. + // Returns: + // ALL_OK if successful. All other return values imply failure. + virtual ResultCode SpawnTarget(const wchar_t* exe_path, + const wchar_t* command_line, + TargetPolicy* policy, + PROCESS_INFORMATION* target) = 0; + + // This call blocks (waits) for all the targets to terminate. + // Returns: + // ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode WaitForAllTargets() = 0; + + // Adds an unsandboxed process as a peer for policy decisions (e.g. + // HANDLES_DUP_ANY policy). + // Returns: + // ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode AddTargetPeer(HANDLE peer_process) = 0; + + // Install the AppContainer with the specified sid an name. Returns ALL_OK if + // successful or an error code if the AppContainer cannot be installed. + virtual ResultCode InstallAppContainer(const wchar_t* sid, + const wchar_t* name) = 0; + + // Removes from the system the AppContainer with the specified sid. + // Returns ALL_OK if successful or an error code otherwise. + virtual ResultCode UninstallAppContainer(const wchar_t* sid) = 0; +}; + +// TargetServices models the current process from the perspective +// of a target process. To obtain a pointer to it use +// Sandbox::GetTargetServices(). Note that this call returns a non-null +// pointer only if this process is in fact a target. A process is a target +// only if the process was spawned by a call to BrokerServices::SpawnTarget(). +// +// This API allows the target to gain access to resources with a high +// privilege token and then when it is ready to perform dangerous activities +// (such as download content from the web) it can lower its token and +// enter into locked-down (sandbox) mode. +// The typical usage is as follows: +// +// TargetServices* target_services = Sandbox::GetTargetServices(); +// if (NULL != target_services) { +// // We are the target. +// target_services->Init(); +// // Do work that requires high privileges here. +// // .... +// // When ready to enter lock-down mode call LowerToken: +// target_services->LowerToken(); +// } +// +// For more information see the BrokerServices API documentation. +class TargetServices { + public: + // Initializes the target. Must call this function before any other. + // returns ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode Init() = 0; + + // Discards the impersonation token and uses the lower token, call before + // processing any untrusted data or running third-party code. If this call + // fails the current process could be terminated immediately. + virtual void LowerToken() = 0; + + // Returns the ProcessState object. Through that object it's possible to have + // information about the current state of the process, such as whether + // LowerToken has been called or not. + virtual ProcessState* GetState() = 0; + + // Requests the broker to duplicate the supplied handle into the target + // process. The target process must be an active sandbox child process + // and the source process must have a corresponding policy allowing + // handle duplication for this object type. + // Returns: + // ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode DuplicateHandle(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) = 0; +}; + +} // namespace sandbox + + +#endif // SANDBOX_WIN_SRC_SANDBOX_H_ diff --git a/sandbox/win/src/sandbox.vcproj b/sandbox/win/src/sandbox.vcproj new file mode 100644 index 0000000000..f206e01a1f --- /dev/null +++ b/sandbox/win/src/sandbox.vcproj @@ -0,0 +1,658 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="sandbox" + ProjectGUID="{881F6A97-D539-4C48-B401-DF04385B2343}" + RootNamespace="sandbox" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="4" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + Description="Copy wow_helper to output directory" + CommandLine="copy $(ProjectDir)\..\wow_helper\wow_helper.exe $(OutDir) && copy $(ProjectDir)\..\wow_helper\wow_helper.pdb $(OutDir)" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="4" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + Description="Copy wow_helper to output directory" + CommandLine="copy $(ProjectDir)\..\wow_helper\wow_helper.exe $(OutDir) && copy $(ProjectDir)\..\wow_helper\wow_helper.pdb $(OutDir)" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="security" + > + <File + RelativePath=".\acl.cc" + > + </File> + <File + RelativePath=".\acl.h" + > + </File> + <File + RelativePath=".\dep.cc" + > + </File> + <File + RelativePath=".\dep.h" + > + </File> + <File + RelativePath=".\job.cc" + > + </File> + <File + RelativePath=".\job.h" + > + </File> + <File + RelativePath=".\restricted_token.cc" + > + </File> + <File + RelativePath=".\restricted_token.h" + > + </File> + <File + RelativePath=".\restricted_token_utils.cc" + > + </File> + <File + RelativePath=".\restricted_token_utils.h" + > + </File> + <File + RelativePath=".\security_level.h" + > + </File> + <File + RelativePath=".\sid.cc" + > + </File> + <File + RelativePath=".\sid.h" + > + </File> + <File + RelativePath=".\window.cc" + > + </File> + <File + RelativePath=".\window.h" + > + </File> + </Filter> + <Filter + Name="Interception" + > + <File + RelativePath=".\eat_resolver.cc" + > + </File> + <File + RelativePath=".\eat_resolver.h" + > + </File> + <File + RelativePath=".\interception.cc" + > + </File> + <File + RelativePath=".\interception.h" + > + </File> + <File + RelativePath=".\interception_agent.cc" + > + </File> + <File + RelativePath=".\interception_agent.h" + > + </File> + <File + RelativePath=".\interception_internal.h" + > + </File> + <File + RelativePath=".\pe_image.cc" + > + </File> + <File + RelativePath=".\pe_image.h" + > + </File> + <File + RelativePath=".\resolver.cc" + > + </File> + <File + RelativePath=".\resolver.h" + > + </File> + <File + RelativePath=".\service_resolver.cc" + > + </File> + <File + RelativePath=".\service_resolver.h" + > + </File> + <File + RelativePath=".\sidestep_resolver.cc" + > + </File> + <File + RelativePath=".\sidestep_resolver.h" + > + </File> + <File + RelativePath=".\target_interceptions.cc" + > + </File> + <File + RelativePath=".\target_interceptions.h" + > + </File> + <File + RelativePath=".\Wow64.cc" + > + </File> + <File + RelativePath=".\Wow64.h" + > + </File> + <Filter + Name="sidestep" + > + <File + RelativePath=".\sidestep\ia32_modrm_map.cpp" + > + </File> + <File + RelativePath=".\sidestep\ia32_opcode_map.cpp" + > + </File> + <File + RelativePath=".\sidestep\mini_disassembler.cpp" + > + </File> + <File + RelativePath=".\sidestep\mini_disassembler.h" + > + </File> + <File + RelativePath=".\sidestep\mini_disassembler_types.h" + > + </File> + <File + RelativePath=".\sidestep\preamble_patcher.h" + > + </File> + <File + RelativePath=".\sidestep\preamble_patcher_with_stub.cpp" + > + </File> + </Filter> + </Filter> + <Filter + Name="nt_level" + > + <File + RelativePath=".\nt_internals.h" + > + </File> + <File + RelativePath=".\policy_target.cc" + > + </File> + <File + RelativePath=".\policy_target.h" + > + </File> + <File + RelativePath=".\sandbox_nt_types.h" + > + </File> + <File + RelativePath=".\sandbox_nt_util.cc" + > + </File> + <File + RelativePath=".\sandbox_nt_util.h" + > + </File> + </Filter> + <Filter + Name="Policy_handlers" + > + <File + RelativePath=".\filesystem_dispatcher.cc" + > + </File> + <File + RelativePath=".\filesystem_dispatcher.h" + > + </File> + <File + RelativePath=".\filesystem_interception.cc" + > + </File> + <File + RelativePath=".\filesystem_interception.h" + > + </File> + <File + RelativePath=".\filesystem_policy.cc" + > + </File> + <File + RelativePath=".\filesystem_policy.h" + > + </File> + <File + RelativePath=".\named_pipe_dispatcher.cc" + > + </File> + <File + RelativePath=".\named_pipe_dispatcher.h" + > + </File> + <File + RelativePath=".\named_pipe_interception.cc" + > + </File> + <File + RelativePath=".\named_pipe_interception.h" + > + </File> + <File + RelativePath=".\named_pipe_policy.cc" + > + </File> + <File + RelativePath=".\named_pipe_policy.h" + > + </File> + <File + RelativePath=".\policy_params.h" + > + </File> + <File + RelativePath=".\process_thread_dispatcher.cc" + > + </File> + <File + RelativePath=".\process_thread_dispatcher.h" + > + </File> + <File + RelativePath=".\process_thread_interception.cc" + > + </File> + <File + RelativePath=".\process_thread_interception.h" + > + </File> + <File + RelativePath=".\process_thread_policy.cc" + > + </File> + <File + RelativePath=".\process_thread_policy.h" + > + </File> + <File + RelativePath=".\registry_dispatcher.cc" + > + </File> + <File + RelativePath=".\registry_dispatcher.h" + > + </File> + <File + RelativePath=".\registry_interception.cc" + > + </File> + <File + RelativePath=".\registry_interception.h" + > + </File> + <File + RelativePath=".\registry_policy.cc" + > + </File> + <File + RelativePath=".\registry_policy.h" + > + </File> + <File + RelativePath=".\sync_dispatcher.cc" + > + </File> + <File + RelativePath=".\sync_dispatcher.h" + > + </File> + <File + RelativePath=".\sync_interception.cc" + > + </File> + <File + RelativePath=".\sync_interception.h" + > + </File> + <File + RelativePath=".\sync_policy.cc" + > + </File> + <File + RelativePath=".\sync_policy.h" + > + </File> + </Filter> + <Filter + Name="IPC" + > + <File + RelativePath=".\crosscall_client.h" + > + </File> + <File + RelativePath=".\crosscall_params.h" + > + </File> + <File + RelativePath=".\crosscall_server.cc" + > + </File> + <File + RelativePath=".\crosscall_server.h" + > + </File> + <File + RelativePath=".\ipc_tags.h" + > + </File> + <File + RelativePath=".\sharedmem_ipc_client.cc" + > + </File> + <File + RelativePath=".\sharedmem_ipc_client.h" + > + </File> + <File + RelativePath=".\sharedmem_ipc_server.cc" + > + </File> + <File + RelativePath=".\sharedmem_ipc_server.h" + > + </File> + </Filter> + <Filter + Name="Policy_base" + > + <File + RelativePath=".\policy_engine_opcodes.cc" + > + </File> + <File + RelativePath=".\policy_engine_opcodes.h" + > + </File> + <File + RelativePath=".\policy_engine_params.h" + > + </File> + <File + RelativePath=".\policy_engine_processor.cc" + > + </File> + <File + RelativePath=".\policy_engine_processor.h" + > + </File> + <File + RelativePath=".\policy_low_level.cc" + > + </File> + <File + RelativePath=".\policy_low_level.h" + > + </File> + <File + RelativePath=".\sandbox_policy_base.cc" + > + </File> + <File + RelativePath=".\sandbox_policy_base.h" + > + </File> + </Filter> + <File + RelativePath=".\broker_services.cc" + > + </File> + <File + RelativePath=".\broker_services.h" + > + </File> + <File + RelativePath=".\internal_types.h" + > + </File> + <File + RelativePath=".\policy_broker.cc" + > + </File> + <File + RelativePath=".\policy_broker.h" + > + </File> + <File + RelativePath=".\sandbox.cc" + > + </File> + <File + RelativePath=".\sandbox.h" + > + </File> + <File + RelativePath=".\sandbox_factory.h" + > + </File> + <File + RelativePath=".\sandbox_policy.h" + > + </File> + <File + RelativePath=".\sandbox_types.h" + > + </File> + <File + RelativePath=".\sandbox_utils.cc" + > + </File> + <File + RelativePath=".\sandbox_utils.h" + > + </File> + <File + RelativePath=".\shared_handles.cc" + > + </File> + <File + RelativePath=".\shared_handles.h" + > + </File> + <File + RelativePath=".\stdafx.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + <File + RelativePath=".\target_process.cc" + > + </File> + <File + RelativePath=".\target_process.h" + > + </File> + <File + RelativePath=".\target_services.cc" + > + </File> + <File + RelativePath=".\target_services.h" + > + </File> + <File + RelativePath=".\win2k_threadpool.cc" + > + </File> + <File + RelativePath=".\win2k_threadpool.h" + > + </File> + <File + RelativePath=".\win_utils.cc" + > + </File> + <File + RelativePath=".\win_utils.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/sandbox/win/src/sandbox_factory.h b/sandbox/win/src/sandbox_factory.h new file mode 100644 index 0000000000..7a0280f908 --- /dev/null +++ b/sandbox/win/src/sandbox_factory.h @@ -0,0 +1,50 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SANDBOX_FACTORY_H__ +#define SANDBOX_SRC_SANDBOX_FACTORY_H__ + +#include "sandbox/win/src/sandbox.h" + +// SandboxFactory is a set of static methods to get access to the broker +// or target services object. Only one of the two methods (GetBrokerServices, +// GetTargetServices) will return a non-null pointer and that should be used +// as the indication that the process is the broker or the target: +// +// BrokerServices* broker_services = SandboxFactory::GetBrokerServices(); +// if (NULL != broker_services) { +// //we are the broker, call broker api here +// broker_services->Init(); +// } else { +// TargetServices* target_services = SandboxFactory::GetTargetServices(); +// if (NULL != target_services) { +// //we are the target, call target api here +// target_services->Init(); +// } +// +// The methods in this class are expected to be called from a single thread +// +// The Sandbox library needs to be linked against the main executable, but +// sometimes the API calls are issued from a DLL that loads into the exe +// process. These factory methods then need to be called from the main +// exe and the interface pointers then can be safely passed to the DLL where +// the Sandbox API calls are made. +namespace sandbox { + +class SandboxFactory { + public: + // Returns the Broker API interface, returns NULL if this process is the + // target. + static BrokerServices* GetBrokerServices(); + + // Returns the Target API interface, returns NULL if this process is the + // broker. + static TargetServices* GetTargetServices(); + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(SandboxFactory); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_FACTORY_H__ diff --git a/sandbox/win/src/sandbox_globals.cc b/sandbox/win/src/sandbox_globals.cc new file mode 100644 index 0000000000..b4ab523921 --- /dev/null +++ b/sandbox/win/src/sandbox_globals.cc @@ -0,0 +1,18 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> + +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +// The section for IPC and policy. +SANDBOX_INTERCEPT HANDLE g_shared_section = NULL; + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt = {}; + +} // namespace sandbox diff --git a/sandbox/win/src/sandbox_nt_types.h b/sandbox/win/src/sandbox_nt_types.h new file mode 100644 index 0000000000..a4a88bba7c --- /dev/null +++ b/sandbox/win/src/sandbox_nt_types.h @@ -0,0 +1,46 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SANDBOX_NT_TYPES_H__ +#define SANDBOX_SRC_SANDBOX_NT_TYPES_H__ + +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +struct NtExports { + NtAllocateVirtualMemoryFunction AllocateVirtualMemory; + NtCloseFunction Close; + NtDuplicateObjectFunction DuplicateObject; + NtFreeVirtualMemoryFunction FreeVirtualMemory; + NtMapViewOfSectionFunction MapViewOfSection; + NtProtectVirtualMemoryFunction ProtectVirtualMemory; + NtQueryInformationProcessFunction QueryInformationProcess; + NtQueryObjectFunction QueryObject; + NtQuerySectionFunction QuerySection; + NtQueryVirtualMemoryFunction QueryVirtualMemory; + NtUnmapViewOfSectionFunction UnmapViewOfSection; + RtlAllocateHeapFunction RtlAllocateHeap; + RtlAnsiStringToUnicodeStringFunction RtlAnsiStringToUnicodeString; + RtlCompareUnicodeStringFunction RtlCompareUnicodeString; + RtlCreateHeapFunction RtlCreateHeap; + RtlCreateUserThreadFunction RtlCreateUserThread; + RtlDestroyHeapFunction RtlDestroyHeap; + RtlFreeHeapFunction RtlFreeHeap; + _strnicmpFunction _strnicmp; + strlenFunction strlen; + wcslenFunction wcslen; + memcpyFunction memcpy; +}; + +// This is the value used for the ntdll level allocator. +enum AllocationType { + NT_ALLOC, + NT_PAGE +}; + +} // namespace sandbox + + +#endif // SANDBOX_SRC_SANDBOX_NT_TYPES_H__ diff --git a/sandbox/win/src/sandbox_nt_util.cc b/sandbox/win/src/sandbox_nt_util.cc new file mode 100644 index 0000000000..64fd1f1f6d --- /dev/null +++ b/sandbox/win/src/sandbox_nt_util.cc @@ -0,0 +1,679 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sandbox_nt_util.h" + +#include "base/win/pe_image.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +} // namespace sandbox + +namespace { + +#if defined(_WIN64) +void* AllocateNearTo(void* source, size_t size) { + using sandbox::g_nt; + + // Start with 1 GB above the source. + const size_t kOneGB = 0x40000000; + void* base = reinterpret_cast<char*>(source) + kOneGB; + SIZE_T actual_size = size; + ULONG_PTR zero_bits = 0; // Not the correct type if used. + ULONG type = MEM_RESERVE; + + NTSTATUS ret; + int attempts = 0; + for (; attempts < 41; attempts++) { + ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, zero_bits, + &actual_size, type, PAGE_READWRITE); + if (NT_SUCCESS(ret)) { + if (base < source || + base >= reinterpret_cast<char*>(source) + 4 * kOneGB) { + // We won't be able to patch this dll. + VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size, + MEM_RELEASE)); + return NULL; + } + break; + } + + if (attempts == 30) { + // Try the first GB. + base = reinterpret_cast<char*>(source); + } else if (attempts == 40) { + // Try the highest available address. + base = NULL; + type |= MEM_TOP_DOWN; + } + + // Try 100 MB higher. + base = reinterpret_cast<char*>(base) + 100 * 0x100000; + }; + + if (attempts == 41) + return NULL; + + ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, zero_bits, + &actual_size, MEM_COMMIT, PAGE_READWRITE); + + if (!NT_SUCCESS(ret)) { + VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size, + MEM_RELEASE)); + base = NULL; + } + + return base; +} +#else // defined(_WIN64). +void* AllocateNearTo(void* source, size_t size) { + using sandbox::g_nt; + UNREFERENCED_PARAMETER(source); + + // In 32-bit processes allocations below 512k are predictable, so mark + // anything in that range as reserved and retry until we get a good address. + const void* const kMinAddress = reinterpret_cast<void*>(512 * 1024); + NTSTATUS ret; + SIZE_T actual_size; + void* base; + do { + base = NULL; + actual_size = 64 * 1024; + ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size, + MEM_RESERVE, PAGE_NOACCESS); + if (!NT_SUCCESS(ret)) + return NULL; + } while (base < kMinAddress); + + actual_size = size; + ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size, + MEM_COMMIT, PAGE_READWRITE); + if (!NT_SUCCESS(ret)) + return NULL; + return base; +} +#endif // defined(_WIN64). + +} // namespace. + +namespace sandbox { + +// Handle for our private heap. +void* g_heap = NULL; + +SANDBOX_INTERCEPT HANDLE g_shared_section; +SANDBOX_INTERCEPT size_t g_shared_IPC_size = 0; +SANDBOX_INTERCEPT size_t g_shared_policy_size = 0; + +void* volatile g_shared_policy_memory = NULL; +void* volatile g_shared_IPC_memory = NULL; + +// Both the IPC and the policy share a single region of memory in which the IPC +// memory is first and the policy memory is last. +bool MapGlobalMemory() { + if (NULL == g_shared_IPC_memory) { + void* memory = NULL; + SIZE_T size = 0; + // Map the entire shared section from the start. + NTSTATUS ret = g_nt.MapViewOfSection(g_shared_section, NtCurrentProcess, + &memory, 0, 0, NULL, &size, ViewUnmap, + 0, PAGE_READWRITE); + + if (!NT_SUCCESS(ret) || NULL == memory) { + NOTREACHED_NT(); + return false; + } + + if (NULL != _InterlockedCompareExchangePointer(&g_shared_IPC_memory, + memory, NULL)) { + // Somebody beat us to the memory setup. + ret = g_nt.UnmapViewOfSection(NtCurrentProcess, memory); + VERIFY_SUCCESS(ret); + } + DCHECK_NT(g_shared_IPC_size > 0); + g_shared_policy_memory = reinterpret_cast<char*>(g_shared_IPC_memory) + + g_shared_IPC_size; + } + DCHECK_NT(g_shared_policy_memory); + DCHECK_NT(g_shared_policy_size > 0); + return true; +} + +void* GetGlobalIPCMemory() { + if (!MapGlobalMemory()) + return NULL; + return g_shared_IPC_memory; +} + +void* GetGlobalPolicyMemory() { + if (!MapGlobalMemory()) + return NULL; + return g_shared_policy_memory; +} + +bool InitHeap() { + if (!g_heap) { + // Create a new heap using default values for everything. + void* heap = g_nt.RtlCreateHeap(HEAP_GROWABLE, NULL, 0, 0, NULL, NULL); + if (!heap) + return false; + + if (NULL != _InterlockedCompareExchangePointer(&g_heap, heap, NULL)) { + // Somebody beat us to the memory setup. + g_nt.RtlDestroyHeap(heap); + } + } + return (g_heap != NULL); +} + +// Physically reads or writes from memory to verify that (at this time), it is +// valid. Returns a dummy value. +int TouchMemory(void* buffer, size_t size_bytes, RequiredAccess intent) { + const int kPageSize = 4096; + int dummy = 0; + char* start = reinterpret_cast<char*>(buffer); + char* end = start + size_bytes - 1; + + if (WRITE == intent) { + for (; start < end; start += kPageSize) { + *start = 0; + } + *end = 0; + } else { + for (; start < end; start += kPageSize) { + dummy += *start; + } + dummy += *end; + } + + return dummy; +} + +bool ValidParameter(void* buffer, size_t size, RequiredAccess intent) { + DCHECK_NT(size); + __try { + TouchMemory(buffer, size, intent); + } __except(EXCEPTION_EXECUTE_HANDLER) { + return false; + } + return true; +} + +NTSTATUS CopyData(void* destination, const void* source, size_t bytes) { + NTSTATUS ret = STATUS_SUCCESS; + __try { + g_nt.memcpy(destination, source, bytes); + } __except(EXCEPTION_EXECUTE_HANDLER) { + ret = GetExceptionCode(); + } + return ret; +} + +NTSTATUS AllocAndGetFullPath(HANDLE root, + wchar_t* path, + wchar_t** full_path) { + if (!InitHeap()) + return STATUS_NO_MEMORY; + + DCHECK_NT(full_path); + DCHECK_NT(path); + *full_path = NULL; + OBJECT_NAME_INFORMATION* handle_name = NULL; + NTSTATUS ret = STATUS_UNSUCCESSFUL; + __try { + do { + static NtQueryObjectFunction NtQueryObject = NULL; + if (!NtQueryObject) + ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); + + ULONG size = 0; + // Query the name information a first time to get the size of the name. + ret = NtQueryObject(root, ObjectNameInformation, NULL, 0, &size); + + if (size) { + handle_name = reinterpret_cast<OBJECT_NAME_INFORMATION*>( + new(NT_ALLOC) BYTE[size]); + + // Query the name information a second time to get the name of the + // object referenced by the handle. + ret = NtQueryObject(root, ObjectNameInformation, handle_name, size, + &size); + } + + if (STATUS_SUCCESS != ret) + break; + + // Space for path + '\' + name + '\0'. + size_t name_length = handle_name->ObjectName.Length + + (wcslen(path) + 2) * sizeof(wchar_t); + *full_path = new(NT_ALLOC) wchar_t[name_length/sizeof(wchar_t)]; + if (NULL == *full_path) + break; + wchar_t* off = *full_path; + ret = CopyData(off, handle_name->ObjectName.Buffer, + handle_name->ObjectName.Length); + if (!NT_SUCCESS(ret)) + break; + off += handle_name->ObjectName.Length / sizeof(wchar_t); + *off = L'\\'; + off += 1; + ret = CopyData(off, path, wcslen(path) * sizeof(wchar_t)); + if (!NT_SUCCESS(ret)) + break; + off += wcslen(path); + *off = L'\0'; + } while (false); + } __except(EXCEPTION_EXECUTE_HANDLER) { + ret = GetExceptionCode(); + } + + if (!NT_SUCCESS(ret)) { + if (*full_path) { + operator delete(*full_path, NT_ALLOC); + *full_path = NULL; + } + if (handle_name) { + operator delete(handle_name, NT_ALLOC); + handle_name = NULL; + } + } + + return ret; +} + +// Hacky code... replace with AllocAndCopyObjectAttributes. +NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object, + wchar_t** out_name, uint32* attributes, + HANDLE* root) { + if (!InitHeap()) + return STATUS_NO_MEMORY; + + DCHECK_NT(out_name); + *out_name = NULL; + NTSTATUS ret = STATUS_UNSUCCESSFUL; + __try { + do { + if (in_object->RootDirectory != static_cast<HANDLE>(0) && !root) + break; + if (NULL == in_object->ObjectName) + break; + if (NULL == in_object->ObjectName->Buffer) + break; + + size_t size = in_object->ObjectName->Length + sizeof(wchar_t); + *out_name = new(NT_ALLOC) wchar_t[size/sizeof(wchar_t)]; + if (NULL == *out_name) + break; + + ret = CopyData(*out_name, in_object->ObjectName->Buffer, + size - sizeof(wchar_t)); + if (!NT_SUCCESS(ret)) + break; + + (*out_name)[size / sizeof(wchar_t) - 1] = L'\0'; + + if (attributes) + *attributes = in_object->Attributes; + + if (root) + *root = in_object->RootDirectory; + ret = STATUS_SUCCESS; + } while (false); + } __except(EXCEPTION_EXECUTE_HANDLER) { + ret = GetExceptionCode(); + } + + if (!NT_SUCCESS(ret) && *out_name) { + operator delete(*out_name, NT_ALLOC); + *out_name = NULL; + } + + return ret; +} + +NTSTATUS GetProcessId(HANDLE process, ULONG *process_id) { + PROCESS_BASIC_INFORMATION proc_info; + ULONG bytes_returned; + + NTSTATUS ret = g_nt.QueryInformationProcess(process, ProcessBasicInformation, + &proc_info, sizeof(proc_info), + &bytes_returned); + if (!NT_SUCCESS(ret) || sizeof(proc_info) != bytes_returned) + return ret; + + *process_id = proc_info.UniqueProcessId; + return STATUS_SUCCESS; +} + +bool IsSameProcess(HANDLE process) { + if (NtCurrentProcess == process) + return true; + + static ULONG s_process_id = 0; + + if (!s_process_id) { + NTSTATUS ret = GetProcessId(NtCurrentProcess, &s_process_id); + if (!NT_SUCCESS(ret)) + return false; + } + + ULONG process_id; + NTSTATUS ret = GetProcessId(process, &process_id); + if (!NT_SUCCESS(ret)) + return false; + + return (process_id == s_process_id); +} + +bool IsValidImageSection(HANDLE section, PVOID *base, PLARGE_INTEGER offset, + PSIZE_T view_size) { + if (!section || !base || !view_size || offset) + return false; + + HANDLE query_section; + + NTSTATUS ret = g_nt.DuplicateObject(NtCurrentProcess, section, + NtCurrentProcess, &query_section, + SECTION_QUERY, 0, 0); + if (!NT_SUCCESS(ret)) + return false; + + SECTION_BASIC_INFORMATION basic_info; + SIZE_T bytes_returned; + ret = g_nt.QuerySection(query_section, SectionBasicInformation, &basic_info, + sizeof(basic_info), &bytes_returned); + + VERIFY_SUCCESS(g_nt.Close(query_section)); + + if (!NT_SUCCESS(ret) || sizeof(basic_info) != bytes_returned) + return false; + + if (!(basic_info.Attributes & SEC_IMAGE)) + return false; + + return true; +} + +UNICODE_STRING* AnsiToUnicode(const char* string) { + ANSI_STRING ansi_string; + ansi_string.Length = static_cast<USHORT>(g_nt.strlen(string)); + ansi_string.MaximumLength = ansi_string.Length + 1; + ansi_string.Buffer = const_cast<char*>(string); + + if (ansi_string.Length > ansi_string.MaximumLength) + return NULL; + + size_t name_bytes = ansi_string.MaximumLength * sizeof(wchar_t) + + sizeof(UNICODE_STRING); + + UNICODE_STRING* out_string = reinterpret_cast<UNICODE_STRING*>( + new(NT_ALLOC) char[name_bytes]); + if (!out_string) + return NULL; + + out_string->MaximumLength = ansi_string.MaximumLength * sizeof(wchar_t); + out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]); + + BOOLEAN alloc_destination = FALSE; + NTSTATUS ret = g_nt.RtlAnsiStringToUnicodeString(out_string, &ansi_string, + alloc_destination); + DCHECK_NT(STATUS_BUFFER_OVERFLOW != ret); + if (!NT_SUCCESS(ret)) { + operator delete(out_string, NT_ALLOC); + return NULL; + } + + return out_string; +} + +UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32* flags) { + // PEImage's dtor won't be run during SEH unwinding, but that's OK. +#pragma warning(push) +#pragma warning(disable: 4509) + UNICODE_STRING* out_name = NULL; + __try { + do { + *flags = 0; + base::win::PEImage pe(module); + + if (!pe.VerifyMagic()) + break; + *flags |= MODULE_IS_PE_IMAGE; + + PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory(); + if (exports) { + char* name = reinterpret_cast<char*>(pe.RVAToAddr(exports->Name)); + out_name = AnsiToUnicode(name); + } + + PIMAGE_NT_HEADERS headers = pe.GetNTHeaders(); + if (headers) { + if (headers->OptionalHeader.AddressOfEntryPoint) + *flags |= MODULE_HAS_ENTRY_POINT; + if (headers->OptionalHeader.SizeOfCode) + *flags |= MODULE_HAS_CODE; + } + } while (false); + } __except(EXCEPTION_EXECUTE_HANDLER) { + } + + return out_name; +#pragma warning(pop) +} + +UNICODE_STRING* GetBackingFilePath(PVOID address) { + // We'll start with something close to max_path charactes for the name. + SIZE_T buffer_bytes = MAX_PATH * 2; + + for (;;) { + MEMORY_SECTION_NAME* section_name = reinterpret_cast<MEMORY_SECTION_NAME*>( + new(NT_ALLOC) char[buffer_bytes]); + + if (!section_name) + return NULL; + + SIZE_T returned_bytes; + NTSTATUS ret = g_nt.QueryVirtualMemory(NtCurrentProcess, address, + MemorySectionName, section_name, + buffer_bytes, &returned_bytes); + + if (STATUS_BUFFER_OVERFLOW == ret) { + // Retry the call with the given buffer size. + operator delete(section_name, NT_ALLOC); + section_name = NULL; + buffer_bytes = returned_bytes; + continue; + } + if (!NT_SUCCESS(ret)) { + operator delete(section_name, NT_ALLOC); + return NULL; + } + + return reinterpret_cast<UNICODE_STRING*>(section_name); + } +} + +UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path) { + if ((!module_path) || (!module_path->Buffer)) + return NULL; + + wchar_t* sep = NULL; + int start_pos = module_path->Length / sizeof(wchar_t) - 1; + int ix = start_pos; + + for (; ix >= 0; --ix) { + if (module_path->Buffer[ix] == L'\\') { + sep = &module_path->Buffer[ix]; + break; + } + } + + // Ends with path separator. Not a valid module name. + if ((ix == start_pos) && sep) + return NULL; + + // No path separator found. Use the entire name. + if (!sep) { + sep = &module_path->Buffer[-1]; + } + + // Add one to the size so we can null terminate the string. + size_t size_bytes = (start_pos - ix + 1) * sizeof(wchar_t); + + // Based on the code above, size_bytes should always be small enough + // to make the static_cast below safe. + DCHECK_NT(kuint16max > size_bytes); + char* str_buffer = new(NT_ALLOC) char[size_bytes + sizeof(UNICODE_STRING)]; + if (!str_buffer) + return NULL; + + UNICODE_STRING* out_string = reinterpret_cast<UNICODE_STRING*>(str_buffer); + out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]); + out_string->Length = static_cast<USHORT>(size_bytes - sizeof(wchar_t)); + out_string->MaximumLength = static_cast<USHORT>(size_bytes); + + NTSTATUS ret = CopyData(out_string->Buffer, &sep[1], out_string->Length); + if (!NT_SUCCESS(ret)) { + operator delete(out_string, NT_ALLOC); + return NULL; + } + + out_string->Buffer[out_string->Length / sizeof(wchar_t)] = L'\0'; + return out_string; +} + +NTSTATUS AutoProtectMemory::ChangeProtection(void* address, size_t bytes, + ULONG protect) { + DCHECK_NT(!changed_); + SIZE_T new_bytes = bytes; + NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address, + &new_bytes, protect, &old_protect_); + if (NT_SUCCESS(ret)) { + changed_ = true; + address_ = address; + bytes_ = new_bytes; + } + + return ret; +} + +NTSTATUS AutoProtectMemory::RevertProtection() { + if (!changed_) + return STATUS_SUCCESS; + + DCHECK_NT(address_); + DCHECK_NT(bytes_); + + SIZE_T new_bytes = bytes_; + NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address_, + &new_bytes, old_protect_, + &old_protect_); + DCHECK_NT(NT_SUCCESS(ret)); + + changed_ = false; + address_ = NULL; + bytes_ = 0; + old_protect_ = 0; + + return ret; +} + +bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, DWORD length, + uint32 file_info_class) { + if (FileRenameInformation != file_info_class) + return false; + + if (length < sizeof(FILE_RENAME_INFORMATION)) + return false; + + // Make sure file name length doesn't exceed the message length + if (length - offsetof(FILE_RENAME_INFORMATION, FileName) < + file_info->FileNameLength) + return false; + + // We don't support a root directory. + if (file_info->RootDirectory) + return false; + + static const wchar_t kPathPrefix[] = { L'\\', L'?', L'?', L'\\'}; + + // Check if it starts with \\??\\. We don't support relative paths. + if (file_info->FileNameLength < sizeof(kPathPrefix) || + file_info->FileNameLength > kuint16max) + return false; + + if (file_info->FileName[0] != kPathPrefix[0] || + file_info->FileName[1] != kPathPrefix[1] || + file_info->FileName[2] != kPathPrefix[2] || + file_info->FileName[3] != kPathPrefix[3]) + return false; + + return true; +} + +} // namespace sandbox + +void* operator new(size_t size, sandbox::AllocationType type, + void* near_to) { + using namespace sandbox; + + void* result = NULL; + if (NT_ALLOC == type) { + if (InitHeap()) { + // Use default flags for the allocation. + result = g_nt.RtlAllocateHeap(sandbox::g_heap, 0, size); + } + } else if (NT_PAGE == type) { + result = AllocateNearTo(near_to, size); + } else { + NOTREACHED_NT(); + } + + // TODO: Returning NULL from operator new has undefined behavior, but + // the Allocate() functions called above can return NULL. Consider checking + // for NULL here and crashing or throwing. + + return result; +} + +void operator delete(void* memory, sandbox::AllocationType type) { + using namespace sandbox; + + if (NT_ALLOC == type) { + // Use default flags. + VERIFY(g_nt.RtlFreeHeap(sandbox::g_heap, 0, memory)); + } else if (NT_PAGE == type) { + void* base = memory; + SIZE_T size = 0; + VERIFY_SUCCESS(g_nt.FreeVirtualMemory(NtCurrentProcess, &base, &size, + MEM_RELEASE)); + } else { + NOTREACHED_NT(); + } +} + +void operator delete(void* memory, sandbox::AllocationType type, + void* near_to) { + UNREFERENCED_PARAMETER(near_to); + operator delete(memory, type); +} + +void* __cdecl operator new(size_t size, void* buffer, + sandbox::AllocationType type) { + UNREFERENCED_PARAMETER(size); + UNREFERENCED_PARAMETER(type); + return buffer; +} + +void __cdecl operator delete(void* memory, void* buffer, + sandbox::AllocationType type) { + UNREFERENCED_PARAMETER(memory); + UNREFERENCED_PARAMETER(buffer); + UNREFERENCED_PARAMETER(type); +} diff --git a/sandbox/win/src/sandbox_nt_util.h b/sandbox/win/src/sandbox_nt_util.h new file mode 100644 index 0000000000..83dd7c090e --- /dev/null +++ b/sandbox/win/src/sandbox_nt_util.h @@ -0,0 +1,191 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SANDBOX_NT_UTIL_H_ +#define SANDBOX_SRC_SANDBOX_NT_UTIL_H_ + +#include <intrin.h> + +#include "base/basictypes.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_nt_types.h" + +// Placement new and delete to be used from ntdll interception code. +void* __cdecl operator new(size_t size, sandbox::AllocationType type, + void* near_to = NULL); +void __cdecl operator delete(void* memory, sandbox::AllocationType type); +// Add operator delete that matches the placement form of the operator new +// above. This is required by compiler to generate code to call operator delete +// in case the object's constructor throws an exception. +// See http://msdn.microsoft.com/en-us/library/cxdxz3x6.aspx +void __cdecl operator delete(void* memory, sandbox::AllocationType type, + void* near_to); + +// Regular placement new and delete +void* __cdecl operator new(size_t size, void* buffer, + sandbox::AllocationType type); +void __cdecl operator delete(void* memory, void* buffer, + sandbox::AllocationType type); + +// DCHECK_NT is defined to be pretty much an assert at this time because we +// don't have logging from the ntdll layer on the child. +// +// VERIFY_NT and VERIFY_SUCCESS_NT are the standard asserts on debug, but +// execute the actual argument on release builds. VERIFY_NT expects an action +// returning a bool, while VERIFY_SUCCESS_NT expects an action returning +// NTSTATUS. +#ifndef NDEBUG +#define DCHECK_NT(condition) { (condition) ? (void)0 : __debugbreak(); } +#define VERIFY(action) DCHECK_NT(action) +#define VERIFY_SUCCESS(action) DCHECK_NT(NT_SUCCESS(action)) +#else +#define DCHECK_NT(condition) +#define VERIFY(action) (action) +#define VERIFY_SUCCESS(action) (action) +#endif + +#define CHECK_NT(condition) { (condition) ? (void)0 : __debugbreak(); } + +#define NOTREACHED_NT() DCHECK_NT(false) + +namespace sandbox { + +#if defined(_M_X64) +#pragma intrinsic(_InterlockedCompareExchange) +#pragma intrinsic(_InterlockedCompareExchangePointer) + +#elif defined(_M_IX86) +extern "C" long _InterlockedCompareExchange(long volatile* destination, + long exchange, long comperand); + +#pragma intrinsic(_InterlockedCompareExchange) + +// We want to make sure that we use an intrinsic version of the function, not +// the one provided by kernel32. +__forceinline void* _InterlockedCompareExchangePointer( + void* volatile* destination, void* exchange, void* comperand) { + size_t ret = _InterlockedCompareExchange( + reinterpret_cast<long volatile*>(destination), + static_cast<long>(reinterpret_cast<size_t>(exchange)), + static_cast<long>(reinterpret_cast<size_t>(comperand))); + + return reinterpret_cast<void*>(static_cast<size_t>(ret)); +} + +#else +#error Architecture not supported. + +#endif + +// Returns a pointer to the IPC shared memory. +void* GetGlobalIPCMemory(); + +// Returns a pointer to the Policy shared memory. +void* GetGlobalPolicyMemory(); + +enum RequiredAccess { + READ, + WRITE +}; + +// Performs basic user mode buffer validation. In any case, buffers access must +// be protected by SEH. intent specifies if the buffer should be tested for read +// or write. +// Note that write intent implies destruction of the buffer content (we actually +// write) +bool ValidParameter(void* buffer, size_t size, RequiredAccess intent); + +// Copies data from a user buffer to our buffer. Returns the operation status. +NTSTATUS CopyData(void* destination, const void* source, size_t bytes); + +// Copies the name from an object attributes. +NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object, + wchar_t** out_name, uint32* attributes, HANDLE* root); + +// Determine full path name from object root and path. +NTSTATUS AllocAndGetFullPath(HANDLE root, + wchar_t* path, + wchar_t** full_path); + +// Initializes our ntdll level heap +bool InitHeap(); + +// Returns true if the provided handle refers to the current process. +bool IsSameProcess(HANDLE process); + +enum MappedModuleFlags { + MODULE_IS_PE_IMAGE = 1, // Module is an executable. + MODULE_HAS_ENTRY_POINT = 2, // Execution entry point found. + MODULE_HAS_CODE = 4 // Non zero size of executable sections. +}; + +// Returns the name and characteristics for a given PE module. The return +// value is the name as defined by the export table and the flags is any +// combination of the MappedModuleFlags enumeration. +// +// The returned buffer must be freed with a placement delete from the ntdll +// level allocator: +// +// UNICODE_STRING* name = GetPEImageInfoFromModule(HMODULE module, &flags); +// if (!name) { +// // probably not a valid dll +// return; +// } +// InsertYourLogicHere(name); +// operator delete(name, NT_ALLOC); +UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32* flags); + +// Returns the full path and filename for a given dll. +// May return NULL if the provided address is not backed by a named section, or +// if the current OS version doesn't support the call. The returned buffer must +// be freed with a placement delete (see GetImageNameFromModule example). +UNICODE_STRING* GetBackingFilePath(PVOID address); + +// Returns the last component of a path that contains the module name. +// It will return NULL if the path ends with the path separator. The returned +// buffer must be freed with a placement delete (see GetImageNameFromModule +// example). +UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path); + +// Returns true if the parameters correspond to a dll mapped as code. +bool IsValidImageSection(HANDLE section, PVOID *base, PLARGE_INTEGER offset, + PSIZE_T view_size); + +// Converts an ansi string to an UNICODE_STRING. +UNICODE_STRING* AnsiToUnicode(const char* string); + +// Provides a simple way to temporarily change the protection of a memory page. +class AutoProtectMemory { + public: + AutoProtectMemory() + : changed_(false), address_(NULL), bytes_(0), old_protect_(0) {} + + ~AutoProtectMemory() { + RevertProtection(); + } + + // Sets the desired protection of a given memory range. + NTSTATUS ChangeProtection(void* address, size_t bytes, ULONG protect); + + // Restores the original page protection. + NTSTATUS RevertProtection(); + + private: + bool changed_; + void* address_; + size_t bytes_; + ULONG old_protect_; + + DISALLOW_COPY_AND_ASSIGN(AutoProtectMemory); +}; + +// Returns true if the file_rename_information structure is supported by our +// rename handler. +bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, DWORD length, + uint32 file_info_class); + +} // namespace sandbox + + +#endif // SANDBOX_SRC_SANDBOX_NT_UTIL_H__ diff --git a/sandbox/win/src/sandbox_policy.h b/sandbox/win/src/sandbox_policy.h new file mode 100644 index 0000000000..54bb2a9096 --- /dev/null +++ b/sandbox/win/src/sandbox_policy.h @@ -0,0 +1,256 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_POLICY_H_ +#define SANDBOX_WIN_SRC_SANDBOX_POLICY_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/security_level.h" + +namespace sandbox { + +class TargetPolicy { + public: + // Windows subsystems that can have specific rules. + // Note: The process subsystem(SUBSY_PROCESS) does not evaluate the request + // exactly like the CreateProcess API does. See the comment at the top of + // process_thread_dispatcher.cc for more details. + enum SubSystem { + SUBSYS_FILES, // Creation and opening of files and pipes. + SUBSYS_NAMED_PIPES, // Creation of named pipes. + SUBSYS_PROCESS, // Creation of child processes. + SUBSYS_REGISTRY, // Creation and opening of registry keys. + SUBSYS_SYNC, // Creation of named sync objects. + SUBSYS_HANDLES, // Duplication of handles to other processes. + SUBSYS_WIN32K_LOCKDOWN // Win32K Lockdown related policy. + }; + + // Allowable semantics when a rule is matched. + enum Semantics { + FILES_ALLOW_ANY, // Allows open or create for any kind of access that + // the file system supports. + FILES_ALLOW_READONLY, // Allows open or create with read access only. + FILES_ALLOW_QUERY, // Allows access to query the attributes of a file. + FILES_ALLOW_DIR_ANY, // Allows open or create with directory semantics + // only. + HANDLES_DUP_ANY, // Allows duplicating handles opened with any + // access permissions. + HANDLES_DUP_BROKER, // Allows duplicating handles to the broker process. + NAMEDPIPES_ALLOW_ANY, // Allows creation of a named pipe. + PROCESS_MIN_EXEC, // Allows to create a process with minimal rights + // over the resulting process and thread handles. + // No other parameters besides the command line are + // passed to the child process. + PROCESS_ALL_EXEC, // Allows the creation of a process and return fill + // access on the returned handles. + // This flag can be used only when the main token of + // the sandboxed application is at least INTERACTIVE. + EVENTS_ALLOW_ANY, // Allows the creation of an event with full access. + EVENTS_ALLOW_READONLY, // Allows opening an even with synchronize access. + REG_ALLOW_READONLY, // Allows readonly access to a registry key. + REG_ALLOW_ANY, // Allows read and write access to a registry key. + FAKE_USER_GDI_INIT // Fakes user32 and gdi32 initialization. This can + // be used to allow the DLLs to load and initialize + // even if the process cannot access that subsystem. + }; + + // Increments the reference count of this object. The reference count must + // be incremented if this interface is given to another component. + virtual void AddRef() = 0; + + // Decrements the reference count of this object. When the reference count + // is zero the object is automatically destroyed. + // Indicates that the caller is done with this interface. After calling + // release no other method should be called. + virtual void Release() = 0; + + // Sets the security level for the target process' two tokens. + // This setting is permanent and cannot be changed once the target process is + // spawned. + // initial: the security level for the initial token. This is the token that + // is used by the process from the creation of the process until the moment + // the process calls TargetServices::LowerToken() or the process calls + // win32's ReverToSelf(). Once this happens the initial token is no longer + // available and the lockdown token is in effect. Using an initial token is + // not compatible with AppContainer, see SetAppContainer. + // lockdown: the security level for the token that comes into force after the + // process calls TargetServices::LowerToken() or the process calls + // ReverToSelf(). See the explanation of each level in the TokenLevel + // definition. + // Return value: SBOX_ALL_OK if the setting succeeds and false otherwise. + // Returns false if the lockdown value is more permissive than the initial + // value. + // + // Important: most of the sandbox-provided security relies on this single + // setting. The caller should strive to set the lockdown level as restricted + // as possible. + virtual ResultCode SetTokenLevel(TokenLevel initial, TokenLevel lockdown) = 0; + + // Returns the initial token level. + virtual TokenLevel GetInitialTokenLevel() const = 0; + + // Returns the lockdown token level. + virtual TokenLevel GetLockdownTokenLevel() const = 0; + + // Sets the security level of the Job Object to which the target process will + // belong. This setting is permanent and cannot be changed once the target + // process is spawned. The job controls the global security settings which + // can not be specified in the token security profile. + // job_level: the security level for the job. See the explanation of each + // level in the JobLevel definition. + // ui_exceptions: specify what specific rights that are disabled in the + // chosen job_level that need to be granted. Use this parameter to avoid + // selecting the next permissive job level unless you need all the rights + // that are granted in such level. + // The exceptions can be specified as a combination of the following + // constants: + // JOB_OBJECT_UILIMIT_HANDLES : grant access to all user-mode handles. These + // include windows, icons, menus and various GDI objects. In addition the + // target process can set hooks, and broadcast messages to other processes + // that belong to the same desktop. + // JOB_OBJECT_UILIMIT_READCLIPBOARD : grant read-only access to the clipboard. + // JOB_OBJECT_UILIMIT_WRITECLIPBOARD : grant write access to the clipboard. + // JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS : allow changes to the system-wide + // parameters as defined by the Win32 call SystemParametersInfo(). + // JOB_OBJECT_UILIMIT_DISPLAYSETTINGS : allow programmatic changes to the + // display settings. + // JOB_OBJECT_UILIMIT_GLOBALATOMS : allow access to the global atoms table. + // JOB_OBJECT_UILIMIT_DESKTOP : allow the creation of new desktops. + // JOB_OBJECT_UILIMIT_EXITWINDOWS : allow the call to ExitWindows(). + // + // Return value: SBOX_ALL_OK if the setting succeeds and false otherwise. + // + // Note: JOB_OBJECT_XXXX constants are defined in winnt.h and documented at + // length in: + // http://msdn2.microsoft.com/en-us/library/ms684152.aspx + // + // Note: the recommended level is JOB_RESTRICTED or JOB_LOCKDOWN. + virtual ResultCode SetJobLevel(JobLevel job_level, uint32 ui_exceptions) = 0; + + // Sets a hard limit on the size of the commit set for the sandboxed process. + // If the limit is reached, the process will be terminated with + // SBOX_FATAL_MEMORY_EXCEEDED (7012). + virtual ResultCode SetJobMemoryLimit(size_t memory_limit) = 0; + + // Specifies the desktop on which the application is going to run. If the + // desktop does not exist, it will be created. If alternate_winstation is + // set to true, the desktop will be created on an alternate window station. + virtual ResultCode SetAlternateDesktop(bool alternate_winstation) = 0; + + // Returns the name of the alternate desktop used. If an alternate window + // station is specified, the name is prepended by the window station name, + // followed by a backslash. + virtual base::string16 GetAlternateDesktop() const = 0; + + // Precreates the desktop and window station, if any. + virtual ResultCode CreateAlternateDesktop(bool alternate_winstation) = 0; + + // Destroys the desktop and windows station. + virtual void DestroyAlternateDesktop() = 0; + + // Sets the integrity level of the process in the sandbox. Both the initial + // token and the main token will be affected by this. If the integrity level + // is set to a level higher than the current level, the sandbox will fail + // to start. + virtual ResultCode SetIntegrityLevel(IntegrityLevel level) = 0; + + // Returns the initial integrity level used. + virtual IntegrityLevel GetIntegrityLevel() const = 0; + + // Sets the integrity level of the process in the sandbox. The integrity level + // will not take effect before you call LowerToken. User Interface Privilege + // Isolation is not affected by this setting and will remain off for the + // process in the sandbox. If the integrity level is set to a level higher + // than the current level, the sandbox will fail to start. + virtual ResultCode SetDelayedIntegrityLevel(IntegrityLevel level) = 0; + + // Sets the AppContainer to be used for the sandboxed process. Any capability + // to be enabled for the process should be added before this method is invoked + // (by calling SetCapability() as many times as needed). + // The desired AppContainer must be already installed on the system, otherwise + // launching the sandboxed process will fail. See BrokerServices for details + // about installing an AppContainer. + // Note that currently Windows restricts the use of impersonation within + // AppContainers, so this function is incompatible with the use of an initial + // token. + virtual ResultCode SetAppContainer(const wchar_t* sid) = 0; + + // Sets a capability to be enabled for the sandboxed process' AppContainer. + virtual ResultCode SetCapability(const wchar_t* sid) = 0; + + // Sets the LowBox token for sandboxed process. This is mutually exclusive + // with SetAppContainer method. + virtual ResultCode SetLowBox(const wchar_t* sid) = 0; + + // Sets the mitigations enabled when the process is created. Most of these + // are implemented as attributes passed via STARTUPINFOEX. So they take + // effect before any thread in the target executes. The declaration of + // MitigationFlags is followed by a detailed description of each flag. + virtual ResultCode SetProcessMitigations(MitigationFlags flags) = 0; + + // Returns the currently set mitigation flags. + virtual MitigationFlags GetProcessMitigations() = 0; + + // Sets process mitigation flags that don't take effect before the call to + // LowerToken(). + virtual ResultCode SetDelayedProcessMitigations(MitigationFlags flags) = 0; + + // Returns the currently set delayed mitigation flags. + virtual MitigationFlags GetDelayedProcessMitigations() const = 0; + + // Sets the interceptions to operate in strict mode. By default, interceptions + // are performed in "relaxed" mode, where if something inside NTDLL.DLL is + // already patched we attempt to intercept it anyway. Setting interceptions + // to strict mode means that when we detect that the function is patched we'll + // refuse to perform the interception. + virtual void SetStrictInterceptions() = 0; + + // Set the handles the target process should inherit for stdout and + // stderr. The handles the caller passes must remain valid for the + // lifetime of the policy object. This only has an effect on + // Windows Vista and later versions. These methods accept pipe and + // file handles, but not console handles. + virtual ResultCode SetStdoutHandle(HANDLE handle) = 0; + virtual ResultCode SetStderrHandle(HANDLE handle) = 0; + + // Adds a policy rule effective for processes spawned using this policy. + // subsystem: One of the above enumerated windows subsystems. + // semantics: One of the above enumerated FileSemantics. + // pattern: A specific full path or a full path with wildcard patterns. + // The valid wildcards are: + // '*' : Matches zero or more character. Only one in series allowed. + // '?' : Matches a single character. One or more in series are allowed. + // Examples: + // "c:\\documents and settings\\vince\\*.dmp" + // "c:\\documents and settings\\*\\crashdumps\\*.dmp" + // "c:\\temp\\app_log_?????_chrome.txt" + virtual ResultCode AddRule(SubSystem subsystem, Semantics semantics, + const wchar_t* pattern) = 0; + + // Adds a dll that will be unloaded in the target process before it gets + // a chance to initialize itself. Typically, dlls that cause the target + // to crash go here. + virtual ResultCode AddDllToUnload(const wchar_t* dll_name) = 0; + + // Adds a handle that will be closed in the target process after lockdown. + // A NULL value for handle_name indicates all handles of the specified type. + // An empty string for handle_name indicates the handle is unnamed. + virtual ResultCode AddKernelObjectToClose(const wchar_t* handle_type, + const wchar_t* handle_name) = 0; + + // Adds a handle that will be shared with the target process. + // Returns the handle which was actually shared with the target. This is + // achieved by duplicating the handle to ensure that it is inheritable by + // the target. The caller should treat this as an opaque value. + virtual void* AddHandleToShare(HANDLE handle) = 0; +}; + +} // namespace sandbox + + +#endif // SANDBOX_WIN_SRC_SANDBOX_POLICY_H_ diff --git a/sandbox/win/src/sandbox_policy_base.cc b/sandbox/win/src/sandbox_policy_base.cc new file mode 100644 index 0000000000..07a7d09581 --- /dev/null +++ b/sandbox/win/src/sandbox_policy_base.cc @@ -0,0 +1,887 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sandbox_policy_base.h" + +#include <sddl.h> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/app_container.h" +#include "sandbox/win/src/filesystem_dispatcher.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/handle_dispatcher.h" +#include "sandbox/win/src/handle_policy.h" +#include "sandbox/win/src/job.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/named_pipe_dispatcher.h" +#include "sandbox/win/src/named_pipe_policy.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/process_mitigations_win32k_dispatcher.h" +#include "sandbox/win/src/process_mitigations_win32k_policy.h" +#include "sandbox/win/src/process_thread_dispatcher.h" +#include "sandbox/win/src/process_thread_policy.h" +#include "sandbox/win/src/registry_dispatcher.h" +#include "sandbox/win/src/registry_policy.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/sync_dispatcher.h" +#include "sandbox/win/src/sync_policy.h" +#include "sandbox/win/src/target_process.h" +#include "sandbox/win/src/window.h" + +namespace { + +// The standard windows size for one memory page. +const size_t kOneMemPage = 4096; +// The IPC and Policy shared memory sizes. +const size_t kIPCMemSize = kOneMemPage * 2; +const size_t kPolMemSize = kOneMemPage * 14; + +// Helper function to allocate space (on the heap) for policy. +sandbox::PolicyGlobal* MakeBrokerPolicyMemory() { + const size_t kTotalPolicySz = kPolMemSize; + sandbox::PolicyGlobal* policy = static_cast<sandbox::PolicyGlobal*> + (::operator new(kTotalPolicySz)); + DCHECK(policy); + memset(policy, 0, kTotalPolicySz); + policy->data_size = kTotalPolicySz - sizeof(sandbox::PolicyGlobal); + return policy; +} + +bool IsInheritableHandle(HANDLE handle) { + if (!handle) + return false; + if (handle == INVALID_HANDLE_VALUE) + return false; + // File handles (FILE_TYPE_DISK) and pipe handles are known to be + // inheritable. Console handles (FILE_TYPE_CHAR) are not + // inheritable via PROC_THREAD_ATTRIBUTE_HANDLE_LIST. + DWORD handle_type = GetFileType(handle); + return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE; +} + +HANDLE CreateLowBoxObjectDirectory(PSID lowbox_sid) { + DWORD session_id = 0; + if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) + return NULL; + + LPWSTR sid_string = NULL; + if (!::ConvertSidToStringSid(lowbox_sid, &sid_string)) + return NULL; + + base::string16 directory_path = base::StringPrintf( + L"\\Sessions\\%d\\AppContainerNamedObjects\\%ls", + session_id, sid_string).c_str(); + ::LocalFree(sid_string); + + NtCreateDirectoryObjectFunction CreateObjectDirectory = NULL; + ResolveNTFunctionPtr("NtCreateDirectoryObject", &CreateObjectDirectory); + + OBJECT_ATTRIBUTES obj_attr; + UNICODE_STRING obj_name; + sandbox::InitObjectAttribs(directory_path, + OBJ_CASE_INSENSITIVE | OBJ_OPENIF, + NULL, + &obj_attr, + &obj_name, + NULL); + + HANDLE handle = NULL; + NTSTATUS status = CreateObjectDirectory(&handle, + DIRECTORY_ALL_ACCESS, + &obj_attr); + + if (!NT_SUCCESS(status)) + return NULL; + + return handle; +} + +} + +namespace sandbox { + +SANDBOX_INTERCEPT IntegrityLevel g_shared_delayed_integrity_level; +SANDBOX_INTERCEPT MitigationFlags g_shared_delayed_mitigations; + +// Initializes static members. +HWINSTA PolicyBase::alternate_winstation_handle_ = NULL; +HDESK PolicyBase::alternate_desktop_handle_ = NULL; +IntegrityLevel PolicyBase::alternate_desktop_integrity_level_label_ = + INTEGRITY_LEVEL_SYSTEM; + +PolicyBase::PolicyBase() + : ref_count(1), + lockdown_level_(USER_LOCKDOWN), + initial_level_(USER_LOCKDOWN), + job_level_(JOB_LOCKDOWN), + ui_exceptions_(0), + memory_limit_(0), + use_alternate_desktop_(false), + use_alternate_winstation_(false), + file_system_init_(false), + relaxed_interceptions_(true), + stdout_handle_(INVALID_HANDLE_VALUE), + stderr_handle_(INVALID_HANDLE_VALUE), + integrity_level_(INTEGRITY_LEVEL_LAST), + delayed_integrity_level_(INTEGRITY_LEVEL_LAST), + mitigations_(0), + delayed_mitigations_(0), + policy_maker_(NULL), + policy_(NULL), + lowbox_sid_(NULL) { + ::InitializeCriticalSection(&lock_); + // Initialize the IPC dispatcher array. + memset(&ipc_targets_, NULL, sizeof(ipc_targets_)); + Dispatcher* dispatcher = NULL; + + dispatcher = new FilesystemDispatcher(this); + ipc_targets_[IPC_NTCREATEFILE_TAG] = dispatcher; + ipc_targets_[IPC_NTOPENFILE_TAG] = dispatcher; + ipc_targets_[IPC_NTSETINFO_RENAME_TAG] = dispatcher; + ipc_targets_[IPC_NTQUERYATTRIBUTESFILE_TAG] = dispatcher; + ipc_targets_[IPC_NTQUERYFULLATTRIBUTESFILE_TAG] = dispatcher; + + dispatcher = new NamedPipeDispatcher(this); + ipc_targets_[IPC_CREATENAMEDPIPEW_TAG] = dispatcher; + + dispatcher = new ThreadProcessDispatcher(this); + ipc_targets_[IPC_NTOPENTHREAD_TAG] = dispatcher; + ipc_targets_[IPC_NTOPENPROCESS_TAG] = dispatcher; + ipc_targets_[IPC_CREATEPROCESSW_TAG] = dispatcher; + ipc_targets_[IPC_NTOPENPROCESSTOKEN_TAG] = dispatcher; + ipc_targets_[IPC_NTOPENPROCESSTOKENEX_TAG] = dispatcher; + + dispatcher = new SyncDispatcher(this); + ipc_targets_[IPC_CREATEEVENT_TAG] = dispatcher; + ipc_targets_[IPC_OPENEVENT_TAG] = dispatcher; + + dispatcher = new RegistryDispatcher(this); + ipc_targets_[IPC_NTCREATEKEY_TAG] = dispatcher; + ipc_targets_[IPC_NTOPENKEY_TAG] = dispatcher; + + dispatcher = new HandleDispatcher(this); + ipc_targets_[IPC_DUPLICATEHANDLEPROXY_TAG] = dispatcher; + + dispatcher = new ProcessMitigationsWin32KDispatcher(this); + ipc_targets_[IPC_GDI_GDIDLLINITIALIZE_TAG] = dispatcher; + ipc_targets_[IPC_GDI_GETSTOCKOBJECT_TAG] = dispatcher; + ipc_targets_[IPC_USER_REGISTERCLASSW_TAG] = dispatcher; +} + +PolicyBase::~PolicyBase() { + ClearSharedHandles(); + + TargetSet::iterator it; + for (it = targets_.begin(); it != targets_.end(); ++it) { + TargetProcess* target = (*it); + delete target; + } + delete ipc_targets_[IPC_NTCREATEFILE_TAG]; + delete ipc_targets_[IPC_CREATENAMEDPIPEW_TAG]; + delete ipc_targets_[IPC_NTOPENTHREAD_TAG]; + delete ipc_targets_[IPC_CREATEEVENT_TAG]; + delete ipc_targets_[IPC_NTCREATEKEY_TAG]; + delete ipc_targets_[IPC_DUPLICATEHANDLEPROXY_TAG]; + delete policy_maker_; + delete policy_; + + if (lowbox_sid_) + ::LocalFree(lowbox_sid_); + + ::DeleteCriticalSection(&lock_); +} + +void PolicyBase::AddRef() { + ::InterlockedIncrement(&ref_count); +} + +void PolicyBase::Release() { + if (0 == ::InterlockedDecrement(&ref_count)) + delete this; +} + +ResultCode PolicyBase::SetTokenLevel(TokenLevel initial, TokenLevel lockdown) { + if (initial < lockdown) { + return SBOX_ERROR_BAD_PARAMS; + } + initial_level_ = initial; + lockdown_level_ = lockdown; + return SBOX_ALL_OK; +} + +TokenLevel PolicyBase::GetInitialTokenLevel() const { + return initial_level_; +} + +TokenLevel PolicyBase::GetLockdownTokenLevel() const{ + return lockdown_level_; +} + +ResultCode PolicyBase::SetJobLevel(JobLevel job_level, uint32 ui_exceptions) { + if (memory_limit_ && job_level == JOB_NONE) { + return SBOX_ERROR_BAD_PARAMS; + } + job_level_ = job_level; + ui_exceptions_ = ui_exceptions; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetJobMemoryLimit(size_t memory_limit) { + if (memory_limit && job_level_ == JOB_NONE) { + return SBOX_ERROR_BAD_PARAMS; + } + memory_limit_ = memory_limit; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetAlternateDesktop(bool alternate_winstation) { + use_alternate_desktop_ = true; + use_alternate_winstation_ = alternate_winstation; + return CreateAlternateDesktop(alternate_winstation); +} + +base::string16 PolicyBase::GetAlternateDesktop() const { + // No alternate desktop or winstation. Return an empty string. + if (!use_alternate_desktop_ && !use_alternate_winstation_) { + return base::string16(); + } + + // The desktop and winstation should have been created by now. + // If we hit this scenario, it means that the user ignored the failure + // during SetAlternateDesktop, so we ignore it here too. + if (use_alternate_desktop_ && !alternate_desktop_handle_) { + return base::string16(); + } + if (use_alternate_winstation_ && (!alternate_desktop_handle_ || + !alternate_winstation_handle_)) { + return base::string16(); + } + + return GetFullDesktopName(alternate_winstation_handle_, + alternate_desktop_handle_); +} + +ResultCode PolicyBase::CreateAlternateDesktop(bool alternate_winstation) { + if (alternate_winstation) { + // Previously called with alternate_winstation = false? + if (!alternate_winstation_handle_ && alternate_desktop_handle_) + return SBOX_ERROR_UNSUPPORTED; + + // Check if it's already created. + if (alternate_winstation_handle_ && alternate_desktop_handle_) + return SBOX_ALL_OK; + + DCHECK(!alternate_winstation_handle_); + // Create the window station. + ResultCode result = CreateAltWindowStation(&alternate_winstation_handle_); + if (SBOX_ALL_OK != result) + return result; + + // Verify that everything is fine. + if (!alternate_winstation_handle_ || + GetWindowObjectName(alternate_winstation_handle_).empty()) + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + + // Create the destkop. + result = CreateAltDesktop(alternate_winstation_handle_, + &alternate_desktop_handle_); + if (SBOX_ALL_OK != result) + return result; + + // Verify that everything is fine. + if (!alternate_desktop_handle_ || + GetWindowObjectName(alternate_desktop_handle_).empty()) + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + } else { + // Previously called with alternate_winstation = true? + if (alternate_winstation_handle_) + return SBOX_ERROR_UNSUPPORTED; + + // Check if it already exists. + if (alternate_desktop_handle_) + return SBOX_ALL_OK; + + // Create the destkop. + ResultCode result = CreateAltDesktop(NULL, &alternate_desktop_handle_); + if (SBOX_ALL_OK != result) + return result; + + // Verify that everything is fine. + if (!alternate_desktop_handle_ || + GetWindowObjectName(alternate_desktop_handle_).empty()) + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + } + + return SBOX_ALL_OK; +} + +void PolicyBase::DestroyAlternateDesktop() { + if (alternate_desktop_handle_) { + ::CloseDesktop(alternate_desktop_handle_); + alternate_desktop_handle_ = NULL; + } + + if (alternate_winstation_handle_) { + ::CloseWindowStation(alternate_winstation_handle_); + alternate_winstation_handle_ = NULL; + } +} + +ResultCode PolicyBase::SetIntegrityLevel(IntegrityLevel integrity_level) { + integrity_level_ = integrity_level; + return SBOX_ALL_OK; +} + +IntegrityLevel PolicyBase::GetIntegrityLevel() const { + return integrity_level_; +} + +ResultCode PolicyBase::SetDelayedIntegrityLevel( + IntegrityLevel integrity_level) { + delayed_integrity_level_ = integrity_level; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetAppContainer(const wchar_t* sid) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return SBOX_ALL_OK; + + // SetLowBox and SetAppContainer are mutually exclusive. + if (lowbox_sid_) + return SBOX_ERROR_UNSUPPORTED; + + // Windows refuses to work with an impersonation token for a process inside + // an AppContainer. If the caller wants to use a more privileged initial + // token, or if the lockdown level will prevent the process from starting, + // we have to fail the operation. + if (lockdown_level_ < USER_LIMITED || lockdown_level_ != initial_level_) + return SBOX_ERROR_CANNOT_INIT_APPCONTAINER; + + DCHECK(!appcontainer_list_.get()); + appcontainer_list_.reset(new AppContainerAttributes); + ResultCode rv = appcontainer_list_->SetAppContainer(sid, capabilities_); + if (rv != SBOX_ALL_OK) + return rv; + + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetCapability(const wchar_t* sid) { + capabilities_.push_back(sid); + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetLowBox(const wchar_t* sid) { + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + return SBOX_ERROR_UNSUPPORTED; + + // SetLowBox and SetAppContainer are mutually exclusive. + if (appcontainer_list_.get()) + return SBOX_ERROR_UNSUPPORTED; + + DCHECK(sid); + + if (lowbox_sid_) + return SBOX_ERROR_BAD_PARAMS; + + if (!ConvertStringSidToSid(sid, &lowbox_sid_)) + return SBOX_ERROR_GENERIC; + + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetProcessMitigations( + MitigationFlags flags) { + if (!CanSetProcessMitigationsPreStartup(flags)) + return SBOX_ERROR_BAD_PARAMS; + mitigations_ = flags; + return SBOX_ALL_OK; +} + +MitigationFlags PolicyBase::GetProcessMitigations() { + return mitigations_; +} + +ResultCode PolicyBase::SetDelayedProcessMitigations( + MitigationFlags flags) { + if (!CanSetProcessMitigationsPostStartup(flags)) + return SBOX_ERROR_BAD_PARAMS; + delayed_mitigations_ = flags; + return SBOX_ALL_OK; +} + +MitigationFlags PolicyBase::GetDelayedProcessMitigations() const { + return delayed_mitigations_; +} + +void PolicyBase::SetStrictInterceptions() { + relaxed_interceptions_ = false; +} + +ResultCode PolicyBase::SetStdoutHandle(HANDLE handle) { + if (!IsInheritableHandle(handle)) + return SBOX_ERROR_BAD_PARAMS; + stdout_handle_ = handle; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetStderrHandle(HANDLE handle) { + if (!IsInheritableHandle(handle)) + return SBOX_ERROR_BAD_PARAMS; + stderr_handle_ = handle; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::AddRule(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) { + ResultCode result = AddRuleInternal(subsystem, semantics, pattern); + LOG_IF(ERROR, result != SBOX_ALL_OK) << "Failed to add sandbox rule." + << " error = " << result + << ", subsystem = " << subsystem + << ", semantics = " << semantics + << ", pattern = '" << pattern << "'"; + return result; +} + +ResultCode PolicyBase::AddDllToUnload(const wchar_t* dll_name) { + blacklisted_dlls_.push_back(dll_name); + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::AddKernelObjectToClose(const base::char16* handle_type, + const base::char16* handle_name) { + return handle_closer_.AddHandle(handle_type, handle_name); +} + +void* PolicyBase::AddHandleToShare(HANDLE handle) { + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return NULL; + + if (!handle) + return NULL; + + HANDLE duped_handle = NULL; + ::DuplicateHandle(::GetCurrentProcess(), + handle, + ::GetCurrentProcess(), + &duped_handle, + 0, + TRUE, + DUPLICATE_SAME_ACCESS); + DCHECK(duped_handle); + handles_to_share_.push_back(duped_handle); + return duped_handle; +} + +HandleList PolicyBase::GetHandlesBeingShared() { + return handles_to_share_; +} + +void PolicyBase::ClearSharedHandles() { + for (auto handle : handles_to_share_) { + ::CloseHandle(handle); + } + handles_to_share_.clear(); +} + +// When an IPC is ready in any of the targets we get called. We manage an array +// of IPC dispatchers which are keyed on the IPC tag so we normally delegate +// to the appropriate dispatcher unless we can handle the IPC call ourselves. +Dispatcher* PolicyBase::OnMessageReady(IPCParams* ipc, + CallbackGeneric* callback) { + DCHECK(callback); + static const IPCParams ping1 = {IPC_PING1_TAG, UINT32_TYPE}; + static const IPCParams ping2 = {IPC_PING2_TAG, INOUTPTR_TYPE}; + + if (ping1.Matches(ipc) || ping2.Matches(ipc)) { + *callback = reinterpret_cast<CallbackGeneric>( + static_cast<Callback1>(&PolicyBase::Ping)); + return this; + } + + Dispatcher* dispatch = GetDispatcher(ipc->ipc_tag); + if (!dispatch) { + NOTREACHED(); + return NULL; + } + return dispatch->OnMessageReady(ipc, callback); +} + +// Delegate to the appropriate dispatcher. +bool PolicyBase::SetupService(InterceptionManager* manager, int service) { + if (IPC_PING1_TAG == service || IPC_PING2_TAG == service) + return true; + + Dispatcher* dispatch = GetDispatcher(service); + if (!dispatch) { + NOTREACHED(); + return false; + } + return dispatch->SetupService(manager, service); +} + +ResultCode PolicyBase::MakeJobObject(HANDLE* job) { + if (job_level_ != JOB_NONE) { + // Create the windows job object. + Job job_obj; + DWORD result = job_obj.Init(job_level_, NULL, ui_exceptions_, + memory_limit_); + if (ERROR_SUCCESS != result) { + return SBOX_ERROR_GENERIC; + } + *job = job_obj.Detach(); + } else { + *job = NULL; + } + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::MakeTokens(HANDLE* initial, HANDLE* lockdown) { + if (appcontainer_list_.get() && appcontainer_list_->HasAppContainer() && + lowbox_sid_) { + return SBOX_ERROR_BAD_PARAMS; + } + + // Create the 'naked' token. This will be the permanent token associated + // with the process and therefore with any thread that is not impersonating. + DWORD result = CreateRestrictedToken(lockdown, lockdown_level_, + integrity_level_, PRIMARY); + if (ERROR_SUCCESS != result) + return SBOX_ERROR_GENERIC; + + // If we're launching on the alternate desktop we need to make sure the + // integrity label on the object is no higher than the sandboxed process's + // integrity level. So, we lower the label on the desktop process if it's + // not already low enough for our process. + if (alternate_desktop_handle_ && use_alternate_desktop_ && + integrity_level_ != INTEGRITY_LEVEL_LAST && + alternate_desktop_integrity_level_label_ < integrity_level_ && + base::win::OSInfo::GetInstance()->version() >= base::win::VERSION_VISTA) { + // Integrity label enum is reversed (higher level is a lower value). + static_assert(INTEGRITY_LEVEL_SYSTEM < INTEGRITY_LEVEL_UNTRUSTED, + "Integrity level ordering reversed."); + result = SetObjectIntegrityLabel(alternate_desktop_handle_, + SE_WINDOW_OBJECT, + L"", + GetIntegrityLevelString(integrity_level_)); + if (ERROR_SUCCESS != result) + return SBOX_ERROR_GENERIC; + + alternate_desktop_integrity_level_label_ = integrity_level_; + } + + // We are maintaining two mutually exclusive approaches. One is to start an + // AppContainer process through StartupInfoEx and other is replacing + // existing token with LowBox token after process creation. + if (appcontainer_list_.get() && appcontainer_list_->HasAppContainer()) { + // Windows refuses to work with an impersonation token. See SetAppContainer + // implementation for more details. + if (lockdown_level_ < USER_LIMITED || lockdown_level_ != initial_level_) + return SBOX_ERROR_CANNOT_INIT_APPCONTAINER; + + *initial = INVALID_HANDLE_VALUE; + return SBOX_ALL_OK; + } else if (lowbox_sid_) { + NtCreateLowBoxToken CreateLowBoxToken = NULL; + ResolveNTFunctionPtr("NtCreateLowBoxToken", &CreateLowBoxToken); + OBJECT_ATTRIBUTES obj_attr; + InitializeObjectAttributes(&obj_attr, NULL, 0, NULL, NULL); + HANDLE token_lowbox = NULL; + + if (!lowbox_directory_.IsValid()) + lowbox_directory_.Set(CreateLowBoxObjectDirectory(lowbox_sid_)); + DCHECK(lowbox_directory_.IsValid()); + + // The order of handles isn't important in the CreateLowBoxToken call. + // The kernel will maintain a reference to the object directory handle. + HANDLE saved_handles[1] = {lowbox_directory_.Get()}; + DWORD saved_handles_count = lowbox_directory_.IsValid() ? 1 : 0; + + NTSTATUS status = CreateLowBoxToken(&token_lowbox, *lockdown, + TOKEN_ALL_ACCESS, &obj_attr, + lowbox_sid_, 0, NULL, + saved_handles_count, saved_handles); + if (!NT_SUCCESS(status)) + return SBOX_ERROR_GENERIC; + + DCHECK(token_lowbox); + ::CloseHandle(*lockdown); + *lockdown = token_lowbox; + } + + // Create the 'better' token. We use this token as the one that the main + // thread uses when booting up the process. It should contain most of + // what we need (before reaching main( )) + result = CreateRestrictedToken(initial, initial_level_, + integrity_level_, IMPERSONATION); + if (ERROR_SUCCESS != result) { + ::CloseHandle(*lockdown); + return SBOX_ERROR_GENERIC; + } + return SBOX_ALL_OK; +} + +const AppContainerAttributes* PolicyBase::GetAppContainer() const { + if (!appcontainer_list_.get() || !appcontainer_list_->HasAppContainer()) + return NULL; + + return appcontainer_list_.get(); +} + +const PSID PolicyBase::GetLowBoxSid() const { + return lowbox_sid_; +} + +bool PolicyBase::AddTarget(TargetProcess* target) { + if (NULL != policy_) + policy_maker_->Done(); + + if (!ApplyProcessMitigationsToSuspendedProcess(target->Process(), + mitigations_)) { + return false; + } + + if (!SetupAllInterceptions(target)) + return false; + + if (!SetupHandleCloser(target)) + return false; + + // Initialize the sandbox infrastructure for the target. + if (ERROR_SUCCESS != target->Init(this, policy_, kIPCMemSize, kPolMemSize)) + return false; + + g_shared_delayed_integrity_level = delayed_integrity_level_; + ResultCode ret = target->TransferVariable( + "g_shared_delayed_integrity_level", + &g_shared_delayed_integrity_level, + sizeof(g_shared_delayed_integrity_level)); + g_shared_delayed_integrity_level = INTEGRITY_LEVEL_LAST; + if (SBOX_ALL_OK != ret) + return false; + + // Add in delayed mitigations and pseudo-mitigations enforced at startup. + g_shared_delayed_mitigations = delayed_mitigations_ | + FilterPostStartupProcessMitigations(mitigations_); + if (!CanSetProcessMitigationsPostStartup(g_shared_delayed_mitigations)) + return false; + + ret = target->TransferVariable("g_shared_delayed_mitigations", + &g_shared_delayed_mitigations, + sizeof(g_shared_delayed_mitigations)); + g_shared_delayed_mitigations = 0; + if (SBOX_ALL_OK != ret) + return false; + + AutoLock lock(&lock_); + targets_.push_back(target); + return true; +} + +bool PolicyBase::OnJobEmpty(HANDLE job) { + AutoLock lock(&lock_); + TargetSet::iterator it; + for (it = targets_.begin(); it != targets_.end(); ++it) { + if ((*it)->Job() == job) + break; + } + if (it == targets_.end()) { + return false; + } + TargetProcess* target = *it; + targets_.erase(it); + delete target; + return true; +} + +EvalResult PolicyBase::EvalPolicy(int service, + CountedParameterSetBase* params) { + if (NULL != policy_) { + if (NULL == policy_->entry[service]) { + // There is no policy for this particular service. This is not a big + // deal. + return DENY_ACCESS; + } + for (int i = 0; i < params->count; i++) { + if (!params->parameters[i].IsValid()) { + NOTREACHED(); + return SIGNAL_ALARM; + } + } + PolicyProcessor pol_evaluator(policy_->entry[service]); + PolicyResult result = pol_evaluator.Evaluate(kShortEval, + params->parameters, + params->count); + if (POLICY_MATCH == result) { + return pol_evaluator.GetAction(); + } + DCHECK(POLICY_ERROR != result); + } + + return DENY_ACCESS; +} + +HANDLE PolicyBase::GetStdoutHandle() { + return stdout_handle_; +} + +HANDLE PolicyBase::GetStderrHandle() { + return stderr_handle_; +} + +// We service IPC_PING_TAG message which is a way to test a round trip of the +// IPC subsystem. We receive a integer cookie and we are expected to return the +// cookie times two (or three) and the current tick count. +bool PolicyBase::Ping(IPCInfo* ipc, void* arg1) { + switch (ipc->ipc_tag) { + case IPC_PING1_TAG: { + IPCInt ipc_int(arg1); + uint32 cookie = ipc_int.As32Bit(); + ipc->return_info.extended_count = 2; + ipc->return_info.extended[0].unsigned_int = ::GetTickCount(); + ipc->return_info.extended[1].unsigned_int = 2 * cookie; + return true; + } + case IPC_PING2_TAG: { + CountedBuffer* io_buffer = reinterpret_cast<CountedBuffer*>(arg1); + if (sizeof(uint32) != io_buffer->Size()) + return false; + + uint32* cookie = reinterpret_cast<uint32*>(io_buffer->Buffer()); + *cookie = (*cookie) * 3; + return true; + } + default: return false; + } +} + +Dispatcher* PolicyBase::GetDispatcher(int ipc_tag) { + if (ipc_tag >= IPC_LAST_TAG || ipc_tag <= IPC_UNUSED_TAG) + return NULL; + + return ipc_targets_[ipc_tag]; +} + +bool PolicyBase::SetupAllInterceptions(TargetProcess* target) { + InterceptionManager manager(target, relaxed_interceptions_); + + if (policy_) { + for (int i = 0; i < IPC_LAST_TAG; i++) { + if (policy_->entry[i] && !ipc_targets_[i]->SetupService(&manager, i)) + return false; + } + } + + if (!blacklisted_dlls_.empty()) { + std::vector<base::string16>::iterator it = blacklisted_dlls_.begin(); + for (; it != blacklisted_dlls_.end(); ++it) { + manager.AddToUnloadModules(it->c_str()); + } + } + + if (!SetupBasicInterceptions(&manager)) + return false; + + if (!manager.InitializeInterceptions()) + return false; + + // Finally, setup imports on the target so the interceptions can work. + return SetupNtdllImports(target); +} + +bool PolicyBase::SetupHandleCloser(TargetProcess* target) { + return handle_closer_.InitializeTargetHandles(target); +} + +ResultCode PolicyBase::AddRuleInternal(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) { + if (NULL == policy_) { + policy_ = MakeBrokerPolicyMemory(); + DCHECK(policy_); + policy_maker_ = new LowLevelPolicy(policy_); + DCHECK(policy_maker_); + } + + switch (subsystem) { + case SUBSYS_FILES: { + if (!file_system_init_) { + if (!FileSystemPolicy::SetInitialRules(policy_maker_)) + return SBOX_ERROR_BAD_PARAMS; + file_system_init_ = true; + } + if (!FileSystemPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_SYNC: { + if (!SyncPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_PROCESS: { + if (lockdown_level_ < USER_INTERACTIVE && + TargetPolicy::PROCESS_ALL_EXEC == semantics) { + // This is unsupported. This is a huge security risk to give full access + // to a process handle. + return SBOX_ERROR_UNSUPPORTED; + } + if (!ProcessPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_NAMED_PIPES: { + if (!NamedPipePolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_REGISTRY: { + if (!RegistryPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_HANDLES: { + if (!HandlePolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + + case SUBSYS_WIN32K_LOCKDOWN: { + if (!ProcessMitigationsWin32KLockdownPolicy::GenerateRules( + pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + + default: { return SBOX_ERROR_UNSUPPORTED; } + } + + return SBOX_ALL_OK; +} + +} // namespace sandbox diff --git a/sandbox/win/src/sandbox_policy_base.h b/sandbox/win/src/sandbox_policy_base.h new file mode 100644 index 0000000000..1de5cf8c36 --- /dev/null +++ b/sandbox/win/src/sandbox_policy_base.h @@ -0,0 +1,187 @@ +// Copyright (c) 2011 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_WIN_SRC_SANDBOX_POLICY_BASE_H_ +#define SANDBOX_WIN_SRC_SANDBOX_POLICY_BASE_H_ + +#include <windows.h> + +#include <list> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/handle_closer.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +class AppContainerAttributes; +class LowLevelPolicy; +class TargetProcess; +struct PolicyGlobal; + +typedef std::vector<HANDLE> HandleList; + +// We act as a policy dispatcher, implementing the handler for the "ping" IPC, +// so we have to provide the appropriate handler on the OnMessageReady method. +// There is a static_cast for the handler, and the compiler only performs the +// cast if the first base class is Dispatcher. +class PolicyBase : public Dispatcher, public TargetPolicy { + public: + PolicyBase(); + + // TargetPolicy: + void AddRef() override; + void Release() override; + ResultCode SetTokenLevel(TokenLevel initial, TokenLevel lockdown) override; + TokenLevel GetInitialTokenLevel() const override; + TokenLevel GetLockdownTokenLevel() const override; + ResultCode SetJobLevel(JobLevel job_level, uint32 ui_exceptions) override; + ResultCode SetJobMemoryLimit(size_t memory_limit) override; + ResultCode SetAlternateDesktop(bool alternate_winstation) override; + base::string16 GetAlternateDesktop() const override; + ResultCode CreateAlternateDesktop(bool alternate_winstation) override; + void DestroyAlternateDesktop() override; + ResultCode SetIntegrityLevel(IntegrityLevel integrity_level) override; + IntegrityLevel GetIntegrityLevel() const override; + ResultCode SetDelayedIntegrityLevel(IntegrityLevel integrity_level) override; + ResultCode SetAppContainer(const wchar_t* sid) override; + ResultCode SetCapability(const wchar_t* sid) override; + ResultCode SetLowBox(const wchar_t* sid) override; + ResultCode SetProcessMitigations(MitigationFlags flags) override; + MitigationFlags GetProcessMitigations() override; + ResultCode SetDelayedProcessMitigations(MitigationFlags flags) override; + MitigationFlags GetDelayedProcessMitigations() const override; + void SetStrictInterceptions() override; + ResultCode SetStdoutHandle(HANDLE handle) override; + ResultCode SetStderrHandle(HANDLE handle) override; + ResultCode AddRule(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) override; + ResultCode AddDllToUnload(const wchar_t* dll_name) override; + ResultCode AddKernelObjectToClose(const base::char16* handle_type, + const base::char16* handle_name) override; + void* AddHandleToShare(HANDLE handle) override; + + // Dispatcher: + Dispatcher* OnMessageReady(IPCParams* ipc, + CallbackGeneric* callback) override; + bool SetupService(InterceptionManager* manager, int service) override; + + // Creates a Job object with the level specified in a previous call to + // SetJobLevel(). + ResultCode MakeJobObject(HANDLE* job); + + // Creates the two tokens with the levels specified in a previous call to + // SetTokenLevel(). + ResultCode MakeTokens(HANDLE* initial, HANDLE* lockdown); + + const AppContainerAttributes* GetAppContainer() const; + + const PSID GetLowBoxSid() const; + + // Adds a target process to the internal list of targets. Internally a + // call to TargetProcess::Init() is issued. + bool AddTarget(TargetProcess* target); + + // Called when there are no more active processes in a Job. + // Removes a Job object associated with this policy and the target associated + // with the job. + bool OnJobEmpty(HANDLE job); + + EvalResult EvalPolicy(int service, CountedParameterSetBase* params); + + HANDLE GetStdoutHandle(); + HANDLE GetStderrHandle(); + + // Returns the list of handles being shared with the target process. + HandleList GetHandlesBeingShared(); + + // Closes the handles being shared with the target and clears out the list. + void ClearSharedHandles(); + + private: + ~PolicyBase() override; + + // Test IPC providers. + bool Ping(IPCInfo* ipc, void* cookie); + + // Returns a dispatcher from ipc_targets_. + Dispatcher* GetDispatcher(int ipc_tag); + + // Sets up interceptions for a new target. + bool SetupAllInterceptions(TargetProcess* target); + + // Sets up the handle closer for a new target. + bool SetupHandleCloser(TargetProcess* target); + + ResultCode AddRuleInternal(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern); + + // This lock synchronizes operations on the targets_ collection. + CRITICAL_SECTION lock_; + // Maintains the list of target process associated with this policy. + // The policy takes ownership of them. + typedef std::list<TargetProcess*> TargetSet; + TargetSet targets_; + // Standard object-lifetime reference counter. + volatile LONG ref_count; + // The user-defined global policy settings. + TokenLevel lockdown_level_; + TokenLevel initial_level_; + JobLevel job_level_; + uint32 ui_exceptions_; + size_t memory_limit_; + bool use_alternate_desktop_; + bool use_alternate_winstation_; + // Helps the file system policy initialization. + bool file_system_init_; + bool relaxed_interceptions_; + HANDLE stdout_handle_; + HANDLE stderr_handle_; + IntegrityLevel integrity_level_; + IntegrityLevel delayed_integrity_level_; + MitigationFlags mitigations_; + MitigationFlags delayed_mitigations_; + // The array of objects that will answer IPC calls. + Dispatcher* ipc_targets_[IPC_LAST_TAG]; + // Object in charge of generating the low level policy. + LowLevelPolicy* policy_maker_; + // Memory structure that stores the low level policy. + PolicyGlobal* policy_; + // The list of dlls to unload in the target process. + std::vector<base::string16> blacklisted_dlls_; + // This is a map of handle-types to names that we need to close in the + // target process. A null set means we need to close all handles of the + // given type. + HandleCloser handle_closer_; + std::vector<base::string16> capabilities_; + scoped_ptr<AppContainerAttributes> appcontainer_list_; + PSID lowbox_sid_; + base::win::ScopedHandle lowbox_directory_; + + static HDESK alternate_desktop_handle_; + static HWINSTA alternate_winstation_handle_; + static IntegrityLevel alternate_desktop_integrity_level_label_; + + // Contains the list of handles being shared with the target process. + // This list contains handles other than the stderr/stdout handles which are + // shared with the target at times. + std::vector<HANDLE> handles_to_share_; + + DISALLOW_COPY_AND_ASSIGN(PolicyBase); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SANDBOX_POLICY_BASE_H_ diff --git a/sandbox/win/src/sandbox_types.h b/sandbox/win/src/sandbox_types.h new file mode 100644 index 0000000000..3e531be4f4 --- /dev/null +++ b/sandbox/win/src/sandbox_types.h @@ -0,0 +1,94 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_TYPES_H_ +#define SANDBOX_WIN_SRC_SANDBOX_TYPES_H_ + +namespace sandbox { + +// Operation result codes returned by the sandbox API. +enum ResultCode { + SBOX_ALL_OK = 0, + // Error is originating on the win32 layer. Call GetlastError() for more + // information. + SBOX_ERROR_GENERIC = 1, + // An invalid combination of parameters was given to the API. + SBOX_ERROR_BAD_PARAMS = 2, + // The desired operation is not supported at this time. + SBOX_ERROR_UNSUPPORTED = 3, + // The request requires more memory that allocated or available. + SBOX_ERROR_NO_SPACE = 4, + // The ipc service requested does not exist. + SBOX_ERROR_INVALID_IPC = 5, + // The ipc service did not complete. + SBOX_ERROR_FAILED_IPC = 6, + // The requested handle was not found. + SBOX_ERROR_NO_HANDLE = 7, + // This function was not expected to be called at this time. + SBOX_ERROR_UNEXPECTED_CALL = 8, + // WaitForAllTargets is already called. + SBOX_ERROR_WAIT_ALREADY_CALLED = 9, + // A channel error prevented DoCall from executing. + SBOX_ERROR_CHANNEL_ERROR = 10, + // Failed to create the alternate desktop. + SBOX_ERROR_CANNOT_CREATE_DESKTOP = 11, + // Failed to create the alternate window station. + SBOX_ERROR_CANNOT_CREATE_WINSTATION = 12, + // Failed to switch back to the interactive window station. + SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION = 13, + // The supplied AppContainer is not valid. + SBOX_ERROR_INVALID_APP_CONTAINER = 14, + // The supplied capability is not valid. + SBOX_ERROR_INVALID_CAPABILITY = 15, + // There is a failure initializing the AppContainer. + SBOX_ERROR_CANNOT_INIT_APPCONTAINER = 16, + // Initializing or updating ProcThreadAttributes failed. + SBOX_ERROR_PROC_THREAD_ATTRIBUTES = 17, + // Error in creating process. + SBOX_ERROR_CREATE_PROCESS = 18, + // Placeholder for last item of the enum. + SBOX_ERROR_LAST +}; + +// If the sandbox cannot create a secure environment for the target, the +// target will be forcibly terminated. These are the process exit codes. +enum TerminationCodes { + SBOX_FATAL_INTEGRITY = 7006, // Could not set the integrity level. + SBOX_FATAL_DROPTOKEN = 7007, // Could not lower the token. + SBOX_FATAL_FLUSHANDLES = 7008, // Failed to flush registry handles. + SBOX_FATAL_CACHEDISABLE = 7009, // Failed to forbid HCKU caching. + SBOX_FATAL_CLOSEHANDLES = 7010, // Failed to close pending handles. + SBOX_FATAL_MITIGATION = 7011, // Could not set the mitigation policy. + SBOX_FATAL_MEMORY_EXCEEDED = 7012, // Exceeded the job memory limit. + SBOX_FATAL_LAST +}; + +class BrokerServices; +class TargetServices; + +// Contains the pointer to a target or broker service. +struct SandboxInterfaceInfo { + BrokerServices* broker_services; + TargetServices* target_services; +}; + +#if SANDBOX_EXPORTS +#define SANDBOX_INTERCEPT extern "C" __declspec(dllexport) +#else +#define SANDBOX_INTERCEPT extern "C" +#endif + +enum InterceptionType { + INTERCEPTION_INVALID = 0, + INTERCEPTION_SERVICE_CALL, // Trampoline of an NT native call + INTERCEPTION_EAT, + INTERCEPTION_SIDESTEP, // Preamble patch + INTERCEPTION_SMART_SIDESTEP, // Preamble patch but bypass internal calls + INTERCEPTION_UNLOAD_MODULE, // Unload the module (don't patch) + INTERCEPTION_LAST // Placeholder for last item in the enumeration +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SANDBOX_TYPES_H_ diff --git a/sandbox/win/src/sandbox_utils.cc b/sandbox/win/src/sandbox_utils.cc new file mode 100644 index 0000000000..6057caffc1 --- /dev/null +++ b/sandbox/win/src/sandbox_utils.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sandbox_utils.h" + +#include <windows.h> + +#include "base/logging.h" +#include "sandbox/win/src/internal_types.h" + +namespace sandbox { + +void InitObjectAttribs(const base::string16& name, + ULONG attributes, + HANDLE root, + OBJECT_ATTRIBUTES* obj_attr, + UNICODE_STRING* uni_name, + SECURITY_QUALITY_OF_SERVICE* security_qos) { + static RtlInitUnicodeStringFunction RtlInitUnicodeString; + if (!RtlInitUnicodeString) { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + RtlInitUnicodeString = reinterpret_cast<RtlInitUnicodeStringFunction>( + GetProcAddress(ntdll, "RtlInitUnicodeString")); + DCHECK(RtlInitUnicodeString); + } + RtlInitUnicodeString(uni_name, name.c_str()); + InitializeObjectAttributes(obj_attr, uni_name, attributes, root, NULL); + obj_attr->SecurityQualityOfService = security_qos; +} + +} // namespace sandbox diff --git a/sandbox/win/src/sandbox_utils.h b/sandbox/win/src/sandbox_utils.h new file mode 100644 index 0000000000..fc3100dcb9 --- /dev/null +++ b/sandbox/win/src/sandbox_utils.h @@ -0,0 +1,26 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SANDBOX_UTILS_H_ +#define SANDBOX_SRC_SANDBOX_UTILS_H_ + +#include <windows.h> +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +void InitObjectAttribs(const base::string16& name, + ULONG attributes, + HANDLE root, + OBJECT_ATTRIBUTES* obj_attr, + UNICODE_STRING* uni_name, + SECURITY_QUALITY_OF_SERVICE* security_qos); + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_UTILS_H_ diff --git a/sandbox/win/src/security_level.h b/sandbox/win/src/security_level.h new file mode 100644 index 0000000000..c89bbb4e24 --- /dev/null +++ b/sandbox/win/src/security_level.h @@ -0,0 +1,209 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SECURITY_LEVEL_H_ +#define SANDBOX_SRC_SECURITY_LEVEL_H_ + +#include "base/basictypes.h" + +namespace sandbox { + +// List of all the integrity levels supported in the sandbox. This is used +// only on Windows Vista. You can't set the integrity level of the process +// in the sandbox to a level higher than yours. +enum IntegrityLevel { + INTEGRITY_LEVEL_SYSTEM, + INTEGRITY_LEVEL_HIGH, + INTEGRITY_LEVEL_MEDIUM, + INTEGRITY_LEVEL_MEDIUM_LOW, + INTEGRITY_LEVEL_LOW, + INTEGRITY_LEVEL_BELOW_LOW, + INTEGRITY_LEVEL_UNTRUSTED, + INTEGRITY_LEVEL_LAST +}; + +// The Token level specifies a set of security profiles designed to +// provide the bulk of the security of sandbox. +// +// TokenLevel |Restricting |Deny Only |Privileges| +// |Sids |Sids | | +// ----------------------------|--------------|----------------|----------| +// USER_LOCKDOWN | Null Sid | All | None | +// ----------------------------|--------------|----------------|----------| +// USER_RESTRICTED | RESTRICTED | All | Traverse | +// ----------------------------|--------------|----------------|----------| +// USER_LIMITED | Users | All except: | Traverse | +// | Everyone | Users | | +// | RESTRICTED | Everyone | | +// | | Interactive | | +// ----------------------------|--------------|----------------|----------| +// USER_INTERACTIVE | Users | All except: | Traverse | +// | Everyone | Users | | +// | RESTRICTED | Everyone | | +// | Owner | Interactive | | +// | | Local | | +// | | Authent-users | | +// | | User | | +// ----------------------------|--------------|----------------|----------| +// USER_NON_ADMIN | None | All except: | Traverse | +// | | Users | | +// | | Everyone | | +// | | Interactive | | +// | | Local | | +// | | Authent-users | | +// | | User | | +// ----------------------------|--------------|----------------|----------| +// USER_RESTRICTED_SAME_ACCESS | All | None | All | +// ----------------------------|--------------|----------------|----------| +// USER_UNPROTECTED | None | None | All | +// ----------------------------|--------------|----------------|----------| +// +// The above restrictions are actually a transformation that is applied to +// the existing broker process token. The resulting token that will be +// applied to the target process depends both on the token level selected +// and on the broker token itself. +// +// The LOCKDOWN and RESTRICTED are designed to allow access to almost +// nothing that has security associated with and they are the recommended +// levels to run sandboxed code specially if there is a chance that the +// broker is process might be started by a user that belongs to the Admins +// or power users groups. +enum TokenLevel { + USER_LOCKDOWN = 0, + USER_RESTRICTED, + USER_LIMITED, + USER_INTERACTIVE, + USER_NON_ADMIN, + USER_RESTRICTED_SAME_ACCESS, + USER_UNPROTECTED, + USER_LAST +}; + +// The Job level specifies a set of decreasing security profiles for the +// Job object that the target process will be placed into. +// This table summarizes the security associated with each level: +// +// JobLevel |General |Quota | +// |restrictions |restrictions | +// -----------------|---------------------------------- |--------------------| +// JOB_NONE | No job is assigned to the | None | +// | sandboxed process. | | +// -----------------|---------------------------------- |--------------------| +// JOB_UNPROTECTED | None | *Kill on Job close.| +// -----------------|---------------------------------- |--------------------| +// JOB_INTERACTIVE | *Forbid system-wide changes using | | +// | SystemParametersInfo(). | *Kill on Job close.| +// | *Forbid the creation/switch of | | +// | Desktops. | | +// | *Forbids calls to ExitWindows(). | | +// -----------------|---------------------------------- |--------------------| +// JOB_LIMITED_USER | Same as INTERACTIVE_USER plus: | *One active process| +// | *Forbid changes to the display | limit. | +// | settings. | *Kill on Job close.| +// -----------------|---------------------------------- |--------------------| +// JOB_RESTRICTED | Same as LIMITED_USER plus: | *One active process| +// | * No read/write to the clipboard. | limit. | +// | * No access to User Handles that | *Kill on Job close.| +// | belong to other processes. | | +// | * Forbid message broadcasts. | | +// | * Forbid setting global hooks. | | +// | * No access to the global atoms | | +// | table. | | +// -----------------|-----------------------------------|--------------------| +// JOB_LOCKDOWN | Same as RESTRICTED | *One active process| +// | | limit. | +// | | *Kill on Job close.| +// | | *Kill on unhandled | +// | | exception. | +// | | | +// In the context of the above table, 'user handles' refers to the handles of +// windows, bitmaps, menus, etc. Files, treads and registry handles are kernel +// handles and are not affected by the job level settings. +enum JobLevel { + JOB_LOCKDOWN = 0, + JOB_RESTRICTED, + JOB_LIMITED_USER, + JOB_INTERACTIVE, + JOB_UNPROTECTED, + JOB_NONE +}; + +// These flags correspond to various process-level mitigations (eg. ASLR and +// DEP). Most are implemented via UpdateProcThreadAttribute() plus flags for +// the PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY attribute argument; documented +// here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686880 +// Some mitigations are implemented directly by the sandbox or emulated to +// the greatest extent possible when not directly supported by the OS. +// Flags that are unsupported for the target OS will be silently ignored. +// Flags that are invalid for their application (pre or post startup) will +// return SBOX_ERROR_BAD_PARAMS. +typedef uint64 MitigationFlags; + +// Permanently enables DEP for the target process. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_DEP_ENABLE. +const MitigationFlags MITIGATION_DEP = 0x00000001; + +// Permanently Disables ATL thunk emulation when DEP is enabled. Valid +// only when MITIGATION_DEP is passed. Corresponds to not passing +// PROCESS_CREATION_MITIGATION_POLICY_DEP_ATL_THUNK_ENABLE. +const MitigationFlags MITIGATION_DEP_NO_ATL_THUNK = 0x00000002; + +// Enables Structured exception handling override prevention. Must be +// enabled prior to process start. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_SEHOP_ENABLE. +const MitigationFlags MITIGATION_SEHOP = 0x00000004; + +// Forces ASLR on all images in the child process. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON . +const MitigationFlags MITIGATION_RELOCATE_IMAGE = 0x00000008; + +// Refuses to load DLLs that cannot support ASLR. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON_REQ_RELOCS. +const MitigationFlags MITIGATION_RELOCATE_IMAGE_REQUIRED = 0x00000010; + +// Terminates the process on Windows heap corruption. Coresponds to +// PROCESS_CREATION_MITIGATION_POLICY_HEAP_TERMINATE_ALWAYS_ON. +const MitigationFlags MITIGATION_HEAP_TERMINATE = 0x00000020; + +// Sets a random lower bound as the minimum user address. Must be +// enabled prior to process start. On 32-bit processes this is +// emulated to a much smaller degree. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_BOTTOM_UP_ASLR_ALWAYS_ON. +const MitigationFlags MITIGATION_BOTTOM_UP_ASLR = 0x00000040; + +// Increases the randomness range of bottom-up ASLR to up to 1TB. Must be +// enabled prior to process start and with MITIGATION_BOTTOM_UP_ASLR. +// Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_HIGH_ENTROPY_ASLR_ALWAYS_ON +const MitigationFlags MITIGATION_HIGH_ENTROPY_ASLR = 0x00000080; + +// Immediately raises an exception on a bad handle reference. Must be +// enabled after startup. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_STRICT_HANDLE_CHECKS_ALWAYS_ON. +const MitigationFlags MITIGATION_STRICT_HANDLE_CHECKS = 0x00000100; + +// Prevents the process from making Win32k calls. Must be enabled after +// startup. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON. +const MitigationFlags MITIGATION_WIN32K_DISABLE = 0x00000200; + +// Disables common DLL injection methods (e.g. window hooks and +// App_InitDLLs). Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON. +const MitigationFlags MITIGATION_EXTENSION_DLL_DISABLE = 0x00000400; + +// Sets the DLL search order to LOAD_LIBRARY_SEARCH_DEFAULT_DIRS. Additional +// directories can be added via the Windows AddDllDirectory() function. +// http://msdn.microsoft.com/en-us/library/windows/desktop/hh310515 +// Must be enabled after startup. +const MitigationFlags MITIGATION_DLL_SEARCH_ORDER = 0x00000001ULL << 32; + +// Changes the mandatory integrity level policy on the current process' token +// to enable no-read and no-execute up. This prevents a lower IL process from +// opening the process token for impersonate/duplicate/assignment. +const MitigationFlags MITIGATION_HARDEN_TOKEN_IL_POLICY = 0x00000001ULL << 33; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SECURITY_LEVEL_H_ diff --git a/sandbox/win/src/service_resolver.cc b/sandbox/win/src/service_resolver.cc new file mode 100644 index 0000000000..92f21a7c2c --- /dev/null +++ b/sandbox/win/src/service_resolver.cc @@ -0,0 +1,46 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/service_resolver.h" + +#include "base/win/pe_image.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +NTSTATUS ServiceResolverThunk::ResolveInterceptor( + const void* interceptor_module, + const char* interceptor_name, + const void** address) { + // After all, we are using a locally mapped version of the exe, so the + // action is the same as for a target function. + return ResolveTarget(interceptor_module, interceptor_name, + const_cast<void**>(address)); +} + +// In this case all the work is done from the parent, so resolve is +// just a simple GetProcAddress. +NTSTATUS ServiceResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + if (NULL == module) + return STATUS_UNSUCCESSFUL; + + base::win::PEImage module_image(module); + *address = module_image.GetProcAddress(function_name); + + if (NULL == *address) { + NOTREACHED_NT(); + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +void ServiceResolverThunk::AllowLocalPatches() { + ntdll_base_ = ::GetModuleHandle(kNtdllName); +} + +} // namespace sandbox diff --git a/sandbox/win/src/service_resolver.h b/sandbox/win/src/service_resolver.h new file mode 100644 index 0000000000..ab125fbe5a --- /dev/null +++ b/sandbox/win/src/service_resolver.h @@ -0,0 +1,139 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SERVICE_RESOLVER_H__ +#define SANDBOX_SRC_SERVICE_RESOLVER_H__ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/resolver.h" + +namespace sandbox { + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll. +class ServiceResolverThunk : public ResolverThunk { + public: + // The service resolver needs a child process to write to. + ServiceResolverThunk(HANDLE process, bool relaxed) + : process_(process), ntdll_base_(NULL), + relaxed_(relaxed), relative_jump_(0) {} + ~ServiceResolverThunk() override {} + + // Implementation of Resolver::Setup. + NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) override; + + // Implementation of Resolver::ResolveInterceptor. + NTSTATUS ResolveInterceptor(const void* module, + const char* function_name, + const void** address) override; + + // Implementation of Resolver::ResolveTarget. + NTSTATUS ResolveTarget(const void* module, + const char* function_name, + void** address) override; + + // Implementation of Resolver::GetThunkSize. + size_t GetThunkSize() const override; + + // Call this to set up ntdll_base_ which will allow for local patches. + virtual void AllowLocalPatches(); + + // Verifies that the function specified by |target_name| in |target_module| is + // a service and copies the data from that function into |thunk_storage|. If + // |storage_bytes| is too small, then the method fails. + virtual NTSTATUS CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used); + + protected: + // The unit test will use this member to allow local patch on a buffer. + HMODULE ntdll_base_; + + // Handle of the child process. + HANDLE process_; + + private: + // Returns true if the code pointer by target_ corresponds to the expected + // type of function. Saves that code on the first part of the thunk pointed + // by local_thunk (should be directly accessible from the parent). + virtual bool IsFunctionAService(void* local_thunk) const; + + // Performs the actual patch of target_. + // local_thunk must be already fully initialized, and the first part must + // contain the original code. The real type of this buffer is ServiceFullThunk + // (yes, private). remote_thunk (real type ServiceFullThunk), must be + // allocated on the child, and will contain the thunk data, after this call. + // Returns the apropriate status code. + virtual NTSTATUS PerformPatch(void* local_thunk, void* remote_thunk); + + // Provides basically the same functionality as IsFunctionAService but it + // continues even if it does not recognize the function code. remote_thunk + // is the address of our memory on the child. + bool SaveOriginalFunction(void* local_thunk, void* remote_thunk); + + // true if we are allowed to patch already-patched functions. + bool relaxed_; + ULONG relative_jump_; + + DISALLOW_COPY_AND_ASSIGN(ServiceResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on WOW64 (32 bit ntdll on 64 bit Vista). +class Wow64ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Wow64ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Wow64ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Wow64ResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on WOW64 for Windows 8. +class Wow64W8ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Wow64W8ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Wow64W8ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Wow64W8ResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on Windows 8. +class Win8ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Win8ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Win8ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Win8ResolverThunk); +}; + +} // namespace sandbox + + +#endif // SANDBOX_SRC_SERVICE_RESOLVER_H__ diff --git a/sandbox/win/src/service_resolver_32.cc b/sandbox/win/src/service_resolver_32.cc new file mode 100644 index 0000000000..ab69ab8538 --- /dev/null +++ b/sandbox/win/src/service_resolver_32.cc @@ -0,0 +1,427 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/service_resolver.h" + +#include "base/memory/scoped_ptr.h" +#include "sandbox/win/src/win_utils.h" + +namespace { +#pragma pack(push, 1) + +const BYTE kMovEax = 0xB8; +const BYTE kMovEdx = 0xBA; +const USHORT kMovEdxEsp = 0xD48B; +const USHORT kCallPtrEdx = 0x12FF; +const USHORT kCallEdx = 0xD2FF; +const BYTE kCallEip = 0xE8; +const BYTE kRet = 0xC2; +const BYTE kRet2 = 0xC3; +const BYTE kNop = 0x90; +const USHORT kJmpEdx = 0xE2FF; +const USHORT kXorEcx = 0xC933; +const ULONG kLeaEdx = 0x0424548D; +const ULONG kCallFs1 = 0xC015FF64; +const USHORT kCallFs2 = 0; +const BYTE kCallFs3 = 0; +const BYTE kAddEsp1 = 0x83; +const USHORT kAddEsp2 = 0x4C4; +const BYTE kJmp32 = 0xE9; +const USHORT kSysenter = 0x340F; + +const int kMaxService = 1000; + +// Service code for 32 bit systems. +// NOTE: on win2003 "call dword ptr [edx]" is "call edx". +struct ServiceEntry { + // This struct contains roughly the following code: + // 00 mov eax,25h + // 05 mov edx,offset SharedUserData!SystemCallStub (7ffe0300) + // 0a call dword ptr [edx] + // 0c ret 2Ch + // 0f nop + BYTE mov_eax; // = B8 + ULONG service_id; + BYTE mov_edx; // = BA + ULONG stub; + USHORT call_ptr_edx; // = FF 12 + BYTE ret; // = C2 + USHORT num_params; + BYTE nop; +}; + +// Service code for 32 bit Windows 8. +struct ServiceEntryW8 { + // This struct contains the following code: + // 00 b825000000 mov eax,25h + // 05 e803000000 call eip+3 + // 0a c22c00 ret 2Ch + // 0d 8bd4 mov edx,esp + // 0f 0f34 sysenter + // 11 c3 ret + // 12 8bff mov edi,edi + BYTE mov_eax; // = B8 + ULONG service_id; + BYTE call_eip; // = E8 + ULONG call_offset; + BYTE ret_p; // = C2 + USHORT num_params; + USHORT mov_edx_esp; // = BD D4 + USHORT sysenter; // = 0F 34 + BYTE ret; // = C3 + USHORT nop; +}; + +// Service code for a 32 bit process running on a 64 bit os. +struct Wow64Entry { + // This struct may contain one of two versions of code: + // 1. For XP, Vista and 2K3: + // 00 b825000000 mov eax, 25h + // 05 33c9 xor ecx, ecx + // 07 8d542404 lea edx, [esp + 4] + // 0b 64ff15c0000000 call dword ptr fs:[0C0h] + // 12 c22c00 ret 2Ch + // + // 2. For Windows 7: + // 00 b825000000 mov eax, 25h + // 05 33c9 xor ecx, ecx + // 07 8d542404 lea edx, [esp + 4] + // 0b 64ff15c0000000 call dword ptr fs:[0C0h] + // 12 83c404 add esp, 4 + // 15 c22c00 ret 2Ch + // + // So we base the structure on the bigger one: + BYTE mov_eax; // = B8 + ULONG service_id; + USHORT xor_ecx; // = 33 C9 + ULONG lea_edx; // = 8D 54 24 04 + ULONG call_fs1; // = 64 FF 15 C0 + USHORT call_fs2; // = 00 00 + BYTE call_fs3; // = 00 + BYTE add_esp1; // = 83 or ret + USHORT add_esp2; // = C4 04 or num_params + BYTE ret; // = C2 + USHORT num_params; +}; + +// Service code for a 32 bit process running on 64 bit Windows 8. +struct Wow64EntryW8 { + // 00 b825000000 mov eax, 25h + // 05 64ff15c0000000 call dword ptr fs:[0C0h] + // 0b c22c00 ret 2Ch + // 0f 90 nop + BYTE mov_eax; // = B8 + ULONG service_id; + ULONG call_fs1; // = 64 FF 15 C0 + USHORT call_fs2; // = 00 00 + BYTE call_fs3; // = 00 + BYTE ret; // = C2 + USHORT num_params; + BYTE nop; +}; + +// Make sure that relaxed patching works as expected. +const size_t kMinServiceSize = offsetof(ServiceEntry, ret); +static_assert(sizeof(ServiceEntryW8) >= kMinServiceSize, + "wrong service length"); +static_assert(sizeof(Wow64Entry) >= kMinServiceSize, "wrong service length"); +static_assert(sizeof(Wow64EntryW8) >= kMinServiceSize, "wrong service length"); + +struct ServiceFullThunk { + union { + ServiceEntry original; + ServiceEntryW8 original_w8; + Wow64Entry wow_64; + Wow64EntryW8 wow_64_w8; + }; + int internal_thunk; // Dummy member to the beginning of the internal thunk. +}; + +#pragma pack(pop) + +}; // namespace + +namespace sandbox { + +NTSTATUS ServiceResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = Init(target_module, interceptor_module, target_name, + interceptor_name, interceptor_entry_point, + thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + relative_jump_ = 0; + size_t thunk_bytes = GetThunkSize(); + scoped_ptr<char[]> thunk_buffer(new char[thunk_bytes]); + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>( + thunk_buffer.get()); + + if (!IsFunctionAService(&thunk->original) && + (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) + return STATUS_UNSUCCESSFUL; + + ret = PerformPatch(thunk, thunk_storage); + + if (NULL != storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +size_t ServiceResolverThunk::GetThunkSize() const { + return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize(); +} + +NTSTATUS ServiceResolverThunk::CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + if (storage_bytes < thunk_bytes) + return STATUS_UNSUCCESSFUL; + + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(thunk_storage); + + if (!IsFunctionAService(&thunk->original) && + (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) { + return STATUS_UNSUCCESSFUL; + } + + if (NULL != storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { + ServiceEntry function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) + return false; + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || + kMovEdx != function_code.mov_edx || + (kCallPtrEdx != function_code.call_ptr_edx && + kCallEdx != function_code.call_ptr_edx) || + kRet != function_code.ret) + return false; + + // Find the system call pointer if we don't already have it. + if (kCallEdx != function_code.call_ptr_edx) { + DWORD ki_system_call; + if (!::ReadProcessMemory(process_, + bit_cast<const void*>(function_code.stub), + &ki_system_call, sizeof(ki_system_call), &read)) + return false; + + if (sizeof(ki_system_call) != read) + return false; + + HMODULE module_1, module_2; + // last check, call_stub should point to a KiXXSystemCall function on ntdll + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + bit_cast<const wchar_t*>(ki_system_call), &module_1)) + return false; + + if (NULL != ntdll_base_) { + // This path is only taken when running the unit tests. We want to be + // able to patch a buffer in memory, so target_ is not inside ntdll. + module_2 = ntdll_base_; + } else { + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<const wchar_t*>(target_), + &module_2)) + return false; + } + + if (module_1 != module_2) + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, + void* remote_thunk) { + ServiceEntry intercepted_code; + size_t bytes_to_write = sizeof(intercepted_code); + ServiceFullThunk *full_local_thunk = reinterpret_cast<ServiceFullThunk*>( + local_thunk); + ServiceFullThunk *full_remote_thunk = reinterpret_cast<ServiceFullThunk*>( + remote_thunk); + + // patch the original code + memcpy(&intercepted_code, &full_local_thunk->original, + sizeof(intercepted_code)); + intercepted_code.mov_eax = kMovEax; + intercepted_code.service_id = full_local_thunk->original.service_id; + intercepted_code.mov_edx = kMovEdx; + intercepted_code.stub = bit_cast<ULONG>(&full_remote_thunk->internal_thunk); + intercepted_code.call_ptr_edx = kJmpEdx; + bytes_to_write = kMinServiceSize; + + if (relative_jump_) { + intercepted_code.mov_eax = kJmp32; + intercepted_code.service_id = relative_jump_; + bytes_to_write = offsetof(ServiceEntry, mov_edx); + } + + // setup the thunk + SetInternalThunk(&full_local_thunk->internal_thunk, GetInternalThunkSize(), + remote_thunk, interceptor_); + + size_t thunk_size = GetThunkSize(); + + // copy the local thunk buffer to the child + SIZE_T written; + if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, + thunk_size, &written)) + return STATUS_UNSUCCESSFUL; + + if (thunk_size != written) + return STATUS_UNSUCCESSFUL; + + // and now change the function to intercept, on the child + if (NULL != ntdll_base_) { + // running a unit test + if (!::WriteProcessMemory(process_, target_, &intercepted_code, + bytes_to_write, &written)) + return STATUS_UNSUCCESSFUL; + } else { + if (!WriteProtectedChildMemory(process_, target_, &intercepted_code, + bytes_to_write)) + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +bool ServiceResolverThunk::SaveOriginalFunction(void* local_thunk, + void* remote_thunk) { + ServiceEntry function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) + return false; + + if (sizeof(function_code) != read) + return false; + + if (kJmp32 == function_code.mov_eax) { + // Plain old entry point patch. The relative jump address follows it. + ULONG relative = function_code.service_id; + + // First, fix our copy of their patch. + relative += bit_cast<ULONG>(target_) - bit_cast<ULONG>(remote_thunk); + + function_code.service_id = relative; + + // And now, remember how to re-patch it. + ServiceFullThunk *full_thunk = + reinterpret_cast<ServiceFullThunk*>(remote_thunk); + + const ULONG kJmp32Size = 5; + + relative_jump_ = bit_cast<ULONG>(&full_thunk->internal_thunk) - + bit_cast<ULONG>(target_) - kJmp32Size; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { + Wow64Entry function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) + return false; + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kXorEcx != function_code.xor_ecx || + kLeaEdx != function_code.lea_edx || kCallFs1 != function_code.call_fs1 || + kCallFs2 != function_code.call_fs2 || kCallFs3 != function_code.call_fs3) + return false; + + if ((kAddEsp1 == function_code.add_esp1 && + kAddEsp2 == function_code.add_esp2 && + kRet == function_code.ret) || kRet == function_code.add_esp1) { + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + return true; + } + + return false; +} + +bool Wow64W8ResolverThunk::IsFunctionAService(void* local_thunk) const { + Wow64EntryW8 function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) + return false; + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kCallFs1 != function_code.call_fs1 || + kCallFs2 != function_code.call_fs2 || + kCallFs3 != function_code.call_fs3 || kRet != function_code.ret) { + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + return true; +} + +bool Win8ResolverThunk::IsFunctionAService(void* local_thunk) const { + ServiceEntryW8 function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) + return false; + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kCallEip != function_code.call_eip || + function_code.call_offset != 3 || kRet != function_code.ret_p || + kMovEdxEsp != function_code.mov_edx_esp || + kSysenter != function_code.sysenter || kRet2 != function_code.ret) { + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/service_resolver_64.cc b/sandbox/win/src/service_resolver_64.cc new file mode 100644 index 0000000000..984cb384e2 --- /dev/null +++ b/sandbox/win/src/service_resolver_64.cc @@ -0,0 +1,207 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/service_resolver.h" + +#include "base/memory/scoped_ptr.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/win_utils.h" + +namespace { +#pragma pack(push, 1) + +const ULONG kMmovR10EcxMovEax = 0xB8D18B4C; +const USHORT kSyscall = 0x050F; +const BYTE kRetNp = 0xC3; +const ULONG64 kMov1 = 0x54894808244C8948; +const ULONG64 kMov2 = 0x4C182444894C1024; +const ULONG kMov3 = 0x20244C89; + +// Service code for 64 bit systems. +struct ServiceEntry { + // This struct contains roughly the following code: + // 00 mov r10,rcx + // 03 mov eax,52h + // 08 syscall + // 0a ret + // 0b xchg ax,ax + // 0e xchg ax,ax + + ULONG mov_r10_rcx_mov_eax; // = 4C 8B D1 B8 + ULONG service_id; + USHORT syscall; // = 0F 05 + BYTE ret; // = C3 + BYTE pad; // = 66 + USHORT xchg_ax_ax1; // = 66 90 + USHORT xchg_ax_ax2; // = 66 90 +}; + +// Service code for 64 bit Windows 8. +struct ServiceEntryW8 { + // This struct contains the following code: + // 00 48894c2408 mov [rsp+8], rcx + // 05 4889542410 mov [rsp+10], rdx + // 0a 4c89442418 mov [rsp+18], r8 + // 0f 4c894c2420 mov [rsp+20], r9 + // 14 4c8bd1 mov r10,rcx + // 17 b825000000 mov eax,25h + // 1c 0f05 syscall + // 1e c3 ret + // 1f 90 nop + + ULONG64 mov_1; // = 48 89 4C 24 08 48 89 54 + ULONG64 mov_2; // = 24 10 4C 89 44 24 18 4C + ULONG mov_3; // = 89 4C 24 20 + ULONG mov_r10_rcx_mov_eax; // = 4C 8B D1 B8 + ULONG service_id; + USHORT syscall; // = 0F 05 + BYTE ret; // = C3 + BYTE nop; // = 90 +}; + +// We don't have an internal thunk for x64. +struct ServiceFullThunk { + union { + ServiceEntry original; + ServiceEntryW8 original_w8; + }; +}; + +#pragma pack(pop) + +bool IsService(const void* source) { + const ServiceEntry* service = + reinterpret_cast<const ServiceEntry*>(source); + + return (kMmovR10EcxMovEax == service->mov_r10_rcx_mov_eax && + kSyscall == service->syscall && kRetNp == service->ret); +} + +}; // namespace + +namespace sandbox { + +NTSTATUS ServiceResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = Init(target_module, interceptor_module, target_name, + interceptor_name, interceptor_entry_point, + thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + scoped_ptr<char[]> thunk_buffer(new char[thunk_bytes]); + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>( + thunk_buffer.get()); + + if (!IsFunctionAService(&thunk->original)) + return STATUS_UNSUCCESSFUL; + + ret = PerformPatch(thunk, thunk_storage); + + if (NULL != storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +size_t ServiceResolverThunk::GetThunkSize() const { + return sizeof(ServiceFullThunk); +} + +NTSTATUS ServiceResolverThunk::CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + if (storage_bytes < thunk_bytes) + return STATUS_UNSUCCESSFUL; + + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(thunk_storage); + + if (!IsFunctionAService(&thunk->original)) + return STATUS_UNSUCCESSFUL; + + if (NULL != storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { + ServiceFullThunk function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) + return false; + + if (sizeof(function_code) != read) + return false; + + if (!IsService(&function_code)) { + // See if it's the Win8 signature. + ServiceEntryW8* w8_service = &function_code.original_w8; + if (!IsService(&w8_service->mov_r10_rcx_mov_eax) || + w8_service->mov_1 != kMov1 || w8_service->mov_1 != kMov1 || + w8_service->mov_1 != kMov1) { + return false; + } + } + + // Save the verified code. + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, + void* remote_thunk) { + // Patch the original code. + ServiceEntry local_service; + DCHECK_NT(GetInternalThunkSize() >= sizeof(local_service)); + if (!SetInternalThunk(&local_service, sizeof(local_service), NULL, + interceptor_)) + return STATUS_UNSUCCESSFUL; + + // Copy the local thunk buffer to the child. + SIZE_T actual; + if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, + sizeof(ServiceFullThunk), &actual)) + return STATUS_UNSUCCESSFUL; + + if (sizeof(ServiceFullThunk) != actual) + return STATUS_UNSUCCESSFUL; + + // And now change the function to intercept, on the child. + if (NULL != ntdll_base_) { + // Running a unit test. + if (!::WriteProcessMemory(process_, target_, &local_service, + sizeof(local_service), &actual)) + return STATUS_UNSUCCESSFUL; + } else { + if (!WriteProtectedChildMemory(process_, target_, &local_service, + sizeof(local_service))) + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { + NOTREACHED_NT(); + return false; +} + +} // namespace sandbox diff --git a/sandbox/win/src/service_resolver_unittest.cc b/sandbox/win/src/service_resolver_unittest.cc new file mode 100644 index 0000000000..c7ac7eab16 --- /dev/null +++ b/sandbox/win/src/service_resolver_unittest.cc @@ -0,0 +1,262 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for ServiceResolverThunk. + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/resolver.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/service_resolver.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll. +template<typename T> +class ResolverThunkTest : public T { + public: + // The service resolver needs a child process to write to. + explicit ResolverThunkTest(bool relaxed) + : T(::GetCurrentProcess(), relaxed) {} + + // Sets the interception target to the desired address. + void set_target(void* target) { + fake_target_ = target; + } + + protected: + // Overrides Resolver::Init + virtual NTSTATUS Init(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes) { + NTSTATUS ret = STATUS_SUCCESS; + ret = ResolverThunk::Init(target_module, interceptor_module, target_name, + interceptor_name, interceptor_entry_point, + thunk_storage, storage_bytes); + EXPECT_EQ(STATUS_SUCCESS, ret); + + target_ = fake_target_; + + return ret; + }; + + private: + // Holds the address of the fake target. + void* fake_target_; + + DISALLOW_COPY_AND_ASSIGN(ResolverThunkTest); +}; + +typedef ResolverThunkTest<sandbox::ServiceResolverThunk> WinXpResolverTest; + +#if !defined(_WIN64) +typedef ResolverThunkTest<sandbox::Win8ResolverThunk> Win8ResolverTest; +typedef ResolverThunkTest<sandbox::Wow64ResolverThunk> Wow64ResolverTest; +typedef ResolverThunkTest<sandbox::Wow64W8ResolverThunk> Wow64W8ResolverTest; +#endif + +const BYTE kJump32 = 0xE9; + +void CheckJump(void* source, void* target) { +#pragma pack(push) +#pragma pack(1) + struct Code { + BYTE jump; + ULONG delta; + }; +#pragma pack(pop) + +#if defined(_WIN64) + FAIL() << "Running 32-bit codepath"; +#else + Code* patched = reinterpret_cast<Code*>(source); + EXPECT_EQ(kJump32, patched->jump); + + ULONG source_addr = bit_cast<ULONG>(source); + ULONG target_addr = bit_cast<ULONG>(target); + EXPECT_EQ(target_addr + 19 - source_addr, patched->delta); +#endif +} + +NTSTATUS PatchNtdllWithResolver(const char* function, bool relaxed, + sandbox::ServiceResolverThunk* resolver) { + HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll"); + EXPECT_TRUE(NULL != ntdll_base); + + void* target = ::GetProcAddress(ntdll_base, function); + EXPECT_TRUE(NULL != target); + if (NULL == target) + return STATUS_UNSUCCESSFUL; + + BYTE service[50]; + memcpy(service, target, sizeof(service)); + + static_cast<WinXpResolverTest*>(resolver)->set_target(service); + + // Any pointer will do as an interception_entry_point + void* function_entry = resolver; + size_t thunk_size = resolver->GetThunkSize(); + scoped_ptr<char[]> thunk(new char[thunk_size]); + size_t used; + + resolver->AllowLocalPatches(); + + NTSTATUS ret = resolver->Setup(ntdll_base, NULL, function, NULL, + function_entry, thunk.get(), thunk_size, + &used); + if (NT_SUCCESS(ret)) { + EXPECT_EQ(thunk_size, used); + EXPECT_NE(0, memcmp(service, target, sizeof(service))); + EXPECT_NE(kJump32, service[0]); + + if (relaxed) { + // It's already patched, let's patch again, and simulate a direct patch. + service[0] = kJump32; + ret = resolver->Setup(ntdll_base, NULL, function, NULL, function_entry, + thunk.get(), thunk_size, &used); + CheckJump(service, thunk.get()); + } + } + + return ret; +} + +sandbox::ServiceResolverThunk* GetTestResolver(bool relaxed) { +#if defined(_WIN64) + return new WinXpResolverTest(relaxed); +#else + base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); + if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) { + if (os_info->version() >= base::win::VERSION_WIN8) + return new Wow64W8ResolverTest(relaxed); + return new Wow64ResolverTest(relaxed); + } + + if (os_info->version() >= base::win::VERSION_WIN8) + return new Win8ResolverTest(relaxed); + + return new WinXpResolverTest(relaxed); +#endif +} + +NTSTATUS PatchNtdll(const char* function, bool relaxed) { + sandbox::ServiceResolverThunk* resolver = GetTestResolver(relaxed); + + NTSTATUS ret = PatchNtdllWithResolver(function, relaxed, resolver); + delete resolver; + return ret; +} + +TEST(ServiceResolverTest, PatchesServices) { + NTSTATUS ret = PatchNtdll("NtClose", false); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtCreateFile", false); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateFile, last error: " << + ::GetLastError(); + + ret = PatchNtdll("NtCreateMutant", false); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateMutant, last error: " << + ::GetLastError(); + + ret = PatchNtdll("NtMapViewOfSection", false); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtMapViewOfSection, last error: " << + ::GetLastError(); +} + +TEST(ServiceResolverTest, FailsIfNotService) { +#if !defined(_WIN64) + EXPECT_NE(STATUS_SUCCESS, PatchNtdll("RtlUlongByteSwap", false)); +#endif + + EXPECT_NE(STATUS_SUCCESS, PatchNtdll("LdrLoadDll", false)); +} + +TEST(ServiceResolverTest, PatchesPatchedServices) { +// We don't support "relaxed mode" for Win64 apps. +#if !defined(_WIN64) + NTSTATUS ret = PatchNtdll("NtClose", true); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtCreateFile", true); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateFile, last error: " << + ::GetLastError(); + + ret = PatchNtdll("NtCreateMutant", true); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateMutant, last error: " << + ::GetLastError(); + + ret = PatchNtdll("NtMapViewOfSection", true); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtMapViewOfSection, last error: " << + ::GetLastError(); +#endif +} + +TEST(ServiceResolverTest, MultiplePatchedServices) { +// We don't support "relaxed mode" for Win64 apps. +#if !defined(_WIN64) + sandbox::ServiceResolverThunk* resolver = GetTestResolver(true); + NTSTATUS ret = PatchNtdllWithResolver("NtClose", true, resolver); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError(); + + ret = PatchNtdllWithResolver("NtCreateFile", true, resolver); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateFile, last error: " << + ::GetLastError(); + + ret = PatchNtdllWithResolver("NtCreateMutant", true, resolver); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateMutant, last error: " << + ::GetLastError(); + + ret = PatchNtdllWithResolver("NtMapViewOfSection", true, resolver); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtMapViewOfSection, last error: " << + ::GetLastError(); + delete resolver; +#endif +} + +TEST(ServiceResolverTest, LocalPatchesAllowed) { + sandbox::ServiceResolverThunk* resolver = GetTestResolver(true); + + HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll"); + ASSERT_TRUE(NULL != ntdll_base); + + const char kFunctionName[] = "NtClose"; + + void* target = ::GetProcAddress(ntdll_base, kFunctionName); + ASSERT_TRUE(NULL != target); + + BYTE service[50]; + memcpy(service, target, sizeof(service)); + static_cast<WinXpResolverTest*>(resolver)->set_target(service); + + // Any pointer will do as an interception_entry_point + void* function_entry = resolver; + size_t thunk_size = resolver->GetThunkSize(); + scoped_ptr<char[]> thunk(new char[thunk_size]); + size_t used; + + NTSTATUS ret = STATUS_UNSUCCESSFUL; + + // First try patching without having allowed local patches. + ret = resolver->Setup(ntdll_base, NULL, kFunctionName, NULL, + function_entry, thunk.get(), thunk_size, + &used); + EXPECT_FALSE(NT_SUCCESS(ret)); + + // Now allow local patches and check that things work. + resolver->AllowLocalPatches(); + ret = resolver->Setup(ntdll_base, NULL, kFunctionName, NULL, + function_entry, thunk.get(), thunk_size, + &used); + EXPECT_EQ(STATUS_SUCCESS, ret); +} + +} // namespace diff --git a/sandbox/win/src/shared_handles.cc b/sandbox/win/src/shared_handles.cc new file mode 100644 index 0000000000..423b67bab3 --- /dev/null +++ b/sandbox/win/src/shared_handles.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/shared_handles.h" + +namespace sandbox { + +// Note once again the the assumption here is that the shared memory is +// initialized with zeros in the process that calls SetHandle and that +// the process that calls GetHandle 'sees' this memory. + +SharedHandles::SharedHandles() { + shared_.items = NULL; + shared_.max_items = 0; +} + +bool SharedHandles::Init(void* raw_mem, size_t size_bytes) { + if (size_bytes < sizeof(shared_.items[0])) { + // The shared memory is too small! + return false; + } + shared_.items = static_cast<SharedItem*>(raw_mem); + shared_.max_items = size_bytes / sizeof(shared_.items[0]); + return true; +} + +// Note that an empty slot is marked with a tag == 0 that is why is +// not a valid imput tag +bool SharedHandles::SetHandle(uint32 tag, HANDLE handle) { + if (0 == tag) { + // Invalid tag + return false; + } + // Find empty slot and put the tag and the handle there + SharedItem* empty_slot = FindByTag(0); + if (NULL == empty_slot) { + return false; + } + empty_slot->tag = tag; + empty_slot->item = handle; + return true; +} + +bool SharedHandles::GetHandle(uint32 tag, HANDLE* handle) { + if (0 == tag) { + // Invalid tag + return false; + } + SharedItem* found = FindByTag(tag); + if (NULL == found) { + return false; + } + *handle = found->item; + return true; +} + +SharedHandles::SharedItem* SharedHandles::FindByTag(uint32 tag) { + for (size_t ix = 0; ix != shared_.max_items; ++ix) { + if (tag == shared_.items[ix].tag) { + return &shared_.items[ix]; + } + } + return NULL; +} + +} // namespace sandbox diff --git a/sandbox/win/src/shared_handles.h b/sandbox/win/src/shared_handles.h new file mode 100644 index 0000000000..2c76bfb280 --- /dev/null +++ b/sandbox/win/src/shared_handles.h @@ -0,0 +1,108 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SHARED_HANDLES_H__ +#define SANDBOX_SRC_SHARED_HANDLES_H__ + +#include "base/basictypes.h" + +#ifndef HANDLE +// We can provide our own windows compatilble handle definition, but +// in general we want to rely on the client of this api to include +// the proper windows headers. Note that we don't want to bring the +// whole <windows.h> into scope if we don't have to. +typedef void* HANDLE; +#endif + +namespace sandbox { + +// SharedHandles is a simple class to stash and find windows object handles +// given a raw block of memory which is shared between two processes. +// It addresses the need to communicate a handle value between two windows +// processes given that they are already sharing some memory. +// +// This class is not exposed directly to users of the sanbox API, instead +// we expose the wrapper methods TargetProcess::TransferHandle( ) and +// TargetServices::GetTransferHandle() +// +// Use it for a small number of items, since internaly uses linear seach +// +// The use is very simple. Given a shared memory between proces A and B: +// process A: +// HANDLE handle = SomeFunction(..); +// SharedHandles shared_handes; +// shared_handles.Init(memory) +// shared_handles.SetHandle(3, handle); +// +// process B: +// SharedHandles shared_handes; +// shared_handles.Init(memory) +// HANDLE handle = shared_handles.GetHandle(3); +// +// Note that '3' in this example is a unique id, that must be agreed before +// transfer +// +// Note2: While this class can be used in a single process, there are +// better alternatives such as STL +// +// Note3: Under windows a kernel object handle in one process does not +// make sense for another process unless there is a DuplicateHandle( ) +// call involved which this class DOES NOT do that for you. +// +// Note4: Under windows, shared memory when created is initialized to +// zeros always. If you are not using shared memory it is your responsability +// to zero it for the setter process and to copy it to the getter process. +class SharedHandles { + public: + SharedHandles(); + + // Initializes the shared memory for use. + // Pass the shared memory base and size. It will internally compute + // how many handles can it store. If initialization fails the return value + // is false. + bool Init(void* raw_mem, size_t size_bytes); + + // Sets a handle in the shared memory for transfer. + // Parameters: + // tag : an integer, different from zero that uniquely identfies the + // handle to transfer. + // handle: the handle value associated with 'tag' to tranfer + // Returns false if there is not enough space in the shared memory for + // this handle. + bool SetHandle(uint32 tag, HANDLE handle); + + // Gets a handle previously stored by SetHandle. + // Parameters: + // tag: an integer different from zero that uniquely identfies the handle + // to retrieve. + // *handle: output handle value if the call was succesful. + // If a handle with the provided tag is not found the return value is false. + // If the tag is found the return value is true. + bool GetHandle(uint32 tag, HANDLE* handle); + + private: + // A single item is the tuple handle/tag + struct SharedItem { + uint32 tag; + void* item; + }; + + // SharedMem is used to layout the memory as an array of SharedItems + struct SharedMem { + size_t max_items; + SharedItem* items; + }; + + // Finds an Item tuple provided the handle tag. + // Uses linear search because we expect the number of handles to be + // small (say less than ~100). + SharedItem* FindByTag(uint32 tag); + + SharedMem shared_; + DISALLOW_COPY_AND_ASSIGN(SharedHandles); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SHARED_HANDLES_H__ diff --git a/sandbox/win/src/sharedmem_ipc_client.cc b/sandbox/win/src/sharedmem_ipc_client.cc new file mode 100644 index 0000000000..fa6a8779aa --- /dev/null +++ b/sandbox/win/src/sharedmem_ipc_client.cc @@ -0,0 +1,152 @@ +// Copyright (c) 2006-2008 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 <string.h> +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_params.h" +#include "base/logging.h" + +namespace sandbox { + +// Get the base of the data buffer of the channel; this is where the input +// parameters get serialized. Since they get serialized directly into the +// channel we avoid one copy. +void* SharedMemIPCClient::GetBuffer() { + bool failure = false; + size_t ix = LockFreeChannel(&failure); + if (failure) { + return NULL; + } + return reinterpret_cast<char*>(control_) + + control_->channels[ix].channel_base; +} + +// If we need to cancel an IPC before issuing DoCall +// our client should call FreeBuffer with the same pointer +// returned by GetBuffer. +void SharedMemIPCClient::FreeBuffer(void* buffer) { + size_t num = ChannelIndexFromBuffer(buffer); + ChannelControl* channel = control_->channels; + LONG result = ::InterlockedExchange(&channel[num].state, kFreeChannel); + DCHECK_NE(kFreeChannel, static_cast<ChannelState>(result)); + result; +} + +// The constructor simply casts the shared memory to the internal +// structures. This is a cheap step that is why this IPC object can +// and should be constructed per call. +SharedMemIPCClient::SharedMemIPCClient(void* shared_mem) + : control_(reinterpret_cast<IPCControl*>(shared_mem)) { + first_base_ = reinterpret_cast<char*>(shared_mem) + + control_->channels[0].channel_base; + // There must be at least one channel. + DCHECK(0 != control_->channels_count); +} + +// Do the IPC. At this point the channel should have already been +// filled with the serialized input parameters. +// We follow the pattern explained in the header file. +ResultCode SharedMemIPCClient::DoCall(CrossCallParams* params, + CrossCallReturn* answer) { + if (!control_->server_alive) + return SBOX_ERROR_CHANNEL_ERROR; + + size_t num = ChannelIndexFromBuffer(params->GetBuffer()); + ChannelControl* channel = control_->channels; + // Note that the IPC tag goes outside the buffer as well inside + // the buffer. This should enable the server to prioritize based on + // IPC tags without having to de-serialize the entire message. + channel[num].ipc_tag = params->GetTag(); + + // Wait for the server to service this IPC call. After kIPCWaitTimeOut1 + // we check if the server_alive mutex was abandoned which will indicate + // that the server has died. + + // While the atomic signaling and waiting is not a requirement, it + // is nice because we save a trip to kernel. + DWORD wait = ::SignalObjectAndWait(channel[num].ping_event, + channel[num].pong_event, + kIPCWaitTimeOut1, FALSE); + if (WAIT_TIMEOUT == wait) { + // The server is taking too long. Enter a loop were we check if the + // server_alive mutex has been abandoned which would signal a server crash + // or else we keep waiting for a response. + while (true) { + wait = ::WaitForSingleObject(control_->server_alive, 0); + if (WAIT_TIMEOUT == wait) { + // Server seems still alive. We already signaled so here we just wait. + wait = ::WaitForSingleObject(channel[num].pong_event, kIPCWaitTimeOut1); + if (WAIT_OBJECT_0 == wait) { + // The server took a long time but responded. + break; + } else if (WAIT_TIMEOUT == wait) { + continue; + } else { + return SBOX_ERROR_CHANNEL_ERROR; + } + } else { + // The server has crashed and windows has signaled the mutex as + // abandoned. + ::InterlockedExchange(&channel[num].state, kAbandonedChannel); + control_->server_alive = 0; + return SBOX_ERROR_CHANNEL_ERROR; + } + } + } else if (WAIT_OBJECT_0 != wait) { + // Probably the server crashed before the kIPCWaitTimeOut1 occurred. + return SBOX_ERROR_CHANNEL_ERROR; + } + + // The server has returned an answer, copy it and free the channel. + memcpy(answer, params->GetCallReturn(), sizeof(CrossCallReturn)); + + // Return the IPC state It can indicate that while the IPC has + // completed some error in the Broker has caused to not return valid + // results. + return answer->call_outcome; +} + +// Locking a channel is a simple as looping over all the channels +// looking for one that is has state = kFreeChannel and atomically +// swapping it to kBusyChannel. +// If there is no free channel, then we must back off so some other +// thread makes progress and frees a channel. To back off we sleep. +size_t SharedMemIPCClient::LockFreeChannel(bool* severe_failure) { + if (0 == control_->channels_count) { + *severe_failure = true; + return 0; + } + ChannelControl* channel = control_->channels; + do { + for (size_t ix = 0; ix != control_->channels_count; ++ix) { + if (kFreeChannel == ::InterlockedCompareExchange(&channel[ix].state, + kBusyChannel, + kFreeChannel)) { + *severe_failure = false; + return ix; + } + } + // We did not find any available channel, maybe the server is dead. + DWORD wait = ::WaitForSingleObject(control_->server_alive, + kIPCWaitTimeOut2); + if (WAIT_TIMEOUT != wait) { + // The server is dead and we outlive it enough to get in trouble. + *severe_failure = true; + return 0; + } + } + while (true); +} + +// Find out which channel we are from the pointer returned by GetBuffer. +size_t SharedMemIPCClient::ChannelIndexFromBuffer(const void* buffer) { + ptrdiff_t d = reinterpret_cast<const char*>(buffer) - first_base_; + size_t num = d/kIPCChannelSize; + DCHECK_LT(num, control_->channels_count); + return (num); +} + +} // namespace sandbox diff --git a/sandbox/win/src/sharedmem_ipc_client.h b/sandbox/win/src/sharedmem_ipc_client.h new file mode 100644 index 0000000000..9eec74ac85 --- /dev/null +++ b/sandbox/win/src/sharedmem_ipc_client.h @@ -0,0 +1,136 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SHAREDMEM_IPC_CLIENT_H__ +#define SANDBOX_SRC_SHAREDMEM_IPC_CLIENT_H__ + +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/sandbox.h" + +// IPC transport implementation that uses shared memory. +// This is the client side +// +// The shared memory is divided on blocks called channels, and potentially +// it can perform as many concurrent IPC calls as channels. The IPC over +// each channel is strictly synchronous for the client. +// +// Each channel as a channel control section associated with. Each control +// section has two kernel events (known as ping and pong) and a integer +// variable that maintains a state +// +// this is the state diagram of a channel: +// +// locked in service +// kFreeChannel---------->BusyChannel-------------->kAckChannel +// ^ | +// |_________________________________________________| +// answer ready +// +// The protocol is as follows: +// 1) client finds a free channel: state = kFreeChannel +// 2) does an atomic compare-and-swap, now state = BusyChannel +// 3) client writes the data into the channel buffer +// 4) client signals the ping event and waits (blocks) on the pong event +// 5) eventually the server signals the pong event +// 6) the client awakes and reads the answer from the same channel +// 7) the client updates its InOut parameters with the new data from the +// shared memory section. +// 8) the client atomically sets the state = kFreeChannel +// +// In the shared memory the layout is as follows: +// +// [ channel count ] +// [ channel control 0] +// [ channel control 1] +// [ channel control N] +// [ channel buffer 0 ] 1024 bytes +// [ channel buffer 1 ] 1024 bytes +// [ channel buffer N ] 1024 bytes +// +// By default each channel buffer is 1024 bytes +namespace sandbox { + +// the possible channel states as described above +enum ChannelState { + // channel is free + kFreeChannel = 1, + // IPC in progress client side + kBusyChannel, + // IPC in progress server side + kAckChannel, + // not used right now + kReadyChannel, + // IPC abandoned by client side + kAbandonedChannel +}; + +// The next two constants control the time outs for the IPC. +const DWORD kIPCWaitTimeOut1 = 1000; // Milliseconds. +const DWORD kIPCWaitTimeOut2 = 50; // Milliseconds. + +// the channel control structure +struct ChannelControl { + // points to be beginning of the channel buffer, where data goes + size_t channel_base; + // maintains the state from the ChannelState enumeration + volatile LONG state; + // the ping event is signaled by the client when the IPC data is ready on + // the buffer + HANDLE ping_event; + // the client waits on the pong event for the IPC answer back + HANDLE pong_event; + // the IPC unique identifier + uint32 ipc_tag; +}; + +struct IPCControl { + // total number of channels available, some might be busy at a given time + size_t channels_count; + // handle to a shared mutex to detect when the server is dead + HANDLE server_alive; + // array of channel control structures + ChannelControl channels[1]; +}; + +// the actual shared memory IPC implementation class. This object is designed +// to be lightweight so it can be constructed on-site (at the calling place) +// wherever an IPC call is needed. +class SharedMemIPCClient { + public: + // Creates the IPC client. + // as parameter it takes the base address of the shared memory + explicit SharedMemIPCClient(void* shared_mem); + + // locks a free channel and returns the channel buffer memory base. This call + // blocks until there is a free channel + void* GetBuffer(); + + // releases the lock on the channel, for other to use. call this if you have + // called GetBuffer and you want to abort but have not called yet DoCall() + void FreeBuffer(void* buffer); + + // Performs the actual IPC call. + // params: The blob of packed input parameters. + // answer: upon IPC completion, it contains the server answer to the IPC. + // If the return value is not SBOX_ERROR_CHANNEL_ERROR, the caller has to free + // the channel. + // returns ALL_OK if the IPC mechanism successfully delivered. You still need + // to check on the answer structure to see the actual IPC result. + ResultCode DoCall(CrossCallParams* params, CrossCallReturn* answer); + + private: + // Returns the index of the first free channel. It sets 'severe_failure' + // to true if there is an unrecoverable error that does not allow to + // find a channel. + size_t LockFreeChannel(bool* severe_failure); + // Return the channel index given the address of the buffer. + size_t ChannelIndexFromBuffer(const void* buffer); + IPCControl* control_; + // point to the first channel base + char* first_base_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SHAREDMEM_IPC_CLIENT_H__ diff --git a/sandbox/win/src/sharedmem_ipc_server.cc b/sandbox/win/src/sharedmem_ipc_server.cc new file mode 100644 index 0000000000..5ce7da5d58 --- /dev/null +++ b/sandbox/win/src/sharedmem_ipc_server.cc @@ -0,0 +1,423 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/win/src/sharedmem_ipc_server.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_server.h" + +namespace { +// This handle must not be closed. +volatile HANDLE g_alive_mutex = NULL; +} + +namespace sandbox { + +SharedMemIPCServer::SharedMemIPCServer(HANDLE target_process, + DWORD target_process_id, + HANDLE target_job, + ThreadProvider* thread_provider, + Dispatcher* dispatcher) + : client_control_(NULL), + thread_provider_(thread_provider), + target_process_(target_process), + target_process_id_(target_process_id), + target_job_object_(target_job), + call_dispatcher_(dispatcher) { + // We create a initially owned mutex. If the server dies unexpectedly, + // the thread that owns it will fail to release the lock and windows will + // report to the target (when it tries to acquire it) that the wait was + // abandoned. Note: We purposely leak the local handle because we want it to + // be closed by Windows itself so it is properly marked as abandoned if the + // server dies. + if (!g_alive_mutex) { + HANDLE mutex = ::CreateMutexW(NULL, TRUE, NULL); + if (::InterlockedCompareExchangePointer(&g_alive_mutex, mutex, NULL)) { + // We lost the race to create the mutex. + ::CloseHandle(mutex); + } + } +} + +SharedMemIPCServer::~SharedMemIPCServer() { + // Free the wait handles associated with the thread pool. + if (!thread_provider_->UnRegisterWaits(this)) { + // Better to leak than to crash. + return; + } + // Free the IPC signal events. + ServerContexts::iterator it; + for (it = server_contexts_.begin(); it != server_contexts_.end(); ++it) { + ServerControl* context = (*it); + ::CloseHandle(context->ping_event); + ::CloseHandle(context->pong_event); + delete context; + } + + if (client_control_) + ::UnmapViewOfFile(client_control_); +} + +bool SharedMemIPCServer::Init(void* shared_mem, uint32 shared_size, + uint32 channel_size) { + // The shared memory needs to be at least as big as a channel. + if (shared_size < channel_size) { + return false; + } + // The channel size should be aligned. + if (0 != (channel_size % 32)) { + return false; + } + + // Calculate how many channels we can fit in the shared memory. + shared_size -= offsetof(IPCControl, channels); + size_t channel_count = shared_size / (sizeof(ChannelControl) + channel_size); + + // If we cannot fit even one channel we bail out. + if (0 == channel_count) { + return false; + } + // Calculate the start of the first channel. + size_t base_start = (sizeof(ChannelControl)* channel_count) + + offsetof(IPCControl, channels); + + client_control_ = reinterpret_cast<IPCControl*>(shared_mem); + client_control_->channels_count = 0; + + // This is the initialization that we do per-channel. Basically: + // 1) make two events (ping & pong) + // 2) create handles to the events for the client and the server. + // 3) initialize the channel (client_context) with the state. + // 4) initialize the server side of the channel (service_context). + // 5) call the thread provider RegisterWait to register the ping events. + for (size_t ix = 0; ix != channel_count; ++ix) { + ChannelControl* client_context = &client_control_->channels[ix]; + ServerControl* service_context = new ServerControl; + server_contexts_.push_back(service_context); + + if (!MakeEvents(&service_context->ping_event, + &service_context->pong_event, + &client_context->ping_event, + &client_context->pong_event)) { + return false; + } + + client_context->channel_base = base_start; + client_context->state = kFreeChannel; + + // Note that some of these values are available as members of this + // object but we put them again into the service_context because we + // will be called on a static method (ThreadPingEventReady) + service_context->shared_base = reinterpret_cast<char*>(shared_mem); + service_context->channel_size = channel_size; + service_context->channel = client_context; + service_context->channel_buffer = service_context->shared_base + + client_context->channel_base; + service_context->dispatcher = call_dispatcher_; + service_context->target_info.process = target_process_; + service_context->target_info.process_id = target_process_id_; + service_context->target_info.job_object = target_job_object_; + // Advance to the next channel. + base_start += channel_size; + // Register the ping event with the threadpool. + thread_provider_->RegisterWait(this, service_context->ping_event, + ThreadPingEventReady, service_context); + } + if (!::DuplicateHandle(::GetCurrentProcess(), g_alive_mutex, + target_process_, &client_control_->server_alive, + SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, 0)) { + return false; + } + // This last setting indicates to the client all is setup. + client_control_->channels_count = channel_count; + return true; +} + +// Releases memory allocated for IPC arguments, if needed. +void ReleaseArgs(const IPCParams* ipc_params, void* args[kMaxIpcParams]) { + for (size_t i = 0; i < kMaxIpcParams; i++) { + switch (ipc_params->args[i]) { + case WCHAR_TYPE: { + delete reinterpret_cast<base::string16*>(args[i]); + args[i] = NULL; + break; + } + case INOUTPTR_TYPE: { + delete reinterpret_cast<CountedBuffer*>(args[i]); + args[i] = NULL; + break; + } + default: break; + } + } +} + +// Fills up the list of arguments (args and ipc_params) for an IPC call. +bool GetArgs(CrossCallParamsEx* params, IPCParams* ipc_params, + void* args[kMaxIpcParams]) { + if (kMaxIpcParams < params->GetParamsCount()) + return false; + + for (uint32 i = 0; i < params->GetParamsCount(); i++) { + uint32 size; + ArgType type; + args[i] = params->GetRawParameter(i, &size, &type); + if (args[i]) { + ipc_params->args[i] = type; + switch (type) { + case WCHAR_TYPE: { + scoped_ptr<base::string16> data(new base::string16); + if (!params->GetParameterStr(i, data.get())) { + args[i] = 0; + ReleaseArgs(ipc_params, args); + return false; + } + args[i] = data.release(); + break; + } + case UINT32_TYPE: { + uint32 data; + if (!params->GetParameter32(i, &data)) { + ReleaseArgs(ipc_params, args); + return false; + } + IPCInt ipc_int(data); + args[i] = ipc_int.AsVoidPtr(); + break; + } + case VOIDPTR_TYPE : { + void* data; + if (!params->GetParameterVoidPtr(i, &data)) { + ReleaseArgs(ipc_params, args); + return false; + } + args[i] = data; + break; + } + case INOUTPTR_TYPE: { + if (!args[i]) { + ReleaseArgs(ipc_params, args); + return false; + } + CountedBuffer* buffer = new CountedBuffer(args[i] , size); + args[i] = buffer; + break; + } + default: break; + } + } + } + return true; +} + +bool SharedMemIPCServer::InvokeCallback(const ServerControl* service_context, + void* ipc_buffer, + CrossCallReturn* call_result) { + // Set the default error code; + SetCallError(SBOX_ERROR_INVALID_IPC, call_result); + uint32 output_size = 0; + // Parse, verify and copy the message. The handler operates on a copy + // of the message so the client cannot play dirty tricks by changing the + // data in the channel while the IPC is being processed. + scoped_ptr<CrossCallParamsEx> params( + CrossCallParamsEx::CreateFromBuffer(ipc_buffer, + service_context->channel_size, + &output_size)); + if (!params.get()) + return false; + + uint32 tag = params->GetTag(); + static_assert(0 == INVALID_TYPE, "incorrect type enum"); + IPCParams ipc_params = {0}; + ipc_params.ipc_tag = tag; + + void* args[kMaxIpcParams]; + if (!GetArgs(params.get(), &ipc_params, args)) + return false; + + IPCInfo ipc_info = {0}; + ipc_info.ipc_tag = tag; + ipc_info.client_info = &service_context->target_info; + Dispatcher* dispatcher = service_context->dispatcher; + DCHECK(dispatcher); + bool error = true; + Dispatcher* handler = NULL; + + Dispatcher::CallbackGeneric callback_generic; + handler = dispatcher->OnMessageReady(&ipc_params, &callback_generic); + if (handler) { + switch (params->GetParamsCount()) { + case 0: { + // Ask the IPC dispatcher if she can service this IPC. + Dispatcher::Callback0 callback = + reinterpret_cast<Dispatcher::Callback0>(callback_generic); + if (!(handler->*callback)(&ipc_info)) + break; + error = false; + break; + } + case 1: { + Dispatcher::Callback1 callback = + reinterpret_cast<Dispatcher::Callback1>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0])) + break; + error = false; + break; + } + case 2: { + Dispatcher::Callback2 callback = + reinterpret_cast<Dispatcher::Callback2>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1])) + break; + error = false; + break; + } + case 3: { + Dispatcher::Callback3 callback = + reinterpret_cast<Dispatcher::Callback3>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2])) + break; + error = false; + break; + } + case 4: { + Dispatcher::Callback4 callback = + reinterpret_cast<Dispatcher::Callback4>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], + args[3])) + break; + error = false; + break; + } + case 5: { + Dispatcher::Callback5 callback = + reinterpret_cast<Dispatcher::Callback5>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4])) + break; + error = false; + break; + } + case 6: { + Dispatcher::Callback6 callback = + reinterpret_cast<Dispatcher::Callback6>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5])) + break; + error = false; + break; + } + case 7: { + Dispatcher::Callback7 callback = + reinterpret_cast<Dispatcher::Callback7>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5], args[6])) + break; + error = false; + break; + } + case 8: { + Dispatcher::Callback8 callback = + reinterpret_cast<Dispatcher::Callback8>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5], args[6], args[7])) + break; + error = false; + break; + } + case 9: { + Dispatcher::Callback9 callback = + reinterpret_cast<Dispatcher::Callback9>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5], args[6], args[7], args[8])) + break; + error = false; + break; + } + default: { + NOTREACHED(); + break; + } + } + } + + if (error) { + if (handler) + SetCallError(SBOX_ERROR_FAILED_IPC, call_result); + } else { + memcpy(call_result, &ipc_info.return_info, sizeof(*call_result)); + SetCallSuccess(call_result); + if (params->IsInOut()) { + // Maybe the params got changed by the broker. We need to upadte the + // memory section. + memcpy(ipc_buffer, params.get(), output_size); + } + } + + ReleaseArgs(&ipc_params, args); + + return !error; +} + +// This function gets called by a thread from the thread pool when a +// ping event fires. The context is the same as passed in the RegisterWait() +// call above. +void __stdcall SharedMemIPCServer::ThreadPingEventReady(void* context, + unsigned char) { + if (NULL == context) { + DCHECK(false); + return; + } + ServerControl* service_context = reinterpret_cast<ServerControl*>(context); + // Since the event fired, the channel *must* be busy. Change to kAckChannel + // while we service it. + LONG last_state = + ::InterlockedCompareExchange(&service_context->channel->state, + kAckChannel, kBusyChannel); + if (kBusyChannel != last_state) { + DCHECK(false); + return; + } + + // Prepare the result structure. At this point we will return some result + // even if the IPC is invalid, malformed or has no handler. + CrossCallReturn call_result = {0}; + void* buffer = service_context->channel_buffer; + + InvokeCallback(service_context, buffer, &call_result); + + // Copy the answer back into the channel and signal the pong event. This + // should wake up the client so he can finish the the ipc cycle. + CrossCallParams* call_params = reinterpret_cast<CrossCallParams*>(buffer); + memcpy(call_params->GetCallReturn(), &call_result, sizeof(call_result)); + ::InterlockedExchange(&service_context->channel->state, kAckChannel); + ::SetEvent(service_context->pong_event); +} + +bool SharedMemIPCServer::MakeEvents(HANDLE* server_ping, HANDLE* server_pong, + HANDLE* client_ping, HANDLE* client_pong) { + // Note that the IPC client has no right to delete the events. That would + // cause problems. The server *owns* the events. + const DWORD kDesiredAccess = SYNCHRONIZE | EVENT_MODIFY_STATE; + + // The events are auto reset, and start not signaled. + *server_ping = ::CreateEventW(NULL, FALSE, FALSE, NULL); + if (!::DuplicateHandle(::GetCurrentProcess(), *server_ping, target_process_, + client_ping, kDesiredAccess, FALSE, 0)) { + return false; + } + *server_pong = ::CreateEventW(NULL, FALSE, FALSE, NULL); + if (!::DuplicateHandle(::GetCurrentProcess(), *server_pong, target_process_, + client_pong, kDesiredAccess, FALSE, 0)) { + return false; + } + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/sharedmem_ipc_server.h b/sandbox/win/src/sharedmem_ipc_server.h new file mode 100644 index 0000000000..94d6959b81 --- /dev/null +++ b/sandbox/win/src/sharedmem_ipc_server.h @@ -0,0 +1,127 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SHAREDMEM_IPC_SERVER_H_ +#define SANDBOX_SRC_SHAREDMEM_IPC_SERVER_H_ + +#include <list> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" + +// IPC transport implementation that uses shared memory. +// This is the server side +// +// The server side has knowledge about the layout of the shared memory +// and the state transitions. Both are explained in sharedmem_ipc_client.h +// +// As opposed to SharedMemIPClient, the Server object should be one for the +// entire lifetime of the target process. The server is in charge of creating +// the events (ping, pong) both for the client and for the target that are used +// to signal the IPC and also in charge of setting the initial state of the +// channels. +// +// When an IPC is ready, the server relies on being called by on the +// ThreadPingEventReady callback. The IPC server then retrieves the buffer, +// marshals it into a CrossCallParam object and calls the Dispatcher, who is in +// charge of fulfilling the IPC request. +namespace sandbox { + +// the shared memory implementation of the IPC server. There should be one +// of these objects per target (IPC client) process +class SharedMemIPCServer { + public: + // Creates the IPC server. + // target_process: handle to the target process. It must be suspended. + // target_process_id: process id of the target process. + // target_job: the job object handle associated with the target process. + // thread_provider: a thread provider object. + // dispatcher: an object that can service IPC calls. + SharedMemIPCServer(HANDLE target_process, DWORD target_process_id, + HANDLE target_job, ThreadProvider* thread_provider, + Dispatcher* dispatcher); + + ~SharedMemIPCServer(); + + // Initializes the server structures, shared memory structures and + // creates the kernels events used to signal the IPC. + bool Init(void* shared_mem, uint32 shared_size, uint32 channel_size); + + private: + // Allow tests to be marked DISABLED_. Note that FLAKY_ and FAILS_ prefixes + // do not work with sandbox tests. + FRIEND_TEST_ALL_PREFIXES(IPCTest, SharedMemServerTests); + // When an event fires (IPC request). A thread from the ThreadProvider + // will call this function. The context parameter should be the same as + // provided when ThreadProvider::RegisterWait was called. + static void __stdcall ThreadPingEventReady(void* context, + unsigned char); + + // Makes the client and server events. This function is called once + // per channel. + bool MakeEvents(HANDLE* server_ping, HANDLE* server_pong, + HANDLE* client_ping, HANDLE* client_pong); + + // A copy this structure is maintained per channel. + // Note that a lot of the fields are just the same of what we have in the IPC + // object itself. It is better to have the copies since we can dispatch in the + // static method without worrying about converting back to a member function + // call or about threading issues. + struct ServerControl { + // This channel server ping event. + HANDLE ping_event; + // This channel server pong event. + HANDLE pong_event; + // The size of this channel. + uint32 channel_size; + // The pointer to the actual channel data. + char* channel_buffer; + // The pointer to the base of the shared memory. + char* shared_base; + // A pointer to this channel's client-side control structure this structure + // lives in the shared memory. + ChannelControl* channel; + // the IPC dispatcher associated with this channel. + Dispatcher* dispatcher; + // The target process information associated with this channel. + ClientInfo target_info; + }; + + // Looks for the appropriate handler for this IPC and invokes it. + static bool InvokeCallback(const ServerControl* service_context, + void* ipc_buffer, CrossCallReturn* call_result); + + // Points to the shared memory channel control which lives at + // the start of the shared section. + IPCControl* client_control_; + + // Keeps track of the server side objects that are used to answer an IPC. + typedef std::list<ServerControl*> ServerContexts; + ServerContexts server_contexts_; + + // The thread provider provides the threads that call back into this object + // when the IPC events fire. + ThreadProvider* thread_provider_; + + // The IPC object is associated with a target process. + HANDLE target_process_; + + // The target process id associated with the IPC object. + DWORD target_process_id_; + + // The target object is inside a job too. + HANDLE target_job_object_; + + // The dispatcher handles 'ready' IPC calls. + Dispatcher* call_dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(SharedMemIPCServer); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SHAREDMEM_IPC_SERVER_H_ diff --git a/sandbox/win/src/sid.cc b/sandbox/win/src/sid.cc new file mode 100644 index 0000000000..261605d547 --- /dev/null +++ b/sandbox/win/src/sid.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sid.h" + +#include "base/logging.h" + +namespace sandbox { + +Sid::Sid(const SID *sid) { + ::CopySid(SECURITY_MAX_SID_SIZE, sid_, const_cast<SID*>(sid)); +}; + +Sid::Sid(WELL_KNOWN_SID_TYPE type) { + DWORD size_sid = SECURITY_MAX_SID_SIZE; + BOOL result = ::CreateWellKnownSid(type, NULL, sid_, &size_sid); + DCHECK(result); + DBG_UNREFERENCED_LOCAL_VARIABLE(result); +} + +const SID *Sid::GetPSID() const { + return reinterpret_cast<SID*>(const_cast<BYTE*>(sid_)); +} + +} // namespace sandbox diff --git a/sandbox/win/src/sid.h b/sandbox/win/src/sid.h new file mode 100644 index 0000000000..4656859bec --- /dev/null +++ b/sandbox/win/src/sid.h @@ -0,0 +1,29 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SID_H_ +#define SANDBOX_SRC_SID_H_ + +#include <windows.h> + +namespace sandbox { + +// This class is used to hold and generate SIDS. +class Sid { + public: + // Constructors initializing the object with the SID passed. + // This is a converting constructor. It is not explicit. + Sid(const SID *sid); + Sid(WELL_KNOWN_SID_TYPE type); + + // Returns sid_. + const SID *GetPSID() const; + + private: + BYTE sid_[SECURITY_MAX_SID_SIZE]; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SID_H_ diff --git a/sandbox/win/src/sid_unittest.cc b/sandbox/win/src/sid_unittest.cc new file mode 100644 index 0000000000..76d61e82f5 --- /dev/null +++ b/sandbox/win/src/sid_unittest.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for the sid class. + +#define _ATL_NO_EXCEPTIONS +#include <atlbase.h> +#include <atlsecurity.h> + +#include "sandbox/win/src/sid.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Calls ::EqualSid. This function exists only to simplify the calls to +// ::EqualSid by removing the need to cast the input params. +BOOL EqualSid(const SID *sid1, const SID *sid2) { + return ::EqualSid(const_cast<SID*>(sid1), const_cast<SID*>(sid2)); +} + +// Tests the creation if a Sid +TEST(SidTest, Constructors) { + ATL::CSid sid_world = ATL::Sids::World(); + SID *sid_world_pointer = const_cast<SID*>(sid_world.GetPSID()); + + // Check the SID* constructor + Sid sid_sid_star(sid_world_pointer); + ASSERT_TRUE(EqualSid(sid_world_pointer, sid_sid_star.GetPSID())); + + // Check the copy constructor + Sid sid_copy(sid_sid_star); + ASSERT_TRUE(EqualSid(sid_world_pointer, sid_copy.GetPSID())); + + // Note that the WELL_KNOWN_SID_TYPE constructor is tested in the GetPSID + // test. +} + +// Tests the method GetPSID +TEST(SidTest, GetPSID) { + // Check for non-null result; + ASSERT_NE(static_cast<SID*>(NULL), Sid(::WinLocalSid).GetPSID()); + ASSERT_NE(static_cast<SID*>(NULL), Sid(::WinCreatorOwnerSid).GetPSID()); + ASSERT_NE(static_cast<SID*>(NULL), Sid(::WinBatchSid).GetPSID()); + + ASSERT_TRUE(EqualSid(Sid(::WinNullSid).GetPSID(), + ATL::Sids::Null().GetPSID())); + + ASSERT_TRUE(EqualSid(Sid(::WinWorldSid).GetPSID(), + ATL::Sids::World().GetPSID())); + + ASSERT_TRUE(EqualSid(Sid(::WinDialupSid).GetPSID(), + ATL::Sids::Dialup().GetPSID())); + + ASSERT_TRUE(EqualSid(Sid(::WinNetworkSid).GetPSID(), + ATL::Sids::Network().GetPSID())); + + ASSERT_TRUE(EqualSid(Sid(::WinBuiltinAdministratorsSid).GetPSID(), + ATL::Sids::Admins().GetPSID())); + + ASSERT_TRUE(EqualSid(Sid(::WinBuiltinUsersSid).GetPSID(), + ATL::Sids::Users().GetPSID())); + + ASSERT_TRUE(EqualSid(Sid(::WinBuiltinGuestsSid).GetPSID(), + ATL::Sids::Guests().GetPSID())); + + ASSERT_TRUE(EqualSid(Sid(::WinProxySid).GetPSID(), + ATL::Sids::Proxy().GetPSID())); +} + +} // namespace sandbox diff --git a/sandbox/win/src/sidestep/ia32_modrm_map.cpp b/sandbox/win/src/sidestep/ia32_modrm_map.cpp new file mode 100644 index 0000000000..89bc1895eb --- /dev/null +++ b/sandbox/win/src/sidestep/ia32_modrm_map.cpp @@ -0,0 +1,92 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Table of relevant information about how to decode the ModR/M byte. +// Based on information in the IA-32 Intel Architecture +// Software Developer's Manual Volume 2: Instruction Set Reference. + +#include "sandbox/win/src/sidestep/mini_disassembler.h" +#include "sandbox/win/src/sidestep/mini_disassembler_types.h" + +namespace sidestep { + +const ModrmEntry MiniDisassembler::s_ia16_modrm_map_[] = { +// mod == 00 + /* r/m == 000 */ { false, false, OS_ZERO }, + /* r/m == 001 */ { false, false, OS_ZERO }, + /* r/m == 010 */ { false, false, OS_ZERO }, + /* r/m == 011 */ { false, false, OS_ZERO }, + /* r/m == 100 */ { false, false, OS_ZERO }, + /* r/m == 101 */ { false, false, OS_ZERO }, + /* r/m == 110 */ { true, false, OS_WORD }, + /* r/m == 111 */ { false, false, OS_ZERO }, +// mod == 01 + /* r/m == 000 */ { true, false, OS_BYTE }, + /* r/m == 001 */ { true, false, OS_BYTE }, + /* r/m == 010 */ { true, false, OS_BYTE }, + /* r/m == 011 */ { true, false, OS_BYTE }, + /* r/m == 100 */ { true, false, OS_BYTE }, + /* r/m == 101 */ { true, false, OS_BYTE }, + /* r/m == 110 */ { true, false, OS_BYTE }, + /* r/m == 111 */ { true, false, OS_BYTE }, +// mod == 10 + /* r/m == 000 */ { true, false, OS_WORD }, + /* r/m == 001 */ { true, false, OS_WORD }, + /* r/m == 010 */ { true, false, OS_WORD }, + /* r/m == 011 */ { true, false, OS_WORD }, + /* r/m == 100 */ { true, false, OS_WORD }, + /* r/m == 101 */ { true, false, OS_WORD }, + /* r/m == 110 */ { true, false, OS_WORD }, + /* r/m == 111 */ { true, false, OS_WORD }, +// mod == 11 + /* r/m == 000 */ { false, false, OS_ZERO }, + /* r/m == 001 */ { false, false, OS_ZERO }, + /* r/m == 010 */ { false, false, OS_ZERO }, + /* r/m == 011 */ { false, false, OS_ZERO }, + /* r/m == 100 */ { false, false, OS_ZERO }, + /* r/m == 101 */ { false, false, OS_ZERO }, + /* r/m == 110 */ { false, false, OS_ZERO }, + /* r/m == 111 */ { false, false, OS_ZERO } +}; + +const ModrmEntry MiniDisassembler::s_ia32_modrm_map_[] = { +// mod == 00 + /* r/m == 000 */ { false, false, OS_ZERO }, + /* r/m == 001 */ { false, false, OS_ZERO }, + /* r/m == 010 */ { false, false, OS_ZERO }, + /* r/m == 011 */ { false, false, OS_ZERO }, + /* r/m == 100 */ { false, true, OS_ZERO }, + /* r/m == 101 */ { true, false, OS_DOUBLE_WORD }, + /* r/m == 110 */ { false, false, OS_ZERO }, + /* r/m == 111 */ { false, false, OS_ZERO }, +// mod == 01 + /* r/m == 000 */ { true, false, OS_BYTE }, + /* r/m == 001 */ { true, false, OS_BYTE }, + /* r/m == 010 */ { true, false, OS_BYTE }, + /* r/m == 011 */ { true, false, OS_BYTE }, + /* r/m == 100 */ { true, true, OS_BYTE }, + /* r/m == 101 */ { true, false, OS_BYTE }, + /* r/m == 110 */ { true, false, OS_BYTE }, + /* r/m == 111 */ { true, false, OS_BYTE }, +// mod == 10 + /* r/m == 000 */ { true, false, OS_DOUBLE_WORD }, + /* r/m == 001 */ { true, false, OS_DOUBLE_WORD }, + /* r/m == 010 */ { true, false, OS_DOUBLE_WORD }, + /* r/m == 011 */ { true, false, OS_DOUBLE_WORD }, + /* r/m == 100 */ { true, true, OS_DOUBLE_WORD }, + /* r/m == 101 */ { true, false, OS_DOUBLE_WORD }, + /* r/m == 110 */ { true, false, OS_DOUBLE_WORD }, + /* r/m == 111 */ { true, false, OS_DOUBLE_WORD }, +// mod == 11 + /* r/m == 000 */ { false, false, OS_ZERO }, + /* r/m == 001 */ { false, false, OS_ZERO }, + /* r/m == 010 */ { false, false, OS_ZERO }, + /* r/m == 011 */ { false, false, OS_ZERO }, + /* r/m == 100 */ { false, false, OS_ZERO }, + /* r/m == 101 */ { false, false, OS_ZERO }, + /* r/m == 110 */ { false, false, OS_ZERO }, + /* r/m == 111 */ { false, false, OS_ZERO }, +}; + +}; // namespace sidestep diff --git a/sandbox/win/src/sidestep/ia32_opcode_map.cpp b/sandbox/win/src/sidestep/ia32_opcode_map.cpp new file mode 100644 index 0000000000..b7d8a60bc1 --- /dev/null +++ b/sandbox/win/src/sidestep/ia32_opcode_map.cpp @@ -0,0 +1,1159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Opcode decoding maps. Based on the IA-32 Intel Architecture +// Software Developer's Manual Volume 2: Instruction Set Reference. Idea +// for how to lay out the tables in memory taken from the implementation +// in the Bastard disassembly environment. + +#include "sandbox/win/src/sidestep/mini_disassembler.h" + +namespace sidestep { + +/* +* This is the first table to be searched; the first field of each +* Opcode in the table is either 0 to indicate you're in the +* right table, or an index to the correct table, in the global +* map g_pentiumOpcodeMap +*/ +const Opcode s_first_opcode_byte[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "add", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "add", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_G | OT_B, AM_E | OT_B, AM_NOT_USED, "add", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "add", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "add", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "add", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "or", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "or", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA */ { 0, IT_GENERIC, AM_G | OT_B, AM_E | OT_B, AM_NOT_USED, "or", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "or", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "or", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "or", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF */ { 1, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x10 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "adc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x11 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "adc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x12 */ { 0, IT_GENERIC, AM_G | OT_B, AM_E | OT_B, AM_NOT_USED, "adc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x13 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "adc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x14 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "adc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x15 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "adc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x16 */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x17 */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x18 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "sbb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x19 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "sbb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1A */ { 0, IT_GENERIC, AM_G | OT_B, AM_E | OT_B, AM_NOT_USED, "sbb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1B */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "sbb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1C */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "sbb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1D */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "sbb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1E */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1F */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x20 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "and", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x21 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "and", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x22 */ { 0, IT_GENERIC, AM_G | OT_B, AM_E | OT_B, AM_NOT_USED, "and", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x23 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "and", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x24 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "and", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x25 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "and", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x26 */ { 0, IT_PREFIX, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x27 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "daa", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x28 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "sub", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x29 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "sub", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2A */ { 0, IT_GENERIC, AM_G | OT_B, AM_E | OT_B, AM_NOT_USED, "sub", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2B */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "sub", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2C */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "sub", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2D */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "sub", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2E */ { 0, IT_PREFIX, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2F */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "das", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x30 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "xor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x31 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "xor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x32 */ { 0, IT_GENERIC, AM_G | OT_B, AM_E | OT_B, AM_NOT_USED, "xor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x33 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "xor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x34 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "xor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x35 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "xor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x36 */ { 0, IT_PREFIX, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x37 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "aaa", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x38 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "cmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x39 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "cmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3A */ { 0, IT_GENERIC, AM_G | OT_B, AM_E | OT_B, AM_NOT_USED, "cmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3B */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3C */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "cmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3D */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "cmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3E */ { 0, IT_PREFIX, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3F */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "aas", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x40 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "inc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x41 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "inc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x42 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "inc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x43 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "inc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x44 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "inc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x45 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "inc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x46 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "inc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x47 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "inc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x48 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "dec", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x49 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "dec", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4A */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "dec", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4B */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "dec", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4C */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "dec", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4D */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "dec", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4E */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "dec", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4F */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "dec", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x50 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x51 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x52 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x53 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x54 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x55 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x56 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x57 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x58 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x59 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5A */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5B */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5C */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5D */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5E */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5F */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x60 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "pushad", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x61 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "popad", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x62 */ { 0, IT_GENERIC, AM_G | OT_V, AM_M | OT_A, AM_NOT_USED, "bound", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x63 */ { 0, IT_GENERIC, AM_E | OT_W, AM_G | OT_W, AM_NOT_USED, "arpl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x64 */ { 0, IT_PREFIX, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x65 */ { 0, IT_PREFIX, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x66 */ { 0, IT_PREFIX_OPERAND, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x67 */ { 0, IT_PREFIX_ADDRESS, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x68 */ { 0, IT_GENERIC, AM_I | OT_V, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x69 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_I | OT_V, "imul", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6A */ { 0, IT_GENERIC, AM_I | OT_B, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6B */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_I | OT_B, "imul", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6C */ { 0, IT_GENERIC, AM_Y | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "insb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6D */ { 0, IT_GENERIC, AM_Y | OT_V, AM_REGISTER | OT_V, AM_NOT_USED, "insd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6E */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_X | OT_B, AM_NOT_USED, "outsb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6F */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_X | OT_V, AM_NOT_USED, "outsb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x70 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jo", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x71 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jno", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x72 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x73 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jnc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x74 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x75 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jnz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x76 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jbe", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x77 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "ja", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x78 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "js", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x79 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jns", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7A */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jpe", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7B */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jpo", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7C */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7D */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jge", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7E */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jle", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7F */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x80 */ { 2, IT_REFERENCE, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x81 */ { 3, IT_REFERENCE, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x82 */ { 4, IT_REFERENCE, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x83 */ { 5, IT_REFERENCE, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x84 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "test", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x85 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "test", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x86 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "xchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x87 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "xchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x88 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x89 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8A */ { 0, IT_GENERIC, AM_G | OT_B, AM_E | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8B */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8C */ { 0, IT_GENERIC, AM_E | OT_W, AM_S | OT_W, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8D */ { 0, IT_GENERIC, AM_G | OT_V, AM_M | OT_ADDRESS_MODE_M, AM_NOT_USED, "lea", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8E */ { 0, IT_GENERIC, AM_S | OT_W, AM_E | OT_W, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8F */ { 0, IT_GENERIC, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x90 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "nop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x91 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_REGISTER | OT_V, AM_NOT_USED, "xchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x92 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_REGISTER | OT_V, AM_NOT_USED, "xchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x93 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_REGISTER | OT_V, AM_NOT_USED, "xchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x94 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_REGISTER | OT_V, AM_NOT_USED, "xchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x95 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_REGISTER | OT_V, AM_NOT_USED, "xchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x96 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_REGISTER | OT_V, AM_NOT_USED, "xchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x97 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_REGISTER | OT_V, AM_NOT_USED, "xchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x98 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "cwde", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x99 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "cdq", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9A */ { 0, IT_JUMP, AM_A | OT_P, AM_NOT_USED, AM_NOT_USED, "callf", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9B */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "wait", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9C */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "pushfd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9D */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "popfd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9E */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "sahf", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9F */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "lahf", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA0 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_O | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA1 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_O | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA2 */ { 0, IT_GENERIC, AM_O | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA3 */ { 0, IT_GENERIC, AM_O | OT_V, AM_REGISTER | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA4 */ { 0, IT_GENERIC, AM_X | OT_B, AM_Y | OT_B, AM_NOT_USED, "movsb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA5 */ { 0, IT_GENERIC, AM_X | OT_V, AM_Y | OT_V, AM_NOT_USED, "movsd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA6 */ { 0, IT_GENERIC, AM_X | OT_B, AM_Y | OT_B, AM_NOT_USED, "cmpsb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA7 */ { 0, IT_GENERIC, AM_X | OT_V, AM_Y | OT_V, AM_NOT_USED, "cmpsd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA8 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "test", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA9 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "test", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAA */ { 0, IT_GENERIC, AM_Y | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "stosb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAB */ { 0, IT_GENERIC, AM_Y | OT_V, AM_REGISTER | OT_V, AM_NOT_USED, "stosd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAC */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_X| OT_B, AM_NOT_USED, "lodsb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAD */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_X| OT_V, AM_NOT_USED, "lodsd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAE */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_Y | OT_B, AM_NOT_USED, "scasb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAF */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_Y | OT_V, AM_NOT_USED, "scasd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB0 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB1 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB2 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB3 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB4 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB5 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB6 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB7 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB8 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB9 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBA */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBB */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBC */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBD */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBE */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBF */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_I | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC0 */ { 6, IT_REFERENCE, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC1 */ { 7, IT_REFERENCE, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC2 */ { 0, IT_RETURN, AM_I | OT_W, AM_NOT_USED, AM_NOT_USED, "ret", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC3 */ { 0, IT_RETURN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "ret", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC4 */ { 0, IT_GENERIC, AM_G | OT_V, AM_M | OT_P, AM_NOT_USED, "les", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC5 */ { 0, IT_GENERIC, AM_G | OT_V, AM_M | OT_P, AM_NOT_USED, "lds", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC6 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC7 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC8 */ { 0, IT_GENERIC, AM_I | OT_W, AM_I | OT_B, AM_NOT_USED, "enter", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC9 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "leave", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCA */ { 0, IT_RETURN, AM_I | OT_W, AM_NOT_USED, AM_NOT_USED, "retf", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCB */ { 0, IT_RETURN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "retf", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCC */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "int3", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCD */ { 0, IT_GENERIC, AM_I | OT_B, AM_NOT_USED, AM_NOT_USED, "int", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCE */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "into", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCF */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "iret", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD0 */ { 8, IT_REFERENCE, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD1 */ { 9, IT_REFERENCE, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD2 */ { 10, IT_REFERENCE, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD3 */ { 11, IT_REFERENCE, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD4 */ { 0, IT_GENERIC, AM_I | OT_B, AM_NOT_USED, AM_NOT_USED, "aam", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD5 */ { 0, IT_GENERIC, AM_I | OT_B, AM_NOT_USED, AM_NOT_USED, "aad", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD6 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD7 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "xlat", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + + // The following 8 lines would be references to the FPU tables, but we currently + // do not support the FPU instructions in this disassembler. + + /* 0xD8 */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD9 */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xDA */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xDB */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xDC */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xDD */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xDE */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xDF */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + + + /* 0xE0 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "loopnz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE1 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "loopz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE2 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "loop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE3 */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jcxz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE4 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "in", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE5 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_I | OT_B, AM_NOT_USED, "in", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE6 */ { 0, IT_GENERIC, AM_I | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "out", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE7 */ { 0, IT_GENERIC, AM_I | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "out", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE8 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "call", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE9 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xEA */ { 0, IT_JUMP, AM_A | OT_P, AM_NOT_USED, AM_NOT_USED, "jmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xEB */ { 0, IT_JUMP, AM_J | OT_B, AM_NOT_USED, AM_NOT_USED, "jmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xEC */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_REGISTER | OT_W, AM_NOT_USED, "in", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xED */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_REGISTER | OT_W, AM_NOT_USED, "in", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xEE */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_REGISTER | OT_B, AM_NOT_USED, "out", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xEF */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_REGISTER | OT_V, AM_NOT_USED, "out", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF0 */ { 0, IT_PREFIX, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "lock:", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF1 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF2 */ { 0, IT_PREFIX, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "repne:", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF3 */ { 0, IT_PREFIX, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "rep:", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF4 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "hlt", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF5 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "cmc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF6 */ { 12, IT_REFERENCE, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF7 */ { 13, IT_REFERENCE, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF8 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "clc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF9 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "stc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xFA */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "cli", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xFB */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "sti", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xFC */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "cld", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xFD */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "std", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xFE */ { 14, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xFF */ { 15, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_0f[] = { + /* 0x0 */ { 16, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 17, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_W, AM_NOT_USED, "lar", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_W, AM_NOT_USED, "lsl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "clts", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "invd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "wbinvd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "ud2", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xE */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x10 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "movups", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "movsd" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "movss" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "movupd" } }, + /* 0x11 */ { 0, IT_GENERIC, AM_W | OT_PS, AM_V | OT_PS, AM_NOT_USED, "movups", true, + /* F2h */ { 0, IT_GENERIC, AM_W | OT_SD, AM_V | OT_SD, AM_NOT_USED, "movsd" }, + /* F3h */ { 0, IT_GENERIC, AM_W | OT_SS, AM_V | OT_SS, AM_NOT_USED, "movss" }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_PD, AM_V | OT_PD, AM_NOT_USED, "movupd" } }, + /* 0x12 */ { 0, IT_GENERIC, AM_W | OT_Q, AM_V | OT_Q, AM_NOT_USED, "movlps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_Q, AM_V | OT_Q, AM_NOT_USED, "movhlps" }, // only one of ... + /* F3h */ { 0, IT_GENERIC, AM_V | OT_Q, AM_V | OT_Q, AM_NOT_USED, "movhlps" }, // ...these two is correct, Intel doesn't specify which + /* 66h */ { 0, IT_GENERIC, AM_V | OT_Q, AM_W | OT_S, AM_NOT_USED, "movlpd" } }, + /* 0x13 */ { 0, IT_GENERIC, AM_V | OT_Q, AM_W | OT_Q, AM_NOT_USED, "movlps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_Q, AM_W | OT_Q, AM_NOT_USED, "movlpd" } }, + /* 0x14 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_Q, AM_NOT_USED, "unpcklps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_Q, AM_NOT_USED, "unpcklpd" } }, + /* 0x15 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_Q, AM_NOT_USED, "unpckhps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_Q, AM_NOT_USED, "unpckhpd" } }, + /* 0x16 */ { 0, IT_GENERIC, AM_V | OT_Q, AM_W | OT_Q, AM_NOT_USED, "movhps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_Q, AM_V | OT_Q, AM_NOT_USED, "movlhps" }, // only one of... + /* F3h */ { 0, IT_GENERIC, AM_V | OT_Q, AM_V | OT_Q, AM_NOT_USED, "movlhps" }, // ...these two is correct, Intel doesn't specify which + /* 66h */ { 0, IT_GENERIC, AM_V | OT_Q, AM_W | OT_Q, AM_NOT_USED, "movhpd" } }, + /* 0x17 */ { 0, IT_GENERIC, AM_W | OT_Q, AM_V | OT_Q, AM_NOT_USED, "movhps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_Q, AM_V | OT_Q, AM_NOT_USED, "movhpd" } }, + /* 0x18 */ { 18, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x19 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1A */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1B */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1C */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1D */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1E */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1F */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x20 */ { 0, IT_GENERIC, AM_R | OT_D, AM_C | OT_D, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x21 */ { 0, IT_GENERIC, AM_R | OT_D, AM_D | OT_D, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x22 */ { 0, IT_GENERIC, AM_C | OT_D, AM_R | OT_D, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x23 */ { 0, IT_GENERIC, AM_D | OT_D, AM_R | OT_D, AM_NOT_USED, "mov", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x24 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x25 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x26 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x27 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x28 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "movaps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "movapd" } }, + /* 0x29 */ { 0, IT_GENERIC, AM_W | OT_PS, AM_V | OT_PS, AM_NOT_USED, "movaps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_PD, AM_V | OT_PD, AM_NOT_USED, "movapd" } }, + /* 0x2A */ { 0, IT_GENERIC, AM_V | OT_PS, AM_Q | OT_Q, AM_NOT_USED, "cvtpi2ps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_E | OT_D, AM_NOT_USED, "cvtsi2sd" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_E | OT_D, AM_NOT_USED, "cvtsi2ss" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_Q | OT_DQ, AM_NOT_USED, "cvtpi2pd" } }, + /* 0x2B */ { 0, IT_GENERIC, AM_W | OT_PS, AM_V | OT_PS, AM_NOT_USED, "movntps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_PD, AM_V | OT_PD, AM_NOT_USED, "movntpd" } }, + /* 0x2C */ { 0, IT_GENERIC, AM_Q | OT_Q, AM_W | OT_PS, AM_NOT_USED, "cvttps2pi", true, + /* F2h */ { 0, IT_GENERIC, AM_G | OT_D, AM_W | OT_SD, AM_NOT_USED, "cvttsd2si" }, + /* F3h */ { 0, IT_GENERIC, AM_G | OT_D, AM_W | OT_SS, AM_NOT_USED, "cvttss2si" }, + /* 66h */ { 0, IT_GENERIC, AM_Q | OT_DQ, AM_W | OT_PD, AM_NOT_USED, "cvttpd2pi" } }, + /* 0x2D */ { 0, IT_GENERIC, AM_Q | OT_Q, AM_W | OT_PS, AM_NOT_USED, "cvtps2pi", true, + /* F2h */ { 0, IT_GENERIC, AM_G | OT_D, AM_W | OT_SD, AM_NOT_USED, "cvtsd2si" }, + /* F3h */ { 0, IT_GENERIC, AM_G | OT_D, AM_W | OT_SS, AM_NOT_USED, "cvtss2si" }, + /* 66h */ { 0, IT_GENERIC, AM_Q | OT_DQ, AM_W | OT_PD, AM_NOT_USED, "cvtpd2pi" } }, + /* 0x2E */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "ucomiss", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "ucomisd" } }, + /* 0x2F */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_SS, AM_NOT_USED, "comiss", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "comisd" } }, + /* 0x30 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "wrmsr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x31 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "rdtsc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x32 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "rdmsr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x33 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "rdpmc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x34 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "sysenter", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x35 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "sysexit", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x36 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x37 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x38 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x39 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3A */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3B */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3C */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "movnti", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3D */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3E */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3F */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x40 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovo", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x41 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovno", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x42 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x43 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovnc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x44 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x45 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovnz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x46 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovbe", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x47 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmova", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x48 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovs", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x49 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovns", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4A */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovpe", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4B */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovpo", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4C */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4D */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovge", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4E */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovle", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4F */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "cmovg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x50 */ { 0, IT_GENERIC, AM_E | OT_D, AM_V | OT_PS, AM_NOT_USED, "movmskps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_E | OT_D, AM_V | OT_PD, AM_NOT_USED, "movmskpd" } }, + /* 0x51 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "sqrtps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "sqrtsd" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "sqrtss" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "sqrtpd" } }, + /* 0x52 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "rsqrtps", true, + /* F2h */ { 0 }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "rsqrtss" }, + /* 66h */ { 0 } }, + /* 0x53 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "rcpps", true, + /* F2h */ { 0 }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "rcpss" }, + /* 66h */ { 0 } }, + /* 0x54 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "andps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "andpd" } }, + /* 0x55 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "andnps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "andnpd" } }, + /* 0x56 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "orps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "orpd" } }, + /* 0x57 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "xorps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "xorpd" } }, + /* 0x58 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "addps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "addsd" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "addss" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "addpd" } }, + /* 0x59 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "mulps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "mulsd" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "mulss" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "mulpd" } }, + /* 0x5A */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PS, AM_NOT_USED, "cvtps2pd", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "cvtsd2ss" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "cvtss2sd" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PD, AM_NOT_USED, "cvtpd2ps" } }, + /* 0x5B */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_DQ, AM_NOT_USED, "cvtdq2ps", true, + /* F2h */ { 0 }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_PS, AM_NOT_USED, "cvttps2dq" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_PS, AM_NOT_USED, "cvtps2dq" } }, + /* 0x5C */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "subps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "subsd" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "subss" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "subpd" } }, + /* 0x5D */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "minps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "minsd" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "minss" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "minpd" } }, + /* 0x5E */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "divps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "divsd" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "divss" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "divpd" } }, + /* 0x5F */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_NOT_USED, "maxps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_NOT_USED, "maxsd" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_NOT_USED, "maxss" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_NOT_USED, "maxpd" } }, + /* 0x60 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "punpcklbw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "punpcklbw" } }, + /* 0x61 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "punpcklwd", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "punpcklwd" } }, + /* 0x62 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "punpckldq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "punpckldq" } }, + /* 0x63 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "packsswb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "packsswb" } }, + /* 0x64 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "pcmpgtb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pcmpgtb" } }, + /* 0x65 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "pcmpgtw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pcmpgtw" } }, + /* 0x66 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "pcmpgtd", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pcmpgtd" } }, + /* 0x67 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "packuswb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "packuswb" } }, + /* 0x68 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "punpckhbw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_P | OT_DQ, AM_Q | OT_DQ, AM_NOT_USED, "punpckhbw" } }, + /* 0x69 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "punpckhwd", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_P | OT_DQ, AM_Q | OT_DQ, AM_NOT_USED, "punpckhwd" } }, + /* 0x6A */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "punpckhdq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_P | OT_DQ, AM_Q | OT_DQ, AM_NOT_USED, "punpckhdq" } }, + /* 0x6B */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "packssdw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_P | OT_DQ, AM_Q | OT_DQ, AM_NOT_USED, "packssdw" } }, + /* 0x6C */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "not used without prefix", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "punpcklqdq" } }, + /* 0x6D */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "not used without prefix", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "punpcklqdq" } }, + /* 0x6E */ { 0, IT_GENERIC, AM_P | OT_D, AM_E | OT_D, AM_NOT_USED, "movd", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_E | OT_D, AM_NOT_USED, "movd" } }, + /* 0x6F */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_D, AM_NOT_USED, "movq", true, + /* F2h */ { 0 }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "movdqu" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "movdqa" } }, + /* 0x70 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_I | OT_B, "pshuf", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_I | OT_B, "pshuflw" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_I | OT_B, "pshufhw" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_I | OT_B, "pshufd" } }, + /* 0x71 */ { 19, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x72 */ { 20, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x73 */ { 21, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x74 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pcmpeqb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pcmpeqb" } }, + /* 0x75 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pcmpeqw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pcmpeqw" } }, + /* 0x76 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pcmpeqd", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pcmpeqd" } }, + /* 0x77 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "emms", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + + // The following six opcodes are escapes into the MMX stuff, which this disassembler does not support. + /* 0x78 */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x79 */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7A */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7B */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7C */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7D */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + + /* 0x7E */ { 0, IT_GENERIC, AM_E | OT_D, AM_P | OT_D, AM_NOT_USED, "movd", true, + /* F2h */ { 0 }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_Q, AM_W | OT_Q, AM_NOT_USED, "movq" }, + /* 66h */ { 0, IT_GENERIC, AM_E | OT_D, AM_V | OT_DQ, AM_NOT_USED, "movd" } }, + /* 0x7F */ { 0, IT_GENERIC, AM_Q | OT_Q, AM_P | OT_Q, AM_NOT_USED, "movq", true, + /* F2h */ { 0 }, + /* F3h */ { 0, IT_GENERIC, AM_W | OT_DQ, AM_V | OT_DQ, AM_NOT_USED, "movdqu" }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_DQ, AM_V | OT_DQ, AM_NOT_USED, "movdqa" } }, + /* 0x80 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jo", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x81 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jno", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x82 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x83 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jnc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x84 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x85 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jnz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x86 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jbe", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x87 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "ja", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x88 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "js", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x89 */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jns", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8A */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jpe", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8B */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jpo", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8C */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8D */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jge", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8E */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jle", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x8F */ { 0, IT_JUMP, AM_J | OT_V, AM_NOT_USED, AM_NOT_USED, "jg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x90 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "seto", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x91 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setno", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x92 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x93 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setnc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x94 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x95 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setnz", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x96 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setbe", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x97 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "seta", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x98 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "sets", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x99 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setns", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9A */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setpe", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9B */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setpo", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9C */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9D */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setge", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9E */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setle", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x9F */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "setg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA0 */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA1 */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA2 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "cpuid", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA3 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "bt", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA4 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_I | OT_B, "shld", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA5 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_I | OT_B | AM_REGISTER, "shld", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA6 */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA7 */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA8 */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xA9 */ { 0, IT_GENERIC, AM_REGISTER | OT_W, AM_NOT_USED, AM_NOT_USED, "pop", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAA */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "rsm", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAB */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "bts", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAC */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_I | OT_B, "shrd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAD */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_I | OT_B | AM_REGISTER, "shrd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAE */ { 22, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xAF */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "imul", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB0 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "cmpxchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "cmpxchg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB2 */ { 0, IT_GENERIC, AM_M | OT_P, AM_NOT_USED, AM_NOT_USED, "lss", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB3 */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "btr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB4 */ { 0, IT_GENERIC, AM_M | OT_P, AM_NOT_USED, AM_NOT_USED, "lfs", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB5 */ { 0, IT_GENERIC, AM_M | OT_P, AM_NOT_USED, AM_NOT_USED, "lgs", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB6 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_B, AM_NOT_USED, "movzx", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB7 */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_W, AM_NOT_USED, "movzx", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB8 */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xB9 */ { 0, IT_UNKNOWN, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "ud1", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBA */ { 23, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBB */ { 0, IT_GENERIC, AM_E | OT_V, AM_G | OT_V, AM_NOT_USED, "btc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBC */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "bsf", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBD */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_V, AM_NOT_USED, "bsr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBE */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_B, AM_NOT_USED, "movsx", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xBF */ { 0, IT_GENERIC, AM_G | OT_V, AM_E | OT_W, AM_NOT_USED, "movsx", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC0 */ { 0, IT_GENERIC, AM_E | OT_B, AM_G | OT_B, AM_NOT_USED, "xadd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, "xadd", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC2 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_I | OT_B, "cmpps", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_SD, AM_W | OT_SD, AM_I | OT_B, "cmpsd" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_SS, AM_W | OT_SS, AM_I | OT_B, "cmpss" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_I | OT_B, "cmppd" } }, + /* 0xC3 */ { 0, IT_GENERIC, AM_E | OT_D, AM_G | OT_D, AM_NOT_USED, "movnti", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC4 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_E | OT_D, AM_I | OT_B, "pinsrw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_E | OT_D, AM_I | OT_B, "pinsrw" } }, + /* 0xC5 */ { 0, IT_GENERIC, AM_G | OT_D, AM_P | OT_Q, AM_I | OT_B, "pextrw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_G | OT_D, AM_V | OT_DQ, AM_I | OT_B, "pextrw" } }, + /* 0xC6 */ { 0, IT_GENERIC, AM_V | OT_PS, AM_W | OT_PS, AM_I | OT_B, "shufps", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_PD, AM_I | OT_B, "shufpd" } }, + /* 0xC7 */ { 24, IT_REFERENCE, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC8 */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "bswap", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xC9 */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "bswap", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCA */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "bswap", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCB */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "bswap", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCC */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "bswap", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCD */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "bswap", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCE */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "bswap", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xCF */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "bswap", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD0 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xD1 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psrlw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psrlw" } }, + /* 0xD2 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psrld", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psrld" } }, + /* 0xD3 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psrlq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psrlq" } }, + /* 0xD4 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "paddq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "paddq" } }, + /* 0xD5 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pmullw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pmullw" } }, + /* 0xD6 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "unused without prefix", true, + /* F2h */ { 0, IT_GENERIC, AM_P | OT_Q, AM_W | OT_Q, AM_NOT_USED, "movdq2q" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_Q | OT_Q, AM_NOT_USED, "movq2dq" }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_Q, AM_V | OT_Q, AM_NOT_USED, "movq" } }, + /* 0xD7 */ { 0, IT_GENERIC, AM_G | OT_D, AM_P | OT_Q, AM_NOT_USED, "pmovmskb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_G | OT_D, AM_V | OT_DQ, AM_NOT_USED, "pmovmskb" } }, + /* 0xD8 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psubusb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psubusb" } }, + /* 0xD9 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psubusw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psubusw" } }, + /* 0xDA */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pminub", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pminub" } }, + /* 0xDB */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pand", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pand" } }, + /* 0xDC */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "paddusb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "paddusb" } }, + /* 0xDD */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "paddusw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "paddusw" } }, + /* 0xDE */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pmaxub", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pmaxub" } }, + /* 0xDF */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pandn", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pandn" } }, + /* 0xE0 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pavgb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pavgb" } }, + /* 0xE1 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psraw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psrqw" } }, + /* 0xE2 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psrad", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psrad" } }, + /* 0xE3 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pavgw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pavgw" } }, + /* 0xE4 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pmulhuw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pmulhuw" } }, + /* 0xE5 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pmulhuw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pmulhw" } }, + /* 0xE6 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "not used without prefix", true, + /* F2h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_PD, AM_NOT_USED, "cvtpd2dq" }, + /* F3h */ { 0, IT_GENERIC, AM_V | OT_PD, AM_W | OT_DQ, AM_NOT_USED, "cvtdq2pd" }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_PD, AM_NOT_USED, "cvttpd2dq" } }, + /* 0xE7 */ { 0, IT_GENERIC, AM_W | OT_Q, AM_V | OT_Q, AM_NOT_USED, "movntq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_DQ, AM_V | OT_DQ, AM_NOT_USED, "movntdq" } }, + /* 0xE8 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psubsb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psubsb" } }, + /* 0xE9 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psubsw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psubsw" } }, + /* 0xEA */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pminsw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pminsw" } }, + /* 0xEB */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "por", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "por" } }, + /* 0xEC */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "paddsb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "paddsb" } }, + /* 0xED */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "paddsw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "paddsw" } }, + /* 0xEE */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pmaxsw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pmaxsw" } }, + /* 0xEF */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pxor", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pxor" } }, + /* 0xF0 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0xF1 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psllw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psllw" } }, + /* 0xF2 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pslld", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pslld" } }, + /* 0xF3 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psllq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psllq" } }, + /* 0xF4 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pmuludq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pmuludq" } }, + /* 0xF5 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "pmaddwd", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "pmaddwd" } }, + /* 0xF6 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psadbw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psadbw" } }, + /* 0xF7 */ { 0, IT_GENERIC, AM_P | OT_PI, AM_Q | OT_PI, AM_NOT_USED, "maskmovq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "maskmovdqu" } }, + /* 0xF8 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psubb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psubb" } }, + /* 0xF9 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psubw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psubw" } }, + /* 0xFA */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psubd", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psubd" } }, + /* 0xFB */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "psubq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "psubq" } }, + /* 0xFC */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "paddb", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "paddb" } }, + /* 0xFD */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "paddw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "paddw" } }, + /* 0xFE */ { 0, IT_GENERIC, AM_P | OT_Q, AM_Q | OT_Q, AM_NOT_USED, "paddd", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_V | OT_DQ, AM_W | OT_DQ, AM_NOT_USED, "paddd" } }, + /* 0xFF */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_0f00[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_W, AM_NOT_USED, AM_NOT_USED, "sldt", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_W, AM_NOT_USED, AM_NOT_USED, "str", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_W, AM_NOT_USED, AM_NOT_USED, "lldt", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_W, AM_NOT_USED, AM_NOT_USED, "ltr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_W, AM_NOT_USED, AM_NOT_USED, "verr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_W, AM_NOT_USED, AM_NOT_USED, "verw", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_0f01[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_M | OT_S, AM_NOT_USED, AM_NOT_USED, "sgdt", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_M | OT_S, AM_NOT_USED, AM_NOT_USED, "sidt", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_M | OT_S, AM_NOT_USED, AM_NOT_USED, "lgdt", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_M | OT_S, AM_NOT_USED, AM_NOT_USED, "lidt", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_W, AM_NOT_USED, AM_NOT_USED, "smsw", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_W, AM_NOT_USED, AM_NOT_USED, "lmsw", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_M | OT_B, AM_NOT_USED, AM_NOT_USED, "invlpg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_0f18[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_M | OT_ADDRESS_MODE_M, AM_NOT_USED, AM_NOT_USED, "prefetch", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "prefetch", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "prefetch", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_REGISTER | OT_D, AM_NOT_USED, AM_NOT_USED, "prefetch", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_0f71[] = { + /* 0x0 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_I | OT_B, AM_NOT_USED, "psrlw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_P | OT_DQ, AM_I | OT_B, AM_NOT_USED, "psrlw" } }, + /* 0x3 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_I | OT_B, AM_NOT_USED, "psraw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_P | OT_DQ, AM_I | OT_B, AM_NOT_USED, "psraw" } }, + /* 0x5 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_I | OT_B, AM_NOT_USED, "psllw", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_P | OT_DQ, AM_I | OT_B, AM_NOT_USED, "psllw" } }, + /* 0x7 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_0f72[] = { + /* 0x0 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_I | OT_B, AM_NOT_USED, "psrld", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_DQ, AM_I | OT_B, AM_NOT_USED, "psrld" } }, + /* 0x3 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_I | OT_B, AM_NOT_USED, "psrad", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_DQ, AM_I | OT_B, AM_NOT_USED, "psrad" } }, + /* 0x5 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_I | OT_B, AM_NOT_USED, "pslld", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_DQ, AM_I | OT_B, AM_NOT_USED, "pslld" } }, + /* 0x7 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_0f73[] = { + /* 0x0 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_I | OT_B, AM_NOT_USED, "psrlq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_DQ, AM_I | OT_B, AM_NOT_USED, "psrlq" } }, + /* 0x3 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_P | OT_Q, AM_I | OT_B, AM_NOT_USED, "psllq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_DQ, AM_I | OT_B, AM_NOT_USED, "psllq" } }, + /* 0x7 */ { 0, IT_GENERIC, AM_W | OT_DQ, AM_I | OT_B, AM_NOT_USED, "pslldq", true, + /* F2h */ { 0 }, + /* F3h */ { 0 }, + /* 66h */ { 0, IT_GENERIC, AM_W | OT_DQ, AM_I | OT_B, AM_NOT_USED, "pslldq" } }, +}; + +const Opcode s_opcode_byte_after_0fae[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "fxsave", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "fxrstor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "ldmxcsr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "stmxcsr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "lfence", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "mfence", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, "clflush/sfence", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, +}; + +const Opcode s_opcode_byte_after_0fba[] = { + /* 0x0 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "bt", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "bts", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "btr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "btc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_0fc7[] = { + /* 0x0 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_M | OT_Q, AM_NOT_USED, AM_NOT_USED, "cmpxch8b", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_80[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "add", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "or", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "adc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "sbb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "and", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "sub", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "xor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "cmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_81[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "add", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "or", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "adc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "sbb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "and", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "sub", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "xor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "cmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_82[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "add", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "or", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "adc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "sbb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "and", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "sub", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "xor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "cmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_83[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "add", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "or", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "adc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "sbb", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "and", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "sub", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "xor", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "cmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_c0[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "rol", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "ror", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "rcl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "rcr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "shl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "shr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "sal", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "sar", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_c1[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "rol", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "ror", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "rcl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "rcr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "shl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "shr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "sal", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_B, AM_NOT_USED, "sar", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_d0[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_B, AM_IMPLICIT, AM_NOT_USED, "rol", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_B, AM_IMPLICIT, AM_NOT_USED, "ror", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_B, AM_IMPLICIT, AM_NOT_USED, "rcl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_B, AM_IMPLICIT, AM_NOT_USED, "rcr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_B, AM_IMPLICIT, AM_NOT_USED, "shl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_B, AM_IMPLICIT, AM_NOT_USED, "shr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_B, AM_IMPLICIT, AM_NOT_USED, "sal", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_B, AM_IMPLICIT, AM_NOT_USED, "sar", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_d1[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_V, AM_IMPLICIT, AM_NOT_USED, "rol", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_IMPLICIT, AM_NOT_USED, "ror", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_V, AM_IMPLICIT, AM_NOT_USED, "rcl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_V, AM_IMPLICIT, AM_NOT_USED, "rcr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_V, AM_IMPLICIT, AM_NOT_USED, "shl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_V, AM_IMPLICIT, AM_NOT_USED, "shr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_V, AM_IMPLICIT, AM_NOT_USED, "sal", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_V, AM_IMPLICIT, AM_NOT_USED, "sar", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_d2[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "rol", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "ror", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "rcl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "rcr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "shl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "shr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "sal", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_B, AM_REGISTER | OT_B, AM_NOT_USED, "sar", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_d3[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_V, AM_REGISTER | OT_B, AM_NOT_USED, "rol", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_REGISTER | OT_B, AM_NOT_USED, "ror", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_V, AM_REGISTER | OT_B, AM_NOT_USED, "rcl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_V, AM_REGISTER | OT_B, AM_NOT_USED, "rcr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_E | OT_V, AM_REGISTER | OT_B, AM_NOT_USED, "shl", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_E | OT_V, AM_REGISTER | OT_B, AM_NOT_USED, "shr", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_V, AM_REGISTER | OT_B, AM_NOT_USED, "sal", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_E | OT_V, AM_REGISTER | OT_B, AM_NOT_USED, "sar", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_f6[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "test", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_B, AM_I | OT_B, AM_NOT_USED, "test", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "not", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "neg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, OT_B | AM_REGISTER, AM_E | OT_B, AM_NOT_USED, "mul", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, OT_B | AM_REGISTER, AM_E | OT_B, AM_NOT_USED, "imul", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_E | OT_B, AM_NOT_USED, "div", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_REGISTER | OT_B, AM_E | OT_B, AM_NOT_USED, "idiv", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_f7[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "test", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_I | OT_V, AM_NOT_USED, "test", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_GENERIC, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, "not", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_GENERIC, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, "neg", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_E | OT_V, AM_NOT_USED, "mul", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_E | OT_V, AM_NOT_USED, "imul", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_E | OT_V, AM_NOT_USED, "div", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_GENERIC, AM_REGISTER | OT_V, AM_E | OT_V, AM_NOT_USED, "idiv", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_fe[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "inc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_B, AM_NOT_USED, AM_NOT_USED, "dec", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +const Opcode s_opcode_byte_after_ff[] = { + /* 0x0 */ { 0, IT_GENERIC, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, "inc", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x1 */ { 0, IT_GENERIC, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, "dec", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x2 */ { 0, IT_JUMP, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, "call", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x3 */ { 0, IT_JUMP, AM_E | OT_P, AM_NOT_USED, AM_NOT_USED, "call", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x4 */ { 0, IT_JUMP, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, "jmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x5 */ { 0, IT_JUMP, AM_E | OT_P, AM_NOT_USED, AM_NOT_USED, "jmp", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x6 */ { 0, IT_GENERIC, AM_E | OT_V, AM_NOT_USED, AM_NOT_USED, "push", false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } }, + /* 0x7 */ { 0, IT_UNUSED, AM_NOT_USED, AM_NOT_USED, AM_NOT_USED, 0, false, /* F2h */ { 0 }, /* F3h */ { 0 }, /* 66h */ { 0 } } +}; + +/* +* A table of all the other tables, containing some extra information, e.g. +* how to mask out the byte we're looking at. +*/ +const OpcodeTable MiniDisassembler::s_ia32_opcode_map_[]={ + // One-byte opcodes and jumps to larger + /* 0 */ {s_first_opcode_byte, 0, 0xff, 0, 0xff}, + // Two-byte opcodes (second byte) + /* 1 */ {s_opcode_byte_after_0f, 0, 0xff, 0, 0xff}, + // Start of tables for opcodes using ModR/M bits as extension + /* 2 */ {s_opcode_byte_after_80, 3, 0x07, 0, 0x07}, + /* 3 */ {s_opcode_byte_after_81, 3, 0x07, 0, 0x07}, + /* 4 */ {s_opcode_byte_after_82, 3, 0x07, 0, 0x07}, + /* 5 */ {s_opcode_byte_after_83, 3, 0x07, 0, 0x07}, + /* 6 */ {s_opcode_byte_after_c0, 3, 0x07, 0, 0x07}, + /* 7 */ {s_opcode_byte_after_c1, 3, 0x07, 0, 0x07}, + /* 8 */ {s_opcode_byte_after_d0, 3, 0x07, 0, 0x07}, + /* 9 */ {s_opcode_byte_after_d1, 3, 0x07, 0, 0x07}, + /* 10 */ {s_opcode_byte_after_d2, 3, 0x07, 0, 0x07}, + /* 11 */ {s_opcode_byte_after_d3, 3, 0x07, 0, 0x07}, + /* 12 */ {s_opcode_byte_after_f6, 3, 0x07, 0, 0x07}, + /* 13 */ {s_opcode_byte_after_f7, 3, 0x07, 0, 0x07}, + /* 14 */ {s_opcode_byte_after_fe, 3, 0x07, 0, 0x01}, + /* 15 */ {s_opcode_byte_after_ff, 3, 0x07, 0, 0x07}, + /* 16 */ {s_opcode_byte_after_0f00, 3, 0x07, 0, 0x07}, + /* 17 */ {s_opcode_byte_after_0f01, 3, 0x07, 0, 0x07}, + /* 18 */ {s_opcode_byte_after_0f18, 3, 0x07, 0, 0x07}, + /* 19 */ {s_opcode_byte_after_0f71, 3, 0x07, 0, 0x07}, + /* 20 */ {s_opcode_byte_after_0f72, 3, 0x07, 0, 0x07}, + /* 21 */ {s_opcode_byte_after_0f73, 3, 0x07, 0, 0x07}, + /* 22 */ {s_opcode_byte_after_0fae, 3, 0x07, 0, 0x07}, + /* 23 */ {s_opcode_byte_after_0fba, 3, 0x07, 0, 0x07}, + /* 24 */ {s_opcode_byte_after_0fc7, 3, 0x07, 0, 0x01} +}; + +}; // namespace sidestep diff --git a/sandbox/win/src/sidestep/mini_disassembler.cpp b/sandbox/win/src/sidestep/mini_disassembler.cpp new file mode 100644 index 0000000000..1e8e0bd972 --- /dev/null +++ b/sandbox/win/src/sidestep/mini_disassembler.cpp @@ -0,0 +1,395 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Implementation of MiniDisassembler. + +#ifdef _WIN64 +#error The code in this file should not be used on 64-bit Windows. +#endif + +#include "sandbox/win/src/sidestep/mini_disassembler.h" + +namespace sidestep { + +MiniDisassembler::MiniDisassembler(bool operand_default_is_32_bits, + bool address_default_is_32_bits) + : operand_default_is_32_bits_(operand_default_is_32_bits), + address_default_is_32_bits_(address_default_is_32_bits) { + Initialize(); +} + +MiniDisassembler::MiniDisassembler() + : operand_default_is_32_bits_(true), + address_default_is_32_bits_(true) { + Initialize(); +} + +InstructionType MiniDisassembler::Disassemble( + unsigned char* start_byte, + unsigned int* instruction_bytes) { + // Clean up any state from previous invocations. + Initialize(); + + // Start by processing any prefixes. + unsigned char* current_byte = start_byte; + unsigned int size = 0; + InstructionType instruction_type = ProcessPrefixes(current_byte, &size); + + if (IT_UNKNOWN == instruction_type) + return instruction_type; + + current_byte += size; + size = 0; + + // Invariant: We have stripped all prefixes, and the operand_is_32_bits_ + // and address_is_32_bits_ flags are correctly set. + + instruction_type = ProcessOpcode(current_byte, 0, &size); + + // Check for error processing instruction + if ((IT_UNKNOWN == instruction_type_) || (IT_UNUSED == instruction_type_)) { + return IT_UNKNOWN; + } + + current_byte += size; + + // Invariant: operand_bytes_ indicates the total size of operands + // specified by the opcode and/or ModR/M byte and/or SIB byte. + // pCurrentByte points to the first byte after the ModR/M byte, or after + // the SIB byte if it is present (i.e. the first byte of any operands + // encoded in the instruction). + + // We get the total length of any prefixes, the opcode, and the ModR/M and + // SIB bytes if present, by taking the difference of the original starting + // address and the current byte (which points to the first byte of the + // operands if present, or to the first byte of the next instruction if + // they are not). Adding the count of bytes in the operands encoded in + // the instruction gives us the full length of the instruction in bytes. + *instruction_bytes += operand_bytes_ + (current_byte - start_byte); + + // Return the instruction type, which was set by ProcessOpcode(). + return instruction_type_; +} + +void MiniDisassembler::Initialize() { + operand_is_32_bits_ = operand_default_is_32_bits_; + address_is_32_bits_ = address_default_is_32_bits_; + operand_bytes_ = 0; + have_modrm_ = false; + should_decode_modrm_ = false; + instruction_type_ = IT_UNKNOWN; + got_f2_prefix_ = false; + got_f3_prefix_ = false; + got_66_prefix_ = false; +} + +InstructionType MiniDisassembler::ProcessPrefixes(unsigned char* start_byte, + unsigned int* size) { + InstructionType instruction_type = IT_GENERIC; + const Opcode& opcode = s_ia32_opcode_map_[0].table_[*start_byte]; + + switch (opcode.type_) { + case IT_PREFIX_ADDRESS: + address_is_32_bits_ = !address_default_is_32_bits_; + goto nochangeoperand; + case IT_PREFIX_OPERAND: + operand_is_32_bits_ = !operand_default_is_32_bits_; + nochangeoperand: + case IT_PREFIX: + + if (0xF2 == (*start_byte)) + got_f2_prefix_ = true; + else if (0xF3 == (*start_byte)) + got_f3_prefix_ = true; + else if (0x66 == (*start_byte)) + got_66_prefix_ = true; + + instruction_type = opcode.type_; + (*size)++; + // we got a prefix, so add one and check next byte + ProcessPrefixes(start_byte + 1, size); + default: + break; // not a prefix byte + } + + return instruction_type; +} + +InstructionType MiniDisassembler::ProcessOpcode(unsigned char* start_byte, + unsigned int table_index, + unsigned int* size) { + const OpcodeTable& table = s_ia32_opcode_map_[table_index]; // Get our table + unsigned char current_byte = (*start_byte) >> table.shift_; + current_byte = current_byte & table.mask_; // Mask out the bits we will use + + // Check whether the byte we have is inside the table we have. + if (current_byte < table.min_lim_ || current_byte > table.max_lim_) { + instruction_type_ = IT_UNKNOWN; + return instruction_type_; + } + + const Opcode& opcode = table.table_[current_byte]; + if (IT_UNUSED == opcode.type_) { + // This instruction is not used by the IA-32 ISA, so we indicate + // this to the user. Probably means that we were pointed to + // a byte in memory that was not the start of an instruction. + instruction_type_ = IT_UNUSED; + return instruction_type_; + } else if (IT_REFERENCE == opcode.type_) { + // We are looking at an opcode that has more bytes (or is continued + // in the ModR/M byte). Recursively find the opcode definition in + // the table for the opcode's next byte. + (*size)++; + ProcessOpcode(start_byte + 1, opcode.table_index_, size); + return instruction_type_; + } + + const SpecificOpcode* specific_opcode = reinterpret_cast< + const SpecificOpcode*>(&opcode); + if (opcode.is_prefix_dependent_) { + if (got_f2_prefix_ && opcode.opcode_if_f2_prefix_.mnemonic_ != 0) { + specific_opcode = &opcode.opcode_if_f2_prefix_; + } else if (got_f3_prefix_ && opcode.opcode_if_f3_prefix_.mnemonic_ != 0) { + specific_opcode = &opcode.opcode_if_f3_prefix_; + } else if (got_66_prefix_ && opcode.opcode_if_66_prefix_.mnemonic_ != 0) { + specific_opcode = &opcode.opcode_if_66_prefix_; + } + } + + // Inv: The opcode type is known. + instruction_type_ = specific_opcode->type_; + + // Let's process the operand types to see if we have any immediate + // operands, and/or a ModR/M byte. + + ProcessOperand(specific_opcode->flag_dest_); + ProcessOperand(specific_opcode->flag_source_); + ProcessOperand(specific_opcode->flag_aux_); + + // Inv: We have processed the opcode and incremented operand_bytes_ + // by the number of bytes of any operands specified by the opcode + // that are stored in the instruction (not registers etc.). Now + // we need to return the total number of bytes for the opcode and + // for the ModR/M or SIB bytes if they are present. + + if (table.mask_ != 0xff) { + if (have_modrm_) { + // we're looking at a ModR/M byte so we're not going to + // count that into the opcode size + ProcessModrm(start_byte, size); + return IT_GENERIC; + } else { + // need to count the ModR/M byte even if it's just being + // used for opcode extension + (*size)++; + return IT_GENERIC; + } + } else { + if (have_modrm_) { + // The ModR/M byte is the next byte. + (*size)++; + ProcessModrm(start_byte + 1, size); + return IT_GENERIC; + } else { + (*size)++; + return IT_GENERIC; + } + } +} + +bool MiniDisassembler::ProcessOperand(int flag_operand) { + bool succeeded = true; + if (AM_NOT_USED == flag_operand) + return succeeded; + + // Decide what to do based on the addressing mode. + switch (flag_operand & AM_MASK) { + // No ModR/M byte indicated by these addressing modes, and no + // additional (e.g. immediate) parameters. + case AM_A: // Direct address + case AM_F: // EFLAGS register + case AM_X: // Memory addressed by the DS:SI register pair + case AM_Y: // Memory addressed by the ES:DI register pair + case AM_IMPLICIT: // Parameter is implicit, occupies no space in + // instruction + break; + + // There is a ModR/M byte but it does not necessarily need + // to be decoded. + case AM_C: // reg field of ModR/M selects a control register + case AM_D: // reg field of ModR/M selects a debug register + case AM_G: // reg field of ModR/M selects a general register + case AM_P: // reg field of ModR/M selects an MMX register + case AM_R: // mod field of ModR/M may refer only to a general register + case AM_S: // reg field of ModR/M selects a segment register + case AM_T: // reg field of ModR/M selects a test register + case AM_V: // reg field of ModR/M selects a 128-bit XMM register + have_modrm_ = true; + break; + + // In these addressing modes, there is a ModR/M byte and it needs to be + // decoded. No other (e.g. immediate) params than indicated in ModR/M. + case AM_E: // Operand is either a general-purpose register or memory, + // specified by ModR/M byte + case AM_M: // ModR/M byte will refer only to memory + case AM_Q: // Operand is either an MMX register or memory (complex + // evaluation), specified by ModR/M byte + case AM_W: // Operand is either a 128-bit XMM register or memory (complex + // eval), specified by ModR/M byte + have_modrm_ = true; + should_decode_modrm_ = true; + break; + + // These addressing modes specify an immediate or an offset value + // directly, so we need to look at the operand type to see how many + // bytes. + case AM_I: // Immediate data. + case AM_J: // Jump to offset. + case AM_O: // Operand is at offset. + switch (flag_operand & OT_MASK) { + case OT_B: // Byte regardless of operand-size attribute. + operand_bytes_ += OS_BYTE; + break; + case OT_C: // Byte or word, depending on operand-size attribute. + if (operand_is_32_bits_) + operand_bytes_ += OS_WORD; + else + operand_bytes_ += OS_BYTE; + break; + case OT_D: // Doubleword, regardless of operand-size attribute. + operand_bytes_ += OS_DOUBLE_WORD; + break; + case OT_DQ: // Double-quadword, regardless of operand-size attribute. + operand_bytes_ += OS_DOUBLE_QUAD_WORD; + break; + case OT_P: // 32-bit or 48-bit pointer, depending on operand-size + // attribute. + if (operand_is_32_bits_) + operand_bytes_ += OS_48_BIT_POINTER; + else + operand_bytes_ += OS_32_BIT_POINTER; + break; + case OT_PS: // 128-bit packed single-precision floating-point data. + operand_bytes_ += OS_128_BIT_PACKED_SINGLE_PRECISION_FLOATING; + break; + case OT_Q: // Quadword, regardless of operand-size attribute. + operand_bytes_ += OS_QUAD_WORD; + break; + case OT_S: // 6-byte pseudo-descriptor. + operand_bytes_ += OS_PSEUDO_DESCRIPTOR; + break; + case OT_SD: // Scalar Double-Precision Floating-Point Value + case OT_PD: // Unaligned packed double-precision floating point value + operand_bytes_ += OS_DOUBLE_PRECISION_FLOATING; + break; + case OT_SS: + // Scalar element of a 128-bit packed single-precision + // floating data. + // We simply return enItUnknown since we don't have to support + // floating point + succeeded = false; + break; + case OT_V: // Word or doubleword, depending on operand-size attribute. + if (operand_is_32_bits_) + operand_bytes_ += OS_DOUBLE_WORD; + else + operand_bytes_ += OS_WORD; + break; + case OT_W: // Word, regardless of operand-size attribute. + operand_bytes_ += OS_WORD; + break; + + // Can safely ignore these. + case OT_A: // Two one-word operands in memory or two double-word + // operands in memory + case OT_PI: // Quadword MMX technology register (e.g. mm0) + case OT_SI: // Doubleword integer register (e.g., eax) + break; + + default: + break; + } + break; + + default: + break; + } + + return succeeded; +} + +bool MiniDisassembler::ProcessModrm(unsigned char* start_byte, + unsigned int* size) { + // If we don't need to decode, we just return the size of the ModR/M + // byte (there is never a SIB byte in this case). + if (!should_decode_modrm_) { + (*size)++; + return true; + } + + // We never care about the reg field, only the combination of the mod + // and r/m fields, so let's start by packing those fields together into + // 5 bits. + unsigned char modrm = (*start_byte); + unsigned char mod = modrm & 0xC0; // mask out top two bits to get mod field + modrm = modrm & 0x07; // mask out bottom 3 bits to get r/m field + mod = mod >> 3; // shift the mod field to the right place + modrm = mod | modrm; // combine the r/m and mod fields as discussed + mod = mod >> 3; // shift the mod field to bits 2..0 + + // Invariant: modrm contains the mod field in bits 4..3 and the r/m field + // in bits 2..0, and mod contains the mod field in bits 2..0 + + const ModrmEntry* modrm_entry = 0; + if (address_is_32_bits_) + modrm_entry = &s_ia32_modrm_map_[modrm]; + else + modrm_entry = &s_ia16_modrm_map_[modrm]; + + // Invariant: modrm_entry points to information that we need to decode + // the ModR/M byte. + + // Add to the count of operand bytes, if the ModR/M byte indicates + // that some operands are encoded in the instruction. + if (modrm_entry->is_encoded_in_instruction_) + operand_bytes_ += modrm_entry->operand_size_; + + // Process the SIB byte if necessary, and return the count + // of ModR/M and SIB bytes. + if (modrm_entry->use_sib_byte_) { + (*size)++; + return ProcessSib(start_byte + 1, mod, size); + } else { + (*size)++; + return true; + } +} + +bool MiniDisassembler::ProcessSib(unsigned char* start_byte, + unsigned char mod, + unsigned int* size) { + // get the mod field from the 2..0 bits of the SIB byte + unsigned char sib_base = (*start_byte) & 0x07; + if (0x05 == sib_base) { + switch (mod) { + case 0x00: // mod == 00 + case 0x02: // mod == 10 + operand_bytes_ += OS_DOUBLE_WORD; + break; + case 0x01: // mod == 01 + operand_bytes_ += OS_BYTE; + break; + case 0x03: // mod == 11 + // According to the IA-32 docs, there does not seem to be a disp + // value for this value of mod + default: + break; + } + } + + (*size)++; + return true; +} + +}; // namespace sidestep diff --git a/sandbox/win/src/sidestep/mini_disassembler.h b/sandbox/win/src/sidestep/mini_disassembler.h new file mode 100644 index 0000000000..202c4ecc20 --- /dev/null +++ b/sandbox/win/src/sidestep/mini_disassembler.h @@ -0,0 +1,156 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Definition of MiniDisassembler. + +#ifndef SANDBOX_SRC_SIDESTEP_MINI_DISASSEMBLER_H__ +#define SANDBOX_SRC_SIDESTEP_MINI_DISASSEMBLER_H__ + +#include "sandbox/win/src/sidestep/mini_disassembler_types.h" + +namespace sidestep { + +// This small disassembler is very limited +// in its functionality, and in fact does only the bare minimum required by the +// preamble patching utility. It may be useful for other purposes, however. +// +// The limitations include at least the following: +// -# No support for coprocessor opcodes, MMX, etc. +// -# No machine-readable identification of opcodes or decoding of +// assembly parameters. The name of the opcode (as a string) is given, +// however, to aid debugging. +// +// You may ask what this little disassembler actually does, then? The answer is +// that it does the following, which is exactly what the patching utility needs: +// -# Indicates if opcode is a jump (any kind) or a return (any kind) +// because this is important for the patching utility to determine if +// a function is too short or there are jumps too early in it for it +// to be preamble patched. +// -# The opcode length is always calculated, so that the patching utility +// can figure out where the next instruction starts, and whether it +// already has enough instructions to replace with the absolute jump +// to the patching code. +// +// The usage is quite simple; just create a MiniDisassembler and use its +// Disassemble() method. +// +// If you would like to extend this disassembler, please refer to the +// IA-32 Intel Architecture Software Developer's Manual Volume 2: +// Instruction Set Reference for information about operand decoding +// etc. +class MiniDisassembler { + public: + + // Creates a new instance and sets defaults. + // + // operand_default_32_bits: If true, the default operand size is + // set to 32 bits, which is the default under Win32. Otherwise it is 16 bits. + // address_default_32_bits: If true, the default address size is + // set to 32 bits, which is the default under Win32. Otherwise it is 16 bits. + MiniDisassembler(bool operand_default_32_bits, + bool address_default_32_bits); + + // Equivalent to MiniDisassembler(true, true); + MiniDisassembler(); + + // Attempts to disassemble a single instruction starting from the + // address in memory it is pointed to. + // + // start: Address where disassembly should start. + // instruction_bytes: Variable that will be incremented by + // the length in bytes of the instruction. + // Returns enItJump, enItReturn or enItGeneric on success. enItUnknown + // if unable to disassemble, enItUnused if this seems to be an unused + // opcode. In the last two (error) cases, cbInstruction will be set + // to 0xffffffff. + // + // Postcondition: This instance of the disassembler is ready to be used again, + // with unchanged defaults from creation time. + InstructionType Disassemble(unsigned char* start, + unsigned int* instruction_bytes); + + private: + + // Makes the disassembler ready for reuse. + void Initialize(); + + // Sets the flags for address and operand sizes. + // Returns Number of prefix bytes. + InstructionType ProcessPrefixes(unsigned char* start, unsigned int* size); + + // Sets the flag for whether we have ModR/M, and increments + // operand_bytes_ if any are specifies by the opcode directly. + // Returns Number of opcode bytes. + InstructionType ProcessOpcode(unsigned char* start, + unsigned int table, + unsigned int* size); + + // Checks the type of the supplied operand. Increments + // operand_bytes_ if it directly indicates an immediate etc. + // operand. Asserts have_modrm_ if the operand specifies + // a ModR/M byte. + bool ProcessOperand(int flag_operand); + + // Increments operand_bytes_ by size specified by ModR/M and + // by SIB if present. + // Returns 0 in case of error, 1 if there is just a ModR/M byte, + // 2 if there is a ModR/M byte and a SIB byte. + bool ProcessModrm(unsigned char* start, unsigned int* size); + + // Processes the SIB byte that it is pointed to. + // start: Pointer to the SIB byte. + // mod: The mod field from the ModR/M byte. + // Returns 1 to indicate success (indicates 1 SIB byte) + bool ProcessSib(unsigned char* start, unsigned char mod, unsigned int* size); + + // The instruction type we have decoded from the opcode. + InstructionType instruction_type_; + + // Counts the number of bytes that is occupied by operands in + // the current instruction (note: we don't care about how large + // operands stored in registers etc. are). + unsigned int operand_bytes_; + + // True iff there is a ModR/M byte in this instruction. + bool have_modrm_; + + // True iff we need to decode the ModR/M byte (sometimes it just + // points to a register, we can tell by the addressing mode). + bool should_decode_modrm_; + + // Current operand size is 32 bits if true, 16 bits if false. + bool operand_is_32_bits_; + + // Default operand size is 32 bits if true, 16 bits if false. + bool operand_default_is_32_bits_; + + // Current address size is 32 bits if true, 16 bits if false. + bool address_is_32_bits_; + + // Default address size is 32 bits if true, 16 bits if false. + bool address_default_is_32_bits_; + + // Huge big opcode table based on the IA-32 manual, defined + // in Ia32OpcodeMap.cpp + static const OpcodeTable s_ia32_opcode_map_[]; + + // Somewhat smaller table to help with decoding ModR/M bytes + // when 16-bit addressing mode is being used. Defined in + // Ia32ModrmMap.cpp + static const ModrmEntry s_ia16_modrm_map_[]; + + // Somewhat smaller table to help with decoding ModR/M bytes + // when 32-bit addressing mode is being used. Defined in + // Ia32ModrmMap.cpp + static const ModrmEntry s_ia32_modrm_map_[]; + + // Indicators of whether we got certain prefixes that certain + // silly Intel instructions depend on in nonstandard ways for + // their behaviors. + bool got_f2_prefix_, got_f3_prefix_, got_66_prefix_; +}; + +}; // namespace sidestep + +#endif // SANDBOX_SRC_SIDESTEP_MINI_DISASSEMBLER_H__ diff --git a/sandbox/win/src/sidestep/mini_disassembler_types.h b/sandbox/win/src/sidestep/mini_disassembler_types.h new file mode 100644 index 0000000000..1c10626313 --- /dev/null +++ b/sandbox/win/src/sidestep/mini_disassembler_types.h @@ -0,0 +1,197 @@ +// Copyright (c) 2006-2008 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. +// +// Several simple types used by the disassembler and some of the patching +// mechanisms. + +#ifndef SANDBOX_SRC_SIDESTEP_MINI_DISASSEMBLER_TYPES_H__ +#define SANDBOX_SRC_SIDESTEP_MINI_DISASSEMBLER_TYPES_H__ + +namespace sidestep { + +// Categories of instructions that we care about +enum InstructionType { + // This opcode is not used + IT_UNUSED, + // This disassembler does not recognize this opcode (error) + IT_UNKNOWN, + // This is not an instruction but a reference to another table + IT_REFERENCE, + // This byte is a prefix byte that we can ignore + IT_PREFIX, + // This is a prefix byte that switches to the nondefault address size + IT_PREFIX_ADDRESS, + // This is a prefix byte that switches to the nondefault operand size + IT_PREFIX_OPERAND, + // A jump or call instruction + IT_JUMP, + // A return instruction + IT_RETURN, + // Any other type of instruction (in this case we don't care what it is) + IT_GENERIC, +}; + +// Lists IA-32 operand sizes in multiples of 8 bits +enum OperandSize { + OS_ZERO = 0, + OS_BYTE = 1, + OS_WORD = 2, + OS_DOUBLE_WORD = 4, + OS_QUAD_WORD = 8, + OS_DOUBLE_QUAD_WORD = 16, + OS_32_BIT_POINTER = 32/8, + OS_48_BIT_POINTER = 48/8, + OS_SINGLE_PRECISION_FLOATING = 32/8, + OS_DOUBLE_PRECISION_FLOATING = 64/8, + OS_DOUBLE_EXTENDED_PRECISION_FLOATING = 80/8, + OS_128_BIT_PACKED_SINGLE_PRECISION_FLOATING = 128/8, + OS_PSEUDO_DESCRIPTOR = 6 +}; + +// Operand addressing methods from the IA-32 manual. The enAmMask value +// is a mask for the rest. The other enumeration values are named for the +// names given to the addressing methods in the manual, e.g. enAm_D is for +// the D addressing method. +// +// The reason we use a full 4 bytes and a mask, is that we need to combine +// these flags with the enOperandType to store the details +// on the operand in a single integer. +enum AddressingMethod { + AM_NOT_USED = 0, // This operand is not used for this instruction + AM_MASK = 0x00FF0000, // Mask for the rest of the values in this enumeration + AM_A = 0x00010000, // A addressing type + AM_C = 0x00020000, // C addressing type + AM_D = 0x00030000, // D addressing type + AM_E = 0x00040000, // E addressing type + AM_F = 0x00050000, // F addressing type + AM_G = 0x00060000, // G addressing type + AM_I = 0x00070000, // I addressing type + AM_J = 0x00080000, // J addressing type + AM_M = 0x00090000, // M addressing type + AM_O = 0x000A0000, // O addressing type + AM_P = 0x000B0000, // P addressing type + AM_Q = 0x000C0000, // Q addressing type + AM_R = 0x000D0000, // R addressing type + AM_S = 0x000E0000, // S addressing type + AM_T = 0x000F0000, // T addressing type + AM_V = 0x00100000, // V addressing type + AM_W = 0x00110000, // W addressing type + AM_X = 0x00120000, // X addressing type + AM_Y = 0x00130000, // Y addressing type + AM_REGISTER = 0x00140000, // Specific register is always used as this op + AM_IMPLICIT = 0x00150000, // An implicit, fixed value is used +}; + +// Operand types from the IA-32 manual. The enOtMask value is +// a mask for the rest. The rest of the values are named for the +// names given to these operand types in the manual, e.g. enOt_ps +// is for the ps operand type in the manual. +// +// The reason we use a full 4 bytes and a mask, is that we need +// to combine these flags with the enAddressingMethod to store the details +// on the operand in a single integer. +enum OperandType { + OT_MASK = 0xFF000000, + OT_A = 0x01000000, + OT_B = 0x02000000, + OT_C = 0x03000000, + OT_D = 0x04000000, + OT_DQ = 0x05000000, + OT_P = 0x06000000, + OT_PI = 0x07000000, + OT_PS = 0x08000000, // actually unsupported for (we don't know its size) + OT_Q = 0x09000000, + OT_S = 0x0A000000, + OT_SS = 0x0B000000, + OT_SI = 0x0C000000, + OT_V = 0x0D000000, + OT_W = 0x0E000000, + OT_SD = 0x0F000000, // scalar double-precision floating-point value + OT_PD = 0x10000000, // double-precision floating point + // dummy "operand type" for address mode M - which doesn't specify + // operand type + OT_ADDRESS_MODE_M = 0x80000000 +}; + +// Everything that's in an Opcode (see below) except the three +// alternative opcode structs for different prefixes. +struct SpecificOpcode { + // Index to continuation table, or 0 if this is the last + // byte in the opcode. + int table_index_; + + // The opcode type + InstructionType type_; + + // Description of the type of the dest, src and aux operands, + // put together from an enOperandType flag and an enAddressingMethod + // flag. + int flag_dest_; + int flag_source_; + int flag_aux_; + + // We indicate the mnemonic for debugging purposes + const char* mnemonic_; +}; + +// The information we keep in our tables about each of the different +// valid instructions recognized by the IA-32 architecture. +struct Opcode { + // Index to continuation table, or 0 if this is the last + // byte in the opcode. + int table_index_; + + // The opcode type + InstructionType type_; + + // Description of the type of the dest, src and aux operands, + // put together from an enOperandType flag and an enAddressingMethod + // flag. + int flag_dest_; + int flag_source_; + int flag_aux_; + + // We indicate the mnemonic for debugging purposes + const char* mnemonic_; + + // Alternative opcode info if certain prefixes are specified. + // In most cases, all of these are zeroed-out. Only used if + // bPrefixDependent is true. + bool is_prefix_dependent_; + SpecificOpcode opcode_if_f2_prefix_; + SpecificOpcode opcode_if_f3_prefix_; + SpecificOpcode opcode_if_66_prefix_; +}; + +// Information about each table entry. +struct OpcodeTable { + // Table of instruction entries + const Opcode* table_; + // How many bytes left to shift ModR/M byte <b>before</b> applying mask + unsigned char shift_; + // Mask to apply to byte being looked at before comparing to table + unsigned char mask_; + // Minimum/maximum indexes in table. + unsigned char min_lim_; + unsigned char max_lim_; +}; + +// Information about each entry in table used to decode ModR/M byte. +struct ModrmEntry { + // Is the operand encoded as bytes in the instruction (rather than + // if it's e.g. a register in which case it's just encoded in the + // ModR/M byte) + bool is_encoded_in_instruction_; + + // Is there a SIB byte? In this case we always need to decode it. + bool use_sib_byte_; + + // What is the size of the operand (only important if it's encoded + // in the instruction)? + OperandSize operand_size_; +}; + +}; // namespace sidestep + +#endif // SANDBOX_SRC_SIDESTEP_MINI_DISASSEMBLER_TYPES_H__ diff --git a/sandbox/win/src/sidestep/preamble_patcher.h b/sandbox/win/src/sidestep/preamble_patcher.h new file mode 100644 index 0000000000..3a0985ce9f --- /dev/null +++ b/sandbox/win/src/sidestep/preamble_patcher.h @@ -0,0 +1,111 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Definition of PreamblePatcher + +#ifndef SANDBOX_SRC_SIDESTEP_PREAMBLE_PATCHER_H__ +#define SANDBOX_SRC_SIDESTEP_PREAMBLE_PATCHER_H__ + +#include <stddef.h> + +namespace sidestep { + +// Maximum size of the preamble stub. We overwrite at least the first 5 +// bytes of the function. Considering the worst case scenario, we need 4 +// bytes + the max instruction size + 5 more bytes for our jump back to +// the original code. With that in mind, 32 is a good number :) +const size_t kMaxPreambleStubSize = 32; + +// Possible results of patching/unpatching +enum SideStepError { + SIDESTEP_SUCCESS = 0, + SIDESTEP_INVALID_PARAMETER, + SIDESTEP_INSUFFICIENT_BUFFER, + SIDESTEP_JUMP_INSTRUCTION, + SIDESTEP_FUNCTION_TOO_SMALL, + SIDESTEP_UNSUPPORTED_INSTRUCTION, + SIDESTEP_NO_SUCH_MODULE, + SIDESTEP_NO_SUCH_FUNCTION, + SIDESTEP_ACCESS_DENIED, + SIDESTEP_UNEXPECTED, +}; + +// Implements a patching mechanism that overwrites the first few bytes of +// a function preamble with a jump to our hook function, which is then +// able to call the original function via a specially-made preamble-stub +// that imitates the action of the original preamble. +// +// Note that there are a number of ways that this method of patching can +// fail. The most common are: +// - If there is a jump (jxx) instruction in the first 5 bytes of +// the function being patched, we cannot patch it because in the +// current implementation we do not know how to rewrite relative +// jumps after relocating them to the preamble-stub. Note that +// if you really really need to patch a function like this, it +// would be possible to add this functionality (but at some cost). +// - If there is a return (ret) instruction in the first 5 bytes +// we cannot patch the function because it may not be long enough +// for the jmp instruction we use to inject our patch. +// - If there is another thread currently executing within the bytes +// that are copied to the preamble stub, it will crash in an undefined +// way. +// +// If you get any other error than the above, you're either pointing the +// patcher at an invalid instruction (e.g. into the middle of a multi- +// byte instruction, or not at memory containing executable instructions) +// or, there may be a bug in the disassembler we use to find +// instruction boundaries. +class PreamblePatcher { + public: + // Patches target_function to point to replacement_function using a provided + // preamble_stub of stub_size bytes. + // Returns An error code indicating the result of patching. + template <class T> + static SideStepError Patch(T target_function, T replacement_function, + void* preamble_stub, size_t stub_size) { + return RawPatchWithStub(target_function, replacement_function, + reinterpret_cast<unsigned char*>(preamble_stub), + stub_size, NULL); + } + + private: + + // Patches a function by overwriting its first few bytes with + // a jump to a different function. This is similar to the RawPatch + // function except that it uses the stub allocated by the caller + // instead of allocating it. + // + // To use this function, you first have to call VirtualProtect to make the + // target function writable at least for the duration of the call. + // + // target_function: A pointer to the function that should be + // patched. + // + // replacement_function: A pointer to the function that should + // replace the target function. The replacement function must have + // exactly the same calling convention and parameters as the original + // function. + // + // preamble_stub: A pointer to a buffer where the preamble stub + // should be copied. The size of the buffer should be sufficient to + // hold the preamble bytes. + // + // stub_size: Size in bytes of the buffer allocated for the + // preamble_stub + // + // bytes_needed: Pointer to a variable that receives the minimum + // number of bytes required for the stub. Can be set to NULL if you're + // not interested. + // + // Returns An error code indicating the result of patching. + static SideStepError RawPatchWithStub(void* target_function, + void *replacement_function, + unsigned char* preamble_stub, + size_t stub_size, + size_t* bytes_needed); +}; + +}; // namespace sidestep + +#endif // SANDBOX_SRC_SIDESTEP_PREAMBLE_PATCHER_H__ diff --git a/sandbox/win/src/sidestep/preamble_patcher_with_stub.cpp b/sandbox/win/src/sidestep/preamble_patcher_with_stub.cpp new file mode 100644 index 0000000000..999d76bb21 --- /dev/null +++ b/sandbox/win/src/sidestep/preamble_patcher_with_stub.cpp @@ -0,0 +1,179 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Implementation of PreamblePatcher + +#include "sandbox/win/src/sidestep/preamble_patcher.h" + +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sidestep/mini_disassembler.h" + +// Definitions of assembly statements we need +#define ASM_JMP32REL 0xE9 +#define ASM_INT3 0xCC + +namespace { + +// Very basic memcpy. We are copying 4 to 12 bytes most of the time, so there +// is no attempt to optimize this code or have a general purpose function. +// We don't want to call the crt from this code. +inline void* RawMemcpy(void* destination, const void* source, size_t bytes) { + const char* from = reinterpret_cast<const char*>(source); + char* to = reinterpret_cast<char*>(destination); + + for (size_t i = 0; i < bytes ; i++) + to[i] = from[i]; + + return destination; +} + +// Very basic memset. We are filling 1 to 7 bytes most of the time, so there +// is no attempt to optimize this code or have a general purpose function. +// We don't want to call the crt from this code. +inline void* RawMemset(void* destination, int value, size_t bytes) { + char* to = reinterpret_cast<char*>(destination); + + for (size_t i = 0; i < bytes ; i++) + to[i] = static_cast<char>(value); + + return destination; +} + +} // namespace + +#define ASSERT(a, b) DCHECK_NT(a) + +namespace sidestep { + +SideStepError PreamblePatcher::RawPatchWithStub( + void* target_function, + void* replacement_function, + unsigned char* preamble_stub, + size_t stub_size, + size_t* bytes_needed) { + if ((NULL == target_function) || + (NULL == replacement_function) || + (NULL == preamble_stub)) { + ASSERT(false, (L"Invalid parameters - either pTargetFunction or " + L"pReplacementFunction or pPreambleStub were NULL.")); + return SIDESTEP_INVALID_PARAMETER; + } + + // TODO(V7:joi) Siggi and I just had a discussion and decided that both + // patching and unpatching are actually unsafe. We also discussed a + // method of making it safe, which is to freeze all other threads in the + // process, check their thread context to see if their eip is currently + // inside the block of instructions we need to copy to the stub, and if so + // wait a bit and try again, then unfreeze all threads once we've patched. + // Not implementing this for now since we're only using SideStep for unit + // testing, but if we ever use it for production code this is what we + // should do. + // + // NOTE: Stoyan suggests we can write 8 or even 10 bytes atomically using + // FPU instructions, and on newer processors we could use cmpxchg8b or + // cmpxchg16b. So it might be possible to do the patching/unpatching + // atomically and avoid having to freeze other threads. Note though, that + // doing it atomically does not help if one of the other threads happens + // to have its eip in the middle of the bytes you change while you change + // them. + unsigned char* target = reinterpret_cast<unsigned char*>(target_function); + + // Let's disassemble the preamble of the target function to see if we can + // patch, and to see how much of the preamble we need to take. We need 5 + // bytes for our jmp instruction, so let's find the minimum number of + // instructions to get 5 bytes. + MiniDisassembler disassembler; + unsigned int preamble_bytes = 0; + while (preamble_bytes < 5) { + InstructionType instruction_type = + disassembler.Disassemble(target + preamble_bytes, &preamble_bytes); + if (IT_JUMP == instruction_type) { + ASSERT(false, (L"Unable to patch because there is a jump instruction " + L"in the first 5 bytes.")); + return SIDESTEP_JUMP_INSTRUCTION; + } else if (IT_RETURN == instruction_type) { + ASSERT(false, (L"Unable to patch because function is too short")); + return SIDESTEP_FUNCTION_TOO_SMALL; + } else if (IT_GENERIC != instruction_type) { + ASSERT(false, (L"Disassembler encountered unsupported instruction " + L"(either unused or unknown")); + return SIDESTEP_UNSUPPORTED_INSTRUCTION; + } + } + + if (NULL != bytes_needed) + *bytes_needed = preamble_bytes + 5; + + // Inv: preamble_bytes is the number of bytes (at least 5) that we need to + // take from the preamble to have whole instructions that are 5 bytes or more + // in size total. The size of the stub required is cbPreamble + size of + // jmp (5) + if (preamble_bytes + 5 > stub_size) { + NOTREACHED_NT(); + return SIDESTEP_INSUFFICIENT_BUFFER; + } + + // First, copy the preamble that we will overwrite. + RawMemcpy(reinterpret_cast<void*>(preamble_stub), + reinterpret_cast<void*>(target), preamble_bytes); + + // Now, make a jmp instruction to the rest of the target function (minus the + // preamble bytes we moved into the stub) and copy it into our preamble-stub. + // find address to jump to, relative to next address after jmp instruction +#pragma warning(push) +#pragma warning(disable:4244) + // This assignment generates a warning because it is 32 bit specific. + int relative_offset_to_target_rest + = ((reinterpret_cast<unsigned char*>(target) + preamble_bytes) - + (preamble_stub + preamble_bytes + 5)); +#pragma warning(pop) + // jmp (Jump near, relative, displacement relative to next instruction) + preamble_stub[preamble_bytes] = ASM_JMP32REL; + // copy the address + RawMemcpy(reinterpret_cast<void*>(preamble_stub + preamble_bytes + 1), + reinterpret_cast<void*>(&relative_offset_to_target_rest), 4); + + // Inv: preamble_stub points to assembly code that will execute the + // original function by first executing the first cbPreamble bytes of the + // preamble, then jumping to the rest of the function. + + // Overwrite the first 5 bytes of the target function with a jump to our + // replacement function. + // (Jump near, relative, displacement relative to next instruction) + target[0] = ASM_JMP32REL; + + // Find offset from instruction after jmp, to the replacement function. +#pragma warning(push) +#pragma warning(disable:4244) + int offset_to_replacement_function = + reinterpret_cast<unsigned char*>(replacement_function) - + reinterpret_cast<unsigned char*>(target) - 5; +#pragma warning(pop) + // complete the jmp instruction + RawMemcpy(reinterpret_cast<void*>(target + 1), + reinterpret_cast<void*>(&offset_to_replacement_function), 4); + // Set any remaining bytes that were moved to the preamble-stub to INT3 so + // as not to cause confusion (otherwise you might see some strange + // instructions if you look at the disassembly, or even invalid + // instructions). Also, by doing this, we will break into the debugger if + // some code calls into this portion of the code. If this happens, it + // means that this function cannot be patched using this patcher without + // further thought. + if (preamble_bytes > 5) { + RawMemset(reinterpret_cast<void*>(target + 5), ASM_INT3, + preamble_bytes - 5); + } + + // Inv: The memory pointed to by target_function now points to a relative + // jump instruction that jumps over to the preamble_stub. The preamble + // stub contains the first stub_size bytes of the original target + // function's preamble code, followed by a relative jump back to the next + // instruction after the first cbPreamble bytes. + + return SIDESTEP_SUCCESS; +} + +}; // namespace sidestep + +#undef ASSERT diff --git a/sandbox/win/src/sidestep_resolver.cc b/sandbox/win/src/sidestep_resolver.cc new file mode 100644 index 0000000000..828c000a7c --- /dev/null +++ b/sandbox/win/src/sidestep_resolver.cc @@ -0,0 +1,202 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sidestep_resolver.h" + +#include "base/win/pe_image.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sidestep/preamble_patcher.h" + +namespace { + +const size_t kSizeOfSidestepStub = sidestep::kMaxPreambleStubSize; + +struct SidestepThunk { + char sidestep[kSizeOfSidestepStub]; // Storage for the sidestep stub. + int internal_thunk; // Dummy member to the beginning of the internal thunk. +}; + +struct SmartThunk { + const void* module_base; // Target module's base. + const void* interceptor; // Real interceptor. + SidestepThunk sidestep; // Standard sidestep thunk. +}; + +} // namespace + +namespace sandbox { + +NTSTATUS SidestepResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = Init(target_module, interceptor_module, target_name, + interceptor_name, interceptor_entry_point, + thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + SidestepThunk* thunk = reinterpret_cast<SidestepThunk*>(thunk_storage); + + size_t internal_bytes = storage_bytes - kSizeOfSidestepStub; + if (!SetInternalThunk(&thunk->internal_thunk, internal_bytes, thunk_storage, + interceptor_)) + return STATUS_BUFFER_TOO_SMALL; + + AutoProtectMemory memory; + ret = memory.ChangeProtection(target_, kSizeOfSidestepStub, PAGE_READWRITE); + if (!NT_SUCCESS(ret)) + return ret; + + sidestep::SideStepError rv = sidestep::PreamblePatcher::Patch( + target_, reinterpret_cast<void*>(&thunk->internal_thunk), thunk_storage, + kSizeOfSidestepStub); + + if (sidestep::SIDESTEP_INSUFFICIENT_BUFFER == rv) + return STATUS_BUFFER_TOO_SMALL; + + if (sidestep::SIDESTEP_SUCCESS != rv) + return STATUS_UNSUCCESSFUL; + + if (storage_used) + *storage_used = GetThunkSize(); + + return ret; +} + +size_t SidestepResolverThunk::GetThunkSize() const { + return GetInternalThunkSize() + kSizeOfSidestepStub; +} + +// This is basically a wrapper around the normal sidestep patch that extends +// the thunk to use a chained interceptor. It uses the fact that +// SetInternalThunk generates the code to pass as the first parameter whatever +// it receives as original_function; we let SidestepResolverThunk set this value +// to its saved code, and then we change it to our thunk data. +NTSTATUS SmartSidestepResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + if (storage_bytes < GetThunkSize()) + return STATUS_BUFFER_TOO_SMALL; + + SmartThunk* thunk = reinterpret_cast<SmartThunk*>(thunk_storage); + thunk->module_base = target_module; + + NTSTATUS ret; + if (interceptor_entry_point) { + thunk->interceptor = interceptor_entry_point; + } else { + ret = ResolveInterceptor(interceptor_module, interceptor_name, + &thunk->interceptor); + if (!NT_SUCCESS(ret)) + return ret; + } + + // Perform a standard sidestep patch on the last part of the thunk, but point + // to our internal smart interceptor. + size_t standard_bytes = storage_bytes - offsetof(SmartThunk, sidestep); + ret = SidestepResolverThunk::Setup(target_module, interceptor_module, + target_name, NULL, &SmartStub, + &thunk->sidestep, standard_bytes, NULL); + if (!NT_SUCCESS(ret)) + return ret; + + // Fix the internal thunk to pass the whole buffer to the interceptor. + SetInternalThunk(&thunk->sidestep.internal_thunk, GetInternalThunkSize(), + thunk_storage, &SmartStub); + + if (storage_used) + *storage_used = GetThunkSize(); + + return ret; +} + +size_t SmartSidestepResolverThunk::GetThunkSize() const { + return GetInternalThunkSize() + kSizeOfSidestepStub + + offsetof(SmartThunk, sidestep); +} + +// This code must basically either call the intended interceptor or skip the +// call and invoke instead the original function. In any case, we are saving +// the registers that may be trashed by our c++ code. +// +// This function is called with a first parameter inserted by us, that points +// to our SmartThunk. When we call the interceptor we have to replace this +// parameter with the one expected by that function (stored inside our +// structure); on the other hand, when we skip the interceptor we have to remove +// that extra argument before calling the original function. +// +// When we skip the interceptor, the transformation of the stack looks like: +// On Entry: On Use: On Exit: +// [param 2] = first real argument [param 2] (esp+1c) [param 2] +// [param 1] = our SmartThunk [param 1] (esp+18) [ret address] +// [ret address] = real caller [ret address] (esp+14) [xxx] +// [xxx] [addr to jump to] (esp+10) [xxx] +// [xxx] [saved eax] [xxx] +// [xxx] [saved ebx] [xxx] +// [xxx] [saved ecx] [xxx] +// [xxx] [saved edx] [xxx] +__declspec(naked) +void SmartSidestepResolverThunk::SmartStub() { + __asm { + push eax // Space for the jump. + push eax // Save registers. + push ebx + push ecx + push edx + mov ebx, [esp + 0x18] // First parameter = SmartThunk. + mov edx, [esp + 0x14] // Get the return address. + mov eax, [ebx]SmartThunk.module_base + push edx + push eax + call SmartSidestepResolverThunk::IsInternalCall + add esp, 8 + + test eax, eax + lea edx, [ebx]SmartThunk.sidestep // The original function. + jz call_interceptor + + // Skip this call + mov ecx, [esp + 0x14] // Return address. + mov [esp + 0x18], ecx // Remove first parameter. + mov [esp + 0x10], edx + pop edx // Restore registers. + pop ecx + pop ebx + pop eax + ret 4 // Jump to original function. + + call_interceptor: + mov ecx, [ebx]SmartThunk.interceptor + mov [esp + 0x18], edx // Replace first parameter. + mov [esp + 0x10], ecx + pop edx // Restore registers. + pop ecx + pop ebx + pop eax + ret // Jump to original function. + } +} + +bool SmartSidestepResolverThunk::IsInternalCall(const void* base, + void* return_address) { + DCHECK_NT(base); + DCHECK_NT(return_address); + + base::win::PEImage pe(base); + if (pe.GetImageSectionFromAddr(return_address)) + return true; + return false; +} + +} // namespace sandbox diff --git a/sandbox/win/src/sidestep_resolver.h b/sandbox/win/src/sidestep_resolver.h new file mode 100644 index 0000000000..cf03d6e812 --- /dev/null +++ b/sandbox/win/src/sidestep_resolver.h @@ -0,0 +1,73 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SIDESTEP_RESOLVER_H__ +#define SANDBOX_SRC_SIDESTEP_RESOLVER_H__ + +#include "base/basictypes.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/resolver.h" + +namespace sandbox { + +// This is the concrete resolver used to perform sidestep interceptions. +class SidestepResolverThunk : public ResolverThunk { + public: + SidestepResolverThunk() {} + ~SidestepResolverThunk() override {} + + // Implementation of Resolver::Setup. + NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) override; + + // Implementation of Resolver::GetThunkSize. + size_t GetThunkSize() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(SidestepResolverThunk); +}; + +// This is the concrete resolver used to perform smart sidestep interceptions. +// This means basically a sidestep interception that skips the interceptor when +// the caller resides on the same dll being intercepted. It is intended as +// a helper only, because that determination is not infallible. +class SmartSidestepResolverThunk : public SidestepResolverThunk { + public: + SmartSidestepResolverThunk() {} + ~SmartSidestepResolverThunk() override {} + + // Implementation of Resolver::Setup. + NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) override; + + // Implementation of Resolver::GetThunkSize. + size_t GetThunkSize() const override; + + private: + // Performs the actual call to the interceptor if the conditions are correct + // (as determined by IsInternalCall). + static void SmartStub(); + + // Returns true if return_address is inside the module loaded at base. + static bool IsInternalCall(const void* base, void* return_address); + + DISALLOW_COPY_AND_ASSIGN(SmartSidestepResolverThunk); +}; + +} // namespace sandbox + + +#endif // SANDBOX_SRC_SIDESTEP_RESOLVER_H__ diff --git a/sandbox/win/src/sync_dispatcher.cc b/sandbox/win/src/sync_dispatcher.cc new file mode 100644 index 0000000000..b83055dc30 --- /dev/null +++ b/sandbox/win/src/sync_dispatcher.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sync_dispatcher.h" + +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sync_interception.h" +#include "sandbox/win/src/sync_policy.h" + +namespace sandbox { + +SyncDispatcher::SyncDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IPC_CREATEEVENT_TAG, WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>(&SyncDispatcher::CreateEvent) + }; + + static const IPCCall open_params = { + {IPC_OPENEVENT_TAG, WCHAR_TYPE, UINT32_TYPE}, + reinterpret_cast<CallbackGeneric>(&SyncDispatcher::OpenEvent) + }; + + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(open_params); +} + +bool SyncDispatcher::SetupService(InterceptionManager* manager, + int service) { + if (service == IPC_CREATEEVENT_TAG) { + return INTERCEPT_NT(manager, NtCreateEvent, CREATE_EVENT_ID, 24); + } + return (service == IPC_OPENEVENT_TAG) && + INTERCEPT_NT(manager, NtOpenEvent, OPEN_EVENT_ID, 16); +} + +bool SyncDispatcher::CreateEvent(IPCInfo* ipc, + base::string16* name, + uint32 event_type, + uint32 initial_state) { + const wchar_t* event_name = name->c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(event_name); + + EvalResult result = policy_base_->EvalPolicy(IPC_CREATEEVENT_TAG, + params.GetBase()); + HANDLE handle = NULL; + // Return operation status on the IPC. + ipc->return_info.nt_status = SyncPolicy::CreateEventAction( + result, *ipc->client_info, *name, event_type, initial_state, &handle); + ipc->return_info.handle = handle; + return true; +} + +bool SyncDispatcher::OpenEvent(IPCInfo* ipc, + base::string16* name, + uint32 desired_access) { + const wchar_t* event_name = name->c_str(); + + CountedParameterSet<OpenEventParams> params; + params[OpenEventParams::NAME] = ParamPickerMake(event_name); + params[OpenEventParams::ACCESS] = ParamPickerMake(desired_access); + + EvalResult result = policy_base_->EvalPolicy(IPC_OPENEVENT_TAG, + params.GetBase()); + HANDLE handle = NULL; + // Return operation status on the IPC. + ipc->return_info.nt_status = SyncPolicy::OpenEventAction( + result, *ipc->client_info, *name, desired_access, &handle); + ipc->return_info.handle = handle; + return true; +} + +} // namespace sandbox diff --git a/sandbox/win/src/sync_dispatcher.h b/sandbox/win/src/sync_dispatcher.h new file mode 100644 index 0000000000..29c6c1e08b --- /dev/null +++ b/sandbox/win/src/sync_dispatcher.h @@ -0,0 +1,40 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SYNC_DISPATCHER_H_ +#define SANDBOX_SRC_SYNC_DISPATCHER_H_ + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles sync-related IPC calls. +class SyncDispatcher : public Dispatcher { + public: + explicit SyncDispatcher(PolicyBase* policy_base); + ~SyncDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, int service) override; + +private: + // Processes IPC requests coming from calls to CreateEvent in the target. + bool CreateEvent(IPCInfo* ipc, + base::string16* name, + uint32 event_type, + uint32 initial_state); + + // Processes IPC requests coming from calls to OpenEvent in the target. + bool OpenEvent(IPCInfo* ipc, base::string16* name, uint32 desired_access); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(SyncDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SYNC_DISPATCHER_H_ diff --git a/sandbox/win/src/sync_interception.cc b/sandbox/win/src/sync_interception.cc new file mode 100644 index 0000000000..da612a5b35 --- /dev/null +++ b/sandbox/win/src/sync_interception.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sync_interception.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +ResultCode ProxyCreateEvent(LPCWSTR name, + uint32 initial_state, + EVENT_TYPE event_type, + void* ipc_memory, + CrossCallReturn* answer) { + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(name); + + if (!QueryBroker(IPC_CREATEEVENT_TAG, params.GetBase())) + return SBOX_ERROR_GENERIC; + + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = CrossCall(ipc, IPC_CREATEEVENT_TAG, name, event_type, + initial_state, answer); + return code; +} + +ResultCode ProxyOpenEvent(LPCWSTR name, + uint32 desired_access, + void* ipc_memory, + CrossCallReturn* answer) { + CountedParameterSet<OpenEventParams> params; + params[OpenEventParams::NAME] = ParamPickerMake(name); + params[OpenEventParams::ACCESS] = ParamPickerMake(desired_access); + + if (!QueryBroker(IPC_OPENEVENT_TAG, params.GetBase())) + return SBOX_ERROR_GENERIC; + + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = CrossCall(ipc, IPC_OPENEVENT_TAG, name, desired_access, + answer); + + return code; +} + +NTSTATUS WINAPI TargetNtCreateEvent(NtCreateEventFunction orig_CreateEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + EVENT_TYPE event_type, + BOOLEAN initial_state) { + NTSTATUS status = orig_CreateEvent(event_handle, desired_access, + object_attributes, event_type, + initial_state); + if (status != STATUS_ACCESS_DENIED || !object_attributes) + return status; + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(event_handle, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (memory == NULL) + break; + + OBJECT_ATTRIBUTES object_attribs_copy = *object_attributes; + // The RootDirectory points to BaseNamedObjects. We can ignore it. + object_attribs_copy.RootDirectory = NULL; + + wchar_t* name = NULL; + uint32 attributes = 0; + NTSTATUS ret = AllocAndCopyName(&object_attribs_copy, &name, &attributes, + NULL); + if (!NT_SUCCESS(ret) || name == NULL) + break; + + CrossCallReturn answer = {0}; + answer.nt_status = status; + ResultCode code = ProxyCreateEvent(name, initial_state, event_type, memory, + &answer); + operator delete(name, NT_ALLOC); + + if (code != SBOX_ALL_OK) { + status = answer.nt_status; + break; + } + __try { + *event_handle = answer.handle; + status = STATUS_SUCCESS; + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtOpenEvent(NtOpenEventFunction orig_OpenEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + NTSTATUS status = orig_OpenEvent(event_handle, desired_access, + object_attributes); + if (status != STATUS_ACCESS_DENIED || !object_attributes) + return status; + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(event_handle, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (memory == NULL) + break; + + OBJECT_ATTRIBUTES object_attribs_copy = *object_attributes; + // The RootDirectory points to BaseNamedObjects. We can ignore it. + object_attribs_copy.RootDirectory = NULL; + + wchar_t* name = NULL; + uint32 attributes = 0; + NTSTATUS ret = AllocAndCopyName(&object_attribs_copy, &name, &attributes, + NULL); + if (!NT_SUCCESS(ret) || name == NULL) + break; + + CrossCallReturn answer = {0}; + answer.nt_status = status; + ResultCode code = ProxyOpenEvent(name, desired_access, memory, &answer); + operator delete(name, NT_ALLOC); + + if (code != SBOX_ALL_OK) { + status = answer.nt_status; + break; + } + __try { + *event_handle = answer.handle; + status = STATUS_SUCCESS; + } __except(EXCEPTION_EXECUTE_HANDLER) { + break; + } + } while (false); + + return status; +} + +} // namespace sandbox diff --git a/sandbox/win/src/sync_interception.h b/sandbox/win/src/sync_interception.h new file mode 100644 index 0000000000..0f985a8edc --- /dev/null +++ b/sandbox/win/src/sync_interception.h @@ -0,0 +1,46 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_SYNC_INTERCEPTION_H__ +#define SANDBOX_SRC_SYNC_INTERCEPTION_H__ + +namespace sandbox { + +extern "C" { + +typedef NTSTATUS (WINAPI* NtCreateEventFunction) ( + PHANDLE EventHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + EVENT_TYPE EventType, + BOOLEAN InitialState); + +typedef NTSTATUS (WINAPI *NtOpenEventFunction) ( + PHANDLE EventHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +// Interceptors for NtCreateEvent/NtOpenEvent +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateEvent( + NtCreateEventFunction orig_CreateEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + EVENT_TYPE event_type, + BOOLEAN initial_state); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenEvent( + NtOpenEventFunction orig_OpenEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_SYNC_INTERCEPTION_H__ diff --git a/sandbox/win/src/sync_policy.cc b/sandbox/win/src/sync_policy.cc new file mode 100644 index 0000000000..607b4465d6 --- /dev/null +++ b/sandbox/win/src/sync_policy.cc @@ -0,0 +1,254 @@ +// Copyright (c) 2006-2008 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 <string> + +#include "sandbox/win/src/sync_policy.h" + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/sync_interception.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +// Provides functionality to resolve a symbolic link within the object +// directory passed in. +NTSTATUS ResolveSymbolicLink(const base::string16& directory_name, + const base::string16& name, + base::string16* target) { + NtOpenDirectoryObjectFunction NtOpenDirectoryObject = NULL; + ResolveNTFunctionPtr("NtOpenDirectoryObject", &NtOpenDirectoryObject); + + NtQuerySymbolicLinkObjectFunction NtQuerySymbolicLinkObject = NULL; + ResolveNTFunctionPtr("NtQuerySymbolicLinkObject", + &NtQuerySymbolicLinkObject); + + NtOpenSymbolicLinkObjectFunction NtOpenSymbolicLinkObject = NULL; + ResolveNTFunctionPtr("NtOpenSymbolicLinkObject", &NtOpenSymbolicLinkObject); + + NtCloseFunction NtClose = NULL; + ResolveNTFunctionPtr("NtClose", &NtClose); + + OBJECT_ATTRIBUTES symbolic_link_directory_attributes = {}; + UNICODE_STRING symbolic_link_directory_string = {}; + InitObjectAttribs(directory_name, OBJ_CASE_INSENSITIVE, NULL, + &symbolic_link_directory_attributes, + &symbolic_link_directory_string, NULL); + + HANDLE symbolic_link_directory = NULL; + NTSTATUS status = NtOpenDirectoryObject(&symbolic_link_directory, + DIRECTORY_QUERY, + &symbolic_link_directory_attributes); + if (status != STATUS_SUCCESS) { + DLOG(ERROR) << "Failed to open symbolic link directory. Error: " + << status; + return status; + } + + OBJECT_ATTRIBUTES symbolic_link_attributes = {}; + UNICODE_STRING name_string = {}; + InitObjectAttribs(name, OBJ_CASE_INSENSITIVE, symbolic_link_directory, + &symbolic_link_attributes, &name_string, NULL); + + HANDLE symbolic_link = NULL; + status = NtOpenSymbolicLinkObject(&symbolic_link, GENERIC_READ, + &symbolic_link_attributes); + NtClose(symbolic_link_directory); + if (status != STATUS_SUCCESS) { + DLOG(ERROR) << "Failed to open symbolic link Error: " << status; + return status; + } + + UNICODE_STRING target_path = {}; + unsigned long target_length = 0; + status = NtQuerySymbolicLinkObject(symbolic_link, &target_path, + &target_length); + if (status != STATUS_BUFFER_TOO_SMALL) { + NtClose(symbolic_link); + DLOG(ERROR) << "Failed to get length for symbolic link target. Error: " + << status; + return status; + } + + target_path.Length = 0; + target_path.MaximumLength = static_cast<USHORT>(target_length); + target_path.Buffer = new wchar_t[target_path.MaximumLength + 1]; + status = NtQuerySymbolicLinkObject(symbolic_link, &target_path, + &target_length); + if (status == STATUS_SUCCESS) { + target->assign(target_path.Buffer, target_length); + } else { + DLOG(ERROR) << "Failed to resolve symbolic link. Error: " << status; + } + + NtClose(symbolic_link); + delete[] target_path.Buffer; + return status; +} + +NTSTATUS GetBaseNamedObjectsDirectory(HANDLE* directory) { + static HANDLE base_named_objects_handle = NULL; + if (base_named_objects_handle) { + *directory = base_named_objects_handle; + return STATUS_SUCCESS; + } + + NtOpenDirectoryObjectFunction NtOpenDirectoryObject = NULL; + ResolveNTFunctionPtr("NtOpenDirectoryObject", &NtOpenDirectoryObject); + + DWORD session_id = 0; + ProcessIdToSessionId(::GetCurrentProcessId(), &session_id); + + base::string16 base_named_objects_path; + + NTSTATUS status = ResolveSymbolicLink(L"\\Sessions\\BNOLINKS", + base::StringPrintf(L"%d", session_id), + &base_named_objects_path); + if (status != STATUS_SUCCESS) { + DLOG(ERROR) << "Failed to resolve BaseNamedObjects path. Error: " + << status; + return status; + } + + UNICODE_STRING directory_name = {}; + OBJECT_ATTRIBUTES object_attributes = {}; + InitObjectAttribs(base_named_objects_path, OBJ_CASE_INSENSITIVE, NULL, + &object_attributes, &directory_name, NULL); + status = NtOpenDirectoryObject(&base_named_objects_handle, + DIRECTORY_ALL_ACCESS, + &object_attributes); + if (status == STATUS_SUCCESS) + *directory = base_named_objects_handle; + return status; +} + +bool SyncPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + base::string16 mod_name(name); + if (mod_name.empty()) { + return false; + } + + if (TargetPolicy::EVENTS_ALLOW_ANY != semantics && + TargetPolicy::EVENTS_ALLOW_READONLY != semantics) { + // Other flags are not valid for sync policy yet. + NOTREACHED(); + return false; + } + + // Add the open rule. + EvalResult result = ASK_BROKER; + PolicyRule open(result); + + if (!open.AddStringMatch(IF, OpenEventParams::NAME, name, CASE_INSENSITIVE)) + return false; + + if (TargetPolicy::EVENTS_ALLOW_READONLY == semantics) { + // We consider all flags that are not known to be readonly as potentially + // used for write. + uint32 allowed_flags = SYNCHRONIZE | GENERIC_READ | READ_CONTROL; + uint32 restricted_flags = ~allowed_flags; + open.AddNumberMatch(IF_NOT, OpenEventParams::ACCESS, restricted_flags, AND); + } + + if (!policy->AddRule(IPC_OPENEVENT_TAG, &open)) + return false; + + // If it's not a read only, add the create rule. + if (TargetPolicy::EVENTS_ALLOW_READONLY != semantics) { + PolicyRule create(result); + if (!create.AddStringMatch(IF, NameBased::NAME, name, CASE_INSENSITIVE)) + return false; + + if (!policy->AddRule(IPC_CREATEEVENT_TAG, &create)) + return false; + } + + return true; +} + +NTSTATUS SyncPolicy::CreateEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &event_name, + uint32 event_type, + uint32 initial_state, + HANDLE *handle) { + NtCreateEventFunction NtCreateEvent = NULL; + ResolveNTFunctionPtr("NtCreateEvent", &NtCreateEvent); + + // The only action supported is ASK_BROKER which means create the requested + // file as specified. + if (ASK_BROKER != eval_result) + return false; + + HANDLE object_directory = NULL; + NTSTATUS status = GetBaseNamedObjectsDirectory(&object_directory); + if (status != STATUS_SUCCESS) + return status; + + UNICODE_STRING unicode_event_name = {}; + OBJECT_ATTRIBUTES object_attributes = {}; + InitObjectAttribs(event_name, OBJ_CASE_INSENSITIVE, object_directory, + &object_attributes, &unicode_event_name, NULL); + + HANDLE local_handle = NULL; + status = NtCreateEvent(&local_handle, EVENT_ALL_ACCESS, &object_attributes, + static_cast<EVENT_TYPE>(event_type), + static_cast<BOOLEAN>(initial_state)); + if (NULL == local_handle) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return status; +} + +NTSTATUS SyncPolicy::OpenEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &event_name, + uint32 desired_access, + HANDLE *handle) { + NtOpenEventFunction NtOpenEvent = NULL; + ResolveNTFunctionPtr("NtOpenEvent", &NtOpenEvent); + + // The only action supported is ASK_BROKER which means create the requested + // event as specified. + if (ASK_BROKER != eval_result) + return false; + + HANDLE object_directory = NULL; + NTSTATUS status = GetBaseNamedObjectsDirectory(&object_directory); + if (status != STATUS_SUCCESS) + return status; + + UNICODE_STRING unicode_event_name = {}; + OBJECT_ATTRIBUTES object_attributes = {}; + InitObjectAttribs(event_name, OBJ_CASE_INSENSITIVE, object_directory, + &object_attributes, &unicode_event_name, NULL); + + HANDLE local_handle = NULL; + status = NtOpenEvent(&local_handle, desired_access, &object_attributes); + if (NULL == local_handle) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return status; +} + +} // namespace sandbox diff --git a/sandbox/win/src/sync_policy.h b/sandbox/win/src/sync_policy.h new file mode 100644 index 0000000000..e370e4bdac --- /dev/null +++ b/sandbox/win/src/sync_policy.h @@ -0,0 +1,51 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SYNC_POLICY_H__ +#define SANDBOX_SRC_SYNC_POLICY_H__ + +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum EvalResult; + +// This class centralizes most of the knowledge related to sync policy +class SyncPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for sync calls, in particular open or create actions. + // name is the sync object name, semantics is the desired semantics for the + // open or create and policy is the policy generator to which the rules are + // going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Performs the desired policy action on a request. + // client_info is the target process that is making the request and + // eval_result is the desired policy action to accomplish. + static NTSTATUS CreateEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &event_name, + uint32 event_type, + uint32 initial_state, + HANDLE *handle); + static NTSTATUS OpenEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const base::string16 &event_name, + uint32 desired_access, + HANDLE *handle); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SYNC_POLICY_H__ diff --git a/sandbox/win/src/sync_policy_test.cc b/sandbox/win/src/sync_policy_test.cc new file mode 100644 index 0000000000..9fc08f42bd --- /dev/null +++ b/sandbox/win/src/sync_policy_test.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sync_policy_test.h" + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/nt_internals.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int Event_Open(int argc, wchar_t **argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + DWORD desired_access = SYNCHRONIZE; + if (L'f' == argv[0][0]) + desired_access = EVENT_ALL_ACCESS; + + base::win::ScopedHandle event_open(::OpenEvent( + desired_access, FALSE, argv[1])); + DWORD error_open = ::GetLastError(); + + if (event_open.IsValid()) + return SBOX_TEST_SUCCEEDED; + + if (ERROR_ACCESS_DENIED == error_open || + ERROR_BAD_PATHNAME == error_open || + ERROR_FILE_NOT_FOUND == error_open) + return SBOX_TEST_DENIED; + + return SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int Event_CreateOpen(int argc, wchar_t **argv) { + if (argc < 2 || argc > 3) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + wchar_t *event_name = NULL; + if (3 == argc) + event_name = argv[2]; + + BOOL manual_reset = FALSE; + BOOL initial_state = FALSE; + if (L't' == argv[0][0]) + manual_reset = TRUE; + if (L't' == argv[1][0]) + initial_state = TRUE; + + base::win::ScopedHandle event_create(::CreateEvent( + NULL, manual_reset, initial_state, event_name)); + DWORD error_create = ::GetLastError(); + base::win::ScopedHandle event_open; + if (event_name) + event_open.Set(::OpenEvent(EVENT_ALL_ACCESS, FALSE, event_name)); + + if (event_create.IsValid()) { + DWORD wait = ::WaitForSingleObject(event_create.Get(), 0); + if (initial_state && WAIT_OBJECT_0 != wait) + return SBOX_TEST_FAILED; + + if (!initial_state && WAIT_TIMEOUT != wait) + return SBOX_TEST_FAILED; + } + + if (event_name) { + // Both event_open and event_create have to be valid. + if (event_open.IsValid() && event_create.IsValid()) + return SBOX_TEST_SUCCEEDED; + + if (event_open.IsValid() && !event_create.IsValid() || + !event_open.IsValid() && event_create.IsValid()) + return SBOX_TEST_FAILED; + } else { + // Only event_create has to be valid. + if (event_create.Get()) + return SBOX_TEST_SUCCEEDED; + } + + if (ERROR_ACCESS_DENIED == error_create || + ERROR_BAD_PATHNAME == error_create) + return SBOX_TEST_DENIED; + + return SBOX_TEST_FAILED; +} + +// Tests the creation of events using all the possible combinations. +TEST(SyncPolicyTest, DISABLED_TestEvent) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, + L"test1")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, + L"test2")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f f")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t f")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f t")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t t")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f f test1")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t f test2")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f t test1")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t t test2")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f f test3")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t f test4")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f t test3")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t t test4")); +} + +// Tests opening events with read only access. +TEST(SyncPolicyTest, DISABLED_TestEventReadOnly) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, + L"test1")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, + L"test2")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, + L"test5")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, + L"test6")); + + base::win::ScopedHandle handle1(::CreateEvent(NULL, FALSE, FALSE, L"test1")); + base::win::ScopedHandle handle2(::CreateEvent(NULL, FALSE, FALSE, L"test2")); + base::win::ScopedHandle handle3(::CreateEvent(NULL, FALSE, FALSE, L"test3")); + base::win::ScopedHandle handle4(::CreateEvent(NULL, FALSE, FALSE, L"test4")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f f")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t f")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test1")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_Open s test2")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test3")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open s test4")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f f test5")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t f test6")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f t test5")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t t test6")); +} + +} // namespace sandbox diff --git a/sandbox/win/src/sync_policy_test.h b/sandbox/win/src/sync_policy_test.h new file mode 100644 index 0000000000..4f354b35be --- /dev/null +++ b/sandbox/win/src/sync_policy_test.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SYNC_POLICY_TEST_H_ +#define SANDBOX_WIN_SRC_SYNC_POLICY_TEST_H_ + +#include "sandbox/win/tests/common/controller.h" + +namespace sandbox { + +// Opens the named event received on argv[1]. The requested access is +// EVENT_ALL_ACCESS if argv[0] starts with 'f', or SYNCHRONIZE otherwise. +SBOX_TESTS_COMMAND int Event_Open(int argc, wchar_t **argv); + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SYNC_POLICY_TEST_H_ diff --git a/sandbox/win/src/target_interceptions.cc b/sandbox/win/src/target_interceptions.cc new file mode 100644 index 0000000000..e6b0dcf780 --- /dev/null +++ b/sandbox/win/src/target_interceptions.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/target_interceptions.h" + +#include "sandbox/win/src/interception_agent.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +// Hooks NtMapViewOfSection to detect the load of DLLs. If hot patching is +// required for this dll, this functions patches it. +NTSTATUS WINAPI TargetNtMapViewOfSection( + NtMapViewOfSectionFunction orig_MapViewOfSection, HANDLE section, + HANDLE process, PVOID *base, ULONG_PTR zero_bits, SIZE_T commit_size, + PLARGE_INTEGER offset, PSIZE_T view_size, SECTION_INHERIT inherit, + ULONG allocation_type, ULONG protect) { + NTSTATUS ret = orig_MapViewOfSection(section, process, base, zero_bits, + commit_size, offset, view_size, inherit, + allocation_type, protect); + + static int s_load_count = 0; + if (1 == s_load_count) { + SandboxFactory::GetTargetServices()->GetState()->SetKernel32Loaded(); + s_load_count = 2; + } + + do { + if (!NT_SUCCESS(ret)) + break; + + if (!InitHeap()) + break; + + if (!IsSameProcess(process)) + break; + + if (!IsValidImageSection(section, base, offset, view_size)) + break; + + UINT image_flags; + UNICODE_STRING* module_name = + GetImageInfoFromModule(reinterpret_cast<HMODULE>(*base), &image_flags); + UNICODE_STRING* file_name = GetBackingFilePath(*base); + + if ((!module_name) && (image_flags & MODULE_HAS_CODE)) { + // If the module has no exports we retrieve the module name from the + // full path of the mapped section. + module_name = ExtractModuleName(file_name); + } + + InterceptionAgent* agent = InterceptionAgent::GetInterceptionAgent(); + + if (agent) { + if (!agent->OnDllLoad(file_name, module_name, *base)) { + // Interception agent is demanding to un-map the module. + g_nt.UnmapViewOfSection(process, *base); + ret = STATUS_UNSUCCESSFUL; + } + } + + if (module_name) + operator delete(module_name, NT_ALLOC); + + if (file_name) + operator delete(file_name, NT_ALLOC); + + } while (false); + + if (!s_load_count) + s_load_count = 1; + + return ret; +} + +NTSTATUS WINAPI TargetNtUnmapViewOfSection( + NtUnmapViewOfSectionFunction orig_UnmapViewOfSection, HANDLE process, + PVOID base) { + NTSTATUS ret = orig_UnmapViewOfSection(process, base); + + if (!NT_SUCCESS(ret)) + return ret; + + if (!IsSameProcess(process)) + return ret; + + InterceptionAgent* agent = InterceptionAgent::GetInterceptionAgent(); + + if (agent) + agent->OnDllUnload(base); + + return ret; +} + +} // namespace sandbox diff --git a/sandbox/win/src/target_interceptions.h b/sandbox/win/src/target_interceptions.h new file mode 100644 index 0000000000..f4805fec5c --- /dev/null +++ b/sandbox/win/src/target_interceptions.h @@ -0,0 +1,35 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_TARGET_INTERCEPTIONS_H__ +#define SANDBOX_SRC_TARGET_INTERCEPTIONS_H__ + +namespace sandbox { + +extern "C" { + +// Interception of NtMapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being loaded, so we can patch them if needed. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtMapViewOfSection( + NtMapViewOfSectionFunction orig_MapViewOfSection, HANDLE section, + HANDLE process, PVOID *base, ULONG_PTR zero_bits, SIZE_T commit_size, + PLARGE_INTEGER offset, PSIZE_T view_size, SECTION_INHERIT inherit, + ULONG allocation_type, ULONG protect); + +// Interception of NtUnmapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being unloaded, so we can clean up our interceptions. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtUnmapViewOfSection( + NtUnmapViewOfSectionFunction orig_UnmapViewOfSection, HANDLE process, + PVOID base); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_TARGET_INTERCEPTIONS_H__ diff --git a/sandbox/win/src/target_process.cc b/sandbox/win/src/target_process.cc new file mode 100644 index 0000000000..e0284c3924 --- /dev/null +++ b/sandbox/win/src/target_process.cc @@ -0,0 +1,385 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/target_process.h" + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/pe_image.h" +#include "base/win/startup_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sharedmem_ipc_server.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +void CopyPolicyToTarget(const void* source, size_t size, void* dest) { + if (!source || !size) + return; + memcpy(dest, source, size); + sandbox::PolicyGlobal* policy = + reinterpret_cast<sandbox::PolicyGlobal*>(dest); + + size_t offset = reinterpret_cast<size_t>(source); + + for (size_t i = 0; i < sandbox::kMaxServiceCount; i++) { + size_t buffer = reinterpret_cast<size_t>(policy->entry[i]); + if (buffer) { + buffer -= offset; + policy->entry[i] = reinterpret_cast<sandbox::PolicyBuffer*>(buffer); + } + } +} + +} + +namespace sandbox { + +SANDBOX_INTERCEPT HANDLE g_shared_section; +SANDBOX_INTERCEPT size_t g_shared_IPC_size; +SANDBOX_INTERCEPT size_t g_shared_policy_size; + +// Returns the address of the main exe module in memory taking in account +// address space layout randomization. +void* GetBaseAddress(const wchar_t* exe_name, void* entry_point) { + HMODULE exe = ::LoadLibrary(exe_name); + if (NULL == exe) + return exe; + + base::win::PEImage pe(exe); + if (!pe.VerifyMagic()) { + ::FreeLibrary(exe); + return exe; + } + PIMAGE_NT_HEADERS nt_header = pe.GetNTHeaders(); + char* base = reinterpret_cast<char*>(entry_point) - + nt_header->OptionalHeader.AddressOfEntryPoint; + + ::FreeLibrary(exe); + return base; +} + + +TargetProcess::TargetProcess(HANDLE initial_token, HANDLE lockdown_token, + HANDLE job, ThreadProvider* thread_pool) + // This object owns everything initialized here except thread_pool and + // the job_ handle. The Job handle is closed by BrokerServices and results + // eventually in a call to our dtor. + : lockdown_token_(lockdown_token), + initial_token_(initial_token), + job_(job), + thread_pool_(thread_pool), + base_address_(NULL) { +} + +TargetProcess::~TargetProcess() { + DWORD exit_code = 0; + // Give a chance to the process to die. In most cases the JOB_KILL_ON_CLOSE + // will take effect only when the context changes. As far as the testing went, + // this wait was enough to switch context and kill the processes in the job. + // If this process is already dead, the function will return without waiting. + // TODO(nsylvain): If the process is still alive at the end, we should kill + // it. http://b/893891 + // For now, this wait is there only to do a best effort to prevent some leaks + // from showing up in purify. + if (sandbox_process_info_.IsValid()) { + ::WaitForSingleObject(sandbox_process_info_.process_handle(), 50); + // At this point, the target process should have been killed. Check. + if (!::GetExitCodeProcess(sandbox_process_info_.process_handle(), + &exit_code) || (STILL_ACTIVE == exit_code)) { + // Something went wrong. We don't know if the target is in a state where + // it can manage to do another IPC call. If it can, and we've destroyed + // the |ipc_server_|, it will crash the broker. So we intentionally leak + // that. + if (shared_section_.IsValid()) + shared_section_.Take(); + ipc_server_.release(); + sandbox_process_info_.TakeProcessHandle(); + return; + } + } + + // ipc_server_ references our process handle, so make sure the former is shut + // down before the latter is closed (by ScopedProcessInformation). + ipc_server_.reset(); +} + +// Creates the target (child) process suspended and assigns it to the job +// object. +DWORD TargetProcess::Create(const wchar_t* exe_path, + const wchar_t* command_line, + bool inherit_handles, + bool set_lockdown_token_after_create, + const base::win::StartupInformation& startup_info, + base::win::ScopedProcessInformation* target_info) { + if (set_lockdown_token_after_create && + base::win::GetVersion() < base::win::VERSION_WIN8) { + // We don't allow set_lockdown_token_after_create below Windows 8. + return ERROR_INVALID_PARAMETER; + } + + exe_name_.reset(_wcsdup(exe_path)); + + // the command line needs to be writable by CreateProcess(). + scoped_ptr<wchar_t, base::FreeDeleter> cmd_line(_wcsdup(command_line)); + + // Start the target process suspended. + DWORD flags = + CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS; + + if (startup_info.has_extended_startup_info()) + flags |= EXTENDED_STARTUPINFO_PRESENT; + + if (job_ && base::win::GetVersion() < base::win::VERSION_WIN8) { + // Windows 8 implements nested jobs, but for older systems we need to + // break out of any job we're in to enforce our restrictions. + flags |= CREATE_BREAKAWAY_FROM_JOB; + } + + base::win::ScopedHandle scoped_lockdown_token(lockdown_token_.Take()); + PROCESS_INFORMATION temp_process_info = {}; + if (set_lockdown_token_after_create) { + // First create process with a default token and then replace it later, + // after setting primary thread token. This is required for setting + // an AppContainer token along with an impersonation token. + if (!::CreateProcess(exe_path, + cmd_line.get(), + NULL, // No security attribute. + NULL, // No thread attribute. + inherit_handles, + flags, + NULL, // Use the environment of the caller. + NULL, // Use current directory of the caller. + startup_info.startup_info(), + &temp_process_info)) { + return ::GetLastError(); + } + } else { + if (!::CreateProcessAsUserW(scoped_lockdown_token.Get(), + exe_path, + cmd_line.get(), + NULL, // No security attribute. + NULL, // No thread attribute. + inherit_handles, + flags, + NULL, // Use the environment of the caller. + NULL, // Use current directory of the caller. + startup_info.startup_info(), + &temp_process_info)) { + return ::GetLastError(); + } + } + base::win::ScopedProcessInformation process_info(temp_process_info); + + DWORD win_result = ERROR_SUCCESS; + + if (job_) { + // Assign the suspended target to the windows job object. + if (!::AssignProcessToJobObject(job_, process_info.process_handle())) { + win_result = ::GetLastError(); + ::TerminateProcess(process_info.process_handle(), 0); + return win_result; + } + } + + if (initial_token_.IsValid()) { + // Change the token of the main thread of the new process for the + // impersonation token with more rights. This allows the target to start; + // otherwise it will crash too early for us to help. + HANDLE temp_thread = process_info.thread_handle(); + if (!::SetThreadToken(&temp_thread, initial_token_.Get())) { + win_result = ::GetLastError(); + // It might be a security breach if we let the target run outside the job + // so kill it before it causes damage. + ::TerminateProcess(process_info.process_handle(), 0); + return win_result; + } + initial_token_.Close(); + } + + if (set_lockdown_token_after_create) { + PROCESS_ACCESS_TOKEN process_access_token; + process_access_token.thread = process_info.thread_handle(); + process_access_token.token = scoped_lockdown_token.Get(); + + NtSetInformationProcess SetInformationProcess = NULL; + ResolveNTFunctionPtr("NtSetInformationProcess", &SetInformationProcess); + + NTSTATUS status = SetInformationProcess( + process_info.process_handle(), + static_cast<PROCESS_INFORMATION_CLASS>(NtProcessInformationAccessToken), + &process_access_token, + sizeof(process_access_token)); + if (!NT_SUCCESS(status)) { + win_result = ::GetLastError(); + ::TerminateProcess(process_info.process_handle(), 0); // exit code + return win_result; + } + } + + CONTEXT context; + context.ContextFlags = CONTEXT_ALL; + if (!::GetThreadContext(process_info.thread_handle(), &context)) { + win_result = ::GetLastError(); + ::TerminateProcess(process_info.process_handle(), 0); + return win_result; + } + +#if defined(_WIN64) + void* entry_point = reinterpret_cast<void*>(context.Rcx); +#else +#pragma warning(push) +#pragma warning(disable: 4312) + // This cast generates a warning because it is 32 bit specific. + void* entry_point = reinterpret_cast<void*>(context.Eax); +#pragma warning(pop) +#endif // _WIN64 + + if (!target_info->DuplicateFrom(process_info)) { + win_result = ::GetLastError(); // This may or may not be correct. + ::TerminateProcess(process_info.process_handle(), 0); + return win_result; + } + + base_address_ = GetBaseAddress(exe_path, entry_point); + sandbox_process_info_.Set(process_info.Take()); + return win_result; +} + +ResultCode TargetProcess::TransferVariable(const char* name, void* address, + size_t size) { + if (!sandbox_process_info_.IsValid()) + return SBOX_ERROR_UNEXPECTED_CALL; + + void* child_var = address; + +#if SANDBOX_EXPORTS + HMODULE module = ::LoadLibrary(exe_name_.get()); + if (NULL == module) + return SBOX_ERROR_GENERIC; + + child_var = ::GetProcAddress(module, name); + ::FreeLibrary(module); + + if (NULL == child_var) + return SBOX_ERROR_GENERIC; + + size_t offset = reinterpret_cast<char*>(child_var) - + reinterpret_cast<char*>(module); + child_var = reinterpret_cast<char*>(MainModule()) + offset; +#else + UNREFERENCED_PARAMETER(name); +#endif + + SIZE_T written; + if (!::WriteProcessMemory(sandbox_process_info_.process_handle(), + child_var, address, size, &written)) + return SBOX_ERROR_GENERIC; + + if (written != size) + return SBOX_ERROR_GENERIC; + + return SBOX_ALL_OK; +} + +// Construct the IPC server and the IPC dispatcher. When the target does +// an IPC it will eventually call the dispatcher. +DWORD TargetProcess::Init(Dispatcher* ipc_dispatcher, void* policy, + uint32 shared_IPC_size, uint32 shared_policy_size) { + // We need to map the shared memory on the target. This is necessary for + // any IPC that needs to take place, even if the target has not yet hit + // the main( ) function or even has initialized the CRT. So here we set + // the handle to the shared section. The target on the first IPC must do + // the rest, which boils down to calling MapViewofFile() + + // We use this single memory pool for IPC and for policy. + DWORD shared_mem_size = static_cast<DWORD>(shared_IPC_size + + shared_policy_size); + shared_section_.Set(::CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, + PAGE_READWRITE | SEC_COMMIT, + 0, shared_mem_size, NULL)); + if (!shared_section_.IsValid()) { + return ::GetLastError(); + } + + DWORD access = FILE_MAP_READ | FILE_MAP_WRITE; + HANDLE target_shared_section; + if (!::DuplicateHandle(::GetCurrentProcess(), shared_section_.Get(), + sandbox_process_info_.process_handle(), + &target_shared_section, access, FALSE, 0)) { + return ::GetLastError(); + } + + void* shared_memory = ::MapViewOfFile(shared_section_.Get(), + FILE_MAP_WRITE|FILE_MAP_READ, + 0, 0, 0); + if (NULL == shared_memory) { + return ::GetLastError(); + } + + CopyPolicyToTarget(policy, shared_policy_size, + reinterpret_cast<char*>(shared_memory) + shared_IPC_size); + + ResultCode ret; + // Set the global variables in the target. These are not used on the broker. + g_shared_section = target_shared_section; + ret = TransferVariable("g_shared_section", &g_shared_section, + sizeof(g_shared_section)); + g_shared_section = NULL; + if (SBOX_ALL_OK != ret) { + return (SBOX_ERROR_GENERIC == ret)? + ::GetLastError() : ERROR_INVALID_FUNCTION; + } + g_shared_IPC_size = shared_IPC_size; + ret = TransferVariable("g_shared_IPC_size", &g_shared_IPC_size, + sizeof(g_shared_IPC_size)); + g_shared_IPC_size = 0; + if (SBOX_ALL_OK != ret) { + return (SBOX_ERROR_GENERIC == ret) ? + ::GetLastError() : ERROR_INVALID_FUNCTION; + } + g_shared_policy_size = shared_policy_size; + ret = TransferVariable("g_shared_policy_size", &g_shared_policy_size, + sizeof(g_shared_policy_size)); + g_shared_policy_size = 0; + if (SBOX_ALL_OK != ret) { + return (SBOX_ERROR_GENERIC == ret) ? + ::GetLastError() : ERROR_INVALID_FUNCTION; + } + + ipc_server_.reset( + new SharedMemIPCServer(sandbox_process_info_.process_handle(), + sandbox_process_info_.process_id(), + job_, thread_pool_, ipc_dispatcher)); + + if (!ipc_server_->Init(shared_memory, shared_IPC_size, kIPCChannelSize)) + return ERROR_NOT_ENOUGH_MEMORY; + + // After this point we cannot use this handle anymore. + ::CloseHandle(sandbox_process_info_.TakeThreadHandle()); + + return ERROR_SUCCESS; +} + +void TargetProcess::Terminate() { + if (!sandbox_process_info_.IsValid()) + return; + + ::TerminateProcess(sandbox_process_info_.process_handle(), 0); +} + +TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address) { + TargetProcess* target = new TargetProcess(NULL, NULL, NULL, NULL); + PROCESS_INFORMATION process_info = {}; + process_info.hProcess = process; + target->sandbox_process_info_.Set(process_info); + target->base_address_ = base_address; + return target; +} + +} // namespace sandbox diff --git a/sandbox/win/src/target_process.h b/sandbox/win/src/target_process.h new file mode 100644 index 0000000000..cf5ad9f3c8 --- /dev/null +++ b/sandbox/win/src/target_process.h @@ -0,0 +1,135 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_TARGET_PROCESS_H_ +#define SANDBOX_WIN_SRC_TARGET_PROCESS_H_ + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace base { +namespace win { + +class StartupInformation; + +}; // namespace win +}; // namespace base + +namespace sandbox { + +class AttributeList; +class SharedMemIPCServer; +class ThreadProvider; + +// TargetProcess models a target instance (child process). Objects of this +// class are owned by the Policy used to create them. +class TargetProcess { + public: + // The constructor takes ownership of |initial_token| and |lockdown_token|. + TargetProcess(HANDLE initial_token, HANDLE lockdown_token, HANDLE job, + ThreadProvider* thread_pool); + ~TargetProcess(); + + // TODO(cpu): Currently there does not seem to be a reason to implement + // reference counting for this class since is internal, but kept the + // the same interface so the interception framework does not need to be + // touched at this point. + void AddRef() {} + void Release() {} + + // Creates the new target process. The process is created suspended. + // When |set_lockdown_token_after_create| is set, the lockdown token + // is replaced after the process is created + DWORD Create(const wchar_t* exe_path, + const wchar_t* command_line, + bool inherit_handles, + bool set_lockdown_token_after_create, + const base::win::StartupInformation& startup_info, + base::win::ScopedProcessInformation* target_info); + + // Destroys the target process. + void Terminate(); + + // Creates the IPC objects such as the BrokerDispatcher and the + // IPC server. The IPC server uses the services of the thread_pool. + DWORD Init(Dispatcher* ipc_dispatcher, void* policy, + uint32 shared_IPC_size, uint32 shared_policy_size); + + // Returns the handle to the target process. + HANDLE Process() const { + return sandbox_process_info_.process_handle(); + } + + // Returns the handle to the job object that the target process belongs to. + HANDLE Job() const { + return job_; + } + + // Returns the address of the target main exe. This is used by the + // interceptions framework. + HMODULE MainModule() const { + return reinterpret_cast<HMODULE>(base_address_); + } + + // Returns the name of the executable. + const wchar_t* Name() const { + return exe_name_.get(); + } + + // Returns the process id. + DWORD ProcessId() const { + return sandbox_process_info_.process_id(); + } + + // Returns the handle to the main thread. + HANDLE MainThread() const { + return sandbox_process_info_.thread_handle(); + } + + // Transfers a 32-bit variable between the broker and the target. + ResultCode TransferVariable(const char* name, void* address, size_t size); + + private: + // Details of the target process. + base::win::ScopedProcessInformation sandbox_process_info_; + // The token associated with the process. It provides the core of the + // sbox security. + base::win::ScopedHandle lockdown_token_; + // The token given to the initial thread so that the target process can + // start. It has more powers than the lockdown_token. + base::win::ScopedHandle initial_token_; + // Kernel handle to the shared memory used by the IPC server. + base::win::ScopedHandle shared_section_; + // Job object containing the target process. + HANDLE job_; + // Reference to the IPC subsystem. + scoped_ptr<SharedMemIPCServer> ipc_server_; + // Provides the threads used by the IPC. This class does not own this pointer. + ThreadProvider* thread_pool_; + // Base address of the main executable + void* base_address_; + // Full name of the target executable. + scoped_ptr<wchar_t, base::FreeDeleter> exe_name_; + + // Function used for testing. + friend TargetProcess* MakeTestTargetProcess(HANDLE process, + HMODULE base_address); + + DISALLOW_IMPLICIT_CONSTRUCTORS(TargetProcess); +}; + +// Creates a mock TargetProcess used for testing interceptions. +// TODO(cpu): It seems that this method is not going to be used anymore. +TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address); + + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_TARGET_PROCESS_H_ diff --git a/sandbox/win/src/target_services.cc b/sandbox/win/src/target_services.cc new file mode 100644 index 0000000000..7c0afc210a --- /dev/null +++ b/sandbox/win/src/target_services.cc @@ -0,0 +1,209 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/target_services.h" + +#include <new> + +#include <process.h> + +#include "base/basictypes.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/handle_closer_agent.h" +#include "sandbox/win/src/handle_interception.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +// Flushing a cached key is triggered by just opening the key and closing the +// resulting handle. RegDisablePredefinedCache() is the documented way to flush +// HKCU so do not use it with this function. +bool FlushRegKey(HKEY root) { + HKEY key; + if (ERROR_SUCCESS == ::RegOpenKeyExW(root, NULL, 0, MAXIMUM_ALLOWED, &key)) { + if (ERROR_SUCCESS != ::RegCloseKey(key)) + return false; + } + return true; +} + +// This function forces advapi32.dll to release some internally cached handles +// that were made during calls to RegOpenkey and RegOpenKeyEx if it is called +// with a more restrictive token. Returns true if the flushing is succesful +// although this behavior is undocumented and there is no guarantee that in +// fact this will happen in future versions of windows. +bool FlushCachedRegHandles() { + return (FlushRegKey(HKEY_LOCAL_MACHINE) && + FlushRegKey(HKEY_CLASSES_ROOT) && + FlushRegKey(HKEY_USERS)); +} + +// Checks if we have handle entries pending and runs the closer. +bool CloseOpenHandles() { + if (sandbox::HandleCloserAgent::NeedsHandlesClosed()) { + sandbox::HandleCloserAgent handle_closer; + + handle_closer.InitializeHandlesToClose(); + if (!handle_closer.CloseHandles()) + return false; + } + + return true; +} + +// Used as storage for g_target_services, because other allocation facilities +// are not available early. We can't use a regular function static because on +// VS2015, because the CRT tries to acquire a lock to guard initialization, but +// this code runs before the CRT is initialized. +char g_target_services_memory[sizeof(sandbox::TargetServicesBase)]; +sandbox::TargetServicesBase* g_target_services = nullptr; + +} // namespace + +namespace sandbox { + +SANDBOX_INTERCEPT IntegrityLevel g_shared_delayed_integrity_level = + INTEGRITY_LEVEL_LAST; +SANDBOX_INTERCEPT MitigationFlags g_shared_delayed_mitigations = 0; + +TargetServicesBase::TargetServicesBase() { +} + +ResultCode TargetServicesBase::Init() { + process_state_.SetInitCalled(); + return SBOX_ALL_OK; +} + +// Failure here is a breach of security so the process is terminated. +void TargetServicesBase::LowerToken() { + if (ERROR_SUCCESS != + SetProcessIntegrityLevel(g_shared_delayed_integrity_level)) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_INTEGRITY); + process_state_.SetRevertedToSelf(); + // If the client code as called RegOpenKey, advapi32.dll has cached some + // handles. The following code gets rid of them. + if (!::RevertToSelf()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_DROPTOKEN); + if (!FlushCachedRegHandles()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_FLUSHANDLES); + if (ERROR_SUCCESS != ::RegDisablePredefinedCache()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_CACHEDISABLE); + if (!CloseOpenHandles()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_CLOSEHANDLES); + // Enabling mitigations must happen last otherwise handle closing breaks + if (g_shared_delayed_mitigations && + !ApplyProcessMitigationsToCurrentProcess(g_shared_delayed_mitigations)) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_MITIGATION); +} + +ProcessState* TargetServicesBase::GetState() { + return &process_state_; +} + +TargetServicesBase* TargetServicesBase::GetInstance() { + // Leak on purpose TargetServicesBase. + if (!g_target_services) + g_target_services = new (g_target_services_memory) TargetServicesBase; + return g_target_services; +} + +// The broker services a 'test' IPC service with the IPC_PING_TAG tag. +bool TargetServicesBase::TestIPCPing(int version) { + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) { + return false; + } + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + if (1 == version) { + uint32 tick1 = ::GetTickCount(); + uint32 cookie = 717115; + ResultCode code = CrossCall(ipc, IPC_PING1_TAG, cookie, &answer); + + if (SBOX_ALL_OK != code) { + return false; + } + // We should get two extended returns values from the IPC, one is the + // tick count on the broker and the other is the cookie times two. + if ((answer.extended_count != 2)) { + return false; + } + // We test the first extended answer to be within the bounds of the tick + // count only if there was no tick count wraparound. + uint32 tick2 = ::GetTickCount(); + if (tick2 >= tick1) { + if ((answer.extended[0].unsigned_int < tick1) || + (answer.extended[0].unsigned_int > tick2)) { + return false; + } + } + + if (answer.extended[1].unsigned_int != cookie * 2) { + return false; + } + } else if (2 == version) { + uint32 cookie = 717111; + InOutCountedBuffer counted_buffer(&cookie, sizeof(cookie)); + ResultCode code = CrossCall(ipc, IPC_PING2_TAG, counted_buffer, &answer); + + if (SBOX_ALL_OK != code) { + return false; + } + if (cookie != 717111 * 3) { + return false; + } + } else { + return false; + } + return true; +} + +ProcessState::ProcessState() : process_state_(0) { +} + +bool ProcessState::IsKernel32Loaded() const { + return process_state_ != 0; +} + +bool ProcessState::InitCalled() const { + return process_state_ > 1; +} + +bool ProcessState::RevertedToSelf() const { + return process_state_ > 2; +} + +void ProcessState::SetKernel32Loaded() { + if (!process_state_) + process_state_ = 1; +} + +void ProcessState::SetInitCalled() { + if (process_state_ < 2) + process_state_ = 2; +} + +void ProcessState::SetRevertedToSelf() { + if (process_state_ < 3) + process_state_ = 3; +} + +ResultCode TargetServicesBase::DuplicateHandle(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) { + return sandbox::DuplicateHandleProxy(source_handle, target_process_id, + target_handle, desired_access, options); +} + +} // namespace sandbox diff --git a/sandbox/win/src/target_services.h b/sandbox/win/src/target_services.h new file mode 100644 index 0000000000..867a44a478 --- /dev/null +++ b/sandbox/win/src/target_services.h @@ -0,0 +1,67 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_TARGET_SERVICES_H__ +#define SANDBOX_SRC_TARGET_SERVICES_H__ + +#include "base/basictypes.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +class ProcessState { + public: + ProcessState(); + // Returns true if kernel32.dll has been loaded. + bool IsKernel32Loaded() const; + // Returns true if main has been called. + bool InitCalled() const; + // Returns true if LowerToken has been called. + bool RevertedToSelf() const; + // Set the current state. + void SetKernel32Loaded(); + void SetInitCalled(); + void SetRevertedToSelf(); + + private: + int process_state_; + DISALLOW_COPY_AND_ASSIGN(ProcessState); +}; + +// This class is an implementation of the TargetServices. +// Look in the documentation of sandbox::TargetServices for more info. +// Do NOT add a destructor to this class without changing the implementation of +// the factory method. +class TargetServicesBase : public TargetServices { + public: + TargetServicesBase(); + + // Public interface of TargetServices. + ResultCode Init() override; + void LowerToken() override; + ProcessState* GetState() override; + ResultCode DuplicateHandle(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) override; + + // Factory method. + static TargetServicesBase* GetInstance(); + + // Sends a simple IPC Message that has a well-known answer. Returns true + // if the IPC was successful and false otherwise. There are 2 versions of + // this test: 1 and 2. The first one send a simple message while the + // second one send a message with an in/out param. + bool TestIPCPing(int version); + + private: + ProcessState process_state_; + DISALLOW_COPY_AND_ASSIGN(TargetServicesBase); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_TARGET_SERVICES_H__ diff --git a/sandbox/win/src/threadpool_unittest.cc b/sandbox/win/src/threadpool_unittest.cc new file mode 100644 index 0000000000..f439810851 --- /dev/null +++ b/sandbox/win/src/threadpool_unittest.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/win2k_threadpool.h" +#include "testing/gtest/include/gtest/gtest.h" + +void __stdcall EmptyCallBack(void*, unsigned char) { +} + +void __stdcall TestCallBack(void* context, unsigned char) { + HANDLE event = reinterpret_cast<HANDLE>(context); + ::SetEvent(event); +} + +namespace sandbox { + +// Test that register and unregister work, part 1. +TEST(IPCTest, ThreadPoolRegisterTest1) { + Win2kThreadPool thread_pool; + + EXPECT_EQ(0, thread_pool.OutstandingWaits()); + + HANDLE event1 = ::CreateEventW(NULL, FALSE, FALSE, NULL); + HANDLE event2 = ::CreateEventW(NULL, FALSE, FALSE, NULL); + + uint32 context = 0; + EXPECT_FALSE(thread_pool.RegisterWait(0, event1, EmptyCallBack, &context)); + EXPECT_EQ(0, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.RegisterWait(this, event1, EmptyCallBack, &context)); + EXPECT_EQ(1, thread_pool.OutstandingWaits()); + EXPECT_TRUE(thread_pool.RegisterWait(this, event2, EmptyCallBack, &context)); + EXPECT_EQ(2, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(this)); + EXPECT_EQ(0, thread_pool.OutstandingWaits()); + + EXPECT_EQ(TRUE, ::CloseHandle(event1)); + EXPECT_EQ(TRUE, ::CloseHandle(event2)); +} + +// Test that register and unregister work, part 2. +TEST(IPCTest, ThreadPoolRegisterTest2) { + Win2kThreadPool thread_pool; + + HANDLE event1 = ::CreateEventW(NULL, FALSE, FALSE, NULL); + HANDLE event2 = ::CreateEventW(NULL, FALSE, FALSE, NULL); + + uint32 context = 0; + uint32 c1 = 0; + uint32 c2 = 0; + + EXPECT_TRUE(thread_pool.RegisterWait(&c1, event1, EmptyCallBack, &context)); + EXPECT_EQ(1, thread_pool.OutstandingWaits()); + EXPECT_TRUE(thread_pool.RegisterWait(&c2, event2, EmptyCallBack, &context)); + EXPECT_EQ(2, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(&c2)); + EXPECT_EQ(1, thread_pool.OutstandingWaits()); + EXPECT_TRUE(thread_pool.UnRegisterWaits(&c2)); + EXPECT_EQ(1, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(&c1)); + EXPECT_EQ(0, thread_pool.OutstandingWaits()); + + EXPECT_EQ(TRUE, ::CloseHandle(event1)); + EXPECT_EQ(TRUE, ::CloseHandle(event2)); +} + +// Test that the thread pool has at least a thread that services an event. +// Test that when the event is un-registered is no longer serviced. +TEST(IPCTest, ThreadPoolSignalAndWaitTest) { + Win2kThreadPool thread_pool; + + // The events are auto reset and start not signaled. + HANDLE event1 = ::CreateEventW(NULL, FALSE, FALSE, NULL); + HANDLE event2 = ::CreateEventW(NULL, FALSE, FALSE, NULL); + + EXPECT_TRUE(thread_pool.RegisterWait(this, event1, TestCallBack, event2)); + + EXPECT_EQ(WAIT_OBJECT_0, ::SignalObjectAndWait(event1, event2, 5000, FALSE)); + EXPECT_EQ(WAIT_OBJECT_0, ::SignalObjectAndWait(event1, event2, 5000, FALSE)); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(this)); + EXPECT_EQ(0, thread_pool.OutstandingWaits()); + + EXPECT_EQ(WAIT_TIMEOUT, ::SignalObjectAndWait(event1, event2, 1000, FALSE)); + + EXPECT_EQ(TRUE, ::CloseHandle(event1)); + EXPECT_EQ(TRUE, ::CloseHandle(event2)); +} + +} // namespace sandbox diff --git a/sandbox/win/src/unload_dll_test.cc b/sandbox/win/src/unload_dll_test.cc new file mode 100644 index 0000000000..620016c6bd --- /dev/null +++ b/sandbox/win/src/unload_dll_test.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Loads and or unloads a DLL passed in the second parameter of argv. +// The first parameter of argv is 'L' = load, 'U' = unload or 'B' for both. +SBOX_TESTS_COMMAND int UseOneDLL(int argc, wchar_t **argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_RUN_TEST; + int rv = SBOX_TEST_FAILED_TO_RUN_TEST; + + wchar_t option = (argv[0])[0]; + if ((option == L'L') || (option == L'B')) { + HMODULE module1 = ::LoadLibraryW(argv[1]); + rv = (module1 == NULL) ? SBOX_TEST_FAILED : SBOX_TEST_SUCCEEDED; + } + + if ((option == L'U') || (option == L'B')) { + HMODULE module2 = ::GetModuleHandleW(argv[1]); + rv = ::FreeLibrary(module2) ? SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED; + } + return rv; +} + +// Opens an event passed as the first parameter of argv. +SBOX_TESTS_COMMAND int SimpleOpenEvent(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + base::win::ScopedHandle event_open(::OpenEvent(SYNCHRONIZE, FALSE, argv[0])); + return event_open.Get() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED; +} + +// Flaky on windows, see http://crbug.com/80569. +TEST(UnloadDllTest, DISABLED_BaselineAvicapDll) { + TestRunner runner; + runner.SetTestState(BEFORE_REVERT); + runner.SetTimeout(2000); + // Add a sync rule, because that ensures that the interception agent has + // more than one item in its internal table. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, L"t0001")); + + // Note for the puzzled: avicap32.dll is a 64-bit dll in 64-bit versions of + // windows so this test and the others just work. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"UseOneDLL L avicap32.dll")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"UseOneDLL B avicap32.dll")); +} + +// Flaky on windows, see http://crbug.com/80569. +TEST(UnloadDllTest, DISABLED_UnloadAviCapDllNoPatching) { + TestRunner runner; + runner.SetTestState(BEFORE_REVERT); + runner.SetTimeout(2000); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + policy->AddDllToUnload(L"avicap32.dll"); + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"UseOneDLL L avicap32.dll")); + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"UseOneDLL B avicap32.dll")); +} + +// Flaky: http://crbug.com/38404 +TEST(UnloadDllTest, DISABLED_UnloadAviCapDllWithPatching) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(BEFORE_REVERT); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + policy->AddDllToUnload(L"avicap32.dll"); + + base::win::ScopedHandle handle1(::CreateEvent( + NULL, FALSE, FALSE, L"tst0001")); + + // Add a couple of rules that ensures that the interception agent add EAT + // patching on the client which makes sure that the unload dll record does + // not interact badly with them. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Microsoft")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, L"tst0001")); + + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"UseOneDLL L avicap32.dll")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"SimpleOpenEvent tst0001")); +} + +} // namespace sandbox diff --git a/sandbox/win/src/win2k_threadpool.cc b/sandbox/win/src/win2k_threadpool.cc new file mode 100644 index 0000000000..051cfc1c89 --- /dev/null +++ b/sandbox/win/src/win2k_threadpool.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/win2k_threadpool.h" + +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +Win2kThreadPool::Win2kThreadPool() { + ::InitializeCriticalSection(&lock_); +} + +bool Win2kThreadPool::RegisterWait(const void* cookie, HANDLE waitable_object, + CrossCallIPCCallback callback, + void* context) { + if (0 == cookie) { + return false; + } + HANDLE pool_object = NULL; + // create a wait for a kernel object, with no timeout + if (!::RegisterWaitForSingleObject(&pool_object, waitable_object, callback, + context, INFINITE, WT_EXECUTEDEFAULT)) { + return false; + } + PoolObject pool_obj = {cookie, pool_object}; + AutoLock lock(&lock_); + pool_objects_.push_back(pool_obj); + return true; +} + +bool Win2kThreadPool::UnRegisterWaits(void* cookie) { + if (0 == cookie) { + return false; + } + AutoLock lock(&lock_); + bool success = true; + PoolObjects::iterator it = pool_objects_.begin(); + while (it != pool_objects_.end()) { + if (it->cookie == cookie) { + HANDLE wait = it->wait; + it = pool_objects_.erase(it); + success &= (::UnregisterWaitEx(wait, INVALID_HANDLE_VALUE) != 0); + } else { + ++it; + } + } + return success; +} + +size_t Win2kThreadPool::OutstandingWaits() { + AutoLock lock(&lock_); + return pool_objects_.size(); +} + +Win2kThreadPool::~Win2kThreadPool() { + // Here we used to unregister all the pool wait handles. Now, following the + // rest of the code we avoid lengthy or blocking calls given that the process + // is being torn down. + ::DeleteCriticalSection(&lock_); +} + +} // namespace sandbox diff --git a/sandbox/win/src/win2k_threadpool.h b/sandbox/win/src/win2k_threadpool.h new file mode 100644 index 0000000000..be2791f309 --- /dev/null +++ b/sandbox/win/src/win2k_threadpool.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_WIN2K_THREADPOOL_H_ +#define SANDBOX_SRC_WIN2K_THREADPOOL_H_ + +#include <list> +#include <algorithm> +#include "sandbox/win/src/crosscall_server.h" + +namespace sandbox { + +// Win2kThreadPool a simple implementation of a thread provider as required +// for the sandbox IPC subsystem. See sandbox\crosscall_server.h for the details +// and requirements of this interface. +// +// Implementing the thread provider as a thread pool is desirable in the case +// of shared memory IPC because it can generate a large number of waitable +// events: as many as channels. A thread pool does not create a thread per +// event, instead maintains a few idle threads but can create more if the need +// arises. +// +// This implementation simply thunks to the nice thread pool API of win2k. +class Win2kThreadPool : public ThreadProvider { + public: + Win2kThreadPool(); + ~Win2kThreadPool() override; + + bool RegisterWait(const void* cookie, + HANDLE waitable_object, + CrossCallIPCCallback callback, + void* context) override; + + bool UnRegisterWaits(void* cookie) override; + + // Returns the total number of wait objects associated with + // the thread pool. + size_t OutstandingWaits(); + + private: + // record to keep track of a wait and its associated cookie. + struct PoolObject { + const void* cookie; + HANDLE wait; + }; + // The list of pool wait objects. + typedef std::list<PoolObject> PoolObjects; + PoolObjects pool_objects_; + // This lock protects the list of pool wait objects. + CRITICAL_SECTION lock_; + + DISALLOW_COPY_AND_ASSIGN(Win2kThreadPool); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_WIN2K_THREADPOOL_H_ diff --git a/sandbox/win/src/win_utils.cc b/sandbox/win/src/win_utils.cc new file mode 100644 index 0000000000..2ff1b7343c --- /dev/null +++ b/sandbox/win/src/win_utils.cc @@ -0,0 +1,432 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/win_utils.h" + +#include <map> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/win/pe_image.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +// Holds the information about a known registry key. +struct KnownReservedKey { + const wchar_t* name; + HKEY key; +}; + +// Contains all the known registry key by name and by handle. +const KnownReservedKey kKnownKey[] = { + { L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT }, + { L"HKEY_CURRENT_USER", HKEY_CURRENT_USER }, + { L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE}, + { L"HKEY_USERS", HKEY_USERS}, + { L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA}, + { L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT}, + { L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT}, + { L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG}, + { L"HKEY_DYN_DATA", HKEY_DYN_DATA} +}; + +// These functions perform case independent path comparisons. +bool EqualPath(const base::string16& first, const base::string16& second) { + return _wcsicmp(first.c_str(), second.c_str()) == 0; +} + +bool EqualPath(const base::string16& first, size_t first_offset, + const base::string16& second, size_t second_offset) { + return _wcsicmp(first.c_str() + first_offset, + second.c_str() + second_offset) == 0; +} + +bool EqualPath(const base::string16& first, + const wchar_t* second, size_t second_len) { + return _wcsnicmp(first.c_str(), second, second_len) == 0; +} + +bool EqualPath(const base::string16& first, size_t first_offset, + const wchar_t* second, size_t second_len) { + return _wcsnicmp(first.c_str() + first_offset, second, second_len) == 0; +} + +// Returns true if |path| starts with "\??\" and returns a path without that +// component. +bool IsNTPath(const base::string16& path, base::string16* trimmed_path ) { + if ((path.size() < sandbox::kNTPrefixLen) || + (0 != path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix))) { + *trimmed_path = path; + return false; + } + + *trimmed_path = path.substr(sandbox::kNTPrefixLen); + return true; +} + +// Returns true if |path| starts with "\Device\" and returns a path without that +// component. +bool IsDevicePath(const base::string16& path, base::string16* trimmed_path ) { + if ((path.size() < sandbox::kNTDevicePrefixLen) || + (!EqualPath(path, sandbox::kNTDevicePrefix, + sandbox::kNTDevicePrefixLen))) { + *trimmed_path = path; + return false; + } + + *trimmed_path = path.substr(sandbox::kNTDevicePrefixLen); + return true; +} + +bool StartsWithDriveLetter(const base::string16& path) { + if (path.size() < 3) + return false; + + if (path[1] != L':' || path[2] != L'\\') + return false; + + return (path[0] >= 'a' && path[0] <= 'z') || + (path[0] >= 'A' && path[0] <= 'Z'); +} + +const wchar_t kNTDotPrefix[] = L"\\\\.\\"; +const size_t kNTDotPrefixLen = arraysize(kNTDotPrefix) - 1; + +// Removes "\\\\.\\" from the path. +void RemoveImpliedDevice(base::string16* path) { + if (0 == path->compare(0, kNTDotPrefixLen, kNTDotPrefix)) + *path = path->substr(kNTDotPrefixLen); +} + +} // namespace + +namespace sandbox { + +// Returns true if the provided path points to a pipe. +bool IsPipe(const base::string16& path) { + size_t start = 0; + if (0 == path.compare(0, sandbox::kNTPrefixLen, sandbox::kNTPrefix)) + start = sandbox::kNTPrefixLen; + + const wchar_t kPipe[] = L"pipe\\"; + if (path.size() < start + arraysize(kPipe) - 1) + return false; + + return EqualPath(path, start, kPipe, arraysize(kPipe) - 1); +} + +HKEY GetReservedKeyFromName(const base::string16& name) { + for (size_t i = 0; i < arraysize(kKnownKey); ++i) { + if (name == kKnownKey[i].name) + return kKnownKey[i].key; + } + + return NULL; +} + +bool ResolveRegistryName(base::string16 name, base::string16* resolved_name) { + for (size_t i = 0; i < arraysize(kKnownKey); ++i) { + if (name.find(kKnownKey[i].name) == 0) { + HKEY key; + DWORD disposition; + if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, NULL, 0, + MAXIMUM_ALLOWED, NULL, &key, + &disposition)) + return false; + + bool result = GetPathFromHandle(key, resolved_name); + ::RegCloseKey(key); + + if (!result) + return false; + + *resolved_name += name.substr(wcslen(kKnownKey[i].name)); + return true; + } + } + + return false; +} + +// |full_path| can have any of the following forms: +// \??\c:\some\foo\bar +// \Device\HarddiskVolume0\some\foo\bar +// \??\HarddiskVolume0\some\foo\bar +DWORD IsReparsePoint(const base::string16& full_path, bool* result) { + // Check if it's a pipe. We can't query the attributes of a pipe. + if (IsPipe(full_path)) { + *result = FALSE; + return ERROR_SUCCESS; + } + + base::string16 path; + bool nt_path = IsNTPath(full_path, &path); + bool has_drive = StartsWithDriveLetter(path); + bool is_device_path = IsDevicePath(path, &path); + + if (!has_drive && !is_device_path && !nt_path) + return ERROR_INVALID_NAME; + + bool added_implied_device = false; + if (!has_drive) { + path = base::string16(kNTDotPrefix) + path; + added_implied_device = true; + } + + base::string16::size_type last_pos = base::string16::npos; + bool passed_once = false; + + do { + path = path.substr(0, last_pos); + + DWORD attributes = ::GetFileAttributes(path.c_str()); + if (INVALID_FILE_ATTRIBUTES == attributes) { + DWORD error = ::GetLastError(); + if (error != ERROR_FILE_NOT_FOUND && + error != ERROR_PATH_NOT_FOUND && + error != ERROR_INVALID_NAME) { + // Unexpected error. + if (passed_once && added_implied_device && + (path.rfind(L'\\') == kNTDotPrefixLen - 1)) { + break; + } + NOTREACHED_NT(); + return error; + } + } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) { + // This is a reparse point. + *result = true; + return ERROR_SUCCESS; + } + + passed_once = true; + last_pos = path.rfind(L'\\'); + } while (last_pos > 2); // Skip root dir. + + *result = false; + return ERROR_SUCCESS; +} + +// We get a |full_path| of the forms accepted by IsReparsePoint(), and the name +// we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar. +bool SameObject(HANDLE handle, const wchar_t* full_path) { + // Check if it's a pipe. + if (IsPipe(full_path)) + return true; + + base::string16 actual_path; + if (!GetPathFromHandle(handle, &actual_path)) + return false; + + base::string16 path(full_path); + DCHECK_NT(!path.empty()); + + // This may end with a backslash. + const wchar_t kBackslash = '\\'; + if (path[path.length() - 1] == kBackslash) + path = path.substr(0, path.length() - 1); + + // Perfect match (case-insesitive check). + if (EqualPath(actual_path, path)) + return true; + + bool nt_path = IsNTPath(path, &path); + bool has_drive = StartsWithDriveLetter(path); + + if (!has_drive && nt_path) { + base::string16 simple_actual_path; + if (!IsDevicePath(actual_path, &simple_actual_path)) + return false; + + // Perfect match (case-insesitive check). + return (EqualPath(simple_actual_path, path)); + } + + if (!has_drive) + return false; + + // We only need 3 chars, but let's alloc a buffer for four. + wchar_t drive[4] = {0}; + wchar_t vol_name[MAX_PATH]; + memcpy(drive, &path[0], 2 * sizeof(*drive)); + + // We'll get a double null terminated string. + DWORD vol_length = ::QueryDosDeviceW(drive, vol_name, MAX_PATH); + if (vol_length < 2 || vol_length == MAX_PATH) + return false; + + // Ignore the nulls at the end. + vol_length = static_cast<DWORD>(wcslen(vol_name)); + + // The two paths should be the same length. + if (vol_length + path.size() - 2 != actual_path.size()) + return false; + + // Check up to the drive letter. + if (!EqualPath(actual_path, vol_name, vol_length)) + return false; + + // Check the path after the drive letter. + if (!EqualPath(actual_path, vol_length, path, 2)) + return false; + + return true; +} + +// Paths like \Device\HarddiskVolume0\some\foo\bar are assumed to be already +// expanded. +bool ConvertToLongPath(const base::string16& short_path, + base::string16* long_path) { + if (IsPipe(short_path)) { + // TODO(rvargas): Change the signature to use a single argument. + long_path->assign(short_path); + return true; + } + + base::string16 path; + if (IsDevicePath(short_path, &path)) + return false; + + bool is_nt_path = IsNTPath(path, &path); + bool added_implied_device = false; + if (!StartsWithDriveLetter(path) && is_nt_path) { + path = base::string16(kNTDotPrefix) + path; + added_implied_device = true; + } + + DWORD size = MAX_PATH; + scoped_ptr<wchar_t[]> long_path_buf(new wchar_t[size]); + + DWORD return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), + size); + while (return_value >= size) { + size *= 2; + long_path_buf.reset(new wchar_t[size]); + return_value = ::GetLongPathName(path.c_str(), long_path_buf.get(), size); + } + + DWORD last_error = ::GetLastError(); + if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error || + ERROR_PATH_NOT_FOUND == last_error || + ERROR_INVALID_NAME == last_error)) { + // The file does not exist, but maybe a sub path needs to be expanded. + base::string16::size_type last_slash = path.rfind(L'\\'); + if (base::string16::npos == last_slash) + return false; + + base::string16 begin = path.substr(0, last_slash); + base::string16 end = path.substr(last_slash); + if (!ConvertToLongPath(begin, &begin)) + return false; + + // Ok, it worked. Let's reset the return value. + path = begin + end; + return_value = 1; + } else if (0 != return_value) { + path = long_path_buf.get(); + } + + if (return_value != 0) { + if (added_implied_device) + RemoveImpliedDevice(&path); + + if (is_nt_path) { + *long_path = kNTPrefix; + *long_path += path; + } else { + *long_path = path; + } + + return true; + } + + return false; +} + +bool GetPathFromHandle(HANDLE handle, base::string16* path) { + NtQueryObjectFunction NtQueryObject = NULL; + ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); + + OBJECT_NAME_INFORMATION initial_buffer; + OBJECT_NAME_INFORMATION* name = &initial_buffer; + ULONG size = sizeof(initial_buffer); + // Query the name information a first time to get the size of the name. + // Windows XP requires that the size of the buffer passed in here be != 0. + NTSTATUS status = NtQueryObject(handle, ObjectNameInformation, name, size, + &size); + + scoped_ptr<BYTE[]> name_ptr; + if (size) { + name_ptr.reset(new BYTE[size]); + name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(name_ptr.get()); + + // Query the name information a second time to get the name of the + // object referenced by the handle. + status = NtQueryObject(handle, ObjectNameInformation, name, size, &size); + } + + if (STATUS_SUCCESS != status) + return false; + + path->assign(name->ObjectName.Buffer, name->ObjectName.Length / + sizeof(name->ObjectName.Buffer[0])); + return true; +} + +bool GetNtPathFromWin32Path(const base::string16& path, + base::string16* nt_path) { + HANDLE file = ::CreateFileW(path.c_str(), 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (file == INVALID_HANDLE_VALUE) + return false; + bool rv = GetPathFromHandle(file, nt_path); + ::CloseHandle(file); + return rv; +} + +bool WriteProtectedChildMemory(HANDLE child_process, void* address, + const void* buffer, size_t length) { + // First, remove the protections. + DWORD old_protection; + if (!::VirtualProtectEx(child_process, address, length, + PAGE_WRITECOPY, &old_protection)) + return false; + + SIZE_T written; + bool ok = ::WriteProcessMemory(child_process, address, buffer, length, + &written) && (length == written); + + // Always attempt to restore the original protection. + if (!::VirtualProtectEx(child_process, address, length, + old_protection, &old_protection)) + return false; + + return ok; +} + +}; // namespace sandbox + +void ResolveNTFunctionPtr(const char* name, void* ptr) { + static volatile HMODULE ntdll = NULL; + + if (!ntdll) { + HMODULE ntdll_local = ::GetModuleHandle(sandbox::kNtdllName); + // Use PEImage to sanity-check that we have a valid ntdll handle. + base::win::PEImage ntdll_peimage(ntdll_local); + CHECK_NT(ntdll_peimage.VerifyMagic()); + // Race-safe way to set static ntdll. + ::InterlockedCompareExchangePointer( + reinterpret_cast<PVOID volatile*>(&ntdll), ntdll_local, NULL); + + } + + CHECK_NT(ntdll); + FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr); + *function_ptr = ::GetProcAddress(ntdll, name); + CHECK_NT(*function_ptr); +} diff --git a/sandbox/win/src/win_utils.h b/sandbox/win/src/win_utils.h new file mode 100644 index 0000000000..3e4565f46f --- /dev/null +++ b/sandbox/win/src/win_utils.h @@ -0,0 +1,117 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_WIN_UTILS_H_ +#define SANDBOX_SRC_WIN_UTILS_H_ + +#include <windows.h> +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string16.h" + +namespace sandbox { + +// Prefix for path used by NT calls. +const wchar_t kNTPrefix[] = L"\\??\\"; +const size_t kNTPrefixLen = arraysize(kNTPrefix) - 1; + +const wchar_t kNTDevicePrefix[] = L"\\Device\\"; +const size_t kNTDevicePrefixLen = arraysize(kNTDevicePrefix) - 1; + +// Automatically acquires and releases a lock when the object is +// is destroyed. +class AutoLock { + public: + // Acquires the lock. + explicit AutoLock(CRITICAL_SECTION *lock) : lock_(lock) { + ::EnterCriticalSection(lock); + }; + + // Releases the lock; + ~AutoLock() { + ::LeaveCriticalSection(lock_); + }; + + private: + CRITICAL_SECTION *lock_; + DISALLOW_IMPLICIT_CONSTRUCTORS(AutoLock); +}; + +// Basic implementation of a singleton which calls the destructor +// when the exe is shutting down or the DLL is being unloaded. +template <typename Derived> +class SingletonBase { + public: + static Derived* GetInstance() { + static Derived* instance = NULL; + if (NULL == instance) { + instance = new Derived(); + // Microsoft CRT extension. In an exe this this called after + // winmain returns, in a dll is called in DLL_PROCESS_DETACH + _onexit(OnExit); + } + return instance; + } + + private: + // this is the function that gets called by the CRT when the + // process is shutting down. + static int __cdecl OnExit() { + delete GetInstance(); + return 0; + } +}; + +// Convert a short path (C:\path~1 or \\??\\c:\path~1) to the long version of +// the path. If the path is not a valid filesystem path, the function returns +// false and the output parameter is not modified. +bool ConvertToLongPath(const base::string16& short_path, + base::string16* long_path); + +// Sets result to true if the path contains a reparse point. The return value +// is ERROR_SUCCESS when the function succeeds or the appropriate error code +// when the function fails. +// This function is not smart. It looks for each element in the path and +// returns true if any of them is a reparse point. +DWORD IsReparsePoint(const base::string16& full_path, bool* result); + +// Returns true if the handle corresponds to the object pointed by this path. +bool SameObject(HANDLE handle, const wchar_t* full_path); + +// Resolves a handle to an nt path. Returns true if the handle can be resolved. +bool GetPathFromHandle(HANDLE handle, base::string16* path); + +// Resolves a win32 path to an nt path using GetPathFromHandle. The path must +// exist. Returs true if the translation was succesful. +bool GetNtPathFromWin32Path(const base::string16& path, + base::string16* nt_path); + +// Translates a reserved key name to its handle. +// For example "HKEY_LOCAL_MACHINE" returns HKEY_LOCAL_MACHINE. +// Returns NULL if the name does not represent any reserved key name. +HKEY GetReservedKeyFromName(const base::string16& name); + +// Resolves a user-readable registry path to a system-readable registry path. +// For example, HKEY_LOCAL_MACHINE\\Software\\microsoft is translated to +// \\registry\\machine\\software\\microsoft. Returns false if the path +// cannot be resolved. +bool ResolveRegistryName(base::string16 name, base::string16* resolved_name); + +// Writes |length| bytes from the provided |buffer| into the address space of +// |child_process|, at the specified |address|, preserving the original write +// protection attributes. Returns true on success. +bool WriteProtectedChildMemory(HANDLE child_process, void* address, + const void* buffer, size_t length); + +// Returns true if the provided path points to a pipe. +bool IsPipe(const base::string16& path); + +} // namespace sandbox + +// Resolves a function name in NTDLL to a function pointer. The second parameter +// is a pointer to the function pointer. +void ResolveNTFunctionPtr(const char* name, void* ptr); + +#endif // SANDBOX_SRC_WIN_UTILS_H_ diff --git a/sandbox/win/src/win_utils_unittest.cc b/sandbox/win/src/win_utils_unittest.cc new file mode 100644 index 0000000000..4cf59b85eb --- /dev/null +++ b/sandbox/win/src/win_utils_unittest.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2011 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 <windows.h> + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(WinUtils, IsReparsePoint) { + using sandbox::IsReparsePoint; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t my_folder[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, my_folder), 0u); + + // Delete the file and create a directory instead. + ASSERT_TRUE(::DeleteFile(my_folder)); + ASSERT_TRUE(::CreateDirectory(my_folder, NULL)); + + bool result = true; + EXPECT_EQ(ERROR_SUCCESS, IsReparsePoint(my_folder, &result)); + EXPECT_FALSE(result); + + // We have to fix Bug 32224 to pass this test. + base::string16 not_found = base::string16(my_folder) + L"\\foo\\bar"; + // EXPECT_EQ(ERROR_PATH_NOT_FOUND, IsReparsePoint(not_found, &result)); + + base::string16 new_file = base::string16(my_folder) + L"\\foo"; + EXPECT_EQ(ERROR_SUCCESS, IsReparsePoint(new_file, &result)); + EXPECT_FALSE(result); + + // Replace the directory with a reparse point to %temp%. + HANDLE dir = ::CreateFile(my_folder, FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + EXPECT_NE(INVALID_HANDLE_VALUE, dir); + + base::string16 temp_dir_nt = base::string16(L"\\??\\") + temp_directory; + EXPECT_TRUE(SetReparsePoint(dir, temp_dir_nt.c_str())); + + EXPECT_EQ(ERROR_SUCCESS, IsReparsePoint(new_file, &result)); + EXPECT_TRUE(result); + + EXPECT_TRUE(DeleteReparsePoint(dir)); + EXPECT_TRUE(::CloseHandle(dir)); + EXPECT_TRUE(::RemoveDirectory(my_folder)); +} + +TEST(WinUtils, SameObject) { + using sandbox::SameObject; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t my_folder[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, my_folder), 0u); + + // Delete the file and create a directory instead. + ASSERT_TRUE(::DeleteFile(my_folder)); + ASSERT_TRUE(::CreateDirectory(my_folder, NULL)); + + base::string16 folder(my_folder); + base::string16 file_name = folder + L"\\foo.txt"; + const ULONG kSharing = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; + base::win::ScopedHandle file(CreateFile( + file_name.c_str(), GENERIC_WRITE, kSharing, NULL, CREATE_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, NULL)); + + EXPECT_TRUE(file.IsValid()); + base::string16 file_name_nt1 = base::string16(L"\\??\\") + file_name; + base::string16 file_name_nt2 = + base::string16(L"\\??\\") + folder + L"\\FOO.txT"; + EXPECT_TRUE(SameObject(file.Get(), file_name_nt1.c_str())); + EXPECT_TRUE(SameObject(file.Get(), file_name_nt2.c_str())); + + file.Close(); + EXPECT_TRUE(::RemoveDirectory(my_folder)); +} + +TEST(WinUtils, IsPipe) { + using sandbox::IsPipe; + + base::string16 pipe_name = L"\\??\\pipe\\mypipe"; + EXPECT_TRUE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\PiPe\\mypipe"; + EXPECT_TRUE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\pipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\_pipe_\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\ABCD\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + + // Written as two strings to prevent trigraph '?' '?' '/'. + pipe_name = L"/?" L"?/pipe/mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\XX\\pipe\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\Device\\NamedPipe\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); +} diff --git a/sandbox/win/src/window.cc b/sandbox/win/src/window.cc new file mode 100644 index 0000000000..cfbf280d9c --- /dev/null +++ b/sandbox/win/src/window.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/window.h" + +#include <aclapi.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/sid.h" + +namespace { + +// Gets the security attributes of a window object referenced by |handle|. The +// lpSecurityDescriptor member of the SECURITY_ATTRIBUTES parameter returned +// must be freed using LocalFree by the caller. +bool GetSecurityAttributes(HANDLE handle, SECURITY_ATTRIBUTES* attributes) { + attributes->bInheritHandle = FALSE; + attributes->nLength = sizeof(SECURITY_ATTRIBUTES); + + PACL dacl = NULL; + DWORD result = ::GetSecurityInfo(handle, SE_WINDOW_OBJECT, + DACL_SECURITY_INFORMATION, NULL, NULL, &dacl, + NULL, &attributes->lpSecurityDescriptor); + if (ERROR_SUCCESS == result) + return true; + + return false; +} + +} + +namespace sandbox { + +ResultCode CreateAltWindowStation(HWINSTA* winsta) { + // Get the security attributes from the current window station; we will + // use this as the base security attributes for the new window station. + SECURITY_ATTRIBUTES attributes = {0}; + if (!GetSecurityAttributes(::GetProcessWindowStation(), &attributes)) { + return SBOX_ERROR_CANNOT_CREATE_WINSTATION; + } + + // Create the window station using NULL for the name to ask the os to + // generate it. + *winsta = ::CreateWindowStationW( + NULL, 0, GENERIC_READ | WINSTA_CREATEDESKTOP, &attributes); + LocalFree(attributes.lpSecurityDescriptor); + + if (*winsta) + return SBOX_ALL_OK; + + return SBOX_ERROR_CANNOT_CREATE_WINSTATION; +} + +ResultCode CreateAltDesktop(HWINSTA winsta, HDESK* desktop) { + base::string16 desktop_name = L"sbox_alternate_desktop_"; + + // Append the current PID to the desktop name. + wchar_t buffer[16]; + _snwprintf_s(buffer, sizeof(buffer) / sizeof(wchar_t), L"0x%X", + ::GetCurrentProcessId()); + desktop_name += buffer; + + // Get the security attributes from the current desktop, we will use this as + // the base security attributes for the new desktop. + SECURITY_ATTRIBUTES attributes = {0}; + if (!GetSecurityAttributes(GetThreadDesktop(GetCurrentThreadId()), + &attributes)) { + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + } + + // Back up the current window station, in case we need to switch it. + HWINSTA current_winsta = ::GetProcessWindowStation(); + + if (winsta) { + // We need to switch to the alternate window station before creating the + // desktop. + if (!::SetProcessWindowStation(winsta)) { + ::LocalFree(attributes.lpSecurityDescriptor); + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + } + } + + // Create the destkop. + *desktop = ::CreateDesktop(desktop_name.c_str(), + NULL, + NULL, + 0, + DESKTOP_CREATEWINDOW | DESKTOP_READOBJECTS | + READ_CONTROL | WRITE_DAC | WRITE_OWNER, + &attributes); + ::LocalFree(attributes.lpSecurityDescriptor); + + if (winsta) { + // Revert to the right window station. + if (!::SetProcessWindowStation(current_winsta)) { + return SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION; + } + } + + if (*desktop) { + // Replace the DACL on the new Desktop with a reduced privilege version. + // We can soft fail on this for now, as it's just an extra mitigation. + static const ACCESS_MASK kDesktopDenyMask = WRITE_DAC | WRITE_OWNER | + DELETE | + DESKTOP_CREATEMENU | + DESKTOP_CREATEWINDOW | + DESKTOP_HOOKCONTROL | + DESKTOP_JOURNALPLAYBACK | + DESKTOP_JOURNALRECORD | + DESKTOP_SWITCHDESKTOP; + AddKnownSidToObject(*desktop, SE_WINDOW_OBJECT, Sid(WinRestrictedCodeSid), + DENY_ACCESS, kDesktopDenyMask); + return SBOX_ALL_OK; + } + + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; +} + +base::string16 GetWindowObjectName(HANDLE handle) { + // Get the size of the name. + DWORD size = 0; + ::GetUserObjectInformation(handle, UOI_NAME, NULL, 0, &size); + + if (!size) { + NOTREACHED(); + return base::string16(); + } + + // Create the buffer that will hold the name. + scoped_ptr<wchar_t[]> name_buffer(new wchar_t[size]); + + // Query the name of the object. + if (!::GetUserObjectInformation(handle, UOI_NAME, name_buffer.get(), size, + &size)) { + NOTREACHED(); + return base::string16(); + } + + return base::string16(name_buffer.get()); +} + +base::string16 GetFullDesktopName(HWINSTA winsta, HDESK desktop) { + if (!desktop) { + NOTREACHED(); + return base::string16(); + } + + base::string16 name; + if (winsta) { + name = GetWindowObjectName(winsta); + name += L'\\'; + } + + name += GetWindowObjectName(desktop); + return name; +} + +} // namespace sandbox diff --git a/sandbox/win/src/window.h b/sandbox/win/src/window.h new file mode 100644 index 0000000000..62fe7c4742 --- /dev/null +++ b/sandbox/win/src/window.h @@ -0,0 +1,40 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_WINDOW_H_ +#define SANDBOX_SRC_WINDOW_H_ + +#include <windows.h> +#include <string> + +#include "base/strings/string16.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + + // Creates a window station. The name is generated by the OS. The security + // descriptor is based on the security descriptor of the current window + // station. + ResultCode CreateAltWindowStation(HWINSTA* winsta); + + // Creates a desktop. The name is a static string followed by the pid of the + // current process. The security descriptor on the new desktop is based on the + // security descriptor of the desktop associated with the current thread. + // If a winsta is specified, the function will switch to it before creating + // the desktop. If the functions fails the switch back to the current winsta, + // the function will return SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION. + ResultCode CreateAltDesktop(HWINSTA winsta, HDESK* desktop); + + // Returns the name of a desktop or a window station. + base::string16 GetWindowObjectName(HANDLE handle); + + // Returns the name of the desktop referenced by |desktop|. If a window + // station is specified, the name is prepended with the window station name, + // followed by a backslash. This name can be used as the lpDesktop parameter + // to CreateProcess. + base::string16 GetFullDesktopName(HWINSTA winsta, HDESK desktop); + +} // namespace sandbox + +#endif // SANDBOX_SRC_WINDOW_H_ diff --git a/sandbox/win/tests/common/controller.cc b/sandbox/win/tests/common/controller.cc new file mode 100644 index 0000000000..bdada4b478 --- /dev/null +++ b/sandbox/win/tests/common/controller.cc @@ -0,0 +1,363 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/tests/common/controller.h" + +#include <string> + +#include "base/memory/shared_memory.h" +#include "base/process/process.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/sys_string_conversions.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/sandbox_factory.h" + +namespace { + +static const int kDefaultTimeout = 60000; + +// Constructs a full path to a file inside the system32 folder. +base::string16 MakePathToSys32(const wchar_t* name, bool is_obj_man_path) { + wchar_t windows_path[MAX_PATH] = {0}; + if (0 == ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH)) + return base::string16(); + + base::string16 full_path(windows_path); + if (full_path.empty()) + return full_path; + + if (is_obj_man_path) + full_path.insert(0, L"\\??\\"); + + full_path += L"\\system32\\"; + full_path += name; + return full_path; +} + +// Constructs a full path to a file inside the syswow64 folder. +base::string16 MakePathToSysWow64(const wchar_t* name, bool is_obj_man_path) { + wchar_t windows_path[MAX_PATH] = {0}; + if (0 == ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH)) + return base::string16(); + + base::string16 full_path(windows_path); + if (full_path.empty()) + return full_path; + + if (is_obj_man_path) + full_path.insert(0, L"\\??\\"); + + full_path += L"\\SysWOW64\\"; + full_path += name; + return full_path; +} + +bool IsProcessRunning(HANDLE process) { + DWORD exit_code = 0; + if (::GetExitCodeProcess(process, &exit_code)) + return exit_code == STILL_ACTIVE; + return false; +} + +} // namespace + +namespace sandbox { + +base::string16 MakePathToSys(const wchar_t* name, bool is_obj_man_path) { + return (base::win::OSInfo::GetInstance()->wow64_status() == + base::win::OSInfo::WOW64_ENABLED) ? + MakePathToSysWow64(name, is_obj_man_path) : + MakePathToSys32(name, is_obj_man_path); +} + +BrokerServices* GetBroker() { + static BrokerServices* broker = SandboxFactory::GetBrokerServices(); + static bool is_initialized = false; + + if (!broker) { + return NULL; + } + + if (!is_initialized) { + if (SBOX_ALL_OK != broker->Init()) + return NULL; + + is_initialized = true; + } + + return broker; +} + +TestRunner::TestRunner(JobLevel job_level, TokenLevel startup_token, + TokenLevel main_token) + : is_init_(false), is_async_(false), no_sandbox_(false), + target_process_id_(0) { + Init(job_level, startup_token, main_token); +} + +TestRunner::TestRunner() + : is_init_(false), is_async_(false), no_sandbox_(false), + target_process_id_(0) { + Init(JOB_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); +} + +void TestRunner::Init(JobLevel job_level, TokenLevel startup_token, + TokenLevel main_token) { + broker_ = NULL; + policy_ = NULL; + timeout_ = kDefaultTimeout; + state_ = AFTER_REVERT; + is_async_= false; + kill_on_destruction_ = true; + target_process_id_ = 0; + + broker_ = GetBroker(); + if (!broker_) + return; + + policy_ = broker_->CreatePolicy(); + if (!policy_) + return; + + policy_->SetJobLevel(job_level, 0); + policy_->SetTokenLevel(startup_token, main_token); + + is_init_ = true; +} + +TargetPolicy* TestRunner::GetPolicy() { + return policy_; +} + +TestRunner::~TestRunner() { + if (target_process_.IsValid() && kill_on_destruction_) + ::TerminateProcess(target_process_.Get(), 0); + + if (policy_) + policy_->Release(); +} + +bool TestRunner::AddRule(TargetPolicy::SubSystem subsystem, + TargetPolicy::Semantics semantics, + const wchar_t* pattern) { + if (!is_init_) + return false; + + return (SBOX_ALL_OK == policy_->AddRule(subsystem, semantics, pattern)); +} + +bool TestRunner::AddRuleSys32(TargetPolicy::Semantics semantics, + const wchar_t* pattern) { + if (!is_init_) + return false; + + base::string16 win32_path = MakePathToSys32(pattern, false); + if (win32_path.empty()) + return false; + + if (!AddRule(TargetPolicy::SUBSYS_FILES, semantics, win32_path.c_str())) + return false; + + if (base::win::OSInfo::GetInstance()->wow64_status() != + base::win::OSInfo::WOW64_ENABLED) + return true; + + win32_path = MakePathToSysWow64(pattern, false); + if (win32_path.empty()) + return false; + + return AddRule(TargetPolicy::SUBSYS_FILES, semantics, win32_path.c_str()); +} + +bool TestRunner::AddFsRule(TargetPolicy::Semantics semantics, + const wchar_t* pattern) { + if (!is_init_) + return false; + + return AddRule(TargetPolicy::SUBSYS_FILES, semantics, pattern); +} + +int TestRunner::RunTest(const wchar_t* command) { + if (MAX_STATE > 10) + return SBOX_TEST_INVALID_PARAMETER; + + wchar_t state_number[2]; + state_number[0] = static_cast<wchar_t>(L'0' + state_); + state_number[1] = L'\0'; + base::string16 full_command(state_number); + full_command += L" "; + full_command += command; + + return InternalRunTest(full_command.c_str()); +} + +int TestRunner::InternalRunTest(const wchar_t* command) { + if (!is_init_) + return SBOX_TEST_FAILED_TO_RUN_TEST; + + // For simplicity TestRunner supports only one process per instance. + if (target_process_.IsValid()) { + if (IsProcessRunning(target_process_.Get())) + return SBOX_TEST_FAILED_TO_RUN_TEST; + target_process_.Close(); + target_process_id_ = 0; + } + + // Get the path to the sandboxed process. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(NULL, prog_name, MAX_PATH); + + // Launch the sandboxed process. + ResultCode result = SBOX_ALL_OK; + PROCESS_INFORMATION target = {0}; + + base::string16 arguments(L"\""); + arguments += prog_name; + arguments += L"\" -child"; + arguments += no_sandbox_ ? L"-no-sandbox " : L" "; + arguments += command; + + if (no_sandbox_) { + STARTUPINFO startup_info = {sizeof(STARTUPINFO)}; + if (!::CreateProcessW(prog_name, &arguments[0], NULL, NULL, FALSE, 0, + NULL, NULL, &startup_info, &target)) { + return SBOX_ERROR_GENERIC; + } + broker_->AddTargetPeer(target.hProcess); + } else { + result = broker_->SpawnTarget(prog_name, arguments.c_str(), policy_, + &target); + } + + if (SBOX_ALL_OK != result) + return SBOX_TEST_FAILED_TO_RUN_TEST; + + ::ResumeThread(target.hThread); + + // For an asynchronous run we don't bother waiting. + if (is_async_) { + target_process_.Set(target.hProcess); + target_process_id_ = target.dwProcessId; + ::CloseHandle(target.hThread); + return SBOX_TEST_SUCCEEDED; + } + + if (::IsDebuggerPresent()) { + // Don't kill the target process on a time-out while we are debugging. + timeout_ = INFINITE; + } + + if (WAIT_TIMEOUT == ::WaitForSingleObject(target.hProcess, timeout_)) { + ::TerminateProcess(target.hProcess, static_cast<UINT>(SBOX_TEST_TIMED_OUT)); + ::CloseHandle(target.hProcess); + ::CloseHandle(target.hThread); + return SBOX_TEST_TIMED_OUT; + } + + DWORD exit_code = static_cast<DWORD>(SBOX_TEST_LAST_RESULT); + if (!::GetExitCodeProcess(target.hProcess, &exit_code)) { + ::CloseHandle(target.hProcess); + ::CloseHandle(target.hThread); + return SBOX_TEST_FAILED_TO_RUN_TEST; + } + + ::CloseHandle(target.hProcess); + ::CloseHandle(target.hThread); + + return exit_code; +} + +void TestRunner::SetTimeout(DWORD timeout_ms) { + timeout_ = timeout_ms; +} + +void TestRunner::SetTestState(SboxTestsState desired_state) { + state_ = desired_state; +} + +// This is the main procedure for the target (child) application. We'll find out +// the target test and call it. +// We expect the arguments to be: +// argv[1] = "-child" +// argv[2] = SboxTestsState when to run the command +// argv[3] = command to run +// argv[4...] = command arguments. +int DispatchCall(int argc, wchar_t **argv) { + if (argc < 4) + return SBOX_TEST_INVALID_PARAMETER; + + // We hard code two tests to avoid dispatch failures. + if (0 == _wcsicmp(argv[3], L"wait")) { + Sleep(INFINITE); + return SBOX_TEST_TIMED_OUT; + } + + if (0 == _wcsicmp(argv[3], L"ping")) + return SBOX_TEST_PING_OK; + + // If the caller shared a shared memory handle with us attempt to open it + // in read only mode and sleep infinitely if we succeed. + if (0 == _wcsicmp(argv[3], L"shared_memory_handle")) { + base::SharedMemoryHandle shared_handle = NULL; + base::StringToUint( + argv[4], reinterpret_cast<unsigned int*>(&shared_handle)); + if (shared_handle == NULL) + return SBOX_TEST_INVALID_PARAMETER; + base::SharedMemory read_only_view(shared_handle, true); + if (!read_only_view.Map(0)) + return SBOX_TEST_INVALID_PARAMETER; + std::string contents(reinterpret_cast<char*>(read_only_view.memory())); + if (contents != "Hello World") + return SBOX_TEST_INVALID_PARAMETER; + Sleep(INFINITE); + return SBOX_TEST_TIMED_OUT; + } + + SboxTestsState state = static_cast<SboxTestsState>(_wtoi(argv[2])); + if ((state <= MIN_STATE) || (state >= MAX_STATE)) + return SBOX_TEST_INVALID_PARAMETER; + + HMODULE module; + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<wchar_t*>(&DispatchCall), &module)) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::string command_name = base::SysWideToMultiByte(argv[3], CP_UTF8); + CommandFunction command = reinterpret_cast<CommandFunction>( + ::GetProcAddress(module, command_name.c_str())); + if (!command) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (BEFORE_INIT == state) + return command(argc - 4, argv + 4); + else if (EVERY_STATE == state) + command(argc - 4, argv + 4); + + TargetServices* target = SandboxFactory::GetTargetServices(); + if (target) { + if (SBOX_ALL_OK != target->Init()) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (BEFORE_REVERT == state) + return command(argc - 4, argv + 4); + else if (EVERY_STATE == state) + command(argc - 4, argv + 4); + +#if defined(ADDRESS_SANITIZER) + // Bind and leak dbghelp.dll before the token is lowered, otherwise + // AddressSanitizer will crash when trying to symbolize a report. + if (!LoadLibraryA("dbghelp.dll")) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; +#endif + + target->LowerToken(); + } else if (0 != _wcsicmp(argv[1], L"-child-no-sandbox")) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + return command(argc - 4, argv + 4); +} + +} // namespace sandbox diff --git a/sandbox/win/tests/common/controller.h b/sandbox/win/tests/common/controller.h new file mode 100644 index 0000000000..a6498ed12e --- /dev/null +++ b/sandbox/win/tests/common/controller.h @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_TESTS_COMMON_CONTROLLER_H_ +#define SANDBOX_WIN_TESTS_COMMON_CONTROLLER_H_ + +#include <windows.h> +#include <string> + +#include "base/strings/string16.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/sandbox.h" + +namespace sandbox { + +// See winerror.h for details. +#define SEVERITY_INFO_FLAGS 0x40000000 +#define SEVERITY_ERROR_FLAGS 0xC0000000 +#define CUSTOMER_CODE 0x20000000 +#define SBOX_TESTS_FACILITY 0x05B10000 + +// All the possible error codes returned by the child process in +// the sandbox. +enum SboxTestResult { + SBOX_TEST_FIRST_RESULT = CUSTOMER_CODE | SBOX_TESTS_FACILITY, + SBOX_TEST_SUCCEEDED, + SBOX_TEST_PING_OK, + SBOX_TEST_FIRST_INFO = SBOX_TEST_FIRST_RESULT | SEVERITY_INFO_FLAGS, + SBOX_TEST_DENIED, // Access was denied. + SBOX_TEST_NOT_FOUND, // The resource was not found. + SBOX_TEST_FIRST_ERROR = SBOX_TEST_FIRST_RESULT | SEVERITY_ERROR_FLAGS, + SBOX_TEST_SECOND_ERROR, + SBOX_TEST_THIRD_ERROR, + SBOX_TEST_FOURTH_ERROR, + SBOX_TEST_FIFTH_ERROR, + SBOX_TEST_SIXTH_ERROR, + SBOX_TEST_SEVENTH_ERROR, + SBOX_TEST_INVALID_PARAMETER, + SBOX_TEST_FAILED_TO_RUN_TEST, + SBOX_TEST_FAILED_TO_EXECUTE_COMMAND, + SBOX_TEST_TIMED_OUT, + SBOX_TEST_FAILED, + SBOX_TEST_LAST_RESULT +}; + +inline bool IsSboxTestsResult(SboxTestResult result) { + unsigned int code = static_cast<unsigned int>(result); + unsigned int first = static_cast<unsigned int>(SBOX_TEST_FIRST_RESULT); + unsigned int last = static_cast<unsigned int>(SBOX_TEST_LAST_RESULT); + return (code > first) && (code < last); +} + +enum SboxTestsState { + MIN_STATE = 1, + BEFORE_INIT, + BEFORE_REVERT, + AFTER_REVERT, + EVERY_STATE, + MAX_STATE +}; + +#define SBOX_TESTS_API __declspec(dllexport) +#define SBOX_TESTS_COMMAND extern "C" SBOX_TESTS_API + +extern "C" { +typedef int (*CommandFunction)(int argc, wchar_t **argv); +} + +// Class to facilitate the launch of a test inside the sandbox. +class TestRunner { + public: + TestRunner(JobLevel job_level, TokenLevel startup_token, + TokenLevel main_token); + + TestRunner(); + + ~TestRunner(); + + // Adds a rule to the policy. The parameters are the same as the AddRule + // function in the sandbox. + bool AddRule(TargetPolicy::SubSystem subsystem, + TargetPolicy::Semantics semantics, + const wchar_t* pattern); + + // Adds a filesystem rules with the path of a file in system32. The function + // appends "pattern" to "system32" and then call AddRule. Return true if the + // function succeeds. + bool AddRuleSys32(TargetPolicy::Semantics semantics, const wchar_t* pattern); + + // Adds a filesystem rules to the policy. Returns true if the functions + // succeeds. + bool AddFsRule(TargetPolicy::Semantics semantics, const wchar_t* pattern); + + // Starts a child process in the sandbox and ask it to run |command|. Returns + // a SboxTestResult. By default, the test runs AFTER_REVERT. + int RunTest(const wchar_t* command); + + // Sets the timeout value for the child to run the command and return. + void SetTimeout(DWORD timeout_ms); + + // Sets TestRunner to return without waiting for the process to exit. + void SetAsynchronous(bool is_async) { is_async_ = is_async; } + + // Sets TestRunner to return without waiting for the process to exit. + void SetUnsandboxed(bool is_no_sandbox) { no_sandbox_ = is_no_sandbox; } + + // Sets the desired state for the test to run. + void SetTestState(SboxTestsState desired_state); + + // Sets a flag whether the process should be killed when the TestRunner is + // destroyed. + void SetKillOnDestruction(bool value) { kill_on_destruction_ = value; } + + // Returns the pointers to the policy object. It can be used to modify + // the policy manually. + TargetPolicy* GetPolicy(); + + BrokerServices* broker() { return broker_; } + + // Returns the process handle for an asynchronous test. + HANDLE process() { return target_process_.Get(); } + + // Returns the process ID for an asynchronous test. + DWORD process_id() { return target_process_id_; } + + private: + // Initializes the data in the object. Sets is_init_ to tree if the + // function succeeds. This is meant to be called from the constructor. + void Init(JobLevel job_level, TokenLevel startup_token, + TokenLevel main_token); + + // The actual runner. + int InternalRunTest(const wchar_t* command); + + BrokerServices* broker_; + TargetPolicy* policy_; + DWORD timeout_; + SboxTestsState state_; + bool is_init_; + bool is_async_; + bool no_sandbox_; + bool kill_on_destruction_; + base::win::ScopedHandle target_process_; + DWORD target_process_id_; +}; + +// Returns the broker services. +BrokerServices* GetBroker(); + +// Constructs a full path to a file inside the system32 (or syswow64) folder. +base::string16 MakePathToSys(const wchar_t* name, bool is_obj_man_path); + +// Runs the given test on the target process. +int DispatchCall(int argc, wchar_t **argv); + +} // namespace sandbox + +#endif // SANDBOX_WIN_TESTS_COMMON_CONTROLLER_H_ diff --git a/sandbox/win/tests/common/test_utils.cc b/sandbox/win/tests/common/test_utils.cc new file mode 100644 index 0000000000..cdd86de2db --- /dev/null +++ b/sandbox/win/tests/common/test_utils.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/tests/common/test_utils.h" + +#include <winioctl.h> + +typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; + +// Sets a reparse point. |source| will now point to |target|. Returns true if +// the call succeeds, false otherwise. +bool SetReparsePoint(HANDLE source, const wchar_t* target) { + USHORT size_target = static_cast<USHORT>(wcslen(target)) * sizeof(target[0]); + + char buffer[2000] = {0}; + DWORD returned; + + REPARSE_DATA_BUFFER* data = reinterpret_cast<REPARSE_DATA_BUFFER*>(buffer); + + data->ReparseTag = 0xa0000003; + memcpy(data->MountPointReparseBuffer.PathBuffer, target, size_target + 2); + data->MountPointReparseBuffer.SubstituteNameLength = size_target; + data->MountPointReparseBuffer.PrintNameOffset = size_target + 2; + data->ReparseDataLength = size_target + 4 + 8; + + int data_size = data->ReparseDataLength + 8; + + if (!DeviceIoControl(source, FSCTL_SET_REPARSE_POINT, &buffer, data_size, + NULL, 0, &returned, NULL)) { + return false; + } + return true; +} + +// Delete the reparse point referenced by |source|. Returns true if the call +// succeeds, false otherwise. +bool DeleteReparsePoint(HANDLE source) { + DWORD returned; + REPARSE_DATA_BUFFER data = {0}; + data.ReparseTag = 0xa0000003; + if (!DeviceIoControl(source, FSCTL_DELETE_REPARSE_POINT, &data, 8, NULL, 0, + &returned, NULL)) { + return false; + } + + return true; +} diff --git a/sandbox/win/tests/common/test_utils.h b/sandbox/win/tests/common/test_utils.h new file mode 100644 index 0000000000..9e1766076e --- /dev/null +++ b/sandbox/win/tests/common/test_utils.h @@ -0,0 +1,19 @@ +// Copyright (c) 2006-2010 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_TESTS_COMMON_TEST_UTILS_H_ +#define SANDBOX_TESTS_COMMON_TEST_UTILS_H_ + +#include <windows.h> + +// Sets a reparse point. |source| will now point to |target|. Returns true if +// the call succeeds, false otherwise. +bool SetReparsePoint(HANDLE source, const wchar_t* target); + +// Delete the reparse point referenced by |source|. Returns true if the call +// succeeds, false otherwise. +bool DeleteReparsePoint(HANDLE source); + +#endif // SANDBOX_TESTS_COMMON_TEST_UTILS_H_ + diff --git a/sandbox/win/tests/integration_tests/integration_tests.cc b/sandbox/win/tests/integration_tests/integration_tests.cc new file mode 100644 index 0000000000..3920da198b --- /dev/null +++ b/sandbox/win/tests/integration_tests/integration_tests.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2011 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 "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "sandbox/win/tests/common/controller.h" + +int wmain(int argc, wchar_t **argv) { + if (argc >= 2) { + if (0 == _wcsicmp(argv[1], L"-child") || + 0 == _wcsicmp(argv[1], L"-child-no-sandbox")) + // This instance is a child, not the test. + return sandbox::DispatchCall(argc, argv); + } + + base::TestSuite test_suite(argc, argv); + return base::LaunchUnitTests( + argc, + argv, + false, + base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/sandbox/win/tests/integration_tests/integration_tests_test.cc b/sandbox/win/tests/integration_tests/integration_tests_test.cc new file mode 100644 index 0000000000..44055d3d53 --- /dev/null +++ b/sandbox/win/tests/integration_tests/integration_tests_test.cc @@ -0,0 +1,306 @@ +// Copyright (c) 2006-2008 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. + +// Some tests for the framework itself. + +#include "testing/gtest/include/gtest/gtest.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/tests/common/controller.h" + +namespace sandbox { + +// Returns the current process state. +SBOX_TESTS_COMMAND int IntegrationTestsTest_state(int argc, wchar_t **argv) { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return BEFORE_INIT; + + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) + return BEFORE_REVERT; + + return AFTER_REVERT; +} + +// Returns the current process state, keeping track of it. +SBOX_TESTS_COMMAND int IntegrationTestsTest_state2(int argc, wchar_t **argv) { + static SboxTestsState state = MIN_STATE; + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) { + if (MIN_STATE == state) + state = BEFORE_INIT; + return state; + } + + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) { + if (BEFORE_INIT == state) + state = BEFORE_REVERT; + return state; + } + + if (BEFORE_REVERT == state) + state = AFTER_REVERT; + return state; +} + +// Blocks the process for argv[0] milliseconds simulating stuck child. +SBOX_TESTS_COMMAND int IntegrationTestsTest_stuck(int argc, wchar_t **argv) { + int timeout = 500; + if (argc > 0) { + timeout = _wtoi(argv[0]); + } + + ::Sleep(timeout); + return 1; +} + +// Returns the number of arguments +SBOX_TESTS_COMMAND int IntegrationTestsTest_args(int argc, wchar_t **argv) { + for (int i = 0; i < argc; i++) { + wchar_t argument[20]; + size_t argument_bytes = wcslen(argv[i]) * sizeof(wchar_t); + memcpy(argument, argv[i], __min(sizeof(argument), argument_bytes)); + } + + return argc; +} + +// Creates a job and tries to run a process inside it. The function can be +// called with up to two parameters. The first one if set to "none" means that +// the child process should be run with the JOB_NONE JobLevel else it is run +// with JOB_LOCKDOWN level. The second if present specifies that the +// JOB_OBJECT_LIMIT_BREAKAWAY_OK flag should be set on the job object created +// in this function. The return value is either SBOX_TEST_SUCCEEDED if the test +// has passed or a value between 0 and 4 indicating which part of the test has +// failed. +SBOX_TESTS_COMMAND int IntegrationTestsTest_job(int argc, wchar_t **argv) { + HANDLE job = ::CreateJobObject(NULL, NULL); + if (!job) + return 0; + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_limits; + if (!::QueryInformationJobObject(job, JobObjectExtendedLimitInformation, + &job_limits, sizeof(job_limits), NULL)) { + return 1; + } + // We cheat here and assume no 2-nd parameter means no breakaway flag and any + // value for the second param means with breakaway flag. + if (argc > 1) { + job_limits.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_BREAKAWAY_OK; + } else { + job_limits.BasicLimitInformation.LimitFlags &= + ~JOB_OBJECT_LIMIT_BREAKAWAY_OK; + } + if (!::SetInformationJobObject(job, JobObjectExtendedLimitInformation, + &job_limits, sizeof(job_limits))) { + return 2; + } + if (!::AssignProcessToJobObject(job, ::GetCurrentProcess())) + return 3; + + JobLevel job_level = JOB_LOCKDOWN; + if (argc > 0 && wcscmp(argv[0], L"none") == 0) + job_level = JOB_NONE; + + TestRunner runner(job_level, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner.SetTimeout(2000); + + if (1 != runner.RunTest(L"IntegrationTestsTest_args 1")) + return 4; + + // Terminate the job now. + ::TerminateJobObject(job, SBOX_TEST_SUCCEEDED); + // We should not make it to here but it doesn't mean our test failed. + return SBOX_TEST_SUCCEEDED; +} + +TEST(IntegrationTestsTest, CallsBeforeInit) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(BEFORE_INIT); + ASSERT_EQ(BEFORE_INIT, runner.RunTest(L"IntegrationTestsTest_state")); +} + +TEST(IntegrationTestsTest, CallsBeforeRevert) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(BEFORE_REVERT); + ASSERT_EQ(BEFORE_REVERT, runner.RunTest(L"IntegrationTestsTest_state")); +} + +TEST(IntegrationTestsTest, CallsAfterRevert) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(AFTER_REVERT); + ASSERT_EQ(AFTER_REVERT, runner.RunTest(L"IntegrationTestsTest_state")); +} + +TEST(IntegrationTestsTest, CallsEveryState) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + ASSERT_EQ(AFTER_REVERT, runner.RunTest(L"IntegrationTestsTest_state2")); +} + +TEST(IntegrationTestsTest, ForwardsArguments) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(BEFORE_INIT); + ASSERT_EQ(1, runner.RunTest(L"IntegrationTestsTest_args first")); + ASSERT_EQ(4, runner.RunTest(L"IntegrationTestsTest_args first second third " + L"fourth")); +} + +TEST(IntegrationTestsTest, WaitForStuckChild) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetAsynchronous(true); + runner.SetKillOnDestruction(false); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"IntegrationTestsTest_stuck 100")); + ASSERT_EQ(SBOX_ALL_OK, runner.broker()->WaitForAllTargets()); +} + +TEST(IntegrationTestsTest, NoWaitForStuckChildNoJob) { + TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner.SetTimeout(2000); + runner.SetAsynchronous(true); + runner.SetKillOnDestruction(false); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"IntegrationTestsTest_stuck 2000")); + ASSERT_EQ(SBOX_ALL_OK, runner.broker()->WaitForAllTargets()); + // In this case the processes are not tracked by the broker and should be + // still active. + DWORD exit_code; + ASSERT_TRUE(::GetExitCodeProcess(runner.process(), &exit_code)); + ASSERT_EQ(STILL_ACTIVE, exit_code); + // Terminate the test process now. + ::TerminateProcess(runner.process(), 0); +} + +TEST(IntegrationTestsTest, TwoStuckChildrenSecondOneHasNoJob) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetAsynchronous(true); + runner.SetKillOnDestruction(false); + TestRunner runner2(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner2.SetTimeout(2000); + runner2.SetAsynchronous(true); + runner2.SetKillOnDestruction(false); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"IntegrationTestsTest_stuck 100")); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner2.RunTest(L"IntegrationTestsTest_stuck 2000")); + // Actually both runners share the same singleton broker. + ASSERT_EQ(SBOX_ALL_OK, runner.broker()->WaitForAllTargets()); + // In this case the processes are not tracked by the broker and should be + // still active. + DWORD exit_code; + // Checking the exit code for |runner| is flaky on the slow bots but at + // least we know that the wait above has succeeded if we are here. + ASSERT_TRUE(::GetExitCodeProcess(runner2.process(), &exit_code)); + ASSERT_EQ(STILL_ACTIVE, exit_code); + // Terminate the test process now. + ::TerminateProcess(runner2.process(), 0); +} + +TEST(IntegrationTestsTest, TwoStuckChildrenFirstOneHasNoJob) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetAsynchronous(true); + runner.SetKillOnDestruction(false); + TestRunner runner2(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner2.SetTimeout(2000); + runner2.SetAsynchronous(true); + runner2.SetKillOnDestruction(false); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner2.RunTest(L"IntegrationTestsTest_stuck 2000")); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"IntegrationTestsTest_stuck 100")); + // Actually both runners share the same singleton broker. + ASSERT_EQ(SBOX_ALL_OK, runner.broker()->WaitForAllTargets()); + // In this case the processes are not tracked by the broker and should be + // still active. + DWORD exit_code; + // Checking the exit code for |runner| is flaky on the slow bots but at + // least we know that the wait above has succeeded if we are here. + ASSERT_TRUE(::GetExitCodeProcess(runner2.process(), &exit_code)); + ASSERT_EQ(STILL_ACTIVE, exit_code); + // Terminate the test process now. + ::TerminateProcess(runner2.process(), 0); +} + +TEST(IntegrationTestsTest, MultipleStuckChildrenSequential) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetAsynchronous(true); + runner.SetKillOnDestruction(false); + TestRunner runner2(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner2.SetTimeout(2000); + runner2.SetAsynchronous(true); + runner2.SetKillOnDestruction(false); + + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"IntegrationTestsTest_stuck 100")); + // Actually both runners share the same singleton broker. + ASSERT_EQ(SBOX_ALL_OK, runner.broker()->WaitForAllTargets()); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner2.RunTest(L"IntegrationTestsTest_stuck 2000")); + // Actually both runners share the same singleton broker. + ASSERT_EQ(SBOX_ALL_OK, runner.broker()->WaitForAllTargets()); + + DWORD exit_code; + // Checking the exit code for |runner| is flaky on the slow bots but at + // least we know that the wait above has succeeded if we are here. + ASSERT_TRUE(::GetExitCodeProcess(runner2.process(), &exit_code)); + ASSERT_EQ(STILL_ACTIVE, exit_code); + // Terminate the test process now. + ::TerminateProcess(runner2.process(), 0); + + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"IntegrationTestsTest_stuck 100")); + // Actually both runners share the same singleton broker. + ASSERT_EQ(SBOX_ALL_OK, runner.broker()->WaitForAllTargets()); +} + +// Running from inside job that allows us to escape from it should be ok. +TEST(IntegrationTestsTest, RunChildFromInsideJob) { + TestRunner runner; + runner.SetUnsandboxed(true); + runner.SetTimeout(2000); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"IntegrationTestsTest_job with_job escape_flag")); +} + +// Running from inside job that doesn't allow us to escape from it should fail +// on any windows prior to 8. +TEST(IntegrationTestsTest, RunChildFromInsideJobNoEscape) { + int expect_result = 4; // Means the runner has failed to execute the child. + // Check if we are on Win8 or newer and expect a success as newer windows + // versions support nested jobs. + OSVERSIONINFOEX version_info = { sizeof version_info }; + ::GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info)); + if (version_info.dwMajorVersion > 6 || + (version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 2)) { + expect_result = SBOX_TEST_SUCCEEDED; + } + + TestRunner runner; + runner.SetUnsandboxed(true); + runner.SetTimeout(2000); + ASSERT_EQ(expect_result, + runner.RunTest(L"IntegrationTestsTest_job with_job")); +} + +// Running without a job object should be ok regardless of the fact that we are +// running inside an outter job. +TEST(IntegrationTestsTest, RunJoblessChildFromInsideJob) { + TestRunner runner; + runner.SetUnsandboxed(true); + runner.SetTimeout(2000); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"IntegrationTestsTest_job none")); +} + +} // namespace sandbox diff --git a/sandbox/win/tests/integration_tests/sbox_integration_tests.vcproj b/sandbox/win/tests/integration_tests/sbox_integration_tests.vcproj new file mode 100644 index 0000000000..53816e73e2 --- /dev/null +++ b/sandbox/win/tests/integration_tests/sbox_integration_tests.vcproj @@ -0,0 +1,242 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="sbox_integration_tests" + ProjectGUID="{542D4B3B-98D4-4233-B68D-0103891508C6}" + RootNamespace="unit_tests" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="_CONSOLE" + UsePrecompiledHeader="2" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalOptions="/safeseh /dynamicbase /ignore:4199 $(NoInherit)" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="_CONSOLE" + UsePrecompiledHeader="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalOptions="/safeseh /dynamicbase /ignore:4199 $(NoInherit)" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Common" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{49F2D231-E141-4455-B241-7D37C09B6EEB}" + > + <File + RelativePath="..\common\controller.cc" + > + </File> + <File + RelativePath="..\common\controller.h" + > + </File> + <File + RelativePath="..\..\..\testing\gtest\src\gtest.cc" + > + </File> + <File + RelativePath=".\integration_tests.cc" + > + </File> + <File + RelativePath=".\stdafx.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + </Filter> + <File + RelativePath="..\..\src\dep_test.cc" + > + </File> + <File + RelativePath="..\..\src\file_policy_test.cc" + > + </File> + <File + RelativePath=".\integration_tests_test.cc" + > + </File> + <File + RelativePath="..\..\src\integrity_level_test.cc" + > + </File> + <File + RelativePath="..\..\src\ipc_ping_test.cc" + > + </File> + <File + RelativePath="..\..\src\named_pipe_policy_test.cc" + > + </File> + <File + RelativePath="..\..\src\policy_target_test.cc" + > + </File> + <File + RelativePath="..\..\src\process_policy_test.cc" + > + </File> + <File + RelativePath="..\..\src\registry_policy_test.cc" + > + </File> + <File + RelativePath="..\..\src\sync_policy_test.cc" + > + </File> + <File + RelativePath="..\..\src\unload_dll_test.cc" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/sandbox/win/tests/unit_tests/sbox_unittests.vcproj b/sandbox/win/tests/unit_tests/sbox_unittests.vcproj new file mode 100644 index 0000000000..a2df79210c --- /dev/null +++ b/sandbox/win/tests/unit_tests/sbox_unittests.vcproj @@ -0,0 +1,258 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="sbox_unittests" + ProjectGUID="{883553BE-2A9D-418C-A121-61FE1DFBC562}" + RootNamespace="unit_tests" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="_CONSOLE" + UsePrecompiledHeader="2" + WarningLevel="3" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="_CONSOLE" + UsePrecompiledHeader="0" + WarningLevel="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Common" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <File + RelativePath="..\..\..\testing\gtest\src\gtest.cc" + > + </File> + <File + RelativePath=".\stdafx.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + <File + RelativePath=".\unit_tests.cc" + > + </File> + </Filter> + <Filter + Name="TestInterception" + > + <File + RelativePath="..\..\src\interception_unittest.cc" + > + </File> + <File + RelativePath="..\..\src\pe_image_unittest.cc" + > + </File> + <File + RelativePath="..\..\src\service_resolver_unittest.cc" + > + </File> + </Filter> + <Filter + Name="TestRestrictedToken" + > + <File + RelativePath="..\..\src\restricted_token_unittest.cc" + > + </File> + </Filter> + <Filter + Name="TestJob" + > + <File + RelativePath="..\..\src\job_unittest.cc" + > + </File> + </Filter> + <Filter + Name="Sid" + > + <File + RelativePath="..\..\src\sid_unittest.cc" + > + </File> + </Filter> + <Filter + Name="Policy" + > + <File + RelativePath="..\..\src\policy_engine_unittest.cc" + > + </File> + <File + RelativePath="..\..\src\policy_low_level_unittest.cc" + > + </File> + <File + RelativePath="..\..\src\policy_opcodes_unittest.cc" + > + </File> + </Filter> + <Filter + Name="IPC" + > + <File + RelativePath="..\..\src\ipc_unittest.cc" + > + </File> + <File + RelativePath="..\..\src\threadpool_unittest.cc" + > + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/sandbox/win/tests/unit_tests/unit_tests.cc b/sandbox/win/tests/unit_tests/unit_tests.cc new file mode 100644 index 0000000000..bd56ab0067 --- /dev/null +++ b/sandbox/win/tests/unit_tests/unit_tests.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2011 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 "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "testing/gtest/include/gtest/gtest.h" + +int wmain(int argc, wchar_t **argv) { + if (argc >= 2) { + if (0 == _wcsicmp(argv[1], L"-child")) + // This instance is a child, not the test. + return 0; + } + + base::TestSuite test_suite(argc, argv); + return base::LaunchUnitTests( + argc, + argv, + false, + base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/sandbox/win/tests/validation_tests/commands.cc b/sandbox/win/tests/validation_tests/commands.cc new file mode 100644 index 0000000000..10a4a13761 --- /dev/null +++ b/sandbox/win/tests/validation_tests/commands.cc @@ -0,0 +1,289 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <Aclapi.h> +#include <windows.h> +#include <string> + +#include "sandbox/win/tests/validation_tests/commands.h" + +#include "sandbox/win/tests/common/controller.h" + +namespace { + +// Returns the HKEY corresponding to name. If there is no HKEY corresponding +// to the name it returns NULL. +HKEY GetHKEYFromString(const base::string16 &name) { + if (name == L"HKLM") + return HKEY_LOCAL_MACHINE; + if (name == L"HKCR") + return HKEY_CLASSES_ROOT; + if (name == L"HKCC") + return HKEY_CURRENT_CONFIG; + if (name == L"HKCU") + return HKEY_CURRENT_USER; + if (name == L"HKU") + return HKEY_USERS; + + return NULL; +} + +// Modifies string to remove the leading and trailing quotes. +void trim_quote(base::string16* string) { + base::string16::size_type pos1 = string->find_first_not_of(L'"'); + base::string16::size_type pos2 = string->find_last_not_of(L'"'); + + if (pos1 == base::string16::npos || pos2 == base::string16::npos) + string->clear(); + else + (*string) = string->substr(pos1, pos2 + 1); +} + +int TestOpenFile(base::string16 path, bool for_write) { + wchar_t path_expanded[MAX_PATH + 1] = {0}; + DWORD size = ::ExpandEnvironmentStrings(path.c_str(), path_expanded, + MAX_PATH); + if (!size) + return sandbox::SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + HANDLE file; + file = ::CreateFile(path_expanded, + for_write ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, // No security attributes. + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); // No template. + + if (file != INVALID_HANDLE_VALUE) { + ::CloseHandle(file); + return sandbox::SBOX_TEST_SUCCEEDED; + } + return (::GetLastError() == ERROR_ACCESS_DENIED) ? + sandbox::SBOX_TEST_DENIED : sandbox::SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; +} + +} // namespace + +namespace sandbox { + +SBOX_TESTS_COMMAND int ValidWindow(int argc, wchar_t **argv) { + return (argc == 1) ? + TestValidWindow( + reinterpret_cast<HWND>(static_cast<ULONG_PTR>(_wtoi(argv[0])))) : + SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; +} + +int TestValidWindow(HWND window) { + return ::IsWindow(window) ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; +} + +SBOX_TESTS_COMMAND int OpenProcessCmd(int argc, wchar_t **argv) { + return (argc == 2) ? + TestOpenProcess(_wtol(argv[0]), _wtol(argv[1])) : + SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; +} + +int TestOpenProcess(DWORD process_id, DWORD access_mask) { + HANDLE process = ::OpenProcess(access_mask, + FALSE, // Do not inherit handle. + process_id); + if (process != NULL) { + ::CloseHandle(process); + return SBOX_TEST_SUCCEEDED; + } + return (::GetLastError() == ERROR_ACCESS_DENIED) ? + sandbox::SBOX_TEST_DENIED : sandbox::SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; +} + +SBOX_TESTS_COMMAND int OpenThreadCmd(int argc, wchar_t **argv) { + return (argc == 1) ? + TestOpenThread(_wtoi(argv[0])) : SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; +} + +int TestOpenThread(DWORD thread_id) { + HANDLE thread = ::OpenThread(THREAD_QUERY_INFORMATION, + FALSE, // Do not inherit handles. + thread_id); + if (thread != NULL) { + ::CloseHandle(thread); + return SBOX_TEST_SUCCEEDED; + } + return (::GetLastError() == ERROR_ACCESS_DENIED) ? + sandbox::SBOX_TEST_DENIED : sandbox::SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; +} + +SBOX_TESTS_COMMAND int OpenFileCmd(int argc, wchar_t **argv) { + if (1 != argc) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + base::string16 path = argv[0]; + trim_quote(&path); + + return TestOpenReadFile(path); +} + +int TestOpenReadFile(const base::string16& path) { + return TestOpenFile(path, false); +} + +int TestOpenWriteFile(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + base::string16 path = argv[0]; + trim_quote(&path); + return TestOpenWriteFile(path); +} + +int TestOpenWriteFile(const base::string16& path) { + return TestOpenFile(path, true); +} + +SBOX_TESTS_COMMAND int OpenKey(int argc, wchar_t **argv) { + if (argc != 1 && argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + // Get the hive. + HKEY base_key = GetHKEYFromString(argv[0]); + + // Get the subkey. + base::string16 subkey; + if (argc == 2) { + subkey = argv[1]; + trim_quote(&subkey); + } + + return TestOpenKey(base_key, subkey); +} + +int TestOpenKey(HKEY base_key, base::string16 subkey) { + HKEY key; + LONG err_code = ::RegOpenKeyEx(base_key, + subkey.c_str(), + 0, // Reserved, must be 0. + MAXIMUM_ALLOWED, + &key); + if (err_code == ERROR_SUCCESS) { + ::RegCloseKey(key); + return SBOX_TEST_SUCCEEDED; + } + return (err_code == ERROR_INVALID_HANDLE || err_code == ERROR_ACCESS_DENIED) ? + SBOX_TEST_DENIED : SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; +} + +// Returns true if the current's thread desktop is the interactive desktop. +// In Vista there is a more direct test but for XP and w2k we need to check +// the object name. +bool IsInteractiveDesktop(bool* is_interactive) { + HDESK current_desk = ::GetThreadDesktop(::GetCurrentThreadId()); + if (current_desk == NULL) + return false; + wchar_t current_desk_name[256] = {0}; + if (!::GetUserObjectInformationW(current_desk, UOI_NAME, current_desk_name, + sizeof(current_desk_name), NULL)) + return false; + *is_interactive = (0 == _wcsicmp(L"default", current_desk_name)); + return true; +} + +SBOX_TESTS_COMMAND int OpenInteractiveDesktop(int, wchar_t **) { + return TestOpenInputDesktop(); +} + +int TestOpenInputDesktop() { + bool is_interactive = false; + if (IsInteractiveDesktop(&is_interactive) && is_interactive) + return SBOX_TEST_SUCCEEDED; + HDESK desk = ::OpenInputDesktop(0, FALSE, DESKTOP_CREATEWINDOW); + if (desk) { + ::CloseDesktop(desk); + return SBOX_TEST_SUCCEEDED; + } + return SBOX_TEST_DENIED; +} + +SBOX_TESTS_COMMAND int SwitchToSboxDesktop(int, wchar_t **) { + return TestSwitchDesktop(); +} + +int TestSwitchDesktop() { + HDESK desktop = ::GetThreadDesktop(::GetCurrentThreadId()); + if (desktop == NULL) + return SBOX_TEST_FAILED; + return ::SwitchDesktop(desktop) ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; +} + +SBOX_TESTS_COMMAND int OpenAlternateDesktop(int, wchar_t **argv) { + return TestOpenAlternateDesktop(argv[0]); +} + +int TestOpenAlternateDesktop(wchar_t *desktop_name) { + // Test for WRITE_DAC permission on the handle. + HDESK desktop = ::GetThreadDesktop(::GetCurrentThreadId()); + if (desktop) { + HANDLE test_handle; + if (::DuplicateHandle(::GetCurrentProcess(), desktop, + ::GetCurrentProcess(), &test_handle, + WRITE_DAC, FALSE, 0)) { + DWORD result = ::SetSecurityInfo(test_handle, SE_WINDOW_OBJECT, + DACL_SECURITY_INFORMATION, NULL, NULL, + NULL, NULL); + ::CloseHandle(test_handle); + if (result == ERROR_SUCCESS) + return SBOX_TEST_SUCCEEDED; + } else if (::GetLastError() != ERROR_ACCESS_DENIED) { + return SBOX_TEST_FAILED; + } + } + + // Open by name with WRITE_DAC. + desktop = ::OpenDesktop(desktop_name, 0, FALSE, WRITE_DAC); + if (!desktop && ::GetLastError() == ERROR_ACCESS_DENIED) + return SBOX_TEST_DENIED; + ::CloseDesktop(desktop); + return SBOX_TEST_SUCCEEDED; +} + +BOOL CALLBACK DesktopTestEnumProc(LPTSTR desktop_name, LPARAM result) { + return TRUE; +} + +SBOX_TESTS_COMMAND int EnumAlternateWinsta(int, wchar_t **) { + return TestEnumAlternateWinsta(); +} + +int TestEnumAlternateWinsta() { + // Try to enumerate the destops on the alternate windowstation. + return ::EnumDesktopsW(NULL, DesktopTestEnumProc, 0) ? + SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; +} + +SBOX_TESTS_COMMAND int SleepCmd(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + ::Sleep(_wtoi(argv[0])); + return SBOX_TEST_SUCCEEDED; +} + +SBOX_TESTS_COMMAND int AllocateCmd(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + size_t mem_size = static_cast<size_t>(_wtoll(argv[0])); + void* memory = ::VirtualAlloc(NULL, mem_size, MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE); + if (!memory) { + // We need to give the broker a chance to kill our process on failure. + ::Sleep(5000); + return SBOX_TEST_DENIED; + } + + return ::VirtualFree(memory, 0, MEM_RELEASE) ? + SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED; +} + + +} // namespace sandbox diff --git a/sandbox/win/tests/validation_tests/commands.h b/sandbox/win/tests/validation_tests/commands.h new file mode 100644 index 0000000000..f9b61990ed --- /dev/null +++ b/sandbox/win/tests/validation_tests/commands.h @@ -0,0 +1,48 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_TESTS_VALIDATION_TESTS_COMMANDS_H__ +#define SANDBOX_TESTS_VALIDATION_TESTS_COMMANDS_H__ + +#include <windows.h> + +#include "base/strings/string16.h" + +namespace sandbox { + +// Checks if window is a real window. Returns a SboxTestResult. +int TestValidWindow(HWND window); + +// Tries to open the process_id. Returns a SboxTestResult. +int TestOpenProcess(DWORD process_id, DWORD access_mask); + +// Tries to open thread_id. Returns a SboxTestResult. +int TestOpenThread(DWORD thread_id); + +// Tries to open path for read access. Returns a SboxTestResult. +int TestOpenReadFile(const base::string16& path); + +// Tries to open path for write access. Returns a SboxTestResult. +int TestOpenWriteFile(const base::string16& path); + +// Tries to open a registry key. +int TestOpenKey(HKEY base_key, base::string16 subkey); + +// Tries to open the workstation's input desktop as long as the +// current desktop is not the interactive one. Returns a SboxTestResult. +int TestOpenInputDesktop(); + +// Tries to switch the interactive desktop. Returns a SboxTestResult. +int TestSwitchDesktop(); + +// Tries to open the alternate desktop. Returns a SboxTestResult. +int TestOpenAlternateDesktop(wchar_t *desktop_name); + +// Tries to enumerate desktops on the alternate windowstation. +// Returns a SboxTestResult. +int TestEnumAlternateWinsta(); + +} // namespace sandbox + +#endif // SANDBOX_TESTS_VALIDATION_TESTS_COMMANDS_H__ diff --git a/sandbox/win/tests/validation_tests/sbox_validation_tests.vcproj b/sandbox/win/tests/validation_tests/sbox_validation_tests.vcproj new file mode 100644 index 0000000000..9b7b599295 --- /dev/null +++ b/sandbox/win/tests/validation_tests/sbox_validation_tests.vcproj @@ -0,0 +1,216 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="sbox_validation_tests" + ProjectGUID="{B9CC7B0D-145A-49C2-B887-84E43CFA0F27}" + RootNamespace="unit_tests" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="_CONSOLE" + UsePrecompiledHeader="2" + WarningLevel="3" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="shlwapi.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + PreprocessorDefinitions="_CONSOLE" + UsePrecompiledHeader="0" + WarningLevel="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="shlwapi.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Common" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{2E6C7E35-7538-4883-B80C-C89961A80D66}" + > + <File + RelativePath="..\common\controller.cc" + > + </File> + <File + RelativePath="..\common\controller.h" + > + </File> + <File + RelativePath="..\..\..\testing\gtest\src\gtest.cc" + > + </File> + <File + RelativePath=".\stdafx.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + ExcludedFromBuild="true" + > + <Tool + Name="VCCLCompilerTool" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + <File + RelativePath=".\unit_tests.cc" + > + </File> + </Filter> + <Filter + Name="Suite" + > + <File + RelativePath=".\commands.cc" + > + </File> + <File + RelativePath=".\commands.h" + > + </File> + <File + RelativePath=".\suite.cc" + > + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/sandbox/win/tests/validation_tests/suite.cc b/sandbox/win/tests/validation_tests/suite.cc new file mode 100644 index 0000000000..35fb5ddc23 --- /dev/null +++ b/sandbox/win/tests/validation_tests/suite.cc @@ -0,0 +1,241 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains the validation tests for the sandbox. +// It includes the tests that need to be performed inside the +// sandbox. + +#include <shlwapi.h> + +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "sandbox/win/tests/common/controller.h" + +#pragma comment(lib, "shlwapi.lib") + +namespace { + +void TestProcessAccess(sandbox::TestRunner* runner, DWORD target) { + const wchar_t *kCommandTemplate = L"OpenProcessCmd %d %d"; + wchar_t command[1024] = {0}; + + // Test all the scary process permissions. + wsprintf(command, kCommandTemplate, target, PROCESS_CREATE_THREAD); + EXPECT_EQ(sandbox::SBOX_TEST_DENIED, runner->RunTest(command)); + wsprintf(command, kCommandTemplate, target, PROCESS_DUP_HANDLE); + EXPECT_EQ(sandbox::SBOX_TEST_DENIED, runner->RunTest(command)); + wsprintf(command, kCommandTemplate, target, PROCESS_SET_INFORMATION); + EXPECT_EQ(sandbox::SBOX_TEST_DENIED, runner->RunTest(command)); + wsprintf(command, kCommandTemplate, target, PROCESS_VM_OPERATION); + EXPECT_EQ(sandbox::SBOX_TEST_DENIED, runner->RunTest(command)); + wsprintf(command, kCommandTemplate, target, PROCESS_VM_READ); + EXPECT_EQ(sandbox::SBOX_TEST_DENIED, runner->RunTest(command)); + wsprintf(command, kCommandTemplate, target, PROCESS_VM_WRITE); + EXPECT_EQ(sandbox::SBOX_TEST_DENIED, runner->RunTest(command)); + wsprintf(command, kCommandTemplate, target, PROCESS_QUERY_INFORMATION); + EXPECT_EQ(sandbox::SBOX_TEST_DENIED, runner->RunTest(command)); + wsprintf(command, kCommandTemplate, target, WRITE_DAC); + EXPECT_EQ(sandbox::SBOX_TEST_DENIED, runner->RunTest(command)); + wsprintf(command, kCommandTemplate, target, WRITE_OWNER); + EXPECT_EQ(sandbox::SBOX_TEST_DENIED, runner->RunTest(command)); + wsprintf(command, kCommandTemplate, target, READ_CONTROL); + EXPECT_EQ(sandbox::SBOX_TEST_DENIED, runner->RunTest(command)); +} + +} // namespace + +namespace sandbox { + +// Returns true if the volume that contains any_path supports ACL security. The +// input path can contain unexpanded environment strings. Returns false on any +// failure or if the file system does not support file security (such as FAT). +bool VolumeSupportsACLs(const wchar_t* any_path) { + wchar_t expand[MAX_PATH +1]; + DWORD len =::ExpandEnvironmentStringsW(any_path, expand, _countof(expand)); + if (0 == len) return false; + if (len > _countof(expand)) return false; + if (!::PathStripToRootW(expand)) return false; + DWORD fs_flags = 0; + if (!::GetVolumeInformationW(expand, NULL, 0, 0, NULL, &fs_flags, NULL, 0)) + return false; + if (fs_flags & FILE_PERSISTENT_ACLS) return true; + return false; +} + +// Tests if the suite is working properly. +TEST(ValidationSuite, TestSuite) { + TestRunner runner; + ASSERT_EQ(SBOX_TEST_PING_OK, runner.RunTest(L"ping")); +} + +// Tests if the file system is correctly protected by the sandbox. +TEST(ValidationSuite, TestFileSystem) { + // Do not perform the test if the system is using FAT or any other + // file system that does not have file security. + ASSERT_TRUE(VolumeSupportsACLs(L"%SystemDrive%\\")); + ASSERT_TRUE(VolumeSupportsACLs(L"%SystemRoot%\\")); + ASSERT_TRUE(VolumeSupportsACLs(L"%ProgramFiles%\\")); + ASSERT_TRUE(VolumeSupportsACLs(L"%Temp%\\")); + ASSERT_TRUE(VolumeSupportsACLs(L"%AppData%\\")); + + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"OpenFileCmd %SystemDrive%")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"OpenFileCmd %SystemRoot%")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"OpenFileCmd %ProgramFiles%")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"OpenFileCmd %SystemRoot%\\System32")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"OpenFileCmd %SystemRoot%\\explorer.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"OpenFileCmd %SystemRoot%\\Cursors\\arrow_i.cur")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"OpenFileCmd %AllUsersProfile%")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"OpenFileCmd %Temp%")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"OpenFileCmd %AppData%")); +} + +// Tests if the registry is correctly protected by the sandbox. +TEST(ValidationSuite, TestRegistry) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"OpenKey HKLM")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"OpenKey HKCU")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"OpenKey HKU")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest( + L"OpenKey HKLM " + L"\"Software\\Microsoft\\Windows NT\\CurrentVersion\\WinLogon\"")); +} + +// Tests that the permissions on the Windowstation does not allow the sandbox +// to get to the interactive desktop or to make the sbox desktop interactive. +TEST(ValidationSuite, TestDesktop) { + TestRunner runner; + runner.GetPolicy()->SetAlternateDesktop(true); + runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"OpenInteractiveDesktop NULL")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"SwitchToSboxDesktop NULL")); +} + +// Tests that the permissions on the Windowstation does not allow the sandbox +// to get to the interactive desktop or to make the sbox desktop interactive. +TEST(ValidationSuite, TestAlternateDesktop) { + base::win::Version version = base::win::GetVersion(); + if (version < base::win::VERSION_WIN7) + return; + + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"EnumAlternateWinsta NULL")); + + wchar_t command[1024] = {0}; + runner.SetTimeout(3600000); + runner.GetPolicy()->SetAlternateDesktop(true); + runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + base::string16 desktop_name = runner.GetPolicy()->GetAlternateDesktop(); + desktop_name = desktop_name.substr(desktop_name.find('\\') + 1); + wsprintf(command, L"OpenAlternateDesktop %lS", desktop_name.c_str()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); +} + +// Tests if the windows are correctly protected by the sandbox. +TEST(ValidationSuite, TestWindows) { + TestRunner runner; + wchar_t command[1024] = {0}; + + wsprintf(command, L"ValidWindow %Id", + reinterpret_cast<size_t>(::GetDesktopWindow())); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + + wsprintf(command, L"ValidWindow %Id", + reinterpret_cast<size_t>(::FindWindow(NULL, NULL))); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); +} + +// Tests that a locked-down process cannot open another locked-down process. +TEST(ValidationSuite, TestProcessDenyLockdown) { + TestRunner runner; + TestRunner target; + + target.SetAsynchronous(true); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, target.RunTest(L"SleepCmd 30000")); + + TestProcessAccess(&runner, target.process_id()); +} + +// Tests that a low-integrity process cannot open a locked-down process (due +// to the integrity label changing after startup via SetDelayedIntegrityLevel). +TEST(ValidationSuite, TestProcessDenyLowIntegrity) { + // This test applies only to Vista and above. + if (base::win::Version() < base::win::VERSION_VISTA) + return; + + TestRunner runner; + TestRunner target; + + target.SetAsynchronous(true); + target.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_LOW); + + runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + runner.GetPolicy()->SetTokenLevel(USER_RESTRICTED_SAME_ACCESS, + USER_INTERACTIVE); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, target.RunTest(L"SleepCmd 30000")); + + TestProcessAccess(&runner, target.process_id()); +} + +// Tests that a locked-down process cannot open a low-integrity process. +TEST(ValidationSuite, TestProcessDenyBelowLowIntegrity) { + // This test applies only to Vista and above. + if (base::win::Version() < base::win::VERSION_VISTA) + return; + + TestRunner runner; + TestRunner target; + + target.SetAsynchronous(true); + target.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + target.GetPolicy()->SetTokenLevel(USER_RESTRICTED_SAME_ACCESS, + USER_INTERACTIVE); + + runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_UNTRUSTED); + runner.GetPolicy()->SetTokenLevel(USER_RESTRICTED_SAME_ACCESS, + USER_INTERACTIVE); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, target.RunTest(L"SleepCmd 30000")); + + TestProcessAccess(&runner, target.process_id()); +} + +// Tests if the threads are correctly protected by the sandbox. +TEST(ValidationSuite, TestThread) { + TestRunner runner; + wchar_t command[1024] = {0}; + + wsprintf(command, L"OpenThreadCmd %d", ::GetCurrentThreadId()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); +} + +// Tests if an over-limit allocation will be denied. +TEST(ValidationSuite, TestMemoryLimit) { + TestRunner runner; + wchar_t command[1024] = {0}; + const int kAllocationSize = 256 * 1024 * 1024; + + wsprintf(command, L"AllocateCmd %d", kAllocationSize); + runner.GetPolicy()->SetJobMemoryLimit(kAllocationSize); + EXPECT_EQ(SBOX_FATAL_MEMORY_EXCEEDED, runner.RunTest(command)); +} + +// Tests a large allocation will succeed absent limits. +TEST(ValidationSuite, TestMemoryNoLimit) { + TestRunner runner; + wchar_t command[1024] = {0}; + const int kAllocationSize = 256 * 1024 * 1024; + + wsprintf(command, L"AllocateCmd %d", kAllocationSize); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command)); +} + +} // namespace sandbox diff --git a/sandbox/win/tests/validation_tests/unit_tests.cc b/sandbox/win/tests/validation_tests/unit_tests.cc new file mode 100644 index 0000000000..592b6c0a8d --- /dev/null +++ b/sandbox/win/tests/validation_tests/unit_tests.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2011 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 "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "sandbox/win/tests/common/controller.h" + +int wmain(int argc, wchar_t **argv) { + if (argc >= 2) { + if (0 == _wcsicmp(argv[1], L"-child")) + return sandbox::DispatchCall(argc, argv); + } + + base::TestSuite test_suite(argc, argv); + return base::LaunchUnitTests( + argc, + argv, + false, + base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/sandbox/win/tools/finder/finder.cc b/sandbox/win/tools/finder/finder.cc new file mode 100644 index 0000000000..9b829628e3 --- /dev/null +++ b/sandbox/win/tools/finder/finder.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/tools/finder/finder.h" + +Finder::Finder() { + file_output_ = NULL; + object_type_ = 0; + access_type_ = 0; + token_handle_ = NULL; + memset(filesystem_stats_, 0, sizeof(filesystem_stats_)); + memset(registry_stats_, 0, sizeof(registry_stats_)); + memset(kernel_object_stats_, 0, sizeof(kernel_object_stats_)); +} + +Finder::~Finder() { + if (token_handle_) + ::CloseHandle(token_handle_); +} + +DWORD Finder::Init(sandbox::TokenLevel token_type, + DWORD object_type, + DWORD access_type, + FILE *file_output) { + DWORD err_code = ERROR_SUCCESS; + + err_code = InitNT(); + if (ERROR_SUCCESS != err_code) + return err_code; + + object_type_ = object_type; + access_type_ = access_type; + file_output_ = file_output; + + err_code = sandbox::CreateRestrictedToken(&token_handle_, token_type, + sandbox::INTEGRITY_LEVEL_LAST, + sandbox::PRIMARY); + return err_code; +} + +DWORD Finder::Scan() { + if (!token_handle_) { + return ERROR_NO_TOKEN; + } + + if (object_type_ & kScanRegistry) { + ParseRegistry(HKEY_LOCAL_MACHINE, L"HKLM\\"); + ParseRegistry(HKEY_USERS, L"HKU\\"); + ParseRegistry(HKEY_CURRENT_CONFIG, L"HKCC\\"); + } + + if (object_type_ & kScanFileSystem) { + ParseFileSystem(L"\\\\?\\C:"); + } + + if (object_type_ & kScanKernelObjects) { + ParseKernelObjects(L"\\"); + } + + return ERROR_SUCCESS; +} diff --git a/sandbox/win/tools/finder/finder.h b/sandbox/win/tools/finder/finder.h new file mode 100644 index 0000000000..23255cea43 --- /dev/null +++ b/sandbox/win/tools/finder/finder.h @@ -0,0 +1,143 @@ +// Copyright (c) 2006-2008 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_TOOLS_FINDER_FINDER_H__ +#define SANDBOX_TOOLS_FINDER_FINDER_H__ + +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/tools/finder/ntundoc.h" + +// Type of stats that we calculate during the Scan operation +enum Stats { + READ = 0, // Number of objects with read access + WRITE, // Number of objects with write access + ALL, // Number of objects with r/w access + PARSE, // Number of objects parsed + BROKEN, // Number of errors while parsing the objects + SIZE_STATS // size of the enum +}; + +const int kScanRegistry = 0x01; +const int kScanFileSystem = 0x02; +const int kScanKernelObjects = 0x04; + +const int kTestForRead = 0x01; +const int kTestForWrite = 0x02; +const int kTestForAll = 0x04; + +#define FS_ERR L"FILE-ERROR" +#define OBJ_ERR L"OBJ-ERROR" +#define REG_ERR L"REG_ERROR" +#define OBJ L"OBJ" +#define FS L"FILE" +#define REG L"REG" + +// The impersonater class will impersonate a token when the object is created +// and revert when the object is going out of scope. +class Impersonater { + public: + Impersonater(HANDLE token_handle) { + if (token_handle) + ::ImpersonateLoggedOnUser(token_handle); + }; + ~Impersonater() { + ::RevertToSelf(); + }; +}; + +// The finder class handles the search of objects (file system, registry, kernel +// objects) on the system that can be opened by a restricted token. It can +// support multiple levels of restriction for the restricted token and can check +// for read, write or r/w access. It outputs the results to a file or stdout. +class Finder { + public: + Finder(); + ~Finder(); + DWORD Init(sandbox::TokenLevel token_type, DWORD object_type, + DWORD access_type, FILE *file_output); + DWORD Scan(); + + private: + // Parses a file system path and perform an access check on all files and + // folder found. + // Returns ERROR_SUCCESS if the function succeeded, otherwise, it returns the + // win32 error code associated with the error. + DWORD ParseFileSystem(ATL::CString path); + + // Parses a registry hive referenced by "key" and performs an access check on + // all subkeys found. + // Returns ERROR_SUCCESS if the function succeeded, otherwise, it returns the + // win32 error code associated with the error. + DWORD ParseRegistry(HKEY key, ATL::CString print_name); + + // Parses the kernel namespace beginning at "path" and performs an access + // check on all objects found. However, only some object types are supported, + // all non supported objects are ignored. + // Returns ERROR_SUCCESS if the function succeeded, otherwise, it returns the + // win32 error code associated with the error. + DWORD ParseKernelObjects(ATL::CString path); + + // Checks if "path" can be accessed with the restricted token. + // Returns the access granted. + DWORD TestFileAccess(ATL::CString path); + + // Checks if the registry key with the path key\name can be accessed with the + // restricted token. + // print_name is only use for logging purpose. + // Returns the access granted. + DWORD TestRegAccess(HKEY key, ATL::CString name, ATL::CString print_name); + + // Checks if the kernel object "path" of type "type" can be accessed with + // the restricted token. + // Returns the access granted. + DWORD TestKernelObjectAccess(ATL::CString path, ATL::CString type); + + // Outputs information to the logfile + void Output(ATL::CString type, ATL::CString access, ATL::CString info) { + fprintf(file_output_, "\n%S;%S;%S", type.GetBuffer(), access.GetBuffer(), + info.GetBuffer()); + }; + + // Output information to the log file. + void Output(ATL::CString type, DWORD error, ATL::CString info) { + fprintf(file_output_, "\n%S;0x%X;%S", type.GetBuffer(), error, + info.GetBuffer()); + }; + + // Set func_to_call to the function pointer of the function used to handle + // requests for the kernel objects of type "type". If the type is not + // supported at the moment the function returns false and the func_to_call + // parameter is not modified. + bool GetFunctionForType(ATL::CString type, NTGENERICOPEN * func_to_call); + + // Initializes the NT function pointers to be able to use all the needed + // functions in NTDDL. + // Returns ERROR_SUCCESS if the function succeeded, otherwise, it returns the + // win32 error code associated with the error. + DWORD InitNT(); + + // Calls func_to_call with the parameters desired_access, object_attributes + // and handle. func_to_call is a pointer to a function to open a kernel + // object. + NTSTATUS NtGenericOpen(ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES *object_attributes, + NTGENERICOPEN func_to_call, + HANDLE *handle); + + // Type of object to check for. + DWORD object_type_; + // Access to try. + DWORD access_type_; + // Output file for the results. + FILE * file_output_; + // Handle to the restricted token. + HANDLE token_handle_; + // Stats containing the number of operations performed on the different + // objects. + int filesystem_stats_[SIZE_STATS]; + int registry_stats_[SIZE_STATS]; + int kernel_object_stats_[SIZE_STATS]; +}; + +#endif // SANDBOX_TOOLS_FINDER_FINDER_H__ diff --git a/sandbox/win/tools/finder/finder.vcproj b/sandbox/win/tools/finder/finder.vcproj new file mode 100644 index 0000000000..787c8477c2 --- /dev/null +++ b/sandbox/win/tools/finder/finder.vcproj @@ -0,0 +1,201 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="finder" + ProjectGUID="{ACDC2E06-0366-41A4-A646-C37E130A605D}" + RootNamespace="finder" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\build\common.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\build\common.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath=".\finder.cc" + > + </File> + <File + RelativePath=".\finder.h" + > + </File> + <File + RelativePath=".\finder_fs.cc" + > + </File> + <File + RelativePath=".\finder_kernel.cc" + > + </File> + <File + RelativePath=".\finder_registry.cc" + > + </File> + <File + RelativePath=".\main.cc" + > + </File> + <File + RelativePath=".\ntundoc.h" + > + </File> + <File + RelativePath=".\stdafx.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/sandbox/win/tools/finder/finder_fs.cc b/sandbox/win/tools/finder/finder_fs.cc new file mode 100644 index 0000000000..ddcc4bec60 --- /dev/null +++ b/sandbox/win/tools/finder/finder_fs.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/tools/finder/finder.h" + +DWORD Finder::ParseFileSystem(ATL::CString directory) { + WIN32_FIND_DATA find_data; + HANDLE find; + + //Search for items in the directory. + ATL::CString name_to_search = directory + L"\\*"; + find = ::FindFirstFile(name_to_search, &find_data); + if (INVALID_HANDLE_VALUE == find) { + DWORD error = ::GetLastError(); + Output(FS_ERR, error, directory); + filesystem_stats_[BROKEN]++; + return error; + } + + // parse all files or folders. + do { + if (_tcscmp(find_data.cFileName, L".") == 0 || + _tcscmp(find_data.cFileName, L"..") == 0) + continue; + + ATL::CString complete_name = directory + L"\\" + find_data.cFileName; + TestFileAccess(complete_name); + + // Call recursively the function if the path found is a directory. + if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + ParseFileSystem(complete_name); + } + } while (::FindNextFile(find, &find_data) != 0); + + DWORD err_code = ::GetLastError(); + ::FindClose(find); + + if (ERROR_NO_MORE_FILES != err_code) { + Output(FS_ERR, err_code, directory); + filesystem_stats_[BROKEN]++; + return err_code; + } + + return ERROR_SUCCESS; +} + +DWORD Finder::TestFileAccess(ATL::CString name) { + Impersonater impersonate(token_handle_); + + filesystem_stats_[PARSE]++; + + HANDLE file; + if (access_type_ & kTestForAll) { + file = ::CreateFile(name.GetBuffer(), + GENERIC_ALL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (file != INVALID_HANDLE_VALUE) { + filesystem_stats_[ALL]++; + Output(FS, L"R/W", name.GetBuffer()); + ::CloseHandle(file); + return GENERIC_ALL; + } else if (::GetLastError() != ERROR_ACCESS_DENIED) { + Output(FS_ERR, GetLastError(), name); + filesystem_stats_[BROKEN]++; + } + } + + if (access_type_ & kTestForWrite) { + file = ::CreateFile(name.GetBuffer(), + GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (file != INVALID_HANDLE_VALUE) { + filesystem_stats_[WRITE]++; + Output(FS, L"W", name); + ::CloseHandle(file); + return GENERIC_WRITE; + } else if (::GetLastError() != ERROR_ACCESS_DENIED) { + Output(FS_ERR, ::GetLastError(), name); + filesystem_stats_[BROKEN]++; + } + } + + if (access_type_ & kTestForRead) { + file = ::CreateFile(name.GetBuffer(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (file != INVALID_HANDLE_VALUE) { + filesystem_stats_[READ]++; + Output(FS, L"R", name); + ::CloseHandle(file); + return GENERIC_READ; + } else if (::GetLastError() != ERROR_ACCESS_DENIED) { + Output(FS_ERR, GetLastError(), name); + filesystem_stats_[BROKEN]++; + } + } + + return 0; +} diff --git a/sandbox/win/tools/finder/finder_kernel.cc b/sandbox/win/tools/finder/finder_kernel.cc new file mode 100644 index 0000000000..430a4c08ab --- /dev/null +++ b/sandbox/win/tools/finder/finder_kernel.cc @@ -0,0 +1,248 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/tools/finder/finder.h" +#include "sandbox/win/tools/finder/ntundoc.h" + +#define BUFFER_SIZE 0x800 +#define CHECKPTR(x) if (!x) return ::GetLastError() + +// NT API +NTQUERYDIRECTORYOBJECT NtQueryDirectoryObject; +NTOPENDIRECTORYOBJECT NtOpenDirectoryObject; +NTOPENEVENT NtOpenEvent; +NTOPENJOBOBJECT NtOpenJobObject; +NTOPENKEYEDEVENT NtOpenKeyedEvent; +NTOPENMUTANT NtOpenMutant; +NTOPENSECTION NtOpenSection; +NTOPENSEMAPHORE NtOpenSemaphore; +NTOPENSYMBOLICLINKOBJECT NtOpenSymbolicLinkObject; +NTOPENTIMER NtOpenTimer; +NTOPENFILE NtOpenFile; +NTCLOSE NtClose; + +DWORD Finder::InitNT() { + HMODULE ntdll_handle = ::LoadLibrary(L"ntdll.dll"); + CHECKPTR(ntdll_handle); + + NtOpenSymbolicLinkObject = (NTOPENSYMBOLICLINKOBJECT) ::GetProcAddress( + ntdll_handle, "NtOpenSymbolicLinkObject"); + CHECKPTR(NtOpenSymbolicLinkObject); + + NtQueryDirectoryObject = (NTQUERYDIRECTORYOBJECT) ::GetProcAddress( + ntdll_handle, "NtQueryDirectoryObject"); + CHECKPTR(NtQueryDirectoryObject); + + NtOpenDirectoryObject = (NTOPENDIRECTORYOBJECT) ::GetProcAddress( + ntdll_handle, "NtOpenDirectoryObject"); + CHECKPTR(NtOpenDirectoryObject); + + NtOpenKeyedEvent = (NTOPENKEYEDEVENT) ::GetProcAddress( + ntdll_handle, "NtOpenKeyedEvent"); + CHECKPTR(NtOpenKeyedEvent); + + NtOpenJobObject = (NTOPENJOBOBJECT) ::GetProcAddress( + ntdll_handle, "NtOpenJobObject"); + CHECKPTR(NtOpenJobObject); + + NtOpenSemaphore = (NTOPENSEMAPHORE) ::GetProcAddress( + ntdll_handle, "NtOpenSemaphore"); + CHECKPTR(NtOpenSemaphore); + + NtOpenSection = (NTOPENSECTION) ::GetProcAddress( + ntdll_handle, "NtOpenSection"); + CHECKPTR(NtOpenSection); + + NtOpenMutant= (NTOPENMUTANT) ::GetProcAddress(ntdll_handle, "NtOpenMutant"); + CHECKPTR(NtOpenMutant); + + NtOpenEvent = (NTOPENEVENT) ::GetProcAddress(ntdll_handle, "NtOpenEvent"); + CHECKPTR(NtOpenEvent); + + NtOpenTimer = (NTOPENTIMER) ::GetProcAddress(ntdll_handle, "NtOpenTimer"); + CHECKPTR(NtOpenTimer); + + NtOpenFile = (NTOPENFILE) ::GetProcAddress(ntdll_handle, "NtOpenFile"); + CHECKPTR(NtOpenFile); + + NtClose = (NTCLOSE) ::GetProcAddress(ntdll_handle, "NtClose"); + CHECKPTR(NtClose); + + return ERROR_SUCCESS; +} + +DWORD Finder::ParseKernelObjects(ATL::CString path) { + UNICODE_STRING unicode_str; + unicode_str.Length = (USHORT)path.GetLength()*2; + unicode_str.MaximumLength = (USHORT)path.GetLength()*2+2; + unicode_str.Buffer = path.GetBuffer(); + + OBJECT_ATTRIBUTES path_attributes; + InitializeObjectAttributes(&path_attributes, + &unicode_str, + 0, // No Attributes + NULL, // No Root Directory + NULL); // No Security Descriptor + + + DWORD object_index = 0; + DWORD data_written = 0; + + // TODO(nsylvain): Do not use BUFFER_SIZE. Try to get the size + // dynamically. + OBJDIR_INFORMATION *object_directory_info = + (OBJDIR_INFORMATION*) ::HeapAlloc(GetProcessHeap(), + 0, + BUFFER_SIZE); + + HANDLE file_handle; + NTSTATUS status_code = NtOpenDirectoryObject(&file_handle, + DIRECTORY_QUERY, + &path_attributes); + if (status_code != 0) + return ERROR_UNIDENTIFIED_ERROR; + + status_code = NtQueryDirectoryObject(file_handle, + object_directory_info, + BUFFER_SIZE, + TRUE, // Get Next Index + TRUE, // Ignore Input Index + &object_index, + &data_written); + + if (status_code != 0) + return ERROR_UNIDENTIFIED_ERROR; + + while (NtQueryDirectoryObject(file_handle, object_directory_info, + BUFFER_SIZE, TRUE, FALSE, &object_index, + &data_written) == 0 ) { + ATL::CString cur_path(object_directory_info->ObjectName.Buffer, + object_directory_info->ObjectName.Length / sizeof(WCHAR)); + + ATL::CString cur_type(object_directory_info->ObjectTypeName.Buffer, + object_directory_info->ObjectTypeName.Length / sizeof(WCHAR)); + + ATL::CString new_path; + if (path == L"\\") { + new_path = path + cur_path; + } else { + new_path = path + L"\\" + cur_path; + } + + TestKernelObjectAccess(new_path, cur_type); + + // Call the function recursively for all subdirectories + if (cur_type == L"Directory") { + ParseKernelObjects(new_path); + } + } + + NtClose(file_handle); + return ERROR_SUCCESS; +} + +DWORD Finder::TestKernelObjectAccess(ATL::CString path, ATL::CString type) { + Impersonater impersonate(token_handle_); + + kernel_object_stats_[PARSE]++; + + NTGENERICOPEN func = NULL; + GetFunctionForType(type, &func); + + if (!func) { + kernel_object_stats_[BROKEN]++; + Output(OBJ_ERR, type + L" Unsupported", path); + return ERROR_UNSUPPORTED_TYPE; + } + + UNICODE_STRING unicode_str; + unicode_str.Length = (USHORT)path.GetLength()*2; + unicode_str.MaximumLength = (USHORT)path.GetLength()*2+2; + unicode_str.Buffer = path.GetBuffer(); + + OBJECT_ATTRIBUTES path_attributes; + InitializeObjectAttributes(&path_attributes, + &unicode_str, + 0, // No Attributes + NULL, // No Root Directory + NULL); // No Security Descriptor + + HANDLE handle; + NTSTATUS status_code = 0; + + if (access_type_ & kTestForAll) { + status_code = NtGenericOpen(GENERIC_ALL, &path_attributes, func, &handle); + if (STATUS_SUCCESS == status_code) { + kernel_object_stats_[ALL]++; + Output(OBJ, L"R/W", path); + NtClose(handle); + return GENERIC_ALL; + } else if (status_code != EXCEPTION_ACCESS_VIOLATION && + status_code != STATUS_ACCESS_DENIED) { + Output(OBJ_ERR, status_code, path); + kernel_object_stats_[BROKEN]++; + } + } + + if (access_type_ & kTestForWrite) { + status_code = NtGenericOpen(GENERIC_WRITE, &path_attributes, func, &handle); + if (STATUS_SUCCESS == status_code) { + kernel_object_stats_[WRITE]++; + Output(OBJ, L"W", path); + NtClose(handle); + return GENERIC_WRITE; + } else if (status_code != EXCEPTION_ACCESS_VIOLATION && + status_code != STATUS_ACCESS_DENIED) { + Output(OBJ_ERR, status_code, path); + kernel_object_stats_[BROKEN]++; + } + } + + if (access_type_ & kTestForRead) { + status_code = NtGenericOpen(GENERIC_READ, &path_attributes, func, &handle); + if (STATUS_SUCCESS == status_code) { + kernel_object_stats_[READ]++; + Output(OBJ, L"R", path); + NtClose(handle); + return GENERIC_READ; + } else if (status_code != EXCEPTION_ACCESS_VIOLATION && + status_code != STATUS_ACCESS_DENIED) { + Output(OBJ_ERR, status_code, path); + kernel_object_stats_[BROKEN]++; + } + } + + return 0; +} + +NTSTATUS Finder::NtGenericOpen(ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES *object_attributes, + NTGENERICOPEN func_to_call, + HANDLE *handle) { + return func_to_call(handle, desired_access, object_attributes); +} + +bool Finder::GetFunctionForType(ATL::CString type, + NTGENERICOPEN * func_to_call) { + NTGENERICOPEN func = NULL; + + if (type == L"Event") func = NtOpenEvent; + else if (type == L"Job") func = NtOpenJobObject; + else if (type == L"KeyedEvent") func = NtOpenKeyedEvent; + else if (type == L"Mutant") func = NtOpenMutant; + else if (type == L"Section") func = NtOpenSection; + else if (type == L"Semaphore") func = NtOpenSemaphore; + else if (type == L"Timer") func = NtOpenTimer; + else if (type == L"SymbolicLink") func = NtOpenSymbolicLinkObject; + else if (type == L"Directory") func = NtOpenDirectoryObject; + + if (func) { + *func_to_call = func; + return true; + } + + return false; +} diff --git a/sandbox/win/tools/finder/finder_registry.cc b/sandbox/win/tools/finder/finder_registry.cc new file mode 100644 index 0000000000..a84e413d32 --- /dev/null +++ b/sandbox/win/tools/finder/finder_registry.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/tools/finder/finder.h" + +DWORD Finder::ParseRegistry(HKEY key, ATL::CString print_name) { + DWORD index = 0; + DWORD name_size = 2048; + wchar_t buffer[2048] = {0}; + // TODO(nsylvain): Don't hardcode 2048. Get the key len by calling the + // function. + LONG err_code = ::RegEnumKey(key, index, buffer, name_size); + while (ERROR_SUCCESS == err_code) { + ATL::CString name_complete = print_name + buffer + L"\\"; + TestRegAccess(key, buffer, name_complete); + + // Call the function recursively to parse all subkeys + HKEY key_to_parse; + err_code = ::RegOpenKeyEx(key, buffer, 0, KEY_ENUMERATE_SUB_KEYS, + &key_to_parse); + if (ERROR_SUCCESS == err_code) { + ParseRegistry(key_to_parse, name_complete); + ::RegCloseKey(key_to_parse); + } else { + registry_stats_[BROKEN]++; + Output(REG_ERR, err_code, name_complete); + } + + index++; + err_code = ::RegEnumKey(key, index, buffer, name_size); + } + + if (ERROR_NO_MORE_ITEMS != err_code) { + registry_stats_[BROKEN]++; + Output(REG_ERR, err_code, print_name); + } + + return ERROR_SUCCESS; +} + +DWORD Finder::TestRegAccess(HKEY key, ATL::CString name, + ATL::CString print_name) { + Impersonater impersonate(token_handle_); + + registry_stats_[PARSE]++; + + HKEY key_res; + LONG err_code = 0; + + if (access_type_ & kTestForAll) { + err_code = ::RegOpenKeyEx(key, name, 0, GENERIC_ALL, &key_res); + if (ERROR_SUCCESS == err_code) { + registry_stats_[ALL]++; + Output(REG, L"R/W", print_name); + ::RegCloseKey(key_res); + return GENERIC_ALL; + } else if (err_code != ERROR_ACCESS_DENIED) { + Output(REG_ERR, err_code, print_name); + registry_stats_[BROKEN]++; + } + } + + if (access_type_ & kTestForWrite) { + err_code = ::RegOpenKeyEx(key, name, 0, GENERIC_WRITE, &key_res); + if (ERROR_SUCCESS == err_code) { + registry_stats_[WRITE]++; + Output(REG, L"W", print_name); + ::RegCloseKey(key_res); + return GENERIC_WRITE; + } else if (err_code != ERROR_ACCESS_DENIED) { + Output(REG_ERR, err_code, print_name); + registry_stats_[BROKEN]++; + } + } + + if (access_type_ & kTestForRead) { + err_code = ::RegOpenKeyEx(key, name, 0, GENERIC_READ, &key_res); + if (ERROR_SUCCESS == err_code) { + registry_stats_[READ]++; + Output(REG, L"R", print_name); + ::RegCloseKey(key_res); + return GENERIC_READ; + } else if (err_code != ERROR_ACCESS_DENIED) { + Output(REG_ERR, err_code, print_name); + registry_stats_[BROKEN]++; + } + } + + return 0; +} diff --git a/sandbox/win/tools/finder/main.cc b/sandbox/win/tools/finder/main.cc new file mode 100644 index 0000000000..7cadbef8f6 --- /dev/null +++ b/sandbox/win/tools/finder/main.cc @@ -0,0 +1,147 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/tools/finder/finder.h" + +#define PARAM_IS(y) (argc > i) && (_wcsicmp(argv[i], y) == 0) + +void PrintUsage(wchar_t *application_name) { + wprintf(L"\n\nUsage: \n %ls --token type --object ob1 [ob2 ob3] " + L"--access ac1 [ac2 ac3] [--log filename]", application_name); + wprintf(L"\n\n Token Types : \n\tLOCKDOWN \n\tRESTRICTED " + L"\n\tLIMITED_USER \n\tINTERACTIVE_USER \n\tNON_ADMIN \n\tUNPROTECTED"); + wprintf(L"\n Object Types: \n\tREG \n\tFILE \n\tKERNEL"); + wprintf(L"\n Access Types: \n\tR \n\tW \n\tALL"); + wprintf(L"\n\nSample: \n %ls --token LOCKDOWN --object REG FILE KERNEL " + L"--access R W ALL", application_name); +} + +int wmain(int argc, wchar_t* argv[]) { + // Extract the filename from the path. + wchar_t *app_name = wcsrchr(argv[0], L'\\'); + if (!app_name) { + app_name = argv[0]; + } else { + app_name++; + } + + // parameters to read + ATL::CString log_file; + sandbox::TokenLevel token_type = sandbox::USER_LOCKDOWN; + DWORD object_type = 0; + DWORD access_type = 0; + + // no arguments + if (argc == 1) { + PrintUsage(app_name); + return -1; + } + + // parse command line. + for (int i = 1; i < argc; ++i) { + if (PARAM_IS(L"--token")) { + i++; + if (argc > i) { + if (PARAM_IS(L"LOCKDOWN")) { + token_type = sandbox::USER_LOCKDOWN; + } else if (PARAM_IS(L"RESTRICTED")) { + token_type = sandbox::USER_RESTRICTED; + } else if (PARAM_IS(L"LIMITED_USER")) { + token_type = sandbox::USER_LIMITED; + } else if (PARAM_IS(L"INTERACTIVE_USER")) { + token_type = sandbox::USER_INTERACTIVE; + } else if (PARAM_IS(L"NON_ADMIN")) { + token_type = sandbox::USER_NON_ADMIN; + } else if (PARAM_IS(L"USER_RESTRICTED_SAME_ACCESS")) { + token_type = sandbox::USER_RESTRICTED_SAME_ACCESS; + } else if (PARAM_IS(L"UNPROTECTED")) { + token_type = sandbox::USER_UNPROTECTED; + } else { + wprintf(L"\nAbord. Invalid token type \"%ls\"", argv[i]); + PrintUsage(app_name); + return -1; + } + } + } else if (PARAM_IS(L"--object")) { + bool is_object = true; + do { + i++; + if (PARAM_IS(L"REG")) { + object_type |= kScanRegistry; + } else if (PARAM_IS(L"FILE")) { + object_type |= kScanFileSystem; + } else if (PARAM_IS(L"KERNEL")) { + object_type |= kScanKernelObjects; + } else { + is_object = false; + } + } while(is_object); + i--; + } else if (PARAM_IS(L"--access")) { + bool is_access = true; + do { + i++; + if (PARAM_IS(L"R")) { + access_type |= kTestForRead; + } else if (PARAM_IS(L"W")) { + access_type |= kTestForWrite; + } else if (PARAM_IS(L"ALL")) { + access_type |= kTestForAll; + } else { + is_access = false; + } + } while(is_access); + i--; + } else if (PARAM_IS(L"--log")) { + i++; + if (argc > i) { + log_file = argv[i]; + } + else { + wprintf(L"\nAbord. No log file specified"); + PrintUsage(app_name); + return -1; + } + } else { + wprintf(L"\nAbord. Unrecognized parameter \"%ls\"", argv[i]); + PrintUsage(app_name); + return -1; + } + } + + // validate parameters + if (0 == access_type) { + wprintf(L"\nAbord, Access type not specified"); + PrintUsage(app_name); + return -1; + } + + if (0 == object_type) { + wprintf(L"\nAbord, Object type not specified"); + PrintUsage(app_name); + return -1; + } + + + // Open log file + FILE * file_output; + if (log_file.GetLength()) { + errno_t err = _wfopen_s(&file_output, log_file, L"w"); + if (err) { + wprintf(L"\nAbord, Cannot open file \"%ls\"", log_file.GetBuffer()); + return -1; + } + } else { + file_output = stdout; + } + + Finder finder_obj; + finder_obj.Init(token_type, object_type, access_type, file_output); + finder_obj.Scan(); + + fclose(file_output); + + return 0; +} diff --git a/sandbox/win/tools/finder/ntundoc.h b/sandbox/win/tools/finder/ntundoc.h new file mode 100644 index 0000000000..dc8c3a57cb --- /dev/null +++ b/sandbox/win/tools/finder/ntundoc.h @@ -0,0 +1,275 @@ +// Copyright (c) 2006-2011 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_TOOLS_FINDER_NTUNDOC_H__ +#define SANDBOX_TOOLS_FINDER_NTUNDOC_H__ + +#define NTSTATUS ULONG +#define STATUS_SUCCESS 0x00000000 +#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 +#define STATUS_ACCESS_DENIED 0xC0000022 +#define STATUS_BUFFER_OVERFLOW 0x80000005 + +typedef struct _LSA_UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING; + +typedef struct _OBJDIR_INFORMATION { + UNICODE_STRING ObjectName; + UNICODE_STRING ObjectTypeName; + BYTE Data[1]; +} OBJDIR_INFORMATION; + +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + UNICODE_STRING *ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES; + +typedef struct _PUBLIC_OBJECT_BASIC_INFORMATION { + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + ULONG Reserved[10]; // reserved for internal use + } PUBLIC_OBJECT_BASIC_INFORMATION, *PPUBLIC_OBJECT_BASIC_INFORMATION; + +typedef struct __PUBLIC_OBJECT_TYPE_INFORMATION { + UNICODE_STRING TypeName; + ULONG Reserved [22]; // reserved for internal use +} PUBLIC_OBJECT_TYPE_INFORMATION, *PPUBLIC_OBJECT_TYPE_INFORMATION; + +typedef enum _POOL_TYPE { + NonPagedPool, + PagedPool, + NonPagedPoolMustSucceed, + ReservedType, + NonPagedPoolCacheAligned, + PagedPoolCacheAligned, + NonPagedPoolCacheAlignedMustS +} POOL_TYPE; + +typedef struct _OBJECT_TYPE_INFORMATION { + UNICODE_STRING Name; + ULONG TotalNumberOfObjects; + ULONG TotalNumberOfHandles; + ULONG TotalPagedPoolUsage; + ULONG TotalNonPagedPoolUsage; + ULONG TotalNamePoolUsage; + ULONG TotalHandleTableUsage; + ULONG HighWaterNumberOfObjects; + ULONG HighWaterNumberOfHandles; + ULONG HighWaterPagedPoolUsage; + ULONG HighWaterNonPagedPoolUsage; + ULONG HighWaterNamePoolUsage; + ULONG HighWaterHandleTableUsage; + ULONG InvalidAttributes; + GENERIC_MAPPING GenericMapping; + ULONG ValidAccess; + BOOLEAN SecurityRequired; + BOOLEAN MaintainHandleCount; + USHORT MaintainTypeList; + POOL_TYPE PoolType; + ULONG PagedPoolUsage; + ULONG NonPagedPoolUsage; +} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; + +typedef struct _OBJECT_NAME_INFORMATION { + UNICODE_STRING ObjectName; +} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; + +typedef enum _OBJECT_INFORMATION_CLASS { + ObjectBasicInformation, + ObjectNameInformation, + ObjectTypeInformation, + ObjectAllInformation, + ObjectDataInformation +} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS; + +typedef struct _FILE_NAME_INFORMATION { + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION; + +typedef enum _FILE_INFORMATION_CLASS { + // end_wdm + FileDirectoryInformation = 1, + FileFullDirectoryInformation, // 2 + FileBothDirectoryInformation, // 3 + FileBasicInformation, // 4 wdm + FileStandardInformation, // 5 wdm + FileInternalInformation, // 6 + FileEaInformation, // 7 + FileAccessInformation, // 8 + FileNameInformation, // 9 + FileRenameInformation, // 10 + FileLinkInformation, // 11 + FileNamesInformation, // 12 + FileDispositionInformation, // 13 + FilePositionInformation, // 14 wdm + FileFullEaInformation, // 15 + FileModeInformation, // 16 + FileAlignmentInformation, // 17 + FileAllInformation, // 18 + FileAllocationInformation, // 19 + FileEndOfFileInformation, // 20 wdm + FileAlternateNameInformation, // 21 + FileStreamInformation, // 22 + FilePipeInformation, // 23 + FilePipeLocalInformation, // 24 + FilePipeRemoteInformation, // 25 + FileMailslotQueryInformation, // 26 + FileMailslotSetInformation, // 27 + FileCompressionInformation, // 28 + FileObjectIdInformation, // 29 + FileCompletionInformation, // 30 + FileMoveClusterInformation, // 31 + FileQuotaInformation, // 32 + FileReparsePointInformation, // 33 + FileNetworkOpenInformation, // 34 + FileAttributeTagInformation, // 35 + FileTrackingInformation, // 36 + FileMaximumInformation + // begin_wdm +} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; + +typedef enum _SYSTEM_INFORMATION_CLASS { + SystemHandleInformation = 16 +} SYSTEM_INFORMATION_CLASS; + +typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +#define InitializeObjectAttributes( p, n, a, r, s ) { \ + (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = NULL; \ +} + +typedef struct _SYSTEM_HANDLE_INFORMATION { + USHORT ProcessId; + USHORT CreatorBackTraceIndex; + UCHAR ObjectTypeNumber; + UCHAR Flags; + USHORT Handle; + PVOID Object; + ACCESS_MASK GrantedAccess; +} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; + +typedef struct _SYSTEM_HANDLE_INFORMATION_EX { + ULONG NumberOfHandles; + SYSTEM_HANDLE_INFORMATION Information[1]; +} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; + +#define POBJECT_ATTRIBUTES OBJECT_ATTRIBUTES* + +typedef NTSTATUS (WINAPI* NTQUERYDIRECTORYOBJECT)( + HANDLE, + OBJDIR_INFORMATION*, + DWORD, + DWORD, + DWORD, + DWORD*, + DWORD*); + +typedef NTSTATUS (WINAPI* NTOPENDIRECTORYOBJECT)( + HANDLE *, + DWORD, + OBJECT_ATTRIBUTES* ); + +typedef NTSTATUS (WINAPI* NTGENERICOPEN) ( + OUT PHANDLE EventHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NTOPENEVENT)( + OUT PHANDLE EventHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NTOPENJOBOBJECT)( + OUT PHANDLE JobHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NTOPENKEYEDEVENT)( + OUT PHANDLE KeyedEventHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NTOPENMUTANT)( + OUT PHANDLE MutantHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NTOPENSECTION)( + OUT PHANDLE SectionHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NTOPENSEMAPHORE)( + OUT PHANDLE SemaphoreHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NTOPENSYMBOLICLINKOBJECT)( + OUT PHANDLE SymbolicLinkHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NTOPENTIMER)( + OUT PHANDLE TimerHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS (WINAPI* NTOPENFILE)( + HANDLE *, + DWORD, + OBJECT_ATTRIBUTES *, + IO_STATUS_BLOCK *, + DWORD, + DWORD); + +typedef NTSTATUS (WINAPI* NTQUERYINFORMATIONFILE)( + HANDLE, + PIO_STATUS_BLOCK, + PVOID, + ULONG, + FILE_INFORMATION_CLASS); + +typedef NTSTATUS (WINAPI* NTQUERYSYSTEMINFORMATION)( + SYSTEM_INFORMATION_CLASS SystemInformationClass, + PVOID SystemInformation, + ULONG SystemInformationLength, + PULONG ReturnLength); + +typedef NTSTATUS (WINAPI* NTQUERYOBJECT)( + HANDLE Handle, + OBJECT_INFORMATION_CLASS ObjectInformationClass, + PVOID ObjectInformation, + ULONG ObjectInformationLength, + PULONG ReturnLength); + +typedef NTSTATUS (WINAPI* NTCLOSE) (HANDLE); + +#define DIRECTORY_QUERY 0x0001 +#define DIRECTORY_TRAVERSE 0x0002 +#define DIRECTORY_CREATE_OBJECT 0x0004 +#define DIRECTORY_CREATE_SUBDIRECTORY 0x0008 +#define DIRECTORY_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0xF) + +#endif // SANDBOX_TOOLS_FINDER_NTUNDOC_H__ diff --git a/sandbox/win/tools/launcher/launcher.cc b/sandbox/win/tools/launcher/launcher.cc new file mode 100644 index 0000000000..efcc4a43cc --- /dev/null +++ b/sandbox/win/tools/launcher/launcher.cc @@ -0,0 +1,156 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/restricted_token_utils.h" + +// launcher.exe is an application used to launch another application with a +// restricted token. This is to be used for testing only. +// The parameters are the level of security of the primary token, the +// impersonation token and the job object along with the command line to +// execute. +// See the usage (launcher.exe without parameters) for the correct format. + +#define PARAM_IS(y) (argc > i) && (_wcsicmp(argv[i], y) == 0) + +void PrintUsage(const wchar_t *application_name) { + wprintf(L"\n\nUsage: \n %ls --main level --init level --job level cmd_line ", + application_name); + wprintf(L"\n\n Levels : \n\tLOCKDOWN \n\tRESTRICTED " + L"\n\tLIMITED_USER \n\tINTERACTIVE_USER \n\tNON_ADMIN \n\tUNPROTECTED"); + wprintf(L"\n\n main: Security level of the main token"); + wprintf(L"\n init: Security level of the impersonation token"); + wprintf(L"\n job: Security level of the job object"); +} + +bool GetTokenLevelFromString(const wchar_t *param, + sandbox::TokenLevel* level) { + if (_wcsicmp(param, L"LOCKDOWN") == 0) { + *level = sandbox::USER_LOCKDOWN; + } else if (_wcsicmp(param, L"RESTRICTED") == 0) { + *level = sandbox::USER_RESTRICTED; + } else if (_wcsicmp(param, L"LIMITED_USER") == 0) { + *level = sandbox::USER_LIMITED; + } else if (_wcsicmp(param, L"INTERACTIVE_USER") == 0) { + *level = sandbox::USER_INTERACTIVE; + } else if (_wcsicmp(param, L"NON_ADMIN") == 0) { + *level = sandbox::USER_NON_ADMIN; + } else if (_wcsicmp(param, L"USER_RESTRICTED_SAME_ACCESS") == 0) { + *level = sandbox::USER_RESTRICTED_SAME_ACCESS; + } else if (_wcsicmp(param, L"UNPROTECTED") == 0) { + *level = sandbox::USER_UNPROTECTED; + } else { + return false; + } + + return true; +} + +bool GetJobLevelFromString(const wchar_t *param, sandbox::JobLevel* level) { + if (_wcsicmp(param, L"LOCKDOWN") == 0) { + *level = sandbox::JOB_LOCKDOWN; + } else if (_wcsicmp(param, L"RESTRICTED") == 0) { + *level = sandbox::JOB_RESTRICTED; + } else if (_wcsicmp(param, L"LIMITED_USER") == 0) { + *level = sandbox::JOB_LIMITED_USER; + } else if (_wcsicmp(param, L"INTERACTIVE_USER") == 0) { + *level = sandbox::JOB_INTERACTIVE; + } else if (_wcsicmp(param, L"NON_ADMIN") == 0) { + wprintf(L"\nNON_ADMIN is not a supported job type"); + return false; + } else if (_wcsicmp(param, L"UNPROTECTED") == 0) { + *level = sandbox::JOB_UNPROTECTED; + } else { + return false; + } + + return true; +} + +int wmain(int argc, wchar_t *argv[]) { + // Extract the filename from the path. + wchar_t *app_name = wcsrchr(argv[0], L'\\'); + if (!app_name) { + app_name = argv[0]; + } else { + app_name++; + } + + // no argument + if (argc == 1) { + PrintUsage(app_name); + return -1; + } + + sandbox::TokenLevel primary_level = sandbox::USER_LOCKDOWN; + sandbox::TokenLevel impersonation_level = + sandbox::USER_RESTRICTED_SAME_ACCESS; + sandbox::JobLevel job_level = sandbox::JOB_LOCKDOWN; + ATL::CString command_line; + + // parse command line. + for (int i = 1; i < argc; ++i) { + if (PARAM_IS(L"--main")) { + i++; + if (argc > i) { + if (!GetTokenLevelFromString(argv[i], &primary_level)) { + wprintf(L"\nAbord, Unrecognized main token level \"%ls\"", argv[i]); + PrintUsage(app_name); + return -1; + } + } + } else if (PARAM_IS(L"--init")) { + i++; + if (argc > i) { + if (!GetTokenLevelFromString(argv[i], &impersonation_level)) { + wprintf(L"\nAbord, Unrecognized init token level \"%ls\"", argv[i]); + PrintUsage(app_name); + return -1; + } + } + } else if (PARAM_IS(L"--job")) { + i++; + if (argc > i) { + if (!GetJobLevelFromString(argv[i], &job_level)) { + wprintf(L"\nAbord, Unrecognized job security level \"%ls\"", argv[i]); + PrintUsage(app_name); + return -1; + } + } + } else { + if (command_line.GetLength()) { + command_line += L' '; + } + command_line += argv[i]; + } + } + + if (!command_line.GetLength()) { + wprintf(L"\nAbord, No command line specified"); + PrintUsage(app_name); + return -1; + } + + wprintf(L"\nLaunching command line: \"%ls\"\n", command_line.GetBuffer()); + + HANDLE job_handle; + DWORD err_code = sandbox::StartRestrictedProcessInJob( + command_line.GetBuffer(), + primary_level, + impersonation_level, + job_level, + &job_handle); + if (ERROR_SUCCESS != err_code) { + wprintf(L"\nAbord, Error %d while launching command line.", err_code); + return -1; + } + + wprintf(L"\nPress any key to continue."); + while(!_kbhit()) { + Sleep(100); + } + + ::CloseHandle(job_handle); + + return 0; +} diff --git a/sandbox/win/tools/launcher/launcher.vcproj b/sandbox/win/tools/launcher/launcher.vcproj new file mode 100644 index 0000000000..71ed011d2b --- /dev/null +++ b/sandbox/win/tools/launcher/launcher.vcproj @@ -0,0 +1,177 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="launcher" + ProjectGUID="{386FA217-FBC2-4461-882D-CDAD221ED800}" + RootNamespace="launcher" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\build\common.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="1" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\build\common.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath=".\launcher.cc" + > + </File> + <File + RelativePath=".\stdafx.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/sandbox/win/wow_helper.sln b/sandbox/win/wow_helper.sln new file mode 100644 index 0000000000..26d0da2526 --- /dev/null +++ b/sandbox/win/wow_helper.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wow_helper", "wow_helper\wow_helper.vcproj", "{BCF3A457-39F1-4DAA-9A65-93CFCD559036}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BCF3A457-39F1-4DAA-9A65-93CFCD559036}.Debug|x64.ActiveCfg = Debug|x64 + {BCF3A457-39F1-4DAA-9A65-93CFCD559036}.Debug|x64.Build.0 = Debug|x64 + {BCF3A457-39F1-4DAA-9A65-93CFCD559036}.Release|x64.ActiveCfg = Release|x64 + {BCF3A457-39F1-4DAA-9A65-93CFCD559036}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/sandbox/win/wow_helper/service64_resolver.cc b/sandbox/win/wow_helper/service64_resolver.cc new file mode 100644 index 0000000000..033b9d771e --- /dev/null +++ b/sandbox/win/wow_helper/service64_resolver.cc @@ -0,0 +1,342 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/wow_helper/service64_resolver.h" + +#include "base/memory/scoped_ptr.h" +#include "sandbox/win/wow_helper/target_code.h" + +namespace { +#pragma pack(push, 1) + +const BYTE kMovEax = 0xB8; +const BYTE kMovEdx = 0xBA; +const USHORT kCallPtrEdx = 0x12FF; +const BYTE kRet = 0xC2; +const BYTE kNop = 0x90; +const USHORT kJmpEdx = 0xE2FF; +const USHORT kXorEcx = 0xC933; +const ULONG kLeaEdx = 0x0424548D; +const ULONG kCallFs1 = 0xC015FF64; +const ULONG kCallFs2Ret = 0xC2000000; +const BYTE kPopEdx = 0x5A; +const BYTE kPushEdx = 0x52; +const BYTE kPush32 = 0x68; + +const ULONG kMmovR10EcxMovEax = 0xB8D18B4C; +const USHORT kSyscall = 0x050F; +const BYTE kRetNp = 0xC3; +const BYTE kPad = 0x66; +const USHORT kNop16 = 0x9066; +const BYTE kRelJmp = 0xE9; + +const ULONG kXorRaxMovEax = 0xB8C03148; +const ULONG kSaveRcx = 0x10488948; +const ULONG kMovRcxRaxJmp = 0xE9C88B48; + +// Service code for 64 bit systems. +struct ServiceEntry { + // this struct contains roughly the following code: + // mov r10,rcx + // mov eax,52h + // syscall + // ret + // xchg ax,ax + // xchg ax,ax + + ULONG mov_r10_ecx_mov_eax; // = 4C 8B D1 B8 + ULONG service_id; + USHORT syscall; // = 0F 05 + BYTE ret; // = C3 + BYTE pad; // = 66 + USHORT xchg_ax_ax1; // = 66 90 + USHORT xchg_ax_ax2; // = 66 90 +}; + +struct Redirected { + // this struct contains roughly the following code: + // jmp relative_32 + // xchg ax,ax // 3 byte nop + + Redirected() { + jmp = kRelJmp; + relative = 0; + pad = kPad; + xchg_ax_ax = kNop16; + }; + BYTE jmp; // = E9 + ULONG relative; + BYTE pad; // = 66 + USHORT xchg_ax_ax; // = 66 90 +}; + +struct InternalThunk { + // this struct contains roughly the following code: + // xor rax,rax + // mov eax, 0x00080000 // Thunk storage. + // mov [rax]PatchInfo.service, rcx // Save first argument. + // mov rcx, rax + // jmp relative_to_interceptor + + InternalThunk() { + xor_rax_mov_eax = kXorRaxMovEax; + patch_info = 0; + save_rcx = kSaveRcx; + mov_rcx_rax_jmp = kMovRcxRaxJmp; + relative = 0; + }; + ULONG xor_rax_mov_eax; // = 48 31 C0 B8 + ULONG patch_info; + ULONG save_rcx; // = 48 89 48 10 + ULONG mov_rcx_rax_jmp; // = 48 8b c8 e9 + ULONG relative; +}; + +struct ServiceFullThunk { + sandbox::PatchInfo patch_info; + ServiceEntry original; + InternalThunk internal_thunk; +}; + +#pragma pack(pop) + +// Simple utility function to write to a buffer on the child, if the memery has +// write protection attributes. +// Arguments: +// child_process (in): process to write to. +// address (out): memory position on the child to write to. +// buffer (in): local buffer with the data to write . +// length (in): number of bytes to write. +// Returns true on success. +bool WriteProtectedChildMemory(HANDLE child_process, + void* address, + const void* buffer, + size_t length) { + // first, remove the protections + DWORD old_protection; + if (!::VirtualProtectEx(child_process, address, length, + PAGE_WRITECOPY, &old_protection)) + return false; + + SIZE_T written; + bool ok = ::WriteProcessMemory(child_process, address, buffer, length, + &written) && (length == written); + + // always attempt to restore the original protection + if (!::VirtualProtectEx(child_process, address, length, + old_protection, &old_protection)) + return false; + + return ok; +} + +// Get pointers to the functions that we need from ntdll.dll. +NTSTATUS ResolveNtdll(sandbox::PatchInfo* patch_info) { + wchar_t* ntdll_name = L"ntdll.dll"; + HMODULE ntdll = ::GetModuleHandle(ntdll_name); + if (!ntdll) + return STATUS_PROCEDURE_NOT_FOUND; + + void* signal = ::GetProcAddress(ntdll, "NtSignalAndWaitForSingleObject"); + if (!signal) + return STATUS_PROCEDURE_NOT_FOUND; + + patch_info->signal_and_wait = + reinterpret_cast<NtSignalAndWaitForSingleObjectFunction>(signal); + + return STATUS_SUCCESS; +} + +}; // namespace + +namespace sandbox { + +NTSTATUS ResolverThunk::Init(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes) { + if (NULL == thunk_storage || 0 == storage_bytes || + NULL == target_module || NULL == target_name) + return STATUS_INVALID_PARAMETER; + + if (storage_bytes < GetThunkSize()) + return STATUS_BUFFER_TOO_SMALL; + + NTSTATUS ret = STATUS_SUCCESS; + if (NULL == interceptor_entry_point) { + ret = ResolveInterceptor(interceptor_module, interceptor_name, + &interceptor_entry_point); + if (!NT_SUCCESS(ret)) + return ret; + } + + ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + interceptor_ = interceptor_entry_point; + + return ret; +} + +NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module, + const char* interceptor_name, + const void** address) { + return STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS ResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + return STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS Service64ResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = Init(target_module, interceptor_module, target_name, + interceptor_name, interceptor_entry_point, + thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + scoped_ptr<char[]> thunk_buffer(new char[thunk_bytes]); + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>( + thunk_buffer.get()); + + if (!IsFunctionAService(&thunk->original)) + return STATUS_UNSUCCESSFUL; + + ret = PerformPatch(thunk, thunk_storage); + + if (NULL != storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +NTSTATUS Service64ResolverThunk::ResolveInterceptor( + const void* interceptor_module, + const char* interceptor_name, + const void** address) { + // After all, we are using a locally mapped version of the exe, so the + // action is the same as for a target function. + return ResolveTarget(interceptor_module, interceptor_name, + const_cast<void**>(address)); +} + +// In this case all the work is done from the parent, so resolve is +// just a simple GetProcAddress. +NTSTATUS Service64ResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + if (NULL == module) + return STATUS_UNSUCCESSFUL; + + *address = ::GetProcAddress(bit_cast<HMODULE>(module), function_name); + + if (NULL == *address) + return STATUS_UNSUCCESSFUL; + + return STATUS_SUCCESS; +} + +size_t Service64ResolverThunk::GetThunkSize() const { + return sizeof(ServiceFullThunk); +} + +bool Service64ResolverThunk::IsFunctionAService(void* local_thunk) const { + ServiceEntry function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) + return false; + + if (sizeof(function_code) != read) + return false; + + if (kMmovR10EcxMovEax != function_code.mov_r10_ecx_mov_eax || + kSyscall != function_code.syscall || kRetNp != function_code.ret) + return false; + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +NTSTATUS Service64ResolverThunk::PerformPatch(void* local_thunk, + void* remote_thunk) { + ServiceFullThunk* full_local_thunk = reinterpret_cast<ServiceFullThunk*>( + local_thunk); + ServiceFullThunk* full_remote_thunk = reinterpret_cast<ServiceFullThunk*>( + remote_thunk); + + // If the source or target are above 4GB we cannot do this relative jump. + if (reinterpret_cast<ULONG_PTR>(full_remote_thunk) > + static_cast<ULONG_PTR>(ULONG_MAX)) + return STATUS_CONFLICTING_ADDRESSES; + + if (reinterpret_cast<ULONG_PTR>(target_) > static_cast<ULONG_PTR>(ULONG_MAX)) + return STATUS_CONFLICTING_ADDRESSES; + + // Patch the original code. + Redirected local_service; + Redirected* remote_service = reinterpret_cast<Redirected*>(target_); + ULONG_PTR diff = reinterpret_cast<BYTE*>(&full_remote_thunk->internal_thunk) - + &remote_service->pad; + local_service.relative = static_cast<ULONG>(diff); + + // Setup the PatchInfo structure. + SIZE_T actual; + if (!::ReadProcessMemory(process_, remote_thunk, local_thunk, + sizeof(PatchInfo), &actual)) + return STATUS_UNSUCCESSFUL; + if (sizeof(PatchInfo) != actual) + return STATUS_UNSUCCESSFUL; + + full_local_thunk->patch_info.orig_MapViewOfSection = reinterpret_cast< + NtMapViewOfSectionFunction>(&full_remote_thunk->original); + full_local_thunk->patch_info.patch_location = target_; + NTSTATUS ret = ResolveNtdll(&full_local_thunk->patch_info); + if (!NT_SUCCESS(ret)) + return ret; + + // Setup the thunk. The jump out is performed from right after the end of the + // thunk (full_remote_thunk + 1). + InternalThunk my_thunk; + ULONG_PTR patch_info = reinterpret_cast<ULONG_PTR>(remote_thunk); + my_thunk.patch_info = static_cast<ULONG>(patch_info); + diff = reinterpret_cast<const BYTE*>(interceptor_) - + reinterpret_cast<BYTE*>(full_remote_thunk + 1); + my_thunk.relative = static_cast<ULONG>(diff); + + memcpy(&full_local_thunk->internal_thunk, &my_thunk, sizeof(my_thunk)); + + // copy the local thunk buffer to the child + if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, + sizeof(ServiceFullThunk), &actual)) + return STATUS_UNSUCCESSFUL; + + if (sizeof(ServiceFullThunk) != actual) + return STATUS_UNSUCCESSFUL; + + // and now change the function to intercept, on the child + if (!::WriteProtectedChildMemory(process_, target_, &local_service, + sizeof(local_service))) + return STATUS_UNSUCCESSFUL; + + return STATUS_SUCCESS; +} + +} // namespace sandbox diff --git a/sandbox/win/wow_helper/service64_resolver.h b/sandbox/win/wow_helper/service64_resolver.h new file mode 100644 index 0000000000..abd7efd813 --- /dev/null +++ b/sandbox/win/wow_helper/service64_resolver.h @@ -0,0 +1,72 @@ +// Copyright (c) 2010 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_WOW_HELPER_SERVICE64_RESOLVER_H__ +#define SANDBOX_WOW_HELPER_SERVICE64_RESOLVER_H__ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/resolver.h" + +namespace sandbox { + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll (64-bit). +class Service64ResolverThunk : public ResolverThunk { + public: + // The service resolver needs a child process to write to. + explicit Service64ResolverThunk(HANDLE process) + : process_(process), ntdll_base_(NULL) {} + virtual ~Service64ResolverThunk() {} + + // Implementation of Resolver::Setup. + virtual NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used); + + // Implementation of Resolver::ResolveInterceptor. + virtual NTSTATUS ResolveInterceptor(const void* module, + const char* function_name, + const void** address); + + // Implementation of Resolver::ResolveTarget. + virtual NTSTATUS ResolveTarget(const void* module, + const char* function_name, + void** address); + + // Implementation of Resolver::GetThunkSize. + virtual size_t GetThunkSize() const; + + protected: + // The unit test will use this member to allow local patch on a buffer. + HMODULE ntdll_base_; + + // Handle of the child process. + HANDLE process_; + + private: + // Returns true if the code pointer by target_ corresponds to the expected + // type of function. Saves that code on the first part of the thunk pointed + // by local_thunk (should be directly accessible from the parent). + virtual bool IsFunctionAService(void* local_thunk) const; + + // Performs the actual patch of target_. + // local_thunk must be already fully initialized, and the first part must + // contain the original code. The real type of this buffer is ServiceFullThunk + // (yes, private). remote_thunk (real type ServiceFullThunk), must be + // allocated on the child, and will contain the thunk data, after this call. + // Returns the apropriate status code. + virtual NTSTATUS PerformPatch(void* local_thunk, void* remote_thunk); + + DISALLOW_COPY_AND_ASSIGN(Service64ResolverThunk); +}; + +} // namespace sandbox + + +#endif // SANDBOX_WOW_HELPER_SERVICE64_RESOLVER_H__ diff --git a/sandbox/win/wow_helper/target_code.cc b/sandbox/win/wow_helper/target_code.cc new file mode 100644 index 0000000000..8da27cc576 --- /dev/null +++ b/sandbox/win/wow_helper/target_code.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/wow_helper/target_code.h" + +namespace sandbox { + +// Hooks NtMapViewOfSection to detect the load of dlls. +NTSTATUS WINAPI TargetNtMapViewOfSection( + PatchInfo *patch_info, HANDLE process, PVOID *base, ULONG_PTR zero_bits, + SIZE_T commit_size, PLARGE_INTEGER offset, PSIZE_T view_size, + SECTION_INHERIT inherit, ULONG allocation_type, ULONG protect) { + NTSTATUS ret = patch_info->orig_MapViewOfSection(patch_info->section, process, + base, zero_bits, commit_size, + offset, view_size, inherit, + allocation_type, protect); + + LARGE_INTEGER timeout; + timeout.QuadPart = -(5 * 10000000); // 5 seconds. + + // The wait is alertable. + patch_info->signal_and_wait(patch_info->dll_load, patch_info->continue_load, + TRUE, &timeout); + + return ret; +} + +// Marks the end of the code to copy to the target process. +NTSTATUS WINAPI TargetEnd() { + return STATUS_SUCCESS; +} + +} // namespace sandbox diff --git a/sandbox/win/wow_helper/target_code.h b/sandbox/win/wow_helper/target_code.h new file mode 100644 index 0000000000..c198a852e2 --- /dev/null +++ b/sandbox/win/wow_helper/target_code.h @@ -0,0 +1,41 @@ +// Copyright (c) 2006-2008 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_WOW_HELPER_TARGET_CODE_H__ +#define SANDBOX_WOW_HELPER_TARGET_CODE_H__ + +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +extern "C" { + +// Holds the information needed for the interception of NtMapViewOfSection. +// Changes of this structure must be synchronized with changes of PatchInfo32 +// on sandbox/win/src/wow64.cc. +struct PatchInfo { + HANDLE dll_load; // Event to signal the broker. + HANDLE continue_load; // Event to wait for the broker. + HANDLE section; // First argument of the call. + NtMapViewOfSectionFunction orig_MapViewOfSection; + NtSignalAndWaitForSingleObjectFunction signal_and_wait; + void* patch_location; +}; + +// Interception of NtMapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being loaded, so we can patch them if needed. +NTSTATUS WINAPI TargetNtMapViewOfSection( + PatchInfo* patch_info, HANDLE process, PVOID* base, ULONG_PTR zero_bits, + SIZE_T commit_size, PLARGE_INTEGER offset, PSIZE_T view_size, + SECTION_INHERIT inherit, ULONG allocation_type, ULONG protect); + +// Marker of the end of TargetNtMapViewOfSection. +NTSTATUS WINAPI TargetEnd(); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WOW_HELPER_TARGET_CODE_H__ diff --git a/sandbox/win/wow_helper/wow_helper.cc b/sandbox/win/wow_helper/wow_helper.cc new file mode 100644 index 0000000000..e3493375d6 --- /dev/null +++ b/sandbox/win/wow_helper/wow_helper.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2006-2008 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. + +// Wow_helper.exe is a simple Win32 64-bit executable designed to help to +// sandbox a 32 bit application running on a 64 bit OS. The basic idea is to +// perform a 64 bit interception of the target process and notify the 32-bit +// broker process whenever a DLL is being loaded. This allows the broker to +// setup the interceptions (32-bit) properly on the target. + +#include <windows.h> + +#include <string> + +#include "sandbox/win/wow_helper/service64_resolver.h" +#include "sandbox/win/wow_helper/target_code.h" + +namespace sandbox { + +// Performs the interception of NtMapViewOfSection on the 64-bit version of +// ntdll.dll. 'thunk' is the buffer on the address space of process 'child', +// that will be used to store the information about the patch. +int PatchNtdll(HANDLE child, void* thunk, size_t thunk_bytes) { + wchar_t* ntdll_name = L"ntdll.dll"; + HMODULE ntdll_base = ::GetModuleHandle(ntdll_name); + if (!ntdll_base) + return 100; + + Service64ResolverThunk resolver(child); + size_t used = resolver.GetThunkSize(); + char* code = reinterpret_cast<char*>(thunk) + used; + NTSTATUS ret = resolver.Setup(ntdll_base, NULL, "NtMapViewOfSection", NULL, + code, thunk, thunk_bytes, NULL); + if (!NT_SUCCESS(ret)) + return 101; + + size_t size = reinterpret_cast<char*>(&TargetEnd) - + reinterpret_cast<char*>(&TargetNtMapViewOfSection); + + if (size + used > thunk_bytes) + return 102; + + SIZE_T written; + if (!::WriteProcessMemory(child, code, &TargetNtMapViewOfSection, size, + &written)) + return 103; + + if (size != written) + return 104; + + return 0; +} + +} // namespace sandbox + +// We must receive two arguments: the process id of the target to intercept and +// the address of a page of memory on that process that will be used for the +// interception. We receive the address because the broker will cleanup the +// patch when the work is performed. +// +// It should be noted that we don't wait until the real work is done; this +// program quits as soon as the 64-bit interception is performed. +int wWinMain(HINSTANCE, HINSTANCE, wchar_t* command_line, int) { + static_assert(sizeof(void*) > sizeof(DWORD), "unsupported 32 bits"); + if (!command_line) + return 1; + + wchar_t* next; + DWORD process_id = wcstoul(command_line, &next, 0); + if (!process_id) + return 2; + + DWORD access = PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE; + HANDLE child = ::OpenProcess(access, FALSE, process_id); + if (!child) + return 3; + + DWORD buffer = wcstoul(next, NULL, 0); + if (!buffer) + return 4; + + void* thunk = reinterpret_cast<void*>(static_cast<ULONG_PTR>(buffer)); + + const size_t kPageSize = 4096; + return sandbox::PatchNtdll(child, thunk, kPageSize); +} diff --git a/sandbox/win/wow_helper/wow_helper.exe b/sandbox/win/wow_helper/wow_helper.exe Binary files differnew file mode 100755 index 0000000000..f9bfb4bbdd --- /dev/null +++ b/sandbox/win/wow_helper/wow_helper.exe diff --git a/sandbox/win/wow_helper/wow_helper.pdb b/sandbox/win/wow_helper/wow_helper.pdb Binary files differnew file mode 100644 index 0000000000..9cb67d001d --- /dev/null +++ b/sandbox/win/wow_helper/wow_helper.pdb diff --git a/sandbox/win/wow_helper/wow_helper.vcproj b/sandbox/win/wow_helper/wow_helper.vcproj new file mode 100644 index 0000000000..5482fbddce --- /dev/null +++ b/sandbox/win/wow_helper/wow_helper.vcproj @@ -0,0 +1,223 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="wow_helper" + ProjectGUID="{BCF3A457-39F1-4DAA-9A65-93CFCD559036}" + RootNamespace="wow_helper" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="x64" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|x64" + OutputDirectory="$(ProjectDir)" + IntermediateDirectory="$(PlatformName)\$(ConfigurationName)" + ConfigurationType="1" + CharacterSet="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + TargetEnvironment="3" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="$(SolutionDir)..;$(SolutionDir)..\third_party\platformsdk_win2008_6_1\files\Include;$(VSInstallDir)\VC\atlmfc\include" + PreprocessorDefinitions="_WIN32_WINNT=0x0501;WINVER=0x0501;WIN32;_DEBUG" + MinimalRebuild="true" + BasicRuntimeChecks="0" + RuntimeLibrary="1" + BufferSecurityCheck="false" + RuntimeTypeInfo="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + LinkIncremental="1" + GenerateDebugInformation="true" + SubSystem="2" + TargetMachine="17" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|x64" + OutputDirectory="$(ProjectDir)" + IntermediateDirectory="$(PlatformName)\$(ConfigurationName)" + ConfigurationType="1" + CharacterSet="1" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + TargetEnvironment="3" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="$(SolutionDir)..;$(SolutionDir)..\third_party\platformsdk_win2008_6_1\files\Include;$(VSInstallDir)\VC\atlmfc\include" + PreprocessorDefinitions="_WIN32_WINNT=0x0501;WINVER=0x0501;WIN32;NDEBUG" + RuntimeLibrary="0" + BufferSecurityCheck="false" + RuntimeTypeInfo="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + LinkIncremental="1" + GenerateDebugInformation="true" + SubSystem="2" + OptimizeReferences="2" + EnableCOMDATFolding="2" + TargetMachine="17" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="base" + > + <File + RelativePath="..\..\base\scoped_ptr.h" + > + </File> + </Filter> + <Filter + Name="sandbox" + > + <File + RelativePath="..\src\nt_internals.h" + > + </File> + <File + RelativePath="..\src\resolver.h" + > + </File> + </Filter> + <File + RelativePath=".\service64_resolver.cc" + > + </File> + <File + RelativePath=".\service64_resolver.h" + > + </File> + <File + RelativePath=".\target_code.cc" + > + </File> + <File + RelativePath=".\target_code.h" + > + </File> + <File + RelativePath=".\wow_helper.cc" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> |