diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-11-18 16:35:47 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-11-18 15:45:54 +0000 |
commit | 32f5a1c56531e4210bc4cf8d8c7825d66e081888 (patch) | |
tree | eeeec6822f4d738d8454525233fd0e2e3a659e6d /chromium/sandbox | |
parent | 99677208ff3b216fdfec551fbe548da5520cd6fb (diff) | |
download | qtwebengine-chromium-32f5a1c56531e4210bc4cf8d8c7825d66e081888.tar.gz |
BASELINE: Update Chromium to 87.0.4280.67
Change-Id: Ib157360be8c2ffb2c73125751a89f60e049c1d54
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/sandbox')
61 files changed, 4486 insertions, 892 deletions
diff --git a/chromium/sandbox/linux/BUILD.gn b/chromium/sandbox/linux/BUILD.gn index d1f3f14d44a..4fd639fd0fa 100644 --- a/chromium/sandbox/linux/BUILD.gn +++ b/chromium/sandbox/linux/BUILD.gn @@ -11,6 +11,13 @@ if (is_android) { import("//build/config/android/rules.gni") } +# This file depends on the legacy global sources assignment filter. It should +# be converted to check target platform before assigning source files to the +# sources variable. Remove this import and set_sources_assignment_filter call +# when the file has been converted. See https://crbug.com/1018739 for details. +import("//build/config/deprecated_default_sources_assignment_filter.gni") +set_sources_assignment_filter(deprecated_default_sources_assignment_filter) + declare_args() { compile_suid_client = is_linux || is_chromeos @@ -96,6 +103,7 @@ source_set("sandbox_linux_unittests_sources") { "syscall_broker/broker_file_permission_unittest.cc", "syscall_broker/broker_process_unittest.cc", "syscall_broker/broker_simple_message_unittest.cc", + "syscall_broker/remote_syscall_arg_handler_unittest.cc", "tests/main.cc", "tests/scoped_temporary_file.cc", "tests/scoped_temporary_file.h", @@ -351,6 +359,10 @@ component("sandbox_services") { "syscall_broker/broker_process.h", "syscall_broker/broker_simple_message.cc", "syscall_broker/broker_simple_message.h", + "syscall_broker/remote_syscall_arg_handler.cc", + "syscall_broker/remote_syscall_arg_handler.h", + "syscall_broker/syscall_dispatcher.cc", + "syscall_broker/syscall_dispatcher.h", ] defines = [ "SANDBOX_IMPLEMENTATION" ] @@ -400,6 +412,10 @@ component("sandbox_services") { "syscall_broker/broker_process.h", "syscall_broker/broker_simple_message.cc", "syscall_broker/broker_simple_message.h", + "syscall_broker/remote_syscall_arg_handler.cc", + "syscall_broker/remote_syscall_arg_handler.h", + "syscall_broker/syscall_dispatcher.cc", + "syscall_broker/syscall_dispatcher.h", ] } else if (!is_android) { sources += [ @@ -417,6 +433,7 @@ source_set("sandbox_services_headers") { "system_headers/i386_linux_ucontext.h", "system_headers/linux_filter.h", "system_headers/linux_futex.h", + "system_headers/linux_prctl.h", "system_headers/linux_seccomp.h", "system_headers/linux_signal.h", "system_headers/linux_syscalls.h", diff --git a/chromium/sandbox/linux/bpf_dsl/bpf_dsl.cc b/chromium/sandbox/linux/bpf_dsl/bpf_dsl.cc index 51aa6ccf8c5..82a40696b87 100644 --- a/chromium/sandbox/linux/bpf_dsl/bpf_dsl.cc +++ b/chromium/sandbox/linux/bpf_dsl/bpf_dsl.cc @@ -33,7 +33,8 @@ class ReturnResultExprImpl : public internal::ResultExprImpl { bool IsAllow() const override { return IsAction(SECCOMP_RET_ALLOW); } bool IsDeny() const override { - return IsAction(SECCOMP_RET_ERRNO) || IsAction(SECCOMP_RET_KILL); + return IsAction(SECCOMP_RET_ERRNO) || IsAction(SECCOMP_RET_KILL) || + IsAction(SECCOMP_RET_USER_NOTIF); } private: @@ -262,6 +263,10 @@ ResultExpr UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux) { false /* unsafe */); } +ResultExpr UserNotify() { + return std::make_shared<ReturnResultExprImpl>(SECCOMP_RET_USER_NOTIF); +} + BoolExpr BoolConst(bool value) { return std::make_shared<ConstBoolExprImpl>(value); } diff --git a/chromium/sandbox/linux/bpf_dsl/bpf_dsl.h b/chromium/sandbox/linux/bpf_dsl/bpf_dsl.h index 555d820e017..24261f65021 100644 --- a/chromium/sandbox/linux/bpf_dsl/bpf_dsl.h +++ b/chromium/sandbox/linux/bpf_dsl/bpf_dsl.h @@ -120,6 +120,12 @@ SANDBOX_EXPORT ResultExpr SANDBOX_EXPORT ResultExpr UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux); +// UserNotify specifies that the kernel shall notify a listening process that a +// syscall occurred. The listening process may perform the system call on +// behalf of the sandboxed process, or may instruct the sandboxed process to +// continue the system call. +SANDBOX_EXPORT ResultExpr UserNotify(); + // BoolConst converts a bool value into a BoolExpr. SANDBOX_EXPORT BoolExpr BoolConst(bool value); diff --git a/chromium/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc b/chromium/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc index cae77db0435..ae35b33f0cd 100644 --- a/chromium/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc +++ b/chromium/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc @@ -453,6 +453,10 @@ TEST(BPFDSL, IsAllowDeny) { EXPECT_FALSE(trap->IsAllow()); EXPECT_TRUE(trap->IsDeny()); + ResultExpr user_notify = UserNotify(); + EXPECT_FALSE(user_notify->IsAllow()); + EXPECT_TRUE(user_notify->IsDeny()); + const Arg<int> arg(0); ResultExpr maybe = If(arg == 0, Allow()).Else(Error(EPERM)); EXPECT_FALSE(maybe->IsAllow()); @@ -469,6 +473,9 @@ TEST(BPFDSL, HasUnsafeTraps) { ResultExpr unsafe = UnsafeTrap(DummyTrap, nullptr); EXPECT_TRUE(unsafe->HasUnsafeTraps()); + ResultExpr user_notify = UserNotify(); + EXPECT_FALSE(allow->HasUnsafeTraps()); + const Arg<int> arg(0); ResultExpr maybe = If(arg == 0, allow).Else(unsafe); EXPECT_TRUE(maybe->HasUnsafeTraps()); diff --git a/chromium/sandbox/linux/bpf_dsl/dump_bpf.cc b/chromium/sandbox/linux/bpf_dsl/dump_bpf.cc index 2edf592f68f..677e0761aaa 100644 --- a/chromium/sandbox/linux/bpf_dsl/dump_bpf.cc +++ b/chromium/sandbox/linux/bpf_dsl/dump_bpf.cc @@ -119,6 +119,8 @@ void AppendInstruction(std::string* dst, size_t pc, const sock_filter& insn) { } else if ((insn.k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE) { base::StringAppendF(dst, "Trace #%" PRIu32 "\n", insn.k & SECCOMP_RET_DATA); + } else if (insn.k == SECCOMP_RET_USER_NOTIF) { + base::StringAppendF(dst, "UserNotif\n"); } else if (insn.k == SECCOMP_RET_ALLOW) { base::StringAppendF(dst, "Allowed\n"); } else if (insn.k == SECCOMP_RET_KILL) { diff --git a/chromium/sandbox/linux/bpf_dsl/trap_registry.h b/chromium/sandbox/linux/bpf_dsl/trap_registry.h index 0a5d2f14ccc..2fc78a6fa56 100644 --- a/chromium/sandbox/linux/bpf_dsl/trap_registry.h +++ b/chromium/sandbox/linux/bpf_dsl/trap_registry.h @@ -8,18 +8,11 @@ #include <stdint.h> #include "base/macros.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" #include "sandbox/sandbox_export.h" namespace sandbox { -// This must match the kernel's seccomp_data structure. -struct arch_seccomp_data { - int nr; - uint32_t arch; - uint64_t instruction_pointer; - uint64_t args[6]; -}; - namespace bpf_dsl { // TrapRegistry provides an interface for registering "trap handlers" diff --git a/chromium/sandbox/linux/bpf_dsl/verifier.h b/chromium/sandbox/linux/bpf_dsl/verifier.h index 9b25ab1d714..581f49f9e53 100644 --- a/chromium/sandbox/linux/bpf_dsl/verifier.h +++ b/chromium/sandbox/linux/bpf_dsl/verifier.h @@ -12,10 +12,10 @@ #include "base/macros.h" #include "sandbox/sandbox_export.h" +struct arch_seccomp_data; struct sock_filter; namespace sandbox { -struct arch_seccomp_data; namespace bpf_dsl { diff --git a/chromium/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc b/chromium/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc index 55ecf588227..fd6cd00bc67 100644 --- a/chromium/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc +++ b/chromium/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc @@ -9,88 +9,102 @@ #include <unistd.h> #include <memory> +#include <tuple> +#include <type_traits> #include <vector> #include "base/bind.h" #include "base/callback.h" +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" #include "base/macros.h" #include "base/posix/eintr_wrapper.h" +#include "base/test/bind_test_util.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/seccomp-bpf/sandbox_bpf_test_runner.h" +#include "sandbox/linux/syscall_broker/broker_client.h" #include "sandbox/linux/syscall_broker/broker_command.h" #include "sandbox/linux/syscall_broker/broker_file_permission.h" #include "sandbox/linux/syscall_broker/broker_process.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" #include "sandbox/linux/system_headers/linux_syscalls.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-param-test.h" #include "testing/gtest/include/gtest/gtest.h" namespace sandbox { -namespace { - using bpf_dsl::Allow; using bpf_dsl::ResultExpr; using bpf_dsl::Trap; +using BrokerProcess = syscall_broker::BrokerProcess; +using BrokerType = syscall_broker::BrokerProcess::BrokerType; +using BrokerCommandSet = syscall_broker::BrokerCommandSet; +using BrokerFilePermission = syscall_broker::BrokerFilePermission; + // Test a trap handler that makes use of a broker process to open(). class InitializedOpenBroker { public: - InitializedOpenBroker() : initialized_(false) { + explicit InitializedOpenBroker( + BrokerType broker_type = BrokerType::SIGNAL_BASED) { syscall_broker::BrokerCommandSet command_set; command_set.set(syscall_broker::COMMAND_OPEN); command_set.set(syscall_broker::COMMAND_ACCESS); - std::vector<syscall_broker::BrokerFilePermission> permissions = { - syscall_broker::BrokerFilePermission::ReadOnly("/proc/allowed"), - syscall_broker::BrokerFilePermission::ReadOnly("/proc/cpuinfo")}; - broker_process_ = std::make_unique<syscall_broker::BrokerProcess>( - EPERM, command_set, permissions); + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly("/proc/allowed"), + BrokerFilePermission::ReadOnly("/proc/cpuinfo")}; + broker_process_ = std::make_unique<BrokerProcess>(EPERM, command_set, + permissions, broker_type); BPF_ASSERT(broker_process_->Init(base::BindOnce([]() { return true; }))); - initialized_ = true; } - bool initialized() const { return initialized_; } - - syscall_broker::BrokerProcess* broker_process() const { - return broker_process_.get(); - } + BrokerProcess* broker_process() const { return broker_process_.get(); } private: - bool initialized_; - std::unique_ptr<syscall_broker::BrokerProcess> broker_process_; + std::unique_ptr<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); + BrokerProcess* broker_process = static_cast<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])); + return broker_process->GetBrokerClientSignalBased()->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])); + return broker_process->GetBrokerClientSignalBased()->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])); + return broker_process->GetBrokerClientSignalBased()->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])); + return broker_process->GetBrokerClientSignalBased()->Open( + reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); default: BPF_ASSERT(false); return -ENOSYS; @@ -115,7 +129,7 @@ class DenyOpenPolicy : public bpf_dsl::Policy { #endif case __NR_openat: // We get a InitializedOpenBroker class, but our trap handler wants - // the syscall_broker::BrokerProcess object. + // the BrokerProcess object. return Trap(BrokerOpenTrapHandler, iob_->broker_process()); default: return Allow(); @@ -134,15 +148,18 @@ 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); + BrokerProcess* broker_process = BPF_AUX->broker_process(); + BPF_ASSERT(broker_process != nullptr); // 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); + BPF_ASSERT(broker_process->GetBrokerClientSignalBased()->Open( + "/proc/denied", O_RDONLY) == -EPERM); + BPF_ASSERT(broker_process->GetBrokerClientSignalBased()->Access( + "/proc/denied", R_OK) == -EPERM); + BPF_ASSERT(broker_process->GetBrokerClientSignalBased()->Open( + "/proc/allowed", O_RDONLY) == -ENOENT); + BPF_ASSERT(broker_process->GetBrokerClientSignalBased()->Access( + "/proc/allowed", R_OK) == -ENOENT); // Now use glibc's open() as an external library would. BPF_ASSERT(open("/proc/denied", O_RDONLY) == -1); @@ -172,9 +189,2317 @@ BPF_TEST(SandboxBPF, 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); + BPF_ASSERT(HANDLE_EINTR(read(cpu_info_fd, buf, sizeof(buf))) > 0); } +// The rest of the tests do not run under thread sanitizer, as TSAN starts up an +// extra thread which triggers a sandbox assertion. BPF_TESTs do not run under +// TSAN. +#if !defined(THREAD_SANITIZER) + +namespace { +// Our fake errno must be less than 255 or various libc implementations will +// not accept this as a valid error number. E.g. bionic accepts up to 255, glibc +// and musl up to 4096. +const int kFakeErrnoSentinel = 254; +} // namespace + +// There are a variety of ways to make syscalls in a sandboxed process. One is +// to directly make the syscalls, one is to make the syscalls through libc +// (which oftens uses different underlying syscalls per platform and kernel +// version). With the signals-based broker, the sandboxed process can also make +// calls directly to the broker over the existing IPC channel. +// This interface encompasses the available syscalls so we can test every method +// of making syscalls. +class Syscaller { + public: + virtual ~Syscaller() = default; + + virtual int Open(const char* filepath, int flags) = 0; + virtual int Access(const char* filepath, int mode) = 0; + virtual int Stat(const char* filepath, + bool follow_links, + struct stat* statbuf) = 0; + virtual int Rename(const char* oldpath, const char* newpath) = 0; + virtual int Readlink(const char* path, char* buf, size_t bufsize) = 0; + virtual int Mkdir(const char* pathname, mode_t mode) = 0; + virtual int Rmdir(const char* path) = 0; + virtual int Unlink(const char* path) = 0; +}; + +class IPCSyscaller : public Syscaller { + public: + explicit IPCSyscaller(BrokerProcess* broker) : broker_(broker) {} + ~IPCSyscaller() override = default; + + int Open(const char* filepath, int flags) override { + return broker_->GetBrokerClientSignalBased()->Open(filepath, flags); + } + + int Access(const char* filepath, int mode) override { + return broker_->GetBrokerClientSignalBased()->Access(filepath, mode); + } + + int Stat(const char* filepath, + bool follow_links, + struct stat* statbuf) override { + return broker_->GetBrokerClientSignalBased()->Stat(filepath, follow_links, + statbuf); + } + + int Rename(const char* oldpath, const char* newpath) override { + return broker_->GetBrokerClientSignalBased()->Rename(oldpath, newpath); + } + + int Readlink(const char* path, char* buf, size_t bufsize) override { + return broker_->GetBrokerClientSignalBased()->Readlink(path, buf, bufsize); + } + + int Mkdir(const char* pathname, mode_t mode) override { + return broker_->GetBrokerClientSignalBased()->Mkdir(pathname, mode); + } + + int Rmdir(const char* path) override { + return broker_->GetBrokerClientSignalBased()->Rmdir(path); + } + + int Unlink(const char* path) override { + return broker_->GetBrokerClientSignalBased()->Unlink(path); + } + + private: + BrokerProcess* broker_; +}; + +// Only use syscall(...) on x64 to avoid having to reimplement a libc-like +// layer that uses different syscalls on different architectures. +#if (defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)) && \ + defined(__x86_64__) +#define DIRECT_SYSCALLER_ENABLED +#endif + +#if defined(DIRECT_SYSCALLER_ENABLED) +class DirectSyscaller : public Syscaller { + public: + ~DirectSyscaller() override = default; + + int Open(const char* filepath, int flags) override { + int ret = syscall(__NR_open, filepath, flags); + if (ret < 0) + return -errno; + return ret; + } + + int Access(const char* filepath, int mode) override { + int ret = syscall(__NR_access, filepath, mode); + if (ret < 0) + return -errno; + return ret; + } + + int Stat(const char* filepath, + bool follow_links, + struct stat* statbuf) override { + int ret = follow_links ? syscall(__NR_stat, filepath, statbuf) + : syscall(__NR_lstat, filepath, statbuf); + if (ret < 0) + return -errno; + return ret; + } + + int Rename(const char* oldpath, const char* newpath) override { + int ret = syscall(__NR_rename, oldpath, newpath); + if (ret < 0) + return -errno; + return ret; + } + + int Readlink(const char* path, char* buf, size_t bufsize) override { + int ret = syscall(__NR_readlink, path, buf, bufsize); + if (ret < 0) + return -errno; + return ret; + } + + int Mkdir(const char* pathname, mode_t mode) override { + int ret = syscall(__NR_mkdir, pathname, mode); + if (ret < 0) + return -errno; + return ret; + } + + int Rmdir(const char* path) override { + int ret = syscall(__NR_rmdir, path); + if (ret < 0) + return -errno; + return ret; + } + + int Unlink(const char* path) override { + int ret = syscall(__NR_unlink, path); + if (ret < 0) + return -errno; + return ret; + } +}; +#endif // defined(DIRECT_SYSCALLER_ENABLED) + +class LibcSyscaller : public Syscaller { + public: + ~LibcSyscaller() override = default; + + int Open(const char* filepath, int flags) override { + int ret = HANDLE_EINTR(open(filepath, flags, 0600)); + if (ret < 0) + return -errno; + return ret; + } + int Access(const char* filepath, int mode) override { + int ret = access(filepath, mode); + if (ret < 0) + return -errno; + return ret; + } + + int Stat(const char* filepath, + bool follow_links, + struct stat* statbuf) override { + int ret = follow_links ? stat(filepath, statbuf) : lstat(filepath, statbuf); + if (ret < 0) + return -errno; + return ret; + } + + int Rename(const char* oldpath, const char* newpath) override { + int ret = rename(oldpath, newpath); + if (ret < 0) + return -errno; + return ret; + } + + int Readlink(const char* path, char* buf, size_t bufsize) override { + int ret = readlink(path, buf, bufsize); + if (ret < 0) + return -errno; + return ret; + } + + int Mkdir(const char* pathname, mode_t mode) override { + int ret = mkdir(pathname, mode); + if (ret < 0) + return -errno; + return ret; + } + + int Rmdir(const char* path) override { + int ret = rmdir(path); + if (ret < 0) + return -errno; + return ret; + } + + int Unlink(const char* path) override { + int ret = unlink(path); + if (ret < 0) + return -errno; + return ret; + } +}; + +enum class SyscallerType { IPCSyscaller = 0, DirectSyscaller, LibcSyscaller }; + +// The testing infrastructure for the broker integration tests is built on the +// same infrastructure that BPF_TEST or SANDBOX_TEST uses. Each individual test +// starts up a child process, which itself starts up a broker process and +// sandboxes itself. To create a test, implement this delegate and call +// RunAllBrokerTests() in a TEST(). The bulk of the test body will be in +// RunTestInSandboxedChild(). +class BrokerTestDelegate { + public: + struct BrokerParams { + int denied_errno = kFakeErrnoSentinel; + syscall_broker::BrokerCommandSet allowed_command_set; + std::vector<BrokerFilePermission> permissions; + }; + + virtual ~BrokerTestDelegate() = default; + + // Called in the parent test process before starting the child process. Should + // use GTEST's ASSERT macros. + virtual void ParentSetUp() {} + + // Sets up the test in the child process before applying the sandbox. + // |allowed_command_set| and |permissions| should be filled in with the + // desired commands and permissions the broker should allow. Should use + // BPF_ASSERT. + virtual BrokerParams ChildSetUpPreSandbox() = 0; + + // Gets called in the sandboxed process with the pid of the newly started + // broker. + virtual void OnBrokerStarted(pid_t broker_pid) {} + + // Runs the test after setting up the sandbox in the forked process. + // Assertions should use BPF_ASSERT. + virtual void RunTestInSandboxedChild(Syscaller* syscaller) = 0; + + // Called in the parent test process after the child dies. Should perform + // cleanup, and can also ASSERT like a normal GTEST. Note that modifications + // of class state in the above two functions will not be visible here, as they + // ran in the forked child. + virtual void ParentTearDown() { + ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); + } +}; + +namespace syscall_broker { +// A BPF policy for our BPF_TEST that defers to the broker for syscalls that +// take paths, and allows everything else. +class HandleFilesystemViaBrokerPolicy : public bpf_dsl::Policy { + public: + explicit HandleFilesystemViaBrokerPolicy(BrokerProcess* broker_process, + int denied_errno) + : broker_process_(broker_process), denied_errno_(denied_errno) {} + ~HandleFilesystemViaBrokerPolicy() override = default; + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + // Broker everything that we're supposed to broker. + if (broker_process_->IsSyscallAllowed(sysno)) { + return sandbox::bpf_dsl::Trap( + sandbox::syscall_broker::BrokerClient::SIGSYS_Handler, + broker_process_->GetBrokerClientSignalBased()); + } + + // Otherwise, if this is a syscall that takes a pathname but isn't an + // allowed command, deny it. + if (broker_process_->IsSyscallBrokerable(sysno, + /*fast_check_in_client=*/false)) { + return bpf_dsl::Error(denied_errno_); + } + + // Allow everything else that doesn't take a pathname. + return Allow(); + } + + private: + BrokerProcess* broker_process_; + int denied_errno_; + + DISALLOW_COPY_AND_ASSIGN(HandleFilesystemViaBrokerPolicy); +}; +} // namespace syscall_broker + +// This implements BPFTesterDelegate to layer the broker integration tests on +// top of BPF_TEST infrastructure. +class BPFTesterBrokerDelegate : public BPFTesterDelegate { + public: + explicit BPFTesterBrokerDelegate(bool fast_check_in_client, + BrokerTestDelegate* broker_test_delegate, + SyscallerType syscaller_type, + BrokerType broker_type) + : fast_check_in_client_(fast_check_in_client), + broker_test_delegate_(broker_test_delegate), + syscaller_type_(syscaller_type), + broker_type_(broker_type) {} + ~BPFTesterBrokerDelegate() override = default; + + std::unique_ptr<bpf_dsl::Policy> GetSandboxBPFPolicy() override { + BrokerTestDelegate::BrokerParams broker_params = + broker_test_delegate_->ChildSetUpPreSandbox(); + + broker_process_ = std::make_unique<BrokerProcess>( + broker_params.denied_errno, broker_params.allowed_command_set, + broker_params.permissions, broker_type_, fast_check_in_client_); + BPF_ASSERT(broker_process_->Init(base::BindOnce([]() { return true; }))); + broker_test_delegate_->OnBrokerStarted(broker_process_->broker_pid()); + + BPF_ASSERT(TestUtils::CurrentProcessHasChildren()); + + CreateSyscaller(); + + return std::unique_ptr<bpf_dsl::Policy>( + new syscall_broker::HandleFilesystemViaBrokerPolicy( + broker_process_.get(), broker_params.denied_errno)); + } + + void RunTestFunction() override { + broker_test_delegate_->RunTestInSandboxedChild(syscaller_.get()); + } + + void CreateSyscaller() { + BPF_ASSERT(broker_process_->GetBrokerClientSignalBased()); + switch (syscaller_type_) { + case SyscallerType::IPCSyscaller: + syscaller_ = std::make_unique<IPCSyscaller>(broker_process_.get()); + break; + case SyscallerType::DirectSyscaller: +#if defined(DIRECT_SYSCALLER_ENABLED) + syscaller_ = std::make_unique<DirectSyscaller>(); +#else + CHECK(false) << "Requested instantiation of DirectSyscaller on a " + "platform that doesn't support it"; +#endif + break; + case SyscallerType::LibcSyscaller: + syscaller_ = std::make_unique<LibcSyscaller>(); + break; + } + } + + private: + bool fast_check_in_client_; + BrokerTestDelegate* broker_test_delegate_; + SyscallerType syscaller_type_; + BrokerType broker_type_; + + std::unique_ptr<BrokerProcess> broker_process_; + std::unique_ptr<Syscaller> syscaller_; +}; + +namespace { +struct BrokerTestConfiguration { + std::string test_name; + bool fast_check_in_client; + SyscallerType syscaller_type; + BrokerType broker_type; +}; + +// Lists out all the broker configurations we want to test. +const std::vector<BrokerTestConfiguration> broker_test_configs = { + {"FastCheckInClient_IPCSyscaller", true, SyscallerType::IPCSyscaller, + BrokerType::SIGNAL_BASED}, +#if defined(DIRECT_SYSCALLER_ENABLED) + {"FastCheckInClient_DirectSyscaller", true, SyscallerType::DirectSyscaller, + BrokerType::SIGNAL_BASED}, +#endif + {"FastCheckInClient_LibcSyscaller", true, SyscallerType::LibcSyscaller, + BrokerType::SIGNAL_BASED}, + {"NoFastCheckInClient_IPCSyscaller", false, SyscallerType::IPCSyscaller, + BrokerType::SIGNAL_BASED}, +#if defined(DIRECT_SYSCALLER_ENABLED) + {"NoFastCheckInClient_DirectSyscaller", false, + SyscallerType::DirectSyscaller, BrokerType::SIGNAL_BASED}, +#endif + {"NoFastCheckInClient_LibcSyscaller", false, SyscallerType::LibcSyscaller, + BrokerType::SIGNAL_BASED}}; } // namespace +void RunSingleBrokerTest(BrokerTestDelegate* test_delegate, + const BrokerTestConfiguration& test_config) { + test_delegate->ParentSetUp(); + sandbox::SandboxBPFTestRunner bpf_test_runner(new BPFTesterBrokerDelegate( + test_config.fast_check_in_client, test_delegate, + test_config.syscaller_type, test_config.broker_type)); + sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, DEATH_SUCCESS()); + test_delegate->ParentTearDown(); +} + +template <typename T> +void RunAllBrokerTests() { + for (const BrokerTestConfiguration& test_config : broker_test_configs) { + SCOPED_TRACE(test_config.test_name); + auto test_delegate = std::make_unique<T>(); + RunSingleBrokerTest(test_delegate.get(), test_config); + } +} + +template <typename T> +void RunIPCBrokerTests() { + for (const BrokerTestConfiguration& test_config : broker_test_configs) { + if (test_config.syscaller_type != SyscallerType::IPCSyscaller) + continue; + + SCOPED_TRACE(test_config.test_name); + auto test_delegate = std::make_unique<T>(); + RunSingleBrokerTest(test_delegate.get(), test_config); + } +} + +// Tests that a SIGNALS_BASED broker responds with -EFAULT when open() or +// access() are called with nullptr. +class TestOpenAccessNullDelegate final : public BrokerTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_ACCESS, syscall_broker::COMMAND_OPEN}); + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + int fd = syscaller->Open(nullptr, O_RDONLY); + BPF_ASSERT_EQ(fd, -EFAULT); + + int ret = syscaller->Access(nullptr, F_OK); + BPF_ASSERT_EQ(ret, -EFAULT); + } +}; + +TEST(BrokerProcessIntegrationTest, TestOpenAccessNull) { + RunIPCBrokerTests<TestOpenAccessNullDelegate>(); +} + +// Tests open()/access() for files that do not exist, are not allowed by +// allowlist, and are allowed by allowlist but not accessible. +template <int DENIED_ERRNO> +class TestOpenFilePermsDelegate final : public BrokerTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.denied_errno = DENIED_ERRNO; + + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_ACCESS, syscall_broker::COMMAND_OPEN}); + + params.permissions = { + BrokerFilePermission::ReadOnly(kR_AllowListed), + BrokerFilePermission::ReadOnly(kR_AllowListedButDenied), + BrokerFilePermission::WriteOnly(kW_AllowListed), + BrokerFilePermission::ReadWrite(kRW_AllowListed)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + int fd = -1; + fd = syscaller->Open(kR_AllowListed, O_RDONLY); + BPF_ASSERT_EQ(fd, -ENOENT); + fd = syscaller->Open(kR_AllowListed, O_WRONLY); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + fd = syscaller->Open(kR_AllowListed, O_RDWR); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + int ret = -1; + ret = syscaller->Access(kR_AllowListed, F_OK); + BPF_ASSERT_EQ(ret, -ENOENT); + ret = syscaller->Access(kR_AllowListed, R_OK); + BPF_ASSERT_EQ(ret, -ENOENT); + ret = syscaller->Access(kR_AllowListed, W_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(kR_AllowListed, R_OK | W_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(kR_AllowListed, X_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(kR_AllowListed, R_OK | X_OK); + BPF_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 = syscaller->Open(kR_AllowListedButDenied, O_RDONLY); + // The broker process will allow this, but the normal permission system + // won't. + BPF_ASSERT_EQ(fd, -EACCES); + fd = syscaller->Open(kR_AllowListedButDenied, O_WRONLY); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + fd = syscaller->Open(kR_AllowListedButDenied, O_RDWR); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + ret = syscaller->Access(kR_AllowListedButDenied, F_OK); + // The normal permission system will let us check that the file exists. + BPF_ASSERT_EQ(ret, 0); + ret = syscaller->Access(kR_AllowListedButDenied, R_OK); + BPF_ASSERT_EQ(ret, -EACCES); + ret = syscaller->Access(kR_AllowListedButDenied, W_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(kR_AllowListedButDenied, R_OK | W_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(kR_AllowListedButDenied, X_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(kR_AllowListedButDenied, R_OK | X_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + } + + fd = syscaller->Open(kW_AllowListed, O_RDONLY); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + fd = syscaller->Open(kW_AllowListed, O_WRONLY); + BPF_ASSERT_EQ(fd, -ENOENT); + fd = syscaller->Open(kW_AllowListed, O_RDWR); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + ret = syscaller->Access(kW_AllowListed, F_OK); + BPF_ASSERT_EQ(ret, -ENOENT); + ret = syscaller->Access(kW_AllowListed, R_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(kW_AllowListed, W_OK); + BPF_ASSERT_EQ(ret, -ENOENT); + ret = syscaller->Access(kW_AllowListed, R_OK | W_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(kW_AllowListed, X_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(kW_AllowListed, R_OK | X_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + + fd = syscaller->Open(kRW_AllowListed, O_RDONLY); + BPF_ASSERT_EQ(fd, -ENOENT); + fd = syscaller->Open(kRW_AllowListed, O_WRONLY); + BPF_ASSERT_EQ(fd, -ENOENT); + fd = syscaller->Open(kRW_AllowListed, O_RDWR); + BPF_ASSERT_EQ(fd, -ENOENT); + ret = syscaller->Access(kRW_AllowListed, F_OK); + BPF_ASSERT_EQ(ret, -ENOENT); + ret = syscaller->Access(kRW_AllowListed, R_OK); + BPF_ASSERT_EQ(ret, -ENOENT); + ret = syscaller->Access(kRW_AllowListed, W_OK); + BPF_ASSERT_EQ(ret, -ENOENT); + ret = syscaller->Access(kRW_AllowListed, R_OK | W_OK); + BPF_ASSERT_EQ(ret, -ENOENT); + ret = syscaller->Access(kRW_AllowListed, X_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(kRW_AllowListed, R_OK | X_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + + fd = syscaller->Open(k_NotAllowlisted, O_RDONLY); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + fd = syscaller->Open(k_NotAllowlisted, O_WRONLY); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + fd = syscaller->Open(k_NotAllowlisted, O_RDWR); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + ret = syscaller->Access(k_NotAllowlisted, F_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(k_NotAllowlisted, R_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(k_NotAllowlisted, W_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(k_NotAllowlisted, R_OK | W_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(k_NotAllowlisted, X_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + ret = syscaller->Access(k_NotAllowlisted, R_OK | X_OK); + BPF_ASSERT_EQ(ret, -DENIED_ERRNO); + + // We have some extra sanity check for clearly wrong values. + fd = syscaller->Open(kRW_AllowListed, O_RDONLY | O_WRONLY | O_RDWR); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + + // It makes no sense to allow O_CREAT in a 2-parameters open. Ensure this + // is denied. + fd = syscaller->Open(kRW_AllowListed, O_RDWR | O_CREAT); + BPF_ASSERT_EQ(fd, -DENIED_ERRNO); + } + + private: + const char* const kR_AllowListed = "/proc/DOESNOTEXIST1"; + // We can't debug the init process, and shouldn't be able to access + // its auxv file. + const char* kR_AllowListedButDenied = "/proc/1/auxv"; + const char* kW_AllowListed = "/proc/DOESNOTEXIST2"; + const char* kRW_AllowListed = "/proc/DOESNOTEXIST3"; + const char* k_NotAllowlisted = "/proc/DOESNOTEXIST4"; +}; + +TEST(BrokerProcessIntegrationTest, TestOpenFilePermsEPERM) { + RunAllBrokerTests<TestOpenFilePermsDelegate<EPERM>>(); +} + +TEST(BrokerProcessIntegrationTest, TestOpenFilePermsENOENT) { + RunAllBrokerTests<TestOpenFilePermsDelegate<ENOENT>>(); +} + +class BadPathsDelegate final : public BrokerTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_ACCESS, syscall_broker::COMMAND_OPEN}); + params.permissions = {BrokerFilePermission::ReadOnlyRecursive("/proc/")}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + // Open cpuinfo via the broker. + int cpuinfo_fd = syscaller->Open(kFileCpuInfo, O_RDONLY); + base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); + BPF_ASSERT_GE(cpuinfo_fd, 0); + + int fd = -1; + int can_access; + + can_access = syscaller->Access(kNotAbsPath, R_OK); + BPF_ASSERT_EQ(can_access, -kFakeErrnoSentinel); + fd = syscaller->Open(kNotAbsPath, O_RDONLY); + BPF_ASSERT_EQ(fd, -kFakeErrnoSentinel); + + can_access = syscaller->Access(kDotDotStart, R_OK); + BPF_ASSERT_EQ(can_access, -kFakeErrnoSentinel); + fd = syscaller->Open(kDotDotStart, O_RDONLY); + BPF_ASSERT_EQ(fd, -kFakeErrnoSentinel); + + can_access = syscaller->Access(kDotDotMiddle, R_OK); + BPF_ASSERT_EQ(can_access, -kFakeErrnoSentinel); + fd = syscaller->Open(kDotDotMiddle, O_RDONLY); + BPF_ASSERT_EQ(fd, -kFakeErrnoSentinel); + + can_access = syscaller->Access(kDotDotEnd, R_OK); + BPF_ASSERT_EQ(can_access, -kFakeErrnoSentinel); + fd = syscaller->Open(kDotDotEnd, O_RDONLY); + BPF_ASSERT_EQ(fd, -kFakeErrnoSentinel); + + can_access = syscaller->Access(kTrailingSlash, R_OK); + BPF_ASSERT_EQ(can_access, -kFakeErrnoSentinel); + fd = syscaller->Open(kTrailingSlash, O_RDONLY); + BPF_ASSERT_EQ(fd, -kFakeErrnoSentinel); + } + + private: + const char* const kFileCpuInfo = "/proc/cpuinfo"; + const char* const kNotAbsPath = "proc/cpuinfo"; + const char* const kDotDotStart = "/../proc/cpuinfo"; + const char* const kDotDotMiddle = "/proc/self/../cpuinfo"; + const char* const kDotDotEnd = "/proc/.."; + const char* const kTrailingSlash = "/proc/"; +}; + +TEST(BrokerProcessIntegrationTest, BadPaths) { + RunAllBrokerTests<BadPathsDelegate>(); +} + +template <bool recursive> +class OpenCpuinfoDelegate final : public BrokerTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + // Open cpuinfo directly. + int cpu_info_fd = HANDLE_EINTR(open(kFileCpuInfo, O_RDONLY)); + BPF_ASSERT_GE(cpu_info_fd, 0); + memset(cpuinfo_buf_, 1, sizeof(cpuinfo_buf_)); + read_len_unsandboxed_ = + HANDLE_EINTR(read(cpu_info_fd, cpuinfo_buf_, sizeof(cpuinfo_buf_))); + BPF_ASSERT_GT(read_len_unsandboxed_, 0); + + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_ACCESS, syscall_broker::COMMAND_OPEN}); + params.permissions.push_back( + recursive ? BrokerFilePermission::ReadOnlyRecursive(kDirProc) + : BrokerFilePermission::ReadOnly(kFileCpuInfo)); + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + int fd = syscaller->Open(kFileCpuInfo, O_RDWR); + BPF_ASSERT_EQ(fd, -kFakeErrnoSentinel); + + // Check we can read /proc/cpuinfo. + int can_access = syscaller->Access(kFileCpuInfo, R_OK); + BPF_ASSERT_EQ(can_access, 0); + can_access = syscaller->Access(kFileCpuInfo, W_OK); + BPF_ASSERT_EQ(can_access, -kFakeErrnoSentinel); + // Check we can not write /proc/cpuinfo. + + // Open cpuinfo via the broker. + int cpuinfo_fd = syscaller->Open(kFileCpuInfo, O_RDONLY); + base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); + BPF_ASSERT_GE(cpuinfo_fd, 0); + char buf[3]; + memset(buf, 0, sizeof(buf)); + int read_len_sandboxed = HANDLE_EINTR(read(cpuinfo_fd, buf, sizeof(buf))); + BPF_ASSERT_GT(read_len_sandboxed, 0); + + // The following is not guaranteed true, but will be in practice. + BPF_ASSERT_EQ(read_len_sandboxed, read_len_unsandboxed_); + // Compare the cpuinfo as returned by the broker with the one we opened + // ourselves. + BPF_ASSERT_EQ(memcmp(buf, cpuinfo_buf_, read_len_sandboxed), 0); + } + + private: + const char* const kFileCpuInfo = "/proc/cpuinfo"; + const char* const kDirProc = "/proc/"; + + int read_len_unsandboxed_ = -1; + char cpuinfo_buf_[3]; +}; + +TEST(BrokerProcessIntegrationTest, OpenCpuinfoRecursive) { + RunAllBrokerTests<OpenCpuinfoDelegate</*recursive=*/true>>(); +} + +TEST(BrokerProcessIntegrationTest, OpenCpuinfoNonRecursive) { + RunAllBrokerTests<OpenCpuinfoDelegate</*recursive=*/false>>(); +} + +class OpenFileRWDelegate final : public BrokerTestDelegate { + public: + OpenFileRWDelegate() : tempfile_name_(tempfile.full_file_name()) {} + + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_ACCESS, syscall_broker::COMMAND_OPEN}); + params.permissions = {BrokerFilePermission::ReadWrite(tempfile_name_)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + // Check we can access that file with read or write. + int can_access = syscaller->Access(tempfile_name_, R_OK | W_OK); + BPF_ASSERT_EQ(can_access, 0); + + int tempfile2 = -1; + tempfile2 = syscaller->Open(tempfile_name_, O_RDWR); + BPF_ASSERT_GE(tempfile2, 0); + + // Write to the descriptor opened by the broker. + char test_text[] = "TESTTESTTEST"; + + ssize_t len = HANDLE_EINTR(write(tempfile2, test_text, sizeof(test_text))); + BPF_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 = HANDLE_EINTR(read(tempfile.fd(), buf, sizeof(buf))); + + BPF_ASSERT_EQ(len, static_cast<ssize_t>(sizeof(test_text))); + BPF_ASSERT_EQ(memcmp(test_text, buf, sizeof(test_text)), 0); + + BPF_ASSERT_EQ(close(tempfile2), 0); + } + + private: + ScopedTemporaryFile tempfile; + const char* const tempfile_name_; +}; + +TEST(BrokerProcessIntegrationTest, OpenFileRW) { + RunAllBrokerTests<OpenFileRWDelegate>(); +} + +class BrokerDiedDelegate final : public BrokerTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_ACCESS, syscall_broker::COMMAND_OPEN}); + params.permissions = {BrokerFilePermission::ReadOnly(kCpuInfo)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_GT(broker_pid_, 0); + + // Kill our own broker and check that we get ENOSYS or ENOMEM for our + // syscalls. + BPF_ASSERT_EQ(kill(broker_pid_, SIGKILL), 0); + + int ret = syscaller->Access(kCpuInfo, O_RDONLY); + BPF_ASSERT(ret == -ENOSYS || ret == -ENOMEM); + + ret = syscaller->Open(kCpuInfo, O_RDONLY); + BPF_ASSERT(ret == -ENOSYS || ret == -ENOMEM); + } + + void OnBrokerStarted(pid_t pid) override { broker_pid_ = pid; } + + private: + const char* const kCpuInfo = "/proc/cpuinfo"; + + pid_t broker_pid_ = -1; +}; + +TEST(BrokerProcessIntegrationTest, BrokerDied) { + RunAllBrokerTests<BrokerDiedDelegate>(); +} + +class OpenComplexFlagsDelegate final : public BrokerTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_ACCESS, syscall_broker::COMMAND_OPEN}); + params.permissions = {BrokerFilePermission::ReadOnly(kCpuInfo)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + // Tests that files opened without O_CLOEXEC (resp. O_NONBLOCK) do not have + // O_CLOEXEC (resp. O_NONBLOCK) on their file description, and vice versa. + int fd = -1; + int ret = 0; + fd = syscaller->Open(kCpuInfo, O_RDONLY); + BPF_ASSERT_GE(fd, 0); + ret = fcntl(fd, F_GETFL); + BPF_ASSERT_NE(-1, ret); + // The description shouldn't have the O_CLOEXEC attribute, nor O_NONBLOCK. + BPF_ASSERT_EQ(0, ret & (O_CLOEXEC | O_NONBLOCK)); + ret = fcntl(fd, F_GETFD); + BPF_ASSERT_NE(-1, ret); + // The descriptor also should not have FD_CLOEXEC. + BPF_ASSERT_EQ(FD_CLOEXEC & ret, 0); + BPF_ASSERT_EQ(0, close(fd)); + + fd = syscaller->Open(kCpuInfo, O_RDONLY | O_CLOEXEC); + BPF_ASSERT_GE(fd, 0); + ret = fcntl(fd, F_GETFD); + BPF_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. + BPF_ASSERT(FD_CLOEXEC & ret); + BPF_ASSERT_EQ(0, close(fd)); + + fd = syscaller->Open(kCpuInfo, O_RDONLY | O_NONBLOCK); + BPF_ASSERT_GE(fd, 0); + ret = fcntl(fd, F_GETFL); + BPF_ASSERT_NE(-1, ret); + BPF_ASSERT(O_NONBLOCK & ret); + BPF_ASSERT_EQ(0, close(fd)); + } + + private: + const char* const kCpuInfo = "/proc/cpuinfo"; +}; + +TEST(BrokerProcessIntegrationTest, OpenComplexFlags) { + RunAllBrokerTests<OpenComplexFlagsDelegate>(); +} + +class CreateFileDelegate final : public BrokerTestDelegate { + public: + void ParentSetUp() override { + // Create two temporary files and delete them, but store their file paths + // for later usage. + ScopedTemporaryFile temp_file; + ScopedTemporaryFile perm_file; + temp_str_ = temp_file.full_file_name(); + perm_str_ = perm_file.full_file_name(); + tempfile_name_ = temp_str_.c_str(); + permfile_name_ = perm_str_.c_str(); + + { + ScopedTemporaryFile tempfile; + existing_temp_file_str_ = tempfile.full_file_name(); + } + // Create a conflict for the temp filename. + base::ScopedFD fd( + open(existing_temp_file_str_.c_str(), O_RDWR | O_CREAT, 0600)); + BPF_ASSERT_GE(fd.get(), 0); + } + + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_ACCESS, syscall_broker::COMMAND_OPEN}); + // Note that "temporariness" is determined by the permissions given below. + params.permissions = { + BrokerFilePermission::ReadWriteCreateTemporary(existing_temp_file_str_), + BrokerFilePermission::ReadWriteCreateTemporary(tempfile_name_), + BrokerFilePermission::ReadWriteCreate(permfile_name_), + }; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + int fd = -1; + + // Opening a temp file using O_CREAT but not O_EXCL must not be allowed + // by the broker so as to prevent spying on any pre-existing files. + fd = syscaller->Open(tempfile_name_, O_RDWR | O_CREAT); + BPF_ASSERT_EQ(fd, -kFakeErrnoSentinel); + + // Opening a temp file in a normal way must not be allowed by the broker, + // either. + fd = syscaller->Open(tempfile_name_, O_RDWR); + BPF_ASSERT_EQ(fd, -kFakeErrnoSentinel); + + // Opening a temp file with both O_CREAT and O_EXCL is allowed since the + // file is known not to exist outside the scope of ScopedTemporaryFile. + fd = syscaller->Open(tempfile_name_, O_RDWR | O_CREAT | O_EXCL); + BPF_ASSERT_GE(fd, 0); + close(fd); + + // Opening a temp file with both O_CREAT and O_EXCL is allowed but fails + // per the OS when there is a conflict with a pre-existing file. + fd = syscaller->Open(existing_temp_file_str_.c_str(), + O_RDWR | O_CREAT | O_EXCL); + BPF_ASSERT_EQ(fd, -EEXIST); + + // Opening a new permanent file without specifying O_EXCL is allowed. + fd = syscaller->Open(permfile_name_, O_RDWR | O_CREAT); + BPF_ASSERT_GE(fd, 0); + close(fd); + + // Opening an existing permanent file without specifying O_EXCL is allowed. + fd = syscaller->Open(permfile_name_, O_RDWR | O_CREAT); + BPF_ASSERT_GE(fd, 0); + close(fd); + + // Opening an existing file with O_EXCL is allowed but fails per the OS. + fd = syscaller->Open(permfile_name_, O_RDWR | O_CREAT | O_EXCL); + BPF_ASSERT_EQ(fd, -EEXIST); + + const char kTestText[] = "TESTTESTTEST"; + + fd = syscaller->Open(permfile_name_, O_RDWR); + BPF_ASSERT_GE(fd, 0); + { + // Write to the descriptor opened by the broker and close. + base::ScopedFD scoped_fd(fd); + ssize_t len = HANDLE_EINTR(write(fd, kTestText, sizeof(kTestText))); + BPF_ASSERT_EQ(len, static_cast<ssize_t>(sizeof(kTestText))); + } + + int fd_check = open(permfile_name_, O_RDONLY); + BPF_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))); + BPF_ASSERT_EQ(len, static_cast<ssize_t>(sizeof(kTestText))); + BPF_ASSERT_EQ(memcmp(kTestText, buf, sizeof(kTestText)), 0); + } + } + + void ParentTearDown() override { + // Cleanup. + unlink(existing_temp_file_str_.c_str()); + unlink(tempfile_name_); + unlink(permfile_name_); + BrokerTestDelegate::ParentTearDown(); + } + + private: + std::string existing_temp_file_str_; + std::string temp_str_; + std::string perm_str_; + const char* tempfile_name_; + const char* permfile_name_; +}; + +TEST(BrokerProcessIntegrationTest, CreateFile) { + RunAllBrokerTests<CreateFileDelegate>(); +} + +// StatFileDelegate is the base class for all the Stat() tests. +class StatFileDelegate : public BrokerTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BPF_ASSERT_EQ(12, HANDLE_EINTR(write(tmp_file_.fd(), "blahblahblah", 12))); + memset(&sb_, 0, sizeof(sb_)); + return BrokerParams(); + } + + protected: + ScopedTemporaryFile tmp_file_; + + std::string temp_str_ = tmp_file_.full_file_name(); + const char* const tempfile_name_ = temp_str_.c_str(); + + const char* const nonesuch_name = "/mbogo/fictitious/nonesuch"; + const char* const leading_path1 = "/mbogo/fictitious"; + const char* const leading_path2 = "/mbogo"; + const char* const leading_path3 = "/"; + const char* const bad_leading_path1 = "/mbog"; + const char* const bad_leading_path2 = "/mboga"; + const char* const bad_leading_path3 = "/mbogos"; + const char* const bad_leading_path4 = "/mbogo/fictitiou"; + const char* const bad_leading_path5 = "/mbogo/fictitioux"; + const char* const bad_leading_path6 = "/mbogo/fictitiousa"; + + struct stat sb_; +}; + +// Actual file with permissions to see file but command not allowed. +template <bool follow_links> +class StatFileNoCommandDelegate final : public StatFileDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params = StatFileDelegate::ChildSetUpPreSandbox(); + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet({}); + params.permissions = {BrokerFilePermission::ReadOnly(tempfile_name_)}; + return params; + } + void RunTestInSandboxedChild(Syscaller* syscaller) override { + int ret = syscaller->Stat(tempfile_name_, follow_links, &sb_); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, ret); + } +}; + +TEST(BrokerProcessIntegrationTest, StatFileNoCommandFollowLinks) { + RunAllBrokerTests<StatFileNoCommandDelegate<true>>(); +} + +TEST(BrokerProcessIntegrationTest, StatFileNoCommandNoFollowLinks) { + RunAllBrokerTests<StatFileNoCommandDelegate<false>>(); +} + +// Allows the STAT command without any file permissions. +template <bool follow_links> +class StatFilesNoPermissionDelegate final : public StatFileDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params = StatFileDelegate::ChildSetUpPreSandbox(); + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_STAT}); + params.permissions = {}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + // Nonexistent file with no permissions to see file. + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(nonesuch_name, follow_links, &sb_)); + // Actual file with no permission to see file. + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(tempfile_name_, follow_links, &sb_)); + } +}; + +TEST(BrokerProcessIntegrationTest, StatFilesNoPermissionFollowLinks) { + RunAllBrokerTests<StatFilesNoPermissionDelegate<true>>(); +} + +TEST(BrokerProcessIntegrationTest, StatFilesNoPermissionNoFollowLinks) { + RunAllBrokerTests<StatFilesNoPermissionDelegate<false>>(); +} + +// Nonexistent file with permissions to see file. +template <bool follow_links> +class StatNonexistentFileWithPermissionsDelegate final + : public StatFileDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params = StatFileDelegate::ChildSetUpPreSandbox(); + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_STAT}); + params.permissions = {BrokerFilePermission::ReadOnly(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-ENOENT, syscaller->Stat(nonesuch_name, follow_links, &sb_)); + + // Gets denied all the way back to root since no create permission. + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(leading_path1, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(leading_path2, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(leading_path3, follow_links, &sb_)); + + // Not fooled by substrings. + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path1, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path2, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path3, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path4, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path5, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path6, follow_links, &sb_)); + } +}; + +TEST(BrokerProcessIntegrationTest, + StatNonexistentFileWithPermissionsFollowLinks) { + RunAllBrokerTests<StatNonexistentFileWithPermissionsDelegate<true>>(); +} + +TEST(BrokerProcessIntegrationTest, + StatNonexistentFileWithPermissionsNoFollowLinks) { + RunAllBrokerTests<StatNonexistentFileWithPermissionsDelegate<false>>(); +} + +// Nonexistent file with permissions to create file. +template <bool follow_links> +class StatNonexistentFileWithCreatePermissionsDelegate final + : public StatFileDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params = StatFileDelegate::ChildSetUpPreSandbox(); + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_STAT}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-ENOENT, syscaller->Stat(nonesuch_name, follow_links, &sb_)); + + // Gets ENOENT all the way back to root since it has create permission. + BPF_ASSERT_EQ(-ENOENT, syscaller->Stat(leading_path1, follow_links, &sb_)); + BPF_ASSERT_EQ(-ENOENT, syscaller->Stat(leading_path2, follow_links, &sb_)); + + // But can always get the root. + BPF_ASSERT_EQ(0, syscaller->Stat(leading_path3, follow_links, &sb_)); + + // Not fooled by substrings. + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path1, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path2, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path3, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path4, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path5, follow_links, &sb_)); + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Stat(bad_leading_path6, follow_links, &sb_)); + } +}; + +TEST(BrokerProcessIntegrationTest, + StatNonexistentFileWithCreatePermissionsFollowLinks) { + RunAllBrokerTests<StatNonexistentFileWithCreatePermissionsDelegate<true>>(); +} + +TEST(BrokerProcessIntegrationTest, + StatNonexistentFileWithCreatePermissionsNoFollowLinks) { + RunAllBrokerTests<StatNonexistentFileWithCreatePermissionsDelegate<false>>(); +} + +// Actual file with permissions to see file. +template <bool follow_links> +class StatFileWithPermissionsDelegate final : public StatFileDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params = StatFileDelegate::ChildSetUpPreSandbox(); + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_STAT}); + params.permissions = {BrokerFilePermission::ReadOnly(tempfile_name_)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(0, syscaller->Stat(tempfile_name_, follow_links, &sb_)); + + // Following fields may never be consistent but should be non-zero. + // Don't trust the platform to define fields with any particular + // sign. + BPF_ASSERT_NE(0u, static_cast<unsigned int>(sb_.st_dev)); + BPF_ASSERT_NE(0u, static_cast<unsigned int>(sb_.st_ino)); + BPF_ASSERT_NE(0u, static_cast<unsigned int>(sb_.st_mode)); + BPF_ASSERT_NE(0u, static_cast<unsigned int>(sb_.st_blksize)); + BPF_ASSERT_NE(0u, static_cast<unsigned int>(sb_.st_blocks)); + + // We are the ones that made the file. + BPF_ASSERT_EQ(geteuid(), sb_.st_uid); + BPF_ASSERT_EQ(getegid(), sb_.st_gid); + + // Wrote 12 bytes above which should fit in one block. + BPF_ASSERT_EQ(12, sb_.st_size); + + // Can't go backwards in time, 1500000000 was some time ago. + BPF_ASSERT_LT(1500000000u, static_cast<unsigned int>(sb_.st_atime)); + BPF_ASSERT_LT(1500000000u, static_cast<unsigned int>(sb_.st_mtime)); + BPF_ASSERT_LT(1500000000u, static_cast<unsigned int>(sb_.st_ctime)); + } +}; + +TEST(BrokerProcessIntegrationTest, StatFileWithPermissionsFollowLinks) { + RunAllBrokerTests<StatFileWithPermissionsDelegate<true>>(); +} + +TEST(BrokerProcessIntegrationTest, StatFileWithPermissionsNoFollowLinks) { + RunAllBrokerTests<StatFileWithPermissionsDelegate<false>>(); +} + +class RenameTestDelegate : public BrokerTestDelegate { + public: + void ParentSetUp() override { + { + // Just to generate names and ensure they do not exist upon scope exit. + ScopedTemporaryFile oldfile; + ScopedTemporaryFile newfile; + oldpath_ = oldfile.full_file_name(); + newpath_ = newfile.full_file_name(); + } + + // Now make a file using old path name. + int fd = open(oldpath_.c_str(), O_RDWR | O_CREAT, 0600); + EXPECT_TRUE(fd > 0); + close(fd); + + EXPECT_TRUE(access(oldpath_.c_str(), F_OK) == 0); + EXPECT_TRUE(access(newpath_.c_str(), F_OK) < 0); + } + + void ParentTearDown() override { + unlink(oldpath_.c_str()); + unlink(newpath_.c_str()); + BrokerTestDelegate::ParentTearDown(); + } + + protected: + std::string oldpath_; + std::string newpath_; +}; + +// Check rename fails with write permissions to both files but command +// itself is not allowed. +class RenameNoCommandDelegate final : public RenameTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet({}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(oldpath_), + BrokerFilePermission::ReadWriteCreate(newpath_)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Rename(oldpath_.c_str(), newpath_.c_str())); + } + + void ParentTearDown() override { + if (true) { + EXPECT_TRUE(access(oldpath_.c_str(), 0) == 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) < 0); + } else { + EXPECT_TRUE(access(oldpath_.c_str(), 0) < 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) == 0); + } + RenameTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RenameNoCommand) { + RunAllBrokerTests<RenameNoCommandDelegate>(); +} +// Check rename fails when no permission to new file. +class RenameNoPermNewDelegate final : public RenameTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RENAME}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(newpath_)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Rename(oldpath_.c_str(), newpath_.c_str())); + } + + void ParentTearDown() override { + if (true) { + EXPECT_TRUE(access(oldpath_.c_str(), 0) == 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) < 0); + } else { + EXPECT_TRUE(access(oldpath_.c_str(), 0) < 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) == 0); + } + RenameTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RenameNoPermNew) { + RunAllBrokerTests<RenameNoPermNewDelegate>(); +} +// Check rename fails when no permission to old file. +class RenameNoPermOldDelegate final : public RenameTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RENAME}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(oldpath_)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Rename(oldpath_.c_str(), newpath_.c_str())); + } + + void ParentTearDown() override { + if (true) { + EXPECT_TRUE(access(oldpath_.c_str(), 0) == 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) < 0); + } else { + EXPECT_TRUE(access(oldpath_.c_str(), 0) < 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) == 0); + } + RenameTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RenameNoPermOld) { + RunAllBrokerTests<RenameNoPermOldDelegate>(); +} + +// Check rename fails when only read permission to first file. +class RenameReadPermNewDelegate final : public RenameTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RENAME}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(oldpath_), + BrokerFilePermission::ReadOnly(newpath_)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Rename(oldpath_.c_str(), newpath_.c_str())); + } + + void ParentTearDown() override { + if (true) { + EXPECT_TRUE(access(oldpath_.c_str(), 0) == 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) < 0); + } else { + EXPECT_TRUE(access(oldpath_.c_str(), 0) < 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) == 0); + } + RenameTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RenameReadPermNew) { + RunAllBrokerTests<RenameReadPermNewDelegate>(); +} + +// Check rename fails when only read permission to second file. +class RenameReadPermOldDelegate final : public RenameTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RENAME}); + params.permissions = {BrokerFilePermission::ReadOnly(oldpath_), + BrokerFilePermission::ReadWriteCreate(newpath_)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Rename(oldpath_.c_str(), newpath_.c_str())); + } + + void ParentTearDown() override { + if (true) { + EXPECT_TRUE(access(oldpath_.c_str(), 0) == 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) < 0); + } else { + EXPECT_TRUE(access(oldpath_.c_str(), 0) < 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) == 0); + } + RenameTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RenameReadPermOld) { + RunAllBrokerTests<RenameReadPermOldDelegate>(); +} + +// Check rename passes with write permissions to both files. +class RenameWritePermsBothDelegate final : public RenameTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RENAME}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(oldpath_), + BrokerFilePermission::ReadWriteCreate(newpath_)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(0, syscaller->Rename(oldpath_.c_str(), newpath_.c_str())); + } + + void ParentTearDown() override { + if (false) { + EXPECT_TRUE(access(oldpath_.c_str(), 0) == 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) < 0); + } else { + EXPECT_TRUE(access(oldpath_.c_str(), 0) < 0); + EXPECT_TRUE(access(newpath_.c_str(), 0) == 0); + } + RenameTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RenameWritePermsBoth) { + RunAllBrokerTests<RenameWritePermsBothDelegate>(); +} + +// The base class of all the Readlink() tests. +class ReadlinkTestDelegate : public BrokerTestDelegate { + public: + void ParentSetUp() override { + { + // Just to generate names and ensure they do not exist upon scope exit. + ScopedTemporaryFile oldfile; + ScopedTemporaryFile newfile; + oldpath_ = oldfile.full_file_name(); + newpath_ = newfile.full_file_name(); + } + + // Now make a link from old to new path name. + EXPECT_TRUE(symlink(oldpath_.c_str(), newpath_.c_str()) == 0); + } + + void ParentTearDown() override { + unlink(oldpath_.c_str()); + unlink(newpath_.c_str()); + BrokerTestDelegate::ParentTearDown(); + } + + protected: + const char* const nonesuch_name = "/mbogo/nonesuch"; + + std::string oldpath_; + std::string newpath_; + + char readlink_buf_[1024]; +}; + +// Actual file with permissions to see file but command itself not allowed. +class ReadlinkNoCommandDelegate final : public ReadlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet({}); + params.permissions = {BrokerFilePermission::ReadOnly(newpath_)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override; +}; + +TEST(BrokerProcessIntegrationTest, ReadlinkNoCommand) { + RunAllBrokerTests<ReadlinkNoCommandDelegate>(); +} + +void ReadlinkNoCommandDelegate::RunTestInSandboxedChild(Syscaller* syscaller) { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Readlink(newpath_.c_str(), readlink_buf_, + sizeof(readlink_buf_))); +} + +// Nonexistent file with no permissions to see file. +class ReadlinkNonexistentNoPermissionsDelegate final + : public ReadlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_READLINK}); + params.permissions = {}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Readlink(nonesuch_name, readlink_buf_, + sizeof(readlink_buf_))); + } +}; + +TEST(BrokerProcessIntegrationTest, ReadlinkNonexistentNoPermissions) { + RunAllBrokerTests<ReadlinkNonexistentNoPermissionsDelegate>(); +} + +// Actual file with no permissions to see file. +class ReadlinkNoPermissionsDelegate final : public ReadlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_READLINK}); + params.permissions = {}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, + syscaller->Readlink(newpath_.c_str(), readlink_buf_, + sizeof(readlink_buf_))); + } +}; + +TEST(BrokerProcessIntegrationTest, ReadlinkNoPermissions) { + RunAllBrokerTests<ReadlinkNoPermissionsDelegate>(); +} + +// Nonexistent file with permissions to see file. +class ReadlinkNonexistentWithPermissionsDelegate final + : public ReadlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_READLINK}); + params.permissions = {BrokerFilePermission::ReadOnly(nonesuch_name)}; + return params; + } + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-ENOENT, syscaller->Readlink(nonesuch_name, readlink_buf_, + sizeof(readlink_buf_))); + } +}; + +TEST(BrokerProcessIntegrationTest, ReadlinkNonexistentWithPermissions) { + RunAllBrokerTests<ReadlinkNonexistentWithPermissionsDelegate>(); +} + +// Actual file with permissions to see file. +class ReadlinkFileWithPermissionsDelegate final : public ReadlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_READLINK}); + params.permissions = {BrokerFilePermission::ReadOnly(newpath_)}; + return params; + } + void RunTestInSandboxedChild(Syscaller* syscaller) override { + ssize_t retlen = syscaller->Readlink(newpath_.c_str(), readlink_buf_, + sizeof(readlink_buf_)); + BPF_ASSERT(retlen == static_cast<ssize_t>(oldpath_.length())); + BPF_ASSERT_EQ(0, memcmp(oldpath_.c_str(), readlink_buf_, retlen)); + } +}; + +TEST(BrokerProcessIntegrationTest, ReadlinkFileWithPermissions) { + RunAllBrokerTests<ReadlinkFileWithPermissionsDelegate>(); +} + +// Actual file with permissions to see file, but too small a buffer. +class ReadlinkFileWithPermissionsSmallBufferDelegate final + : public ReadlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet( + {syscall_broker::COMMAND_READLINK}); + params.permissions = {BrokerFilePermission::ReadOnly(newpath_)}; + return params; + } + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(4, syscaller->Readlink(newpath_.c_str(), readlink_buf_, 4)); + } +}; + +TEST(BrokerProcessIntegrationTest, ReadlinkFileWithPermissionsSmallBuffer) { + RunAllBrokerTests<ReadlinkFileWithPermissionsSmallBufferDelegate>(); +} + +// The base class of all the Mkdir() tests. +class MkdirTestDelegate : public BrokerTestDelegate { + public: + void ParentSetUp() override { + ScopedTemporaryFile file; + path_ = file.full_file_name(); + } + + void ParentTearDown() override { + rmdir(path_.c_str()); + BrokerTestDelegate::ParentTearDown(); + } + + protected: + const char* nonesuch_name = "/mbogo/nonesuch"; + + std::string path_; +}; + +// Actual file with permissions to use but command itself not allowed. +class MkdirNoCommandDelegate final : public MkdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet({}); + params.permissions = {BrokerFilePermission::ReadWrite(path_), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Mkdir(path_.c_str(), 0600)); + } +}; + +TEST(BrokerProcessIntegrationTest, MkdirNoCommand) { + RunAllBrokerTests<MkdirNoCommandDelegate>(); +} + +// Nonexistent file with no permissions to see file. +class MkdirNonexistentNoPermissionsDelegate final : public MkdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_MKDIR}); + params.permissions = {}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Mkdir(nonesuch_name, 0600)); + } +}; + +TEST(BrokerProcessIntegrationTest, MkdirNonexistentNoPermissions) { + RunAllBrokerTests<MkdirNonexistentNoPermissionsDelegate>(); +} + +// Actual file with no permissions to see file. +class MkdirFileNoPermissionsDelegate final : public MkdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_MKDIR}); + params.permissions = {}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Mkdir(path_.c_str(), 0600)); + } +}; + +TEST(BrokerProcessIntegrationTest, MkdirFileNoPermissions) { + RunAllBrokerTests<MkdirFileNoPermissionsDelegate>(); +} + +// Nonexistent file with insufficient permissions to see file. +class MkdirNonexistentROPermissionsDelegate final : public MkdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_MKDIR}); + params.permissions = {BrokerFilePermission::ReadOnly(path_), + BrokerFilePermission::ReadOnly(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Mkdir(nonesuch_name, 0600)); + } +}; + +TEST(BrokerProcessIntegrationTest, MkdirNonexistentROPermissions) { + RunAllBrokerTests<MkdirNonexistentROPermissionsDelegate>(); +} + +// Actual file with insufficient permissions to see file. +class MkdirFileROPermissionsDelegate final : public MkdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_MKDIR}); + params.permissions = {BrokerFilePermission::ReadOnly(path_), + BrokerFilePermission::ReadOnly(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Mkdir(path_.c_str(), 0600)); + } +}; + +TEST(BrokerProcessIntegrationTest, MkdirFileROPermissions) { + RunAllBrokerTests<MkdirFileROPermissionsDelegate>(); +} + +// Nonexistent file with insufficient permissions to see file, case 2. +class MkdirNonExistentRWPermissionsDelegate final : public MkdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_MKDIR}); + params.permissions = {BrokerFilePermission::ReadWrite(path_), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Mkdir(nonesuch_name, 0600)); + } +}; + +TEST(BrokerProcessIntegrationTest, MkdirNonExistentRWPermissions) { + RunAllBrokerTests<MkdirNonExistentRWPermissionsDelegate>(); +} + +// Actual file with insufficient permissions to see file, case 2. +class MkdirFileRWPermissionsDelegate final : public MkdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_MKDIR}); + params.permissions = {BrokerFilePermission::ReadWrite(path_), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Mkdir(path_.c_str(), 0600)); + } +}; + +TEST(BrokerProcessIntegrationTest, MkdirFileRWPermissions) { + RunAllBrokerTests<MkdirFileRWPermissionsDelegate>(); +} + +// Nonexistent file with permissions to see file. +class MkdirNonExistentRWCPermissionsDelegate final : public MkdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_MKDIR}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(path_), + BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-2, syscaller->Mkdir(nonesuch_name, 0600)); + } +}; + +TEST(BrokerProcessIntegrationTest, MkdirNonExistentRWCPermissions) { + RunAllBrokerTests<MkdirNonExistentRWCPermissionsDelegate>(); +} + +// Actual file with permissions to see file. +class MkdirFileRWCPermissionsDelegate final : public MkdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_MKDIR}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(path_), + BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(0, syscaller->Mkdir(path_.c_str(), 0600)); + } +}; + +TEST(BrokerProcessIntegrationTest, MkdirFileRWCPermissions) { + RunAllBrokerTests<MkdirFileRWCPermissionsDelegate>(); +} + +class RmdirTestDelegate : public BrokerTestDelegate { + public: + void ParentSetUp() override { + { + // Generate name and ensure it does not exist upon scope exit. + ScopedTemporaryFile file; + path_ = file.full_file_name(); + } + + const char* const path_name = path_.c_str(); + + EXPECT_EQ(0, mkdir(path_name, 0600)); + EXPECT_EQ(0, access(path_name, F_OK)); + } + + void ParentTearDown() override { + rmdir(path_.c_str()); + BrokerTestDelegate::ParentTearDown(); + } + + protected: + const char* const nonesuch_name = "/mbogo/nonesuch"; + + std::string path_; +}; + +// Actual file with permissions to use but command itself not allowed. +class RmdirNoCommandDelegate final : public RmdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet({}); + params.permissions = {BrokerFilePermission::ReadWrite(path_), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Rmdir(path_.c_str())); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + RmdirTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RmdirNoCommand) { + RunAllBrokerTests<RmdirNoCommandDelegate>(); +} + +// Nonexistent file with no permissions to see file. +class RmdirNonexistentNoPermissionsDelegate final : public RmdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RMDIR}); + params.permissions = {}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Rmdir(nonesuch_name)); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + RmdirTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RmdirNonexistentNoPermissions) { + RunAllBrokerTests<RmdirNonexistentNoPermissionsDelegate>(); +} + +// Actual file with no permissions to see file. +class RmdirFileNoPermissionsDelegate final : public RmdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RMDIR}); + params.permissions = {}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Rmdir(path_.c_str())); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + RmdirTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RmdirFileNoPermissions) { + RunAllBrokerTests<RmdirFileNoPermissionsDelegate>(); +} + +// Nonexistent file with insufficient permissions to see file. +class RmdirNonexistentROPermissionsDelegate final : public RmdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RMDIR}); + params.permissions = {BrokerFilePermission::ReadOnly(path_), + BrokerFilePermission::ReadOnly(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Rmdir(nonesuch_name)); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + RmdirTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RmdirNonexistentROPermissions) { + RunAllBrokerTests<RmdirNonexistentROPermissionsDelegate>(); +} + +// Actual file with insufficient permissions to see file. +class RmdirFileROPermissionsDelegate final : public RmdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RMDIR}); + params.permissions = {BrokerFilePermission::ReadOnly(path_), + BrokerFilePermission::ReadOnly(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Rmdir(path_.c_str())); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + RmdirTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RmdirFileROPermissions) { + RunAllBrokerTests<RmdirFileROPermissionsDelegate>(); +} + +// Nonexistent file with insufficient permissions to see file, case 2. +class RmdirNonExistentRWPermissionsDelegate final : public RmdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RMDIR}); + params.permissions = {BrokerFilePermission::ReadWrite(path_), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Rmdir(nonesuch_name)); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + RmdirTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RmdirNonExistentRWPermissions) { + RunAllBrokerTests<RmdirNonExistentRWPermissionsDelegate>(); +} + +// Actual file with insufficient permissions to see file, case 2. +class RmdirFileRWPermissionsDelegate final : public RmdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RMDIR}); + params.permissions = {BrokerFilePermission::ReadWrite(path_), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Rmdir(path_.c_str())); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + RmdirTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RmdirFileRWPermissions) { + RunAllBrokerTests<RmdirFileRWPermissionsDelegate>(); +} + +// Nonexistent file with permissions to see file. +class RmdirNonExistentRWCPermissionsDelegate final : public RmdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RMDIR}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(path_), + BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-2, syscaller->Rmdir(nonesuch_name)); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + RmdirTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RmdirNonExistentRWCPermissions) { + RunAllBrokerTests<RmdirNonExistentRWCPermissionsDelegate>(); +} + +// Actual file with permissions to see file. +class RmdirFileRWCPermissionsDelegate final : public RmdirTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_RMDIR}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(path_), + BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(0, syscaller->Rmdir(path_.c_str())); + } + + void ParentTearDown() override { + EXPECT_EQ(-1, access(path_.c_str(), 0)); + RmdirTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, RmdirFileRWCPermissions) { + RunAllBrokerTests<RmdirFileRWCPermissionsDelegate>(); +} + +// The base class for all the Unlink() tests. +class UnlinkTestDelegate : public BrokerTestDelegate { + public: + void ParentSetUp() override { + { + // Generate name and ensure it does not exist upon scope exit. + ScopedTemporaryFile file; + path_ = file.full_file_name(); + } + + int fd = open(path_.c_str(), O_RDWR | O_CREAT, 0600); + EXPECT_TRUE(fd >= 0); + close(fd); + EXPECT_EQ(0, access(path_.c_str(), F_OK)); + } + + void ParentTearDown() override { + unlink(path_.c_str()); + BrokerTestDelegate::ParentTearDown(); + } + + protected: + const char* const nonesuch_name = "/mbogo/nonesuch"; + + std::string path_; +}; + +// Actual file with permissions to use but command itself not allowed. +class UnlinkNoCommandDelegate final : public UnlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = syscall_broker::MakeBrokerCommandSet({}); + params.permissions = {BrokerFilePermission::ReadWrite(path_), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Unlink(path_.c_str())); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + UnlinkTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, UnlinkNoCommand) { + RunAllBrokerTests<UnlinkNoCommandDelegate>(); +} + +// Nonexistent file with no permissions to see file. +class UnlinkNonexistentNoPermissionsDelegate final : public UnlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_UNLINK}); + params.permissions = {}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Unlink(nonesuch_name)); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + UnlinkTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, UnlinkNonexistentNoPermissions) { + RunAllBrokerTests<UnlinkNonexistentNoPermissionsDelegate>(); +} + +// Actual file with no permissions to see file. +class UnlinkFileNoPermissionsDelegate final : public UnlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_UNLINK}); + params.permissions = {}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Unlink(path_.c_str())); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + UnlinkTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, UnlinkFileNoPermissions) { + RunAllBrokerTests<UnlinkFileNoPermissionsDelegate>(); +} + +// Nonexistent file with insufficient permissions to see file. +class UnlinkNonexistentROPermissionsDelegate final : public UnlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_UNLINK}); + params.permissions = {BrokerFilePermission::ReadOnly(path_), + BrokerFilePermission::ReadOnly(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Unlink(nonesuch_name)); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + UnlinkTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, UnlinkNonexistentROPermissions) { + RunAllBrokerTests<UnlinkNonexistentROPermissionsDelegate>(); +} + +// Actual file with insufficient permissions to see file. +class UnlinkFileROPermissionsDelegate final : public UnlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_UNLINK}); + params.permissions = {BrokerFilePermission::ReadOnly(path_), + BrokerFilePermission::ReadOnly(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Unlink(path_.c_str())); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + UnlinkTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, UnlinkFileROPermissions) { + RunAllBrokerTests<UnlinkFileROPermissionsDelegate>(); +} + +// Nonexistent file with insufficient permissions to see file, case 2. +class UnlinkNonExistentRWPermissionsDelegate final : public UnlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_UNLINK}); + params.permissions = {BrokerFilePermission::ReadWrite(path_), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Unlink(nonesuch_name)); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + UnlinkTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, UnlinkNonExistentRWPermissions) { + RunAllBrokerTests<UnlinkNonExistentRWPermissionsDelegate>(); +} + +// Actual file with insufficient permissions to see file, case 2. +class UnlinkFileRWPermissionsDelegate final : public UnlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_UNLINK}); + params.permissions = {BrokerFilePermission::ReadWrite(path_), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-kFakeErrnoSentinel, syscaller->Unlink(path_.c_str())); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + UnlinkTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, UnlinkFileRWPermissions) { + RunAllBrokerTests<UnlinkFileRWPermissionsDelegate>(); +} + +// Nonexistent file with permissions to see file. +class UnlinkNonExistentRWCPermissionsDelegate final + : public UnlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_UNLINK}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(path_), + BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(-2, syscaller->Unlink(nonesuch_name)); + } + + void ParentTearDown() override { + EXPECT_EQ(0, access(path_.c_str(), 0)); + UnlinkTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, UnlinkNonExistentRWCPermissions) { + RunAllBrokerTests<UnlinkNonExistentRWCPermissionsDelegate>(); +} + +// Actual file with permissions to see file. +class UnlinkFileRWCPermissionsDelegate final : public UnlinkTestDelegate { + public: + BrokerParams ChildSetUpPreSandbox() override { + BrokerParams params; + params.allowed_command_set = + syscall_broker::MakeBrokerCommandSet({syscall_broker::COMMAND_UNLINK}); + params.permissions = {BrokerFilePermission::ReadWriteCreate(path_), + BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + return params; + } + + void RunTestInSandboxedChild(Syscaller* syscaller) override { + BPF_ASSERT_EQ(0, syscaller->Unlink(path_.c_str())); + } + + void ParentTearDown() override { + EXPECT_EQ(-1, access(path_.c_str(), 0)); + UnlinkTestDelegate::ParentTearDown(); + } +}; + +TEST(BrokerProcessIntegrationTest, UnlinkFileRWCPermissions) { + RunAllBrokerTests<UnlinkFileRWCPermissionsDelegate>(); +} + +#endif // !defined(THREAD_SANITIZER) } // namespace sandbox diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc index 222158c643c..35ab90e3f3c 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc @@ -112,9 +112,11 @@ ResultExpr BaselinePolicyAndroid::EvaluateSyscall(int sysno) const { case __NR_openat: case __NR_pwrite64: case __NR_rt_sigtimedwait: - // sched_setaffinity() is required for an experiment to schedule all - // Chromium threads onto LITTLE cores (crbug.com/1111789). Should be removed - // or reconsidered once the experiment is complete. + // sched_getaffinity() and sched_setaffinity() are required for an + // experiment to schedule all Chromium threads onto LITTLE cores + // (crbug.com/1111789). Should be removed or reconsidered once + // the experiment is complete. + case __NR_sched_getaffinity: case __NR_sched_setaffinity: case __NR_sched_getparam: case __NR_sched_getscheduler: diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc index b968c04aa34..76eb32493f5 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc @@ -21,6 +21,7 @@ #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_seccomp.h" #include "sandbox/linux/system_headers/linux_syscalls.h" #if defined(__mips__) @@ -147,7 +148,7 @@ class NumberToHex { // Records the syscall number and first four arguments in a crash key, to help // debug the failure. -void SetSeccompCrashKey(const struct sandbox::arch_seccomp_data& args) { +void SetSeccompCrashKey(const struct arch_seccomp_data& args) { #if !defined(OS_NACL_NONSFI) NumberToHex<int> nr(args.nr); NumberToHex<uint64_t> arg1(args.args[0]); diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h index baac3b61447..7a958b93b27 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h @@ -15,43 +15,43 @@ // guaranteed to be async-signal safe. // See sandbox/linux/seccomp-bpf/trap.h to see how they work. -namespace sandbox { - struct arch_seccomp_data; +namespace sandbox { + // 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); +SANDBOX_EXPORT intptr_t CrashSIGSYS_Handler(const arch_seccomp_data& args, + void* aux); // The following seven handlers are suitable to report failures for specific // system calls with additional information. // 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); +SANDBOX_EXPORT intptr_t SIGSYSCloneFailure(const 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); +SANDBOX_EXPORT intptr_t SIGSYSPrctlFailure(const 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); +SANDBOX_EXPORT intptr_t SIGSYSIoctlFailure(const 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); +SANDBOX_EXPORT intptr_t SIGSYSKillFailure(const 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); +SANDBOX_EXPORT intptr_t SIGSYSFutexFailure(const arch_seccomp_data& args, + void* aux); // The crashing address will be (op & 0xFFF), where op is the second // argument. -SANDBOX_EXPORT intptr_t -SIGSYSPtraceFailure(const struct arch_seccomp_data& args, void* aux); +SANDBOX_EXPORT intptr_t SIGSYSPtraceFailure(const 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 @@ -60,8 +60,8 @@ SIGSYSPtraceFailure(const struct arch_seccomp_data& args, void* aux); // 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); +SANDBOX_EXPORT intptr_t SIGSYSSchedHandler(const arch_seccomp_data& args, + void* aux); // Variants of the above functions for use with bpf_dsl. SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYS(); diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc index f51915edc36..a1002483d78 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc @@ -28,6 +28,7 @@ #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_prctl.h" #include "sandbox/linux/system_headers/linux_syscalls.h" #include "sandbox/linux/system_headers/linux_time.h" @@ -51,19 +52,6 @@ #define F_DUPFD_CLOEXEC (F_LINUX_SPECIFIC_BASE + 6) #endif -#if !defined(PR_SET_TIMERSLACK) -#define PR_SET_TIMERSLACK 29 -#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 - -#ifndef PR_SET_PTRACER -#define PR_SET_PTRACER 0x59616d61 -#endif - #endif // defined(OS_ANDROID) #if defined(__arm__) && !defined(MAP_STACK) diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc index 85bbe65748a..3d7bc172f79 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -145,7 +145,7 @@ bool SandboxBPF::SupportsSeccompSandbox(SeccompLevel level) { return false; } -bool SandboxBPF::StartSandbox(SeccompLevel seccomp_level, bool enable_ibpb) { +bool SandboxBPF::StartSandbox(SeccompLevel seccomp_level) { DCHECK(policy_); CHECK(seccomp_level == SeccompLevel::SINGLE_THREADED || seccomp_level == SeccompLevel::MULTI_THREADED); @@ -183,7 +183,7 @@ bool SandboxBPF::StartSandbox(SeccompLevel seccomp_level, bool enable_ibpb) { } // Install the filters. - InstallFilter(seccomp_level == SeccompLevel::MULTI_THREADED, enable_ibpb); + InstallFilter(seccomp_level == SeccompLevel::MULTI_THREADED); return true; } @@ -222,7 +222,7 @@ CodeGen::Program SandboxBPF::AssembleFilter() { return compiler.Compile(); } -void SandboxBPF::InstallFilter(bool must_sync_threads, bool enable_ibpb) { +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 @@ -267,9 +267,7 @@ void SandboxBPF::InstallFilter(bool must_sync_threads, bool enable_ibpb) { // opt-out SSBD when process is single-threaded and tsync is not necessary. } else if (KernelSupportSeccompSpecAllow()) { seccomp_filter_flags |= SECCOMP_FILTER_FLAG_SPEC_ALLOW; - if (enable_ibpb) { - DisableIBSpec(); - } + DisableIBSpec(); #endif } else { if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h index 3b3d7ba7e6a..eb64d978859 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -15,9 +15,10 @@ #include "sandbox/linux/bpf_dsl/policy.h" #include "sandbox/sandbox_export.h" -namespace sandbox { struct arch_seccomp_data; +namespace sandbox { + // 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, @@ -61,11 +62,7 @@ class SANDBOX_EXPORT SandboxBPF { // disallowed. // Finally, stacking does add more kernel overhead than having a single // combined policy. So, it should only be used if there are no alternatives. - // - // |enable_ibpb| controls if the sandbox will forcibly enable indirect branch - // prediction barrier through prctl(2) to mitigate Spectre variant 2. - bool StartSandbox(SeccompLevel level, - bool enable_ibpb = true) WARN_UNUSED_RESULT; + 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 @@ -103,7 +100,7 @@ class SANDBOX_EXPORT SandboxBPF { // Assembles and installs a filter based on the policy that has previously // been configured with SetSandboxPolicy(). - void InstallFilter(bool must_sync_threads, bool enable_ibpb); + void InstallFilter(bool must_sync_threads); // Disable indirect branch speculation by prctl. This will be done by // seccomp if SECCOMP_FILTER_FLAG_SPEC_ALLOW is not set. Seccomp will diff --git a/chromium/sandbox/linux/services/yama.cc b/chromium/sandbox/linux/services/yama.cc index e67671249fe..a2b33f2b381 100644 --- a/chromium/sandbox/linux/services/yama.cc +++ b/chromium/sandbox/linux/services/yama.cc @@ -17,14 +17,7 @@ #include "base/files/scoped_file.h" #include "base/notreached.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 +#include "sandbox/linux/system_headers/linux_prctl.h" namespace sandbox { diff --git a/chromium/sandbox/linux/syscall_broker/broker_client.cc b/chromium/sandbox/linux/syscall_broker/broker_client.cc index fb897eb6ff8..6b1b5be4338 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_client.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_client.cc @@ -93,7 +93,7 @@ int BrokerClient::Readlink(const char* path, char* buf, size_t bufsize) const { RAW_CHECK(message.AddIntToMessage(COMMAND_READLINK)); RAW_CHECK(message.AddStringToMessage(path)); - int returned_fd = -1; + base::ScopedFD returned_fd; BrokerSimpleMessage reply; ssize_t msg_len = message.SendRecvMsgWithFlags(ipc_channel_.get(), 0, &returned_fd, &reply); @@ -112,11 +112,14 @@ int BrokerClient::Readlink(const char* path, char* buf, size_t bufsize) const { return -ENOMEM; if (return_length < 0) return -ENOMEM; + // Sanity check that our broker is behaving correctly. + RAW_CHECK(return_length == static_cast<size_t>(return_value)); - if (static_cast<size_t>(return_length) > bufsize) - return -ENAMETOOLONG; + if (return_length > bufsize) { + return_length = bufsize; + } memcpy(buf, return_data, return_length); - return return_value; + return return_length; } int BrokerClient::Rename(const char* oldpath, const char* newpath) const { @@ -134,7 +137,7 @@ int BrokerClient::Rename(const char* oldpath, const char* newpath) const { RAW_CHECK(message.AddStringToMessage(oldpath)); RAW_CHECK(message.AddStringToMessage(newpath)); - int returned_fd = -1; + base::ScopedFD returned_fd; BrokerSimpleMessage reply; ssize_t msg_len = message.SendRecvMsgWithFlags(ipc_channel_.get(), 0, &returned_fd, &reply); @@ -209,7 +212,7 @@ int BrokerClient::PathOnlySyscall(BrokerCommand syscall_type, RAW_CHECK(message.AddIntToMessage(syscall_type)); RAW_CHECK(message.AddStringToMessage(pathname)); - int returned_fd = -1; + base::ScopedFD returned_fd; BrokerSimpleMessage reply; ssize_t msg_len = message.SendRecvMsgWithFlags(ipc_channel_.get(), 0, &returned_fd, &reply); @@ -236,7 +239,7 @@ int BrokerClient::PathAndFlagsSyscall(BrokerCommand syscall_type, RAW_CHECK(message.AddStringToMessage(pathname)); RAW_CHECK(message.AddIntToMessage(flags)); - int returned_fd = -1; + base::ScopedFD returned_fd; BrokerSimpleMessage reply; ssize_t msg_len = message.SendRecvMsgWithFlags(ipc_channel_.get(), 0, &returned_fd, &reply); @@ -277,7 +280,7 @@ int BrokerClient::PathAndFlagsSyscallReturningFD(BrokerCommand syscall_type, RAW_CHECK(message.AddStringToMessage(pathname)); RAW_CHECK(message.AddIntToMessage(flags)); - int returned_fd = -1; + base::ScopedFD returned_fd; BrokerSimpleMessage reply; ssize_t msg_len = message.SendRecvMsgWithFlags( ipc_channel_.get(), recvmsg_flags, &returned_fd, &reply); @@ -292,8 +295,8 @@ int BrokerClient::PathAndFlagsSyscallReturningFD(BrokerCommand syscall_type, return return_value; // We have a real file descriptor to return. - RAW_CHECK(returned_fd >= 0); - return returned_fd; + RAW_CHECK(returned_fd.is_valid()); + return returned_fd.release(); } // Make a remote system call over IPC for syscalls that take a path @@ -310,7 +313,7 @@ int BrokerClient::StatFamilySyscall(BrokerCommand syscall_type, RAW_CHECK(message.AddStringToMessage(pathname)); RAW_CHECK(message.AddIntToMessage(static_cast<int>(follow_links))); - int returned_fd = -1; + base::ScopedFD returned_fd; BrokerSimpleMessage reply; ssize_t msg_len = message.SendRecvMsgWithFlags(ipc_channel_.get(), 0, &returned_fd, &reply); @@ -334,5 +337,13 @@ int BrokerClient::StatFamilySyscall(BrokerCommand syscall_type, return return_value; } +// static +intptr_t BrokerClient::SIGSYS_Handler(const arch_seccomp_data& args, + void* aux_broker_client) { + RAW_CHECK(aux_broker_client); + auto* broker_client = static_cast<BrokerClient*>(aux_broker_client); + return broker_client->DispatchSyscall(args); +} + } // namespace syscall_broker } // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/broker_client.h b/chromium/sandbox/linux/syscall_broker/broker_client.h index 842ee16a0c7..05e14c83f20 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_client.h +++ b/chromium/sandbox/linux/syscall_broker/broker_client.h @@ -12,6 +12,8 @@ #include "base/macros.h" #include "sandbox/linux/syscall_broker/broker_channel.h" #include "sandbox/linux/syscall_broker/broker_command.h" +#include "sandbox/linux/syscall_broker/syscall_dispatcher.h" +#include "sandbox/sandbox_export.h" namespace sandbox { namespace syscall_broker { @@ -25,8 +27,13 @@ class BrokerPermissionList; // 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 { +class SANDBOX_EXPORT BrokerClient : public SyscallDispatcher { public: + // Handler to be used with a bpf_dsl Trap() function to forward system calls + // to the methods below. + static intptr_t SIGSYS_Handler(const arch_seccomp_data& args, + void* aux_broker_process); + // |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. @@ -36,43 +43,29 @@ class BrokerClient { BrokerChannel::EndPoint ipc_channel, const BrokerCommandSet& allowed_command_set, bool fast_check_in_client); - ~BrokerClient(); + ~BrokerClient() override; - // Get the file descriptor used for IPC. This is used for tests. - int GetIPCDescriptor() const { return ipc_channel_.get(); } + // Get the file descriptor used for IPC. + int GetIPCDescriptorForTesting() const { return ipc_channel_.get(); } // The following public methods can be used in place of the equivalently // name system calls. They all return -errno on errors. They are all async // signal safe so they may be called from a SIGSYS trap handler. - // 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. - int Access(const char* pathname, int mode) const; - - // Can be used in place of mkdir(). - int Mkdir(const char* path, 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. - int Open(const char* pathname, int flags) const; - - // Can be used in place of Readlink(). - int Readlink(const char* path, char* buf, size_t bufsize) const; - - // Can be used in place of rename(). - int Rename(const char* oldpath, const char* newpath) const; - - // Can be used in place of rmdir(). - int Rmdir(const char* path) const; - - // Can be used in place of stat()/stat64()/lstat()/lstat64() - int Stat(const char* pathname, bool follow_links, struct stat* sb) const; - int Stat64(const char* pathname, bool folllow_links, struct stat64* sb) const; - - // Can be used in place of unlink(). - int Unlink(const char* unlink) const; + // SyscallDispatcher implementation: + int Access(const char* pathname, int mode) const override; + int Mkdir(const char* path, int mode) const override; + int Open(const char* pathname, int flags) const override; + int Readlink(const char* path, char* buf, size_t bufsize) const override; + int Rename(const char* oldpath, const char* newpath) const override; + int Rmdir(const char* path) const override; + int Stat(const char* pathname, + bool follow_links, + struct stat* sb) const override; + int Stat64(const char* pathname, + bool follow_links, + struct stat64* sb) const override; + int Unlink(const char* unlink) const override; private: int PathOnlySyscall(BrokerCommand syscall_type, const char* pathname) const; diff --git a/chromium/sandbox/linux/syscall_broker/broker_host.cc b/chromium/sandbox/linux/syscall_broker/broker_host.cc index 631c90b350c..1cd03a18df8 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_host.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_host.cc @@ -96,7 +96,7 @@ void OpenFileForIPC(const BrokerCommandSet& allowed_command_set, const std::string& requested_filename, int flags, BrokerSimpleMessage* reply, - int* opened_file) { + base::ScopedFD* opened_file) { const char* file_to_open = NULL; bool unlink_after_open = false; if (!CommandOpenIsSafe(allowed_command_set, permission_list, @@ -107,8 +107,8 @@ void OpenFileForIPC(const BrokerCommandSet& allowed_command_set, } CHECK(file_to_open); - int opened_fd = sys_open(file_to_open, flags); - if (opened_fd < 0) { + opened_file->reset(sys_open(file_to_open, flags)); + if (!opened_file->is_valid()) { RAW_CHECK(reply->AddIntToMessage(-errno)); return; } @@ -116,7 +116,6 @@ void OpenFileForIPC(const BrokerCommandSet& allowed_command_set, if (unlink_after_open) unlink(file_to_open); - *opened_file = opened_fd; RAW_CHECK(reply->AddIntToMessage(0)); } @@ -207,12 +206,6 @@ void StatFileForIPC(const BrokerCommandSet& allowed_command_set, reply->AddDataToMessage(reinterpret_cast<char*>(&sb), sizeof(sb))); } else { DCHECK(command_type == COMMAND_STAT64); -#if defined(__ANDROID_API__) && __ANDROID_API__ < 21 - // stat64 is not defined for older Android API versions in newer NDK - // versions. - RAW_CHECK(reply->AddIntToMessage(-ENOSYS)); - return; -#else struct stat64 sb; int sts = follow_links ? stat64(file_to_access, &sb) : lstat64(file_to_access, &sb); @@ -223,7 +216,6 @@ void StatFileForIPC(const BrokerCommandSet& allowed_command_set, RAW_CHECK(reply->AddIntToMessage(0)); RAW_CHECK( reply->AddDataToMessage(reinterpret_cast<char*>(&sb), sizeof(sb))); -#endif // defined(__ANDROID_API__) && __ANDROID_API__ < 21 } } @@ -250,7 +242,7 @@ bool HandleRemoteCommand(const BrokerCommandSet& allowed_command_set, const BrokerPermissionList& permission_list, BrokerSimpleMessage* message, BrokerSimpleMessage* reply, - int* opened_file) { + base::ScopedFD* opened_file) { // Message structure: // int: command type // char[]: pathname @@ -353,47 +345,45 @@ BrokerHost::BrokerHost(const BrokerPermissionList& broker_permission_list, allowed_command_set_(allowed_command_set), ipc_channel_(std::move(ipc_channel)) {} -BrokerHost::~BrokerHost() {} +BrokerHost::~BrokerHost() = default; // 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 { - BrokerSimpleMessage message; - errno = 0; - base::ScopedFD temporary_ipc; - const ssize_t msg_len = - message.RecvMsgWithFlags(ipc_channel_.get(), 0, &temporary_ipc); - - 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 sends exactly one file descriptor, on which we - // will write the reply. - if (msg_len < 0) { - PLOG(ERROR) << "Error reading message from the client"; - return RequestStatus::FAILURE; - } +void BrokerHost::LoopAndHandleRequests() { + for (;;) { + BrokerSimpleMessage message; + errno = 0; + base::ScopedFD temporary_ipc; + const ssize_t msg_len = + message.RecvMsgWithFlags(ipc_channel_.get(), 0, &temporary_ipc); + + if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) { + // EOF from the client, or the client died, we should finish looping. + return; + } - BrokerSimpleMessage reply; - int opened_file = -1; - bool result = - HandleRemoteCommand(allowed_command_set_, broker_permission_list_, - &message, &reply, &opened_file); + // The client sends exactly one file descriptor, on which we + // will write the reply. + if (msg_len < 0) { + PLOG(ERROR) << "Error reading message from the client"; + continue; + } - ssize_t sent = reply.SendMsg(temporary_ipc.get(), opened_file); - if (sent < 0) - LOG(ERROR) << "sent failed"; + BrokerSimpleMessage reply; + base::ScopedFD opened_file; + if (!HandleRemoteCommand(allowed_command_set_, broker_permission_list_, + &message, &reply, &opened_file)) { + // Does not exit if we received a malformed message. + LOG(ERROR) << "Received malformed message from the client"; + continue; + } - if (opened_file >= 0) { - int ret = IGNORE_EINTR(close(opened_file)); - DCHECK(!ret) << "Could not close file descriptor"; + ssize_t sent = reply.SendMsg(temporary_ipc.get(), opened_file.get()); + if (sent < 0) + LOG(ERROR) << "sent failed"; } - - return result ? RequestStatus::SUCCESS : RequestStatus::FAILURE; } } // namespace syscall_broker diff --git a/chromium/sandbox/linux/syscall_broker/broker_host.h b/chromium/sandbox/linux/syscall_broker/broker_host.h index b54e32d3cf7..4b0f3e32587 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_host.h +++ b/chromium/sandbox/linux/syscall_broker/broker_host.h @@ -20,14 +20,13 @@ class BrokerPermissionList; // |ipc_channel| according to |broker_permission_list|. class BrokerHost { public: - enum class RequestStatus { LOST_CLIENT = 0, SUCCESS, FAILURE }; - BrokerHost(const BrokerPermissionList& broker_permission_list, const BrokerCommandSet& allowed_command_set, BrokerChannel::EndPoint ipc_channel); ~BrokerHost(); - RequestStatus HandleRequest() const; + // Receive system call requests and handle them forevermore. + void LoopAndHandleRequests(); private: const BrokerPermissionList& broker_permission_list_; diff --git a/chromium/sandbox/linux/syscall_broker/broker_process.cc b/chromium/sandbox/linux/syscall_broker/broker_process.cc index 8321d23798d..d72c9d2388f 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_process.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_process.cc @@ -34,10 +34,12 @@ BrokerProcess::BrokerProcess( int denied_errno, const syscall_broker::BrokerCommandSet& allowed_command_set, const std::vector<syscall_broker::BrokerFilePermission>& permissions, + BrokerType broker_type, bool fast_check_in_client, bool quiet_failures_for_tests) : initialized_(false), broker_pid_(-1), + broker_type_(broker_type), fast_check_in_client_(fast_check_in_client), quiet_failures_for_tests_(quiet_failures_for_tests), allowed_command_set_(allowed_command_set), @@ -58,16 +60,12 @@ BrokerProcess::~BrokerProcess() { } } -bool BrokerProcess::Init( +bool BrokerProcess::ForkSignalBasedBroker( base::OnceCallback<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; @@ -80,9 +78,11 @@ bool BrokerProcess::Init( // We are the parent and we have just forked our broker process. ipc_reader.reset(); broker_pid_ = child_pid; + broker_client_ = std::make_unique<BrokerClient>( broker_permission_list_, std::move(ipc_writer), allowed_command_set_, fast_check_in_client_); + initialized_ = true; return true; } @@ -95,59 +95,68 @@ bool BrokerProcess::Init( // 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(std::move(broker_process_init_callback).Run()); - BrokerHost broker_host(broker_permission_list_, allowed_command_set_, - std::move(ipc_reader)); - for (;;) { - switch (broker_host.HandleRequest()) { - case BrokerHost::RequestStatus::LOST_CLIENT: - _exit(1); - case BrokerHost::RequestStatus::SUCCESS: - case BrokerHost::RequestStatus::FAILURE: - continue; - } - } + + BrokerHost broker_host_signal_based( + broker_permission_list_, allowed_command_set_, std::move(ipc_reader)); + broker_host_signal_based.LoopAndHandleRequests(); + _exit(1); + NOTREACHED(); + return false; +} + +bool BrokerProcess::Init( + base::OnceCallback<bool(void)> broker_process_init_callback) { + CHECK(!initialized_); + +#if !defined(THREAD_SANITIZER) + DCHECK_EQ(1, base::GetNumberOfThreads(base::GetCurrentProcessHandle())); +#endif + + return ForkSignalBasedBroker(std::move(broker_process_init_callback)); } bool BrokerProcess::IsSyscallAllowed(int sysno) const { + return IsSyscallBrokerable(sysno, fast_check_in_client_); +} + +bool BrokerProcess::IsSyscallBrokerable(int sysno, bool fast_check) const { switch (sysno) { #if !defined(__aarch64__) case __NR_access: #endif case __NR_faccessat: - return !fast_check_in_client_ || - allowed_command_set_.test(COMMAND_ACCESS); + return !fast_check || allowed_command_set_.test(COMMAND_ACCESS); #if !defined(__aarch64__) case __NR_mkdir: #endif case __NR_mkdirat: - return !fast_check_in_client_ || allowed_command_set_.test(COMMAND_MKDIR); + return !fast_check || allowed_command_set_.test(COMMAND_MKDIR); #if !defined(__aarch64__) case __NR_open: #endif case __NR_openat: - return !fast_check_in_client_ || allowed_command_set_.test(COMMAND_OPEN); + return !fast_check || allowed_command_set_.test(COMMAND_OPEN); #if !defined(__aarch64__) case __NR_readlink: #endif case __NR_readlinkat: - return !fast_check_in_client_ || - allowed_command_set_.test(COMMAND_READLINK); + return !fast_check || allowed_command_set_.test(COMMAND_READLINK); #if !defined(__aarch64__) case __NR_rename: #endif case __NR_renameat: case __NR_renameat2: - return !fast_check_in_client_ || - allowed_command_set_.test(COMMAND_RENAME); + return !fast_check || allowed_command_set_.test(COMMAND_RENAME); #if !defined(__aarch64__) case __NR_rmdir: - return !fast_check_in_client_ || allowed_command_set_.test(COMMAND_RMDIR); + return !fast_check || allowed_command_set_.test(COMMAND_RMDIR); #endif #if !defined(__aarch64__) @@ -157,10 +166,13 @@ bool BrokerProcess::IsSyscallAllowed(int sysno) const { #if defined(__NR_fstatat) case __NR_fstatat: #endif +#if defined(__NR_fstatat64) + case __NR_fstatat64: +#endif #if defined(__x86_64__) || defined(__aarch64__) case __NR_newfstatat: #endif - return !fast_check_in_client_ || allowed_command_set_.test(COMMAND_STAT); + return !fast_check || allowed_command_set_.test(COMMAND_STAT); #if defined(__i386__) || defined(__arm__) || \ (defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_32_BITS)) @@ -169,14 +181,16 @@ bool BrokerProcess::IsSyscallAllowed(int sysno) const { // For security purposes, map stat64 to COMMAND_STAT permission. The // separate COMMAND_STAT64 only exists to broker different-sized // argument structs. - return !fast_check_in_client_ || allowed_command_set_.test(COMMAND_STAT); + return !fast_check || allowed_command_set_.test(COMMAND_STAT); #endif #if !defined(__aarch64__) case __NR_unlink: + return !fast_check || allowed_command_set_.test(COMMAND_UNLINK); #endif case __NR_unlinkat: - return !fast_check_in_client_ || + // If rmdir() doesn't exist, unlinkat is used with AT_REMOVEDIR. + return !fast_check || allowed_command_set_.test(COMMAND_RMDIR) || allowed_command_set_.test(COMMAND_UNLINK); default: @@ -188,221 +202,5 @@ 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::Mkdir(const char* path, int mode) const { - RAW_CHECK(initialized_); - return broker_client_->Mkdir(path, mode); -} - -int BrokerProcess::Open(const char* pathname, int flags) const { - RAW_CHECK(initialized_); - return broker_client_->Open(pathname, flags); -} - -int BrokerProcess::Readlink(const char* path, char* buf, size_t bufsize) const { - RAW_CHECK(initialized_); - return broker_client_->Readlink(path, buf, bufsize); -} - -int BrokerProcess::Rename(const char* oldpath, const char* newpath) const { - RAW_CHECK(initialized_); - return broker_client_->Rename(oldpath, newpath); -} - -int BrokerProcess::Rmdir(const char* pathname) const { - RAW_CHECK(initialized_); - return broker_client_->Rmdir(pathname); -} - -int BrokerProcess::Stat(const char* pathname, - bool follow_links, - struct stat* sb) const { - RAW_CHECK(initialized_); - return broker_client_->Stat(pathname, follow_links, sb); -} - -int BrokerProcess::Stat64(const char* pathname, - bool follow_links, - struct stat64* sb) const { - RAW_CHECK(initialized_); - return broker_client_->Stat64(pathname, follow_links, sb); -} - -int BrokerProcess::Unlink(const char* pathname) const { - RAW_CHECK(initialized_); - return broker_client_->Unlink(pathname); -} - -#if defined(MEMORY_SANITIZER) -#define BROKER_UNPOISON_STRING(x) __msan_unpoison_string(x) -#else -#define BROKER_UNPOISON_STRING(x) -#endif - -// static -intptr_t BrokerProcess::SIGSYS_Handler(const sandbox::arch_seccomp_data& args, - void* aux_broker_process) { - RAW_CHECK(aux_broker_process); - auto* broker_process = static_cast<BrokerProcess*>(aux_broker_process); - switch (args.nr) { -#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_faccessat) - case __NR_faccessat: - if (static_cast<int>(args.args[0]) != AT_FDCWD) - return -EPERM; - return broker_process->Access(reinterpret_cast<const char*>(args.args[1]), - static_cast<int>(args.args[2])); -#endif -#if defined(__NR_mkdir) - case __NR_mkdir: - return broker_process->Mkdir(reinterpret_cast<const char*>(args.args[0]), - static_cast<int>(args.args[1])); -#endif -#if defined(__NR_mkdirat) - case __NR_mkdirat: - if (static_cast<int>(args.args[0]) != AT_FDCWD) - return -EPERM; - return broker_process->Mkdir(reinterpret_cast<const char*>(args.args[1]), - static_cast<int>(args.args[2])); -#endif -#if defined(__NR_open) - case __NR_open: - // http://crbug.com/372840 - BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[0])); - return broker_process->Open(reinterpret_cast<const char*>(args.args[0]), - static_cast<int>(args.args[1])); -#endif -#if defined(__NR_openat) - case __NR_openat: - if (static_cast<int>(args.args[0]) != AT_FDCWD) - return -EPERM; - // http://crbug.com/372840 - BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[1])); - return broker_process->Open(reinterpret_cast<const char*>(args.args[1]), - static_cast<int>(args.args[2])); -#endif -#if defined(__NR_readlink) - case __NR_readlink: - return broker_process->Readlink( - reinterpret_cast<const char*>(args.args[0]), - reinterpret_cast<char*>(args.args[1]), - static_cast<size_t>(args.args[2])); -#endif -#if defined(__NR_readlinkat) - case __NR_readlinkat: - if (static_cast<int>(args.args[0]) != AT_FDCWD) - return -EPERM; - return broker_process->Readlink( - reinterpret_cast<const char*>(args.args[1]), - reinterpret_cast<char*>(args.args[2]), - static_cast<size_t>(args.args[3])); -#endif -#if defined(__NR_rename) - case __NR_rename: - return broker_process->Rename( - reinterpret_cast<const char*>(args.args[0]), - reinterpret_cast<const char*>(args.args[1])); -#endif -#if defined(__NR_renameat) - case __NR_renameat: - if (static_cast<int>(args.args[0]) != AT_FDCWD || - static_cast<int>(args.args[2]) != AT_FDCWD) { - return -EPERM; - } - return broker_process->Rename( - reinterpret_cast<const char*>(args.args[1]), - reinterpret_cast<const char*>(args.args[3])); -#endif -#if defined(__NR_renameat2) - case __NR_renameat2: - if (static_cast<int>(args.args[0]) != AT_FDCWD || - static_cast<int>(args.args[2]) != AT_FDCWD) { - return -EPERM; - } - if (static_cast<int>(args.args[4]) != 0) - return -EINVAL; - return broker_process->Rename( - reinterpret_cast<const char*>(args.args[1]), - reinterpret_cast<const char*>(args.args[3])); -#endif -#if defined(__NR_rmdir) - case __NR_rmdir: - return broker_process->Rmdir(reinterpret_cast<const char*>(args.args[0])); -#endif -#if defined(__NR_stat) - case __NR_stat: - return broker_process->Stat(reinterpret_cast<const char*>(args.args[0]), - true, - reinterpret_cast<struct stat*>(args.args[1])); -#endif -#if defined(__NR_stat64) - case __NR_stat64: - return broker_process->Stat64( - reinterpret_cast<const char*>(args.args[0]), true, - reinterpret_cast<struct stat64*>(args.args[1])); -#endif -#if defined(__NR_lstat) - case __NR_lstat: - // See https://crbug.com/847096 - BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[0])); - return broker_process->Stat(reinterpret_cast<const char*>(args.args[0]), - false, - reinterpret_cast<struct stat*>(args.args[1])); -#endif -#if defined(__NR_lstat64) - case __NR_lstat64: - // See https://crbug.com/847096 - BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[0])); - return broker_process->Stat64( - reinterpret_cast<const char*>(args.args[0]), false, - reinterpret_cast<struct stat64*>(args.args[1])); -#endif -#if defined(__NR_fstatat) - case __NR_fstatat: - if (static_cast<int>(args.args[0]) != AT_FDCWD) - return -EPERM; - if (static_cast<int>(args.args[3]) != 0) - return -EINVAL; - return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]), - true, - reinterpret_cast<struct stat*>(args.args[2])); -#endif -#if defined(__NR_newfstatat) - case __NR_newfstatat: - if (static_cast<int>(args.args[0]) != AT_FDCWD) - return -EPERM; - if (static_cast<int>(args.args[3]) != 0) - return -EINVAL; - return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]), - true, - reinterpret_cast<struct stat*>(args.args[2])); -#endif -#if defined(__NR_unlink) - case __NR_unlink: - return broker_process->Unlink( - reinterpret_cast<const char*>(args.args[0])); -#endif -#if defined(__NR_unlinkat) - case __NR_unlinkat: - // TODO(tsepez): does not support AT_REMOVEDIR flag. - if (static_cast<int>(args.args[0]) != AT_FDCWD) - return -EPERM; - return broker_process->Unlink( - reinterpret_cast<const char*>(args.args[1])); -#endif - default: - RAW_CHECK(false); - return -ENOSYS; - } -} - } // namespace syscall_broker } // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/broker_process.h b/chromium/sandbox/linux/syscall_broker/broker_process.h index 022b5e220fa..67f0ca50cfd 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_process.h +++ b/chromium/sandbox/linux/syscall_broker/broker_process.h @@ -39,10 +39,7 @@ class BrokerFilePermission; // 4. Use open_broker.Open() to open files. class SANDBOX_EXPORT BrokerProcess { public: - // Handler to be used with a bpf_dsl Trap() function to forward system calls - // to the methods below. - static intptr_t SIGSYS_Handler(const arch_seccomp_data& args, - void* aux_broker_process); + enum class BrokerType { SIGNAL_BASED }; // |denied_errno| is the error code returned when methods such as Open() // or Access() are invoked on a file which is not in the allowlist (EACCESS @@ -62,6 +59,7 @@ class SANDBOX_EXPORT BrokerProcess { int denied_errno, const syscall_broker::BrokerCommandSet& allowed_command_set, const std::vector<syscall_broker::BrokerFilePermission>& permissions, + BrokerType broker_type, bool fast_check_in_client = true, bool quiet_failures_for_tests = false); @@ -84,49 +82,34 @@ class SANDBOX_EXPORT BrokerProcess { // calls are forwarded to the broker process for handling. bool IsSyscallAllowed(int sysno) const; - // The following methods are used in place of the equivalently-named - // syscalls by the trap handler. They, in turn, forward the call onto - // |broker_client_| for further processing. They will all be async signal - // safe. They all return -errno on errors. - - // 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. - int Access(const char* pathname, int mode) const; - - // Can be used in place of mkdir(). - int Mkdir(const char* path, 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. - int Open(const char* pathname, int flags) const; - - // Can be used in place of readlink(). - int Readlink(const char* path, char* buf, size_t bufsize) const; - - // Can be used in place of rename(). - int Rename(const char* oldpath, const char* newpath) const; - - // Can be used in place of rmdir(). - int Rmdir(const char* path) const; - - // Can be used in place of stat()/stat64()/lstat()/lstat64(). - int Stat(const char* pathname, bool follow_links, struct stat* sb) const; - int Stat64(const char* pathname, bool follow_links, struct stat64* sb) const; - - // Can be used in place of unlink(). - int Unlink(const char* path) const; + // Gets the signal-based BrokerClient created by Init(). + syscall_broker::BrokerClient* GetBrokerClientSignalBased() const { + return broker_client_.get(); + } private: friend class BrokerProcessTestHelper; + friend class HandleFilesystemViaBrokerPolicy; + + // IsSyscallBrokerable() answers the same question as IsSyscallAllowed(), + // but takes |fast_check| as a parameter. If |fast_check| is false, do not + // check |allowed_command_set_| before returning true for a syscall that is + // brokerable. + bool IsSyscallBrokerable(int sysno, bool fast_check) const; // 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. + // by tests and none of the class methods should be used afterwards. void CloseChannel(); + // Forks the signal-based broker, where syscall emulation is performed using + // signals in the sandboxed process that connect to the broker via Unix + // socket. + bool ForkSignalBasedBroker( + base::OnceCallback<bool(void)> broker_process_init_callback); + bool initialized_; // Whether we've been through Init() yet. pid_t broker_pid_; // The PID of the broker (child) created in Init(). + const BrokerType broker_type_; const bool fast_check_in_client_; const bool quiet_failures_for_tests_; syscall_broker::BrokerCommandSet allowed_command_set_; diff --git a/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc b/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc index e4a8b04672e..08096b95aa3 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc @@ -21,6 +21,8 @@ #include "base/bind.h" #include "base/callback.h" +#include "base/containers/flat_map.h" +#include "base/containers/flat_set.h" #include "base/files/file_util.h" #include "base/files/scoped_file.h" #include "base/logging.h" @@ -30,6 +32,7 @@ #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "sandbox/linux/syscall_broker/broker_client.h" +#include "sandbox/linux/syscall_broker/broker_command.h" #include "sandbox/linux/tests/scoped_temporary_file.h" #include "sandbox/linux/tests/test_utils.h" #include "sandbox/linux/tests/unit_tests.h" @@ -38,19 +41,19 @@ namespace sandbox { namespace syscall_broker { +using BrokerType = BrokerProcess::BrokerType; + 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 { -const int kFakeErrnoSentinel = 99999; +// Our fake errno must be less than 255 or various libc implementations will +// not accept this as a valid error number. E.g. bionic accepts up to 255, glibc +// and musl up to 4096. +constexpr int kFakeErrnoSentinel = 254; bool NoOpCallback() { return true; @@ -63,7 +66,7 @@ TEST(BrokerProcess, CreateAndDestroy) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly("/proc/cpuinfo")}; BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), - permissions); + permissions, BrokerType::SIGNAL_BASED); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); } @@ -76,13 +79,14 @@ TEST(BrokerProcess, TestOpenAccessNull) { MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); std::vector<BrokerFilePermission> empty; - BrokerProcess open_broker(kFakeErrnoSentinel, command_set, empty); + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, empty, + BrokerType::SIGNAL_BASED); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - int fd = open_broker.Open(NULL, O_RDONLY); + int fd = open_broker.GetBrokerClientSignalBased()->Open(nullptr, O_RDONLY); ASSERT_EQ(fd, -EFAULT); - int ret = open_broker.Access(NULL, F_OK); + int ret = open_broker.GetBrokerClientSignalBased()->Access(nullptr, F_OK); ASSERT_EQ(ret, -EFAULT); } @@ -93,7 +97,7 @@ void TestOpenFilePerms(bool fast_check_in_client, int denied_errno) { const char kR_AllowListedButDenied[] = "/proc/1/auxv"; const char kW_AllowListed[] = "/proc/DOESNOTEXIST2"; const char kRW_AllowListed[] = "/proc/DOESNOTEXIST3"; - const char k_NotAllowlisted[] = "/proc/DOESNOTEXIST4"; + const char k_NotAllowListed[] = "/proc/DOESNOTEXIST4"; BrokerCommandSet command_set = MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); @@ -104,121 +108,148 @@ void TestOpenFilePerms(bool fast_check_in_client, int denied_errno) { BrokerFilePermission::WriteOnly(kW_AllowListed), BrokerFilePermission::ReadWrite(kRW_AllowListed)}; BrokerProcess open_broker(denied_errno, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); int fd = -1; - fd = open_broker.Open(kR_AllowListed, O_RDONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kR_AllowListed, O_RDONLY); ASSERT_EQ(fd, -ENOENT); - fd = open_broker.Open(kR_AllowListed, O_WRONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kR_AllowListed, O_WRONLY); ASSERT_EQ(fd, -denied_errno); - fd = open_broker.Open(kR_AllowListed, O_RDWR); + fd = open_broker.GetBrokerClientSignalBased()->Open(kR_AllowListed, O_RDWR); ASSERT_EQ(fd, -denied_errno); int ret = -1; - ret = open_broker.Access(kR_AllowListed, F_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kR_AllowListed, F_OK); ASSERT_EQ(ret, -ENOENT); - ret = open_broker.Access(kR_AllowListed, R_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kR_AllowListed, R_OK); ASSERT_EQ(ret, -ENOENT); - ret = open_broker.Access(kR_AllowListed, W_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kR_AllowListed, W_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(kR_AllowListed, R_OK | W_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kR_AllowListed, + R_OK | W_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(kR_AllowListed, X_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kR_AllowListed, X_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(kR_AllowListed, R_OK | X_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kR_AllowListed, + 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_AllowListedButDenied, O_RDONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kR_AllowListedButDenied, + O_RDONLY); // The broker process will allow this, but the normal permission system // won't. ASSERT_EQ(fd, -EACCES); - fd = open_broker.Open(kR_AllowListedButDenied, O_WRONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kR_AllowListedButDenied, + O_WRONLY); ASSERT_EQ(fd, -denied_errno); - fd = open_broker.Open(kR_AllowListedButDenied, O_RDWR); + fd = open_broker.GetBrokerClientSignalBased()->Open(kR_AllowListedButDenied, + O_RDWR); ASSERT_EQ(fd, -denied_errno); - ret = open_broker.Access(kR_AllowListedButDenied, F_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access( + kR_AllowListedButDenied, F_OK); // The normal permission system will let us check that the file exists. ASSERT_EQ(ret, 0); - ret = open_broker.Access(kR_AllowListedButDenied, R_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access( + kR_AllowListedButDenied, R_OK); ASSERT_EQ(ret, -EACCES); - ret = open_broker.Access(kR_AllowListedButDenied, W_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access( + kR_AllowListedButDenied, W_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(kR_AllowListedButDenied, R_OK | W_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access( + kR_AllowListedButDenied, R_OK | W_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(kR_AllowListedButDenied, X_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access( + kR_AllowListedButDenied, X_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(kR_AllowListedButDenied, R_OK | X_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access( + kR_AllowListedButDenied, R_OK | X_OK); ASSERT_EQ(ret, -denied_errno); } - fd = open_broker.Open(kW_AllowListed, O_RDONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kW_AllowListed, O_RDONLY); ASSERT_EQ(fd, -denied_errno); - fd = open_broker.Open(kW_AllowListed, O_WRONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kW_AllowListed, O_WRONLY); ASSERT_EQ(fd, -ENOENT); - fd = open_broker.Open(kW_AllowListed, O_RDWR); + fd = open_broker.GetBrokerClientSignalBased()->Open(kW_AllowListed, O_RDWR); ASSERT_EQ(fd, -denied_errno); - ret = open_broker.Access(kW_AllowListed, F_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kW_AllowListed, F_OK); ASSERT_EQ(ret, -ENOENT); - ret = open_broker.Access(kW_AllowListed, R_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kW_AllowListed, R_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(kW_AllowListed, W_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kW_AllowListed, W_OK); ASSERT_EQ(ret, -ENOENT); - ret = open_broker.Access(kW_AllowListed, R_OK | W_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kW_AllowListed, + R_OK | W_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(kW_AllowListed, X_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kW_AllowListed, X_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(kW_AllowListed, R_OK | X_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kW_AllowListed, + R_OK | X_OK); ASSERT_EQ(ret, -denied_errno); - fd = open_broker.Open(kRW_AllowListed, O_RDONLY); + fd = + open_broker.GetBrokerClientSignalBased()->Open(kRW_AllowListed, O_RDONLY); ASSERT_EQ(fd, -ENOENT); - fd = open_broker.Open(kRW_AllowListed, O_WRONLY); + fd = + open_broker.GetBrokerClientSignalBased()->Open(kRW_AllowListed, O_WRONLY); ASSERT_EQ(fd, -ENOENT); - fd = open_broker.Open(kRW_AllowListed, O_RDWR); + fd = open_broker.GetBrokerClientSignalBased()->Open(kRW_AllowListed, O_RDWR); ASSERT_EQ(fd, -ENOENT); - ret = open_broker.Access(kRW_AllowListed, F_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kRW_AllowListed, F_OK); ASSERT_EQ(ret, -ENOENT); - ret = open_broker.Access(kRW_AllowListed, R_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kRW_AllowListed, R_OK); ASSERT_EQ(ret, -ENOENT); - ret = open_broker.Access(kRW_AllowListed, W_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kRW_AllowListed, W_OK); ASSERT_EQ(ret, -ENOENT); - ret = open_broker.Access(kRW_AllowListed, R_OK | W_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kRW_AllowListed, + R_OK | W_OK); ASSERT_EQ(ret, -ENOENT); - ret = open_broker.Access(kRW_AllowListed, X_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kRW_AllowListed, X_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(kRW_AllowListed, R_OK | X_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(kRW_AllowListed, + R_OK | X_OK); ASSERT_EQ(ret, -denied_errno); - fd = open_broker.Open(k_NotAllowlisted, O_RDONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(k_NotAllowListed, + O_RDONLY); ASSERT_EQ(fd, -denied_errno); - fd = open_broker.Open(k_NotAllowlisted, O_WRONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(k_NotAllowListed, + O_WRONLY); ASSERT_EQ(fd, -denied_errno); - fd = open_broker.Open(k_NotAllowlisted, O_RDWR); + fd = open_broker.GetBrokerClientSignalBased()->Open(k_NotAllowListed, O_RDWR); ASSERT_EQ(fd, -denied_errno); - ret = open_broker.Access(k_NotAllowlisted, F_OK); + ret = + open_broker.GetBrokerClientSignalBased()->Access(k_NotAllowListed, F_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(k_NotAllowlisted, R_OK); + ret = + open_broker.GetBrokerClientSignalBased()->Access(k_NotAllowListed, R_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(k_NotAllowlisted, W_OK); + ret = + open_broker.GetBrokerClientSignalBased()->Access(k_NotAllowListed, W_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(k_NotAllowlisted, R_OK | W_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(k_NotAllowListed, + R_OK | W_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(k_NotAllowlisted, X_OK); + ret = + open_broker.GetBrokerClientSignalBased()->Access(k_NotAllowListed, X_OK); ASSERT_EQ(ret, -denied_errno); - ret = open_broker.Access(k_NotAllowlisted, R_OK | X_OK); + ret = open_broker.GetBrokerClientSignalBased()->Access(k_NotAllowListed, + R_OK | X_OK); ASSERT_EQ(ret, -denied_errno); // We have some extra sanity check for clearly wrong values. - fd = open_broker.Open(kRW_AllowListed, O_RDONLY | O_WRONLY | O_RDWR); + fd = open_broker.GetBrokerClientSignalBased()->Open( + kRW_AllowListed, 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_AllowListed, O_RDWR | O_CREAT); + fd = open_broker.GetBrokerClientSignalBased()->Open(kRW_AllowListed, + O_RDWR | O_CREAT); ASSERT_EQ(fd, -denied_errno); } @@ -263,40 +294,46 @@ void TestBadPaths(bool fast_check_in_client) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnlyRecursive("/proc/")}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); // Open cpuinfo via the broker. - int cpuinfo_fd = open_broker.Open(kFileCpuInfo, O_RDONLY); + int cpuinfo_fd = + open_broker.GetBrokerClientSignalBased()->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); + can_access = + open_broker.GetBrokerClientSignalBased()->Access(kNotAbsPath, R_OK); ASSERT_EQ(can_access, -kFakeErrnoSentinel); - fd = open_broker.Open(kNotAbsPath, O_RDONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kNotAbsPath, O_RDONLY); ASSERT_EQ(fd, -kFakeErrnoSentinel); - can_access = open_broker.Access(kDotDotStart, R_OK); + can_access = + open_broker.GetBrokerClientSignalBased()->Access(kDotDotStart, R_OK); ASSERT_EQ(can_access, -kFakeErrnoSentinel); - fd = open_broker.Open(kDotDotStart, O_RDONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kDotDotStart, O_RDONLY); ASSERT_EQ(fd, -kFakeErrnoSentinel); - can_access = open_broker.Access(kDotDotMiddle, R_OK); + can_access = + open_broker.GetBrokerClientSignalBased()->Access(kDotDotMiddle, R_OK); ASSERT_EQ(can_access, -kFakeErrnoSentinel); - fd = open_broker.Open(kDotDotMiddle, O_RDONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kDotDotMiddle, O_RDONLY); ASSERT_EQ(fd, -kFakeErrnoSentinel); - can_access = open_broker.Access(kDotDotEnd, R_OK); + can_access = + open_broker.GetBrokerClientSignalBased()->Access(kDotDotEnd, R_OK); ASSERT_EQ(can_access, -kFakeErrnoSentinel); - fd = open_broker.Open(kDotDotEnd, O_RDONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kDotDotEnd, O_RDONLY); ASSERT_EQ(fd, -kFakeErrnoSentinel); - can_access = open_broker.Access(kTrailingSlash, R_OK); + can_access = + open_broker.GetBrokerClientSignalBased()->Access(kTrailingSlash, R_OK); ASSERT_EQ(can_access, -kFakeErrnoSentinel); - fd = open_broker.Open(kTrailingSlash, O_RDONLY); + fd = open_broker.GetBrokerClientSignalBased()->Open(kTrailingSlash, O_RDONLY); ASSERT_EQ(fd, -kFakeErrnoSentinel); } @@ -326,21 +363,25 @@ void TestOpenCpuinfo(bool fast_check_in_client, bool recursive) { : BrokerFilePermission::ReadOnly(kFileCpuInfo)); BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - int fd = open_broker.Open(kFileCpuInfo, O_RDWR); + int fd = + open_broker.GetBrokerClientSignalBased()->Open(kFileCpuInfo, O_RDWR); ASSERT_EQ(fd, -kFakeErrnoSentinel); // Check we can read /proc/cpuinfo. - int can_access = open_broker.Access(kFileCpuInfo, R_OK); + int can_access = + open_broker.GetBrokerClientSignalBased()->Access(kFileCpuInfo, R_OK); EXPECT_EQ(can_access, 0); - can_access = open_broker.Access(kFileCpuInfo, W_OK); + can_access = + open_broker.GetBrokerClientSignalBased()->Access(kFileCpuInfo, W_OK); EXPECT_EQ(can_access, -kFakeErrnoSentinel); // Check we can not write /proc/cpuinfo. // Open cpuinfo via the broker. - int cpuinfo_fd = open_broker.Open(kFileCpuInfo, O_RDONLY); + int cpuinfo_fd = + open_broker.GetBrokerClientSignalBased()->Open(kFileCpuInfo, O_RDONLY); base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); EXPECT_GE(cpuinfo_fd, 0); char buf[3]; @@ -404,15 +445,18 @@ TEST(BrokerProcess, OpenFileRW) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadWrite(tempfile_name)}; - BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions); + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + BrokerType::SIGNAL_BASED); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); // Check we can access that file with read or write. - int can_access = open_broker.Access(tempfile_name, R_OK | W_OK); + int can_access = open_broker.GetBrokerClientSignalBased()->Access( + tempfile_name, R_OK | W_OK); ASSERT_EQ(can_access, 0); int tempfile2 = -1; - tempfile2 = open_broker.Open(tempfile_name, O_RDWR); + tempfile2 = + open_broker.GetBrokerClientSignalBased()->Open(tempfile_name, O_RDWR); ASSERT_GE(tempfile2, 0); // Write to the descriptor opened by the broker. @@ -441,25 +485,26 @@ SANDBOX_TEST(BrokerProcess, BrokerDied) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly(kCpuInfo)}; - BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - true /* fast_check_in_client */, - true /* quiet_failures_for_tests */); + BrokerProcess open_broker( + kFakeErrnoSentinel, command_set, permissions, BrokerType::SIGNAL_BASED, + true /* fast_check_in_client */, true /* quiet_failures_for_tests */); SANDBOX_ASSERT(open_broker.Init(base::BindOnce(&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(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); + SANDBOX_ASSERT(open_broker.GetBrokerClientSignalBased()->Open( + kCpuInfo, O_RDONLY) == -ENOMEM); + SANDBOX_ASSERT(open_broker.GetBrokerClientSignalBased()->Access( + kCpuInfo, O_RDONLY) == -ENOMEM); } void TestOpenComplexFlags(bool fast_check_in_client) { @@ -471,21 +516,26 @@ void TestOpenComplexFlags(bool fast_check_in_client) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly(kCpuInfo)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&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); + fd = open_broker.GetBrokerClientSignalBased()->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_NE(-1, ret) << errno; + // The description shouldn't have the O_CLOEXEC attribute, nor O_NONBLOCK. ASSERT_EQ(0, ret & (O_CLOEXEC | O_NONBLOCK)); + ret = fcntl(fd, F_GETFD); + ASSERT_NE(-1, ret) << errno; + // The descriptor also should not have FD_CLOEXEC. + ASSERT_EQ(FD_CLOEXEC & ret, 0); ASSERT_EQ(0, close(fd)); - fd = open_broker.Open(kCpuInfo, O_RDONLY | O_CLOEXEC); + fd = open_broker.GetBrokerClientSignalBased()->Open(kCpuInfo, + O_RDONLY | O_CLOEXEC); ASSERT_GE(fd, 0); ret = fcntl(fd, F_GETFD); ASSERT_NE(-1, ret); @@ -494,7 +544,8 @@ void TestOpenComplexFlags(bool fast_check_in_client) { ASSERT_TRUE(FD_CLOEXEC & ret); ASSERT_EQ(0, close(fd)); - fd = open_broker.Open(kCpuInfo, O_RDONLY | O_NONBLOCK); + fd = open_broker.GetBrokerClientSignalBased()->Open(kCpuInfo, + O_RDONLY | O_NONBLOCK); ASSERT_GE(fd, 0); ret = fcntl(fd, F_GETFL); ASSERT_NE(-1, ret); @@ -561,10 +612,12 @@ SANDBOX_TEST_ALLOW_NOISE(BrokerProcess, MAYBE_RecvMsgDescriptorLeak) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly(kCpuInfo)}; - BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions); + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + BrokerType::SIGNAL_BASED); SANDBOX_ASSERT(open_broker.Init(base::BindOnce(&NoOpCallback))); - const int ipc_fd = BrokerProcessTestHelper::GetIPCDescriptor(&open_broker); + const int ipc_fd = + open_broker.GetBrokerClientSignalBased()->GetIPCDescriptorForTesting(); SANDBOX_ASSERT(ipc_fd >= 0); static const char kBogus[] = "not a pickle"; @@ -578,7 +631,8 @@ SANDBOX_TEST_ALLOW_NOISE(BrokerProcess, MAYBE_RecvMsgDescriptorLeak) { base::UnixDomainSocket::SendMsg(ipc_fd, kBogus, sizeof(kBogus), fds)); } - const int fd = open_broker.Open(kCpuInfo, O_RDONLY); + const int fd = + open_broker.GetBrokerClientSignalBased()->Open(kCpuInfo, O_RDONLY); SANDBOX_ASSERT(fd >= 0); SANDBOX_ASSERT(0 == IGNORE_EINTR(close(fd))); } @@ -612,9 +666,9 @@ TEST(BrokerProcess, BrokerDiesOnClosedChannel) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly("/proc/cpuinfo")}; - BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - true /* fast_check_in_client */, - false /* quiet_failures_for_tests */); + BrokerProcess open_broker( + kFakeErrnoSentinel, command_set, permissions, BrokerType::SIGNAL_BASED, + true /* fast_check_in_client */, false /* quiet_failures_for_tests */); ASSERT_TRUE(open_broker.Init(base::BindOnce(&CloseFD, lifeline_fds[0]))); // Make sure the writing end only exists in the broker process. @@ -641,6 +695,8 @@ TEST(BrokerProcess, BrokerDiesOnClosedChannel) { } TEST(BrokerProcess, CreateFile) { + // Create two temporary files, grab their file names, and then delete the + // files themselves. std::string temp_str; std::string perm_str; { @@ -660,24 +716,27 @@ TEST(BrokerProcess, CreateFile) { BrokerFilePermission::ReadWriteCreate(permfile_name), }; - BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions); + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + BrokerType::SIGNAL_BASED); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); int fd = -1; // Opening a temp file using O_CREAT but not O_EXCL must not be allowed // by the broker so as to prevent spying on any pre-existing files. - fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT); + fd = open_broker.GetBrokerClientSignalBased()->Open(tempfile_name, + O_RDWR | O_CREAT); ASSERT_EQ(fd, -kFakeErrnoSentinel); // Opening a temp file in a normal way must not be allowed by the broker, // either. - fd = open_broker.Open(tempfile_name, O_RDWR); + fd = open_broker.GetBrokerClientSignalBased()->Open(tempfile_name, O_RDWR); ASSERT_EQ(fd, -kFakeErrnoSentinel); // Opening a temp file with both O_CREAT and O_EXCL is allowed since the // file is known not to exist outside the scope of ScopedTemporaryFile. - fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT | O_EXCL); + fd = open_broker.GetBrokerClientSignalBased()->Open( + tempfile_name, O_RDWR | O_CREAT | O_EXCL); ASSERT_GE(fd, 0); close(fd); @@ -688,26 +747,30 @@ TEST(BrokerProcess, CreateFile) { // Opening a temp file with both O_CREAT and O_EXCL is allowed but fails // per the OS when there is a conflict with a pre-existing file. - fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT | O_EXCL); + fd = open_broker.GetBrokerClientSignalBased()->Open( + tempfile_name, O_RDWR | O_CREAT | O_EXCL); ASSERT_EQ(fd, -EEXIST); // Opening a new permanent file without specifying O_EXCL is allowed. - fd = open_broker.Open(permfile_name, O_RDWR | O_CREAT); + fd = open_broker.GetBrokerClientSignalBased()->Open(permfile_name, + O_RDWR | O_CREAT); ASSERT_GE(fd, 0); close(fd); // Opening an existing permanent file without specifying O_EXCL is allowed. - fd = open_broker.Open(permfile_name, O_RDWR | O_CREAT); + fd = open_broker.GetBrokerClientSignalBased()->Open(permfile_name, + O_RDWR | O_CREAT); ASSERT_GE(fd, 0); close(fd); // Opening an existing file with O_EXCL is allowed but fails per the OS. - fd = open_broker.Open(permfile_name, O_RDWR | O_CREAT | O_EXCL); + fd = open_broker.GetBrokerClientSignalBased()->Open( + permfile_name, O_RDWR | O_CREAT | O_EXCL); ASSERT_EQ(fd, -EEXIST); const char kTestText[] = "TESTTESTTEST"; - fd = open_broker.Open(permfile_name, O_RDWR); + fd = open_broker.GetBrokerClientSignalBased()->Open(permfile_name, O_RDWR); ASSERT_GE(fd, 0); { // Write to the descriptor opened by the broker and close. @@ -755,12 +818,14 @@ void TestStatHelper(bool fast_check_in_client, bool follow_links) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly(tempfile_name)}; BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), - permissions, fast_check_in_client); + permissions, BrokerType::SIGNAL_BASED, + fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(tempfile_name, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + tempfile_name, follow_links, &sb)); } BrokerCommandSet command_set; @@ -770,99 +835,122 @@ void TestStatHelper(bool fast_check_in_client, bool follow_links) { // Nonexistent file with no permissions to see file. std::vector<BrokerFilePermission> permissions; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(nonesuch_name, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + nonesuch_name, follow_links, &sb)); } { // Actual file with no permission to see file. std::vector<BrokerFilePermission> permissions; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(tempfile_name, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + tempfile_name, follow_links, &sb)); } { // Nonexistent file with permissions to see file. std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly(nonesuch_name)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); - EXPECT_EQ(-ENOENT, open_broker.Stat(nonesuch_name, follow_links, &sb)); + EXPECT_EQ(-ENOENT, open_broker.GetBrokerClientSignalBased()->Stat( + nonesuch_name, follow_links, &sb)); // Gets denied all the way back to root since no create permission. EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(leading_path1, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + leading_path1, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(leading_path2, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + leading_path2, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(leading_path3, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + leading_path3, follow_links, &sb)); // Not fooled by substrings. EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path1, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path1, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path2, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path2, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path3, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path3, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path4, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path4, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path5, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path5, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path6, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path6, follow_links, &sb)); } { // Nonexistent file with permissions to create file. std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); - EXPECT_EQ(-ENOENT, open_broker.Stat(nonesuch_name, follow_links, &sb)); + EXPECT_EQ(-ENOENT, open_broker.GetBrokerClientSignalBased()->Stat( + nonesuch_name, follow_links, &sb)); // Gets ENOENT all the way back to root since it has create permission. - EXPECT_EQ(-ENOENT, open_broker.Stat(leading_path1, follow_links, &sb)); - EXPECT_EQ(-ENOENT, open_broker.Stat(leading_path2, follow_links, &sb)); + EXPECT_EQ(-ENOENT, open_broker.GetBrokerClientSignalBased()->Stat( + leading_path1, follow_links, &sb)); + EXPECT_EQ(-ENOENT, open_broker.GetBrokerClientSignalBased()->Stat( + leading_path2, follow_links, &sb)); // But can always get the root. - EXPECT_EQ(0, open_broker.Stat(leading_path3, follow_links, &sb)); + EXPECT_EQ(0, open_broker.GetBrokerClientSignalBased()->Stat( + leading_path3, follow_links, &sb)); // Not fooled by substrings. EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path1, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path1, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path2, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path2, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path3, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path3, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path4, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path4, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path5, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path5, follow_links, &sb)); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Stat(bad_leading_path6, follow_links, &sb)); + open_broker.GetBrokerClientSignalBased()->Stat( + bad_leading_path6, follow_links, &sb)); } { // Actual file with permissions to see file. std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly(tempfile_name)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); - EXPECT_EQ(0, open_broker.Stat(tempfile_name, follow_links, &sb)); + EXPECT_EQ(0, open_broker.GetBrokerClientSignalBased()->Stat( + tempfile_name, follow_links, &sb)); // Following fields may never be consistent but should be non-zero. // Don't trust the platform to define fields with any particular sign. @@ -923,10 +1011,12 @@ void TestRenameHelper(bool fast_check_in_client) { // Check rename fails with write permissions to both files but command // itself is not allowed. BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), - rwc_permissions, fast_check_in_client); + rwc_permissions, BrokerType::SIGNAL_BASED, + fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Rename(oldpath.c_str(), newpath.c_str())); + open_broker.GetBrokerClientSignalBased()->Rename( + oldpath.c_str(), newpath.c_str())); // ... and no files moved around. EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); @@ -940,10 +1030,11 @@ void TestRenameHelper(bool fast_check_in_client) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadWriteCreate(oldpath)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Rename(oldpath.c_str(), newpath.c_str())); + open_broker.GetBrokerClientSignalBased()->Rename( + oldpath.c_str(), newpath.c_str())); // ... and no files moved around. EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); @@ -954,10 +1045,11 @@ void TestRenameHelper(bool fast_check_in_client) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadWriteCreate(newpath)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Rename(oldpath.c_str(), newpath.c_str())); + open_broker.GetBrokerClientSignalBased()->Rename( + oldpath.c_str(), newpath.c_str())); // ... and no files moved around. EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); @@ -969,10 +1061,11 @@ void TestRenameHelper(bool fast_check_in_client) { BrokerFilePermission::ReadOnly(oldpath), BrokerFilePermission::ReadWriteCreate(newpath)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Rename(oldpath.c_str(), newpath.c_str())); + open_broker.GetBrokerClientSignalBased()->Rename( + oldpath.c_str(), newpath.c_str())); // ... and no files moved around. EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); @@ -984,10 +1077,11 @@ void TestRenameHelper(bool fast_check_in_client) { BrokerFilePermission::ReadWriteCreate(oldpath), BrokerFilePermission::ReadOnly(newpath)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Rename(oldpath.c_str(), newpath.c_str())); + open_broker.GetBrokerClientSignalBased()->Rename( + oldpath.c_str(), newpath.c_str())); // ... and no files moved around. EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); @@ -996,9 +1090,10 @@ void TestRenameHelper(bool fast_check_in_client) { { // Check rename passes with write permissions to both files. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(0, open_broker.Rename(oldpath.c_str(), newpath.c_str())); + EXPECT_EQ(0, open_broker.GetBrokerClientSignalBased()->Rename( + oldpath.c_str(), newpath.c_str())); // ... and files were moved around. EXPECT_TRUE(access(oldpath.c_str(), F_OK) < 0); @@ -1040,10 +1135,12 @@ void TestReadlinkHelper(bool fast_check_in_client) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly(newpath_name)}; BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), - permissions, fast_check_in_client); + permissions, BrokerType::SIGNAL_BASED, + fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Readlink(newpath_name, buf, sizeof(buf))); + open_broker.GetBrokerClientSignalBased()->Readlink( + newpath_name, buf, sizeof(buf))); } BrokerCommandSet command_set; @@ -1053,37 +1150,41 @@ void TestReadlinkHelper(bool fast_check_in_client) { // Nonexistent file with no permissions to see file. std::vector<BrokerFilePermission> permissions; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Readlink(nonesuch_name, buf, sizeof(buf))); + open_broker.GetBrokerClientSignalBased()->Readlink( + nonesuch_name, buf, sizeof(buf))); } { // Actual file with no permissions to see file. std::vector<BrokerFilePermission> permissions; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); EXPECT_EQ(-kFakeErrnoSentinel, - open_broker.Readlink(newpath_name, buf, sizeof(buf))); + open_broker.GetBrokerClientSignalBased()->Readlink( + newpath_name, buf, sizeof(buf))); } { // Nonexistent file with permissions to see file. std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly(nonesuch_name)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-ENOENT, open_broker.Readlink(nonesuch_name, buf, sizeof(buf))); + EXPECT_EQ(-ENOENT, open_broker.GetBrokerClientSignalBased()->Readlink( + nonesuch_name, buf, sizeof(buf))); } { // Actual file with permissions to see file. std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly(newpath_name)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - ssize_t retlen = open_broker.Readlink(newpath_name, buf, sizeof(buf)); + ssize_t retlen = open_broker.GetBrokerClientSignalBased()->Readlink( + newpath_name, buf, sizeof(buf)); EXPECT_TRUE(retlen == static_cast<ssize_t>(strlen(oldpath_name))); EXPECT_EQ(0, memcmp(oldpath_name, buf, retlen)); } @@ -1092,9 +1193,10 @@ void TestReadlinkHelper(bool fast_check_in_client) { std::vector<BrokerFilePermission> permissions = { BrokerFilePermission::ReadOnly(newpath_name)}; BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-ENAMETOOLONG, open_broker.Readlink(newpath_name, buf, 4)); + EXPECT_EQ(4, open_broker.GetBrokerClientSignalBased()->Readlink( + newpath_name, buf, 4)); } // Cleanup both paths. @@ -1136,9 +1238,11 @@ void TestMkdirHelper(bool fast_check_in_client) { { // Actual file with permissions to use but command itself not allowed. BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), - rw_permissions, fast_check_in_client); + rw_permissions, BrokerType::SIGNAL_BASED, + fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(path_name, 0600)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Mkdir(path_name, 0600)); } BrokerCommandSet command_set = MakeBrokerCommandSet({COMMAND_MKDIR}); @@ -1146,58 +1250,69 @@ void TestMkdirHelper(bool fast_check_in_client) { { // Nonexistent file with no permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(nonesuch_name, 0600)); + EXPECT_EQ( + -kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Mkdir(nonesuch_name, 0600)); } { // Actual file with no permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(path_name, 0600)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Mkdir(path_name, 0600)); } { // Nonexistent file with insufficient permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(nonesuch_name, 0600)); + EXPECT_EQ( + -kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Mkdir(nonesuch_name, 0600)); } { // Actual file with insufficient permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(path_name, 0600)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Mkdir(path_name, 0600)); } { // Nonexistent file with insufficient permissions to see file, case 2. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(nonesuch_name, 0600)); + EXPECT_EQ( + -kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Mkdir(nonesuch_name, 0600)); } { // Actual file with insufficient permissions to see file, case 2. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(path_name, 0600)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Mkdir(path_name, 0600)); } { // Nonexistent file with permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-ENOENT, open_broker.Mkdir(nonesuch_name, 0600)); + EXPECT_EQ(-ENOENT, open_broker.GetBrokerClientSignalBased()->Mkdir( + nonesuch_name, 0600)); } { // Actual file with permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(0, open_broker.Mkdir(path_name, 0600)); + EXPECT_EQ(0, + open_broker.GetBrokerClientSignalBased()->Mkdir(path_name, 0600)); } // Cleanup. @@ -1241,9 +1356,11 @@ void TestRmdirHelper(bool fast_check_in_client) { { // Actual dir with permissions to use but command itself not allowed. BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), - rw_permissions, fast_check_in_client); + rw_permissions, BrokerType::SIGNAL_BASED, + fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(path_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Rmdir(path_name)); } EXPECT_EQ(0, access(path_name, F_OK)); @@ -1252,72 +1369,79 @@ void TestRmdirHelper(bool fast_check_in_client) { { // Nonexistent dir with no permissions to see dir. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(nonesuch_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Rmdir(nonesuch_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Actual dir with no permissions to see dir. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(path_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Rmdir(path_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Nonexistent dir with insufficient permissions to see dir. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(nonesuch_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Rmdir(nonesuch_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Actual dir with insufficient permissions to see dir. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(path_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Rmdir(path_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Nonexistent dir with insufficient permissions to see dir, case 2. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(nonesuch_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Rmdir(nonesuch_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Actual dir with insufficient permissions to see dir, case 2. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(path_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Rmdir(path_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Nonexistent dir with permissions to see dir. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_TRUE(open_broker.Rmdir(nonesuch_name) < 0); + EXPECT_TRUE(open_broker.GetBrokerClientSignalBased()->Rmdir(nonesuch_name) < + 0); } EXPECT_EQ(0, access(path_name, F_OK)); { // Actual dir with permissions to see dir. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(0, open_broker.Rmdir(path_name)); + EXPECT_EQ(0, open_broker.GetBrokerClientSignalBased()->Rmdir(path_name)); } // Confirm it was erased. EXPECT_EQ(-1, access(path_name, F_OK)); @@ -1331,6 +1455,7 @@ TEST(BrokerProcess, RmdirHost) { TestRmdirHelper(false); } +// Will have to split this into many tests, and the "cleanup" will have ASSERTs. void TestUnlinkHelper(bool fast_check_in_client) { std::string path; { @@ -1362,9 +1487,11 @@ void TestUnlinkHelper(bool fast_check_in_client) { { // Actual file with permissions to use but command itself not allowed. BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), - rwc_permissions, fast_check_in_client); + rwc_permissions, BrokerType::SIGNAL_BASED, + fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(path_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Unlink(path_name)); } EXPECT_EQ(0, access(path_name, F_OK)); @@ -1373,72 +1500,79 @@ void TestUnlinkHelper(bool fast_check_in_client) { { // Nonexistent file with no permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(nonesuch_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Unlink(nonesuch_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Actual file with no permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(path_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Unlink(path_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Nonexistent file with insufficient permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(nonesuch_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Unlink(nonesuch_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Actual file with insufficient permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(path_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Unlink(path_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Nonexistent file with insufficient permissions to see file, case 2. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(nonesuch_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Unlink(nonesuch_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Actual file with insufficient permissions to see file, case 2. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(path_name)); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.GetBrokerClientSignalBased()->Unlink(path_name)); } EXPECT_EQ(0, access(path_name, F_OK)); { // Nonexistent file with permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_TRUE(open_broker.Unlink(nonesuch_name) < 0); + EXPECT_TRUE( + open_broker.GetBrokerClientSignalBased()->Unlink(nonesuch_name) < 0); } EXPECT_EQ(0, access(path_name, F_OK)); { // Actual file with permissions to see file. BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, - fast_check_in_client); + BrokerType::SIGNAL_BASED, fast_check_in_client); ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback))); - EXPECT_EQ(0, open_broker.Unlink(path_name)); + EXPECT_EQ(0, open_broker.GetBrokerClientSignalBased()->Unlink(path_name)); } // Confirm it was erased. EXPECT_EQ(-1, access(path_name, F_OK)); @@ -1453,80 +1587,110 @@ TEST(BrokerProcess, UnlinkHost) { } TEST(BrokerProcess, IsSyscallAllowed) { - const struct { - int sysno; - BrokerCommand command; - } kSyscallToCommandMap[] = { + const base::flat_map<BrokerCommand, base::flat_set<int>> kSysnosForCommand = { + {COMMAND_ACCESS, + {__NR_faccessat, #if defined(__NR_access) - {__NR_access, COMMAND_ACCESS}, + __NR_access #endif - {__NR_faccessat, COMMAND_ACCESS}, + }}, + {COMMAND_MKDIR, + {__NR_mkdirat, #if defined(__NR_mkdir) - {__NR_mkdir, COMMAND_MKDIR}, + __NR_mkdir #endif - {__NR_mkdirat, COMMAND_MKDIR}, + }}, + {COMMAND_OPEN, + {__NR_openat, #if defined(__NR_open) - {__NR_open, COMMAND_OPEN}, + __NR_open #endif - {__NR_openat, COMMAND_OPEN}, + }}, + {COMMAND_READLINK, + {__NR_readlinkat, #if defined(__NR_readlink) - {__NR_readlink, COMMAND_READLINK}, + __NR_readlink #endif - {__NR_readlinkat, COMMAND_READLINK}, + }}, + {COMMAND_RENAME, + {__NR_renameat, #if defined(__NR_rename) - {__NR_rename, COMMAND_RENAME}, + __NR_rename #endif - {__NR_renameat, COMMAND_RENAME}, + }}, + {COMMAND_UNLINK, + {__NR_unlinkat, +#if defined(__NR_unlink) + __NR_unlink +#endif + }}, + {COMMAND_RMDIR, + {__NR_unlinkat, #if defined(__NR_rmdir) - {__NR_rmdir, COMMAND_RMDIR}, + __NR_rmdir #endif + }}, + {COMMAND_STAT, + { #if defined(__NR_stat) - {__NR_stat, COMMAND_STAT}, + __NR_stat, #endif #if defined(__NR_lstat) - {__NR_lstat, COMMAND_STAT}, + __NR_lstat, #endif #if defined(__NR_fstatat) - {__NR_fstatat, COMMAND_STAT}, + __NR_fstatat, +#endif +#if defined(__NR_fstatat64) + __NR_fstatat64, #endif #if defined(__NR_newfstatat) - {__NR_newfstatat, COMMAND_STAT}, + __NR_newfstatat, #endif #if defined(__NR_stat64) - {__NR_stat64, COMMAND_STAT}, + __NR_stat64, #endif #if defined(__NR_lstat64) - {__NR_lstat64, COMMAND_STAT}, + __NR_lstat64, #endif -#if defined(__NR_unlink) - {__NR_unlink, COMMAND_UNLINK}, -#endif - {__NR_unlinkat, COMMAND_UNLINK}, - }; + }}}; + + // First gather up all the syscalls numbers we want to test. + base::flat_set<int> all_sysnos; + for (const auto& command_sysno_set_pair : kSysnosForCommand) { + all_sysnos.insert(command_sysno_set_pair.second.begin(), + command_sysno_set_pair.second.end()); + } - for (const auto& test : kSyscallToCommandMap) { + for (const auto& test : kSysnosForCommand) { // Test with fast_check_in_client. { - SCOPED_TRACE(base::StringPrintf("fast check, sysno=%d", test.sysno)); - BrokerProcess process(ENOSYS, MakeBrokerCommandSet({test.command}), {}, - true, true); - EXPECT_TRUE(process.IsSyscallAllowed(test.sysno)); - for (const auto& other : kSyscallToCommandMap) { - SCOPED_TRACE(base::StringPrintf("others test, sysno=%d", other.sysno)); - EXPECT_EQ(other.command == test.command, - process.IsSyscallAllowed(other.sysno)); + BrokerCommand command = test.first; + const base::flat_set<int>& sysnos = test.second; + SCOPED_TRACE(base::StringPrintf("fast check, command=%d", command)); + BrokerProcess process(ENOSYS, MakeBrokerCommandSet({command}), {}, + BrokerType::SIGNAL_BASED, + /*fast_check_in_client=*/true, + /*quiet_failures_for_tests=*/true); + // Check that only the correct system calls are allowed. + for (int sysno : all_sysnos) { + SCOPED_TRACE(base::StringPrintf("test syscalls, sysno=%d", sysno)); + EXPECT_EQ(sysnos.count(sysno) > 0, process.IsSyscallAllowed(sysno)); } } // Test without fast_check_in_client. { - SCOPED_TRACE(base::StringPrintf("no fast check, sysno=%d", test.sysno)); - BrokerProcess process(ENOSYS, MakeBrokerCommandSet({test.command}), {}, - false, true); - EXPECT_TRUE(process.IsSyscallAllowed(test.sysno)); - for (const auto& other : kSyscallToCommandMap) { - SCOPED_TRACE(base::StringPrintf("others test, sysno=%d", other.sysno)); - EXPECT_TRUE(process.IsSyscallAllowed(other.sysno)); + BrokerCommand command = test.first; + SCOPED_TRACE(base::StringPrintf("no fast check, command=%d", command)); + BrokerProcess process(ENOSYS, MakeBrokerCommandSet({command}), {}, + BrokerType::SIGNAL_BASED, + /*fast_check_in_client=*/false, + /*quiet_failures_for_tests=*/true); + // Check that all system calls are allowed. + for (int sysno : all_sysnos) { + SCOPED_TRACE(base::StringPrintf("test syscalls, sysno=%d", sysno)); + EXPECT_TRUE(process.IsSyscallAllowed(sysno)); } } } diff --git a/chromium/sandbox/linux/syscall_broker/broker_simple_message.cc b/chromium/sandbox/linux/syscall_broker/broker_simple_message.cc index 6b447a4ad03..193b485aeff 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_simple_message.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_simple_message.cc @@ -10,6 +10,7 @@ #include <unistd.h> #include "base/check_op.h" +#include "base/containers/span.h" #include "base/files/scoped_file.h" #include "base/notreached.h" #include "base/numerics/safe_math.h" @@ -22,19 +23,22 @@ namespace sandbox { namespace syscall_broker { -BrokerSimpleMessage::BrokerSimpleMessage() - : read_only_(false), - write_only_(false), - broken_(false), - length_(0), - read_next_(message_), - write_next_(message_) {} - ssize_t BrokerSimpleMessage::SendRecvMsgWithFlags(int fd, int recvmsg_flags, - int* result_fd, + base::ScopedFD* result_fd, BrokerSimpleMessage* reply) { + return SendRecvMsgWithFlagsMultipleFds(fd, recvmsg_flags, {}, {result_fd, 1}, + reply); +} + +ssize_t BrokerSimpleMessage::SendRecvMsgWithFlagsMultipleFds( + int fd, + int recvmsg_flags, + base::span<const int> send_fds, + base::span<base::ScopedFD> result_fds, + BrokerSimpleMessage* reply) { RAW_CHECK(reply); + RAW_CHECK(send_fds.size() + 1 <= base::UnixDomainSocket::kMaxFileDescriptors); // This socketpair is only used for the IPC and is cleaned up before // returning. @@ -43,48 +47,68 @@ ssize_t BrokerSimpleMessage::SendRecvMsgWithFlags(int fd, if (!base::CreateSocketPair(&recv_sock, &send_sock)) return -1; - if (!SendMsg(fd, send_sock.get())) + int send_fds_with_reply_socket[base::UnixDomainSocket::kMaxFileDescriptors]; + send_fds_with_reply_socket[0] = send_sock.get(); + for (size_t i = 0; i < send_fds.size(); i++) { + send_fds_with_reply_socket[i + 1] = send_fds[i]; + } + if (!SendMsgMultipleFds(fd, + {send_fds_with_reply_socket, send_fds.size() + 1})) { return -1; + } // Close the sending end of the socket right away so that if our peer closes // it before sending a response (e.g., from exiting), RecvMsgWithFlags() will // return EOF instead of hanging. send_sock.reset(); - base::ScopedFD recv_fd; - const ssize_t reply_len = - reply->RecvMsgWithFlags(recv_sock.get(), recvmsg_flags, &recv_fd); + const ssize_t reply_len = reply->RecvMsgWithFlagsMultipleFds( + recv_sock.get(), recvmsg_flags, result_fds); recv_sock.reset(); if (reply_len == -1) return -1; - if (result_fd) - *result_fd = (recv_fd == -1) ? -1 : recv_fd.release(); - return reply_len; } bool BrokerSimpleMessage::SendMsg(int fd, int send_fd) { + return SendMsgMultipleFds( + fd, send_fd == -1 ? base::span<int>() : base::span<int>(&send_fd, 1)); +} + +bool BrokerSimpleMessage::SendMsgMultipleFds(int fd, + base::span<const int> send_fds) { if (broken_) return false; + RAW_CHECK(send_fds.size() <= base::UnixDomainSocket::kMaxFileDescriptors); + struct msghdr msg = {}; const void* buf = reinterpret_cast<const void*>(message_); struct iovec iov = {const_cast<void*>(buf), length_}; msg.msg_iov = &iov; msg.msg_iovlen = 1; - const unsigned control_len = CMSG_SPACE(sizeof(send_fd)); + const unsigned control_len = CMSG_SPACE(send_fds.size() * sizeof(int)); char control_buffer[control_len]; - if (send_fd >= 0) { + if (send_fds.size() >= 1) { struct cmsghdr* cmsg; msg.msg_control = control_buffer; msg.msg_controllen = control_len; cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd)); - memcpy(CMSG_DATA(cmsg), &send_fd, sizeof(send_fd)); + int len = 0; + + for (size_t i = 0; i < send_fds.size(); i++) { + if (send_fds[i] < 0) + return false; + + // CMSG_DATA() not guaranteed to be aligned so this must use memcpy. + memcpy(CMSG_DATA(cmsg) + (sizeof(int) * i), &send_fds[i], sizeof(int)); + len += sizeof(int); + } + cmsg->cmsg_len = CMSG_LEN(len); msg.msg_controllen = cmsg->cmsg_len; } @@ -100,8 +124,18 @@ bool BrokerSimpleMessage::SendMsg(int fd, int send_fd) { ssize_t BrokerSimpleMessage::RecvMsgWithFlags(int fd, int flags, base::ScopedFD* return_fd) { + ssize_t ret = RecvMsgWithFlagsMultipleFds( + fd, flags, base::span<base::ScopedFD>(return_fd, 1)); + return ret; +} + +ssize_t BrokerSimpleMessage::RecvMsgWithFlagsMultipleFds( + int fd, + int flags, + base::span<base::ScopedFD> return_fds) { // The message must be fresh and unused. RAW_CHECK(!read_only_ && !write_only_); + RAW_CHECK(return_fds.size() <= base::UnixDomainSocket::kMaxFileDescriptors); read_only_ = true; // The message should not be written to again. struct msghdr msg = {}; struct iovec iov = {message_, kMaxMessageLength}; @@ -126,7 +160,7 @@ ssize_t BrokerSimpleMessage::RecvMsgWithFlags(int fd, if (r == -1) return -1; - int* wire_fds = NULL; + int* wire_fds = nullptr; size_t wire_fds_len = 0; base::ProcessId pid = -1; @@ -136,7 +170,7 @@ ssize_t BrokerSimpleMessage::RecvMsgWithFlags(int fd, const size_t payload_len = cmsg->cmsg_len - CMSG_LEN(0); if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { DCHECK_EQ(payload_len % sizeof(fd), 0u); - DCHECK_EQ(wire_fds, static_cast<void*>(nullptr)); + DCHECK_EQ(wire_fds, nullptr); wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); wire_fds_len = payload_len / sizeof(fd); } @@ -162,9 +196,10 @@ ssize_t BrokerSimpleMessage::RecvMsgWithFlags(int fd, } if (wire_fds) { - if (wire_fds_len > 1) { - // Only one FD is accepted by this receive. - for (unsigned i = 0; i < wire_fds_len; ++i) { + if (wire_fds_len > return_fds.size()) { + // The number of fds received is limited to return_fds.size(). If there + // are more in the message than expected, close them and return an error. + for (size_t i = 0; i < wire_fds_len; ++i) { close(wire_fds[i]); } errno = EMSGSIZE; @@ -172,7 +207,9 @@ ssize_t BrokerSimpleMessage::RecvMsgWithFlags(int fd, return -1; } - *return_fd = base::ScopedFD(wire_fds[0]); + for (size_t i = 0; i < wire_fds_len; ++i) { + return_fds[i] = base::ScopedFD(wire_fds[i]); + } } // At this point, |r| is guaranteed to be >= 0. diff --git a/chromium/sandbox/linux/syscall_broker/broker_simple_message.h b/chromium/sandbox/linux/syscall_broker/broker_simple_message.h index d9185b43a77..6203c999591 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_simple_message.h +++ b/chromium/sandbox/linux/syscall_broker/broker_simple_message.h @@ -8,6 +8,7 @@ #include <stdint.h> #include <sys/types.h> +#include "base/containers/span.h" #include "base/files/scoped_file.h" #include "sandbox/sandbox_export.h" @@ -26,25 +27,46 @@ namespace syscall_broker { // that reduces the code duplication. class SANDBOX_EXPORT BrokerSimpleMessage { public: - BrokerSimpleMessage(); + BrokerSimpleMessage() = default; // Signal-safe + // A synchronous version of SendMsg/RecvMsgWithFlags that creates and sends a + // temporary IPC socket over |fd|, then listens for a response on the IPC + // socket using reply->RecvMsgWithFlags(temporary_ipc_socket, recvmsg_flags, + // result_fd); ssize_t SendRecvMsgWithFlags(int fd, int recvmsg_flags, - int* send_fd, + base::ScopedFD* result_fd, BrokerSimpleMessage* reply); + // Same as SendRecvMsgWithFlags(), but allows sending and receiving a variable + // number of fds. The temporary IPC return socket is always sent as the first + // fd in the cmsg. + ssize_t SendRecvMsgWithFlagsMultipleFds(int fd, + int recvmsg_flags, + base::span<const int> send_fds, + base::span<base::ScopedFD> result_fds, + BrokerSimpleMessage* reply); + // Use sendmsg to write the given msg and the file descriptor |send_fd|. // Returns true if successful. Signal-safe. bool SendMsg(int fd, int send_fd); + // Same as SendMsg() but allows sending more than one fd. + bool SendMsgMultipleFds(int fd, base::span<const int> send_fds); + // Similar to RecvMsg, but allows to specify |flags| for recvmsg(2). // Guaranteed to return either 1 or 0 fds. Signal-safe. ssize_t RecvMsgWithFlags(int fd, int flags, base::ScopedFD* return_fd); + // Same as RecvMsgWithFlags() but allows receiving more than one fd. + ssize_t RecvMsgWithFlagsMultipleFds(int fd, + int flags, + base::span<base::ScopedFD> return_fds); + // Adds a NUL-terminated C-style string to the message as a raw buffer. - // Returns true if the internal message buffer has room for the data, and the - // data is successfully appended. + // Returns true if the internal message buffer has room for the data, and + // the data is successfully appended. bool AddStringToMessage(const char* string); // Adds a raw data buffer to the message. If the raw data is actually a @@ -53,8 +75,8 @@ class SANDBOX_EXPORT BrokerSimpleMessage { // data, and the data is successfully appended. bool AddDataToMessage(const char* buffer, size_t length); - // Adds an int to the message. Returns true if the internal message buffer has - // room for the int and the int is successfully added. + // Adds an int to the message. Returns true if the internal message buffer + // has room for the int and the int is successfully added. bool AddIntToMessage(int int_to_add); // This returns a pointer to the next available data buffer in |data|. The @@ -63,8 +85,8 @@ class SANDBOX_EXPORT BrokerSimpleMessage { bool ReadString(const char** string); // This returns a pointer to the next available data buffer in the message - // in |data|, and the length of the buffer in |length|. The buffer is owned by - // |this| class. + // in |data|, and the length of the buffer in |length|. The buffer is owned + // by |this| class. bool ReadData(const char** data, size_t* length); // This reads the next available int from the message and stores it in @@ -79,25 +101,25 @@ class SANDBOX_EXPORT BrokerSimpleMessage { enum class EntryType : uint32_t { DATA = 0xBDBDBD80, INT = 0xBDBDBD81 }; - // Returns whether or not the next available entry matches the expected entry - // type. + // Returns whether or not the next available entry matches the expected + // entry type. bool ValidateType(EntryType expected_type); // Set to true once a message is read from, it may never be written to. - bool read_only_; + bool read_only_ = false; // Set to true once a message is written to, it may never be read from. - bool write_only_; + bool write_only_ = false; // Set when an operation fails, so that all subsequed operations fail, // including any attempt to send the broken message. - bool broken_; + bool broken_ = false; // The current length of the contents in the |message_| buffer. - size_t length_; - // The pointer to the next location in the |message_| buffer to read from. - uint8_t* read_next_; - // The pointer to the next location in the |message_| buffer to write from. - uint8_t* write_next_; + size_t length_ = 0; // The statically allocated buffer of size |kMaxMessageLength|. uint8_t message_[kMaxMessageLength]; + // The pointer to the next location in the |message_| buffer to read from. + uint8_t* read_next_ = message_; + // The pointer to the next location in the |message_| buffer to write from. + uint8_t* write_next_ = message_; }; } // namespace syscall_broker diff --git a/chromium/sandbox/linux/syscall_broker/broker_simple_message_unittest.cc b/chromium/sandbox/linux/syscall_broker/broker_simple_message_unittest.cc index 9d600ecf0c2..721617db22c 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_simple_message_unittest.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_simple_message_unittest.cc @@ -4,14 +4,25 @@ #include "sandbox/linux/syscall_broker/broker_simple_message.h" +#include <linux/kcmp.h> +#include <unistd.h> + #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/callback_forward.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" #include "base/macros.h" -#include "base/single_thread_task_runner.h" +#include "base/run_loop.h" #include "base/synchronization/waitable_event.h" +#include "base/task/thread_pool.h" +#include "base/test/bind_test_util.h" +#include "base/test/task_environment.h" +#include "base/test/test_timeouts.h" #include "base/threading/thread.h" #include "sandbox/linux/syscall_broker/broker_channel.h" #include "sandbox/linux/syscall_broker/broker_simple_message.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" #include "sandbox/linux/tests/test_utils.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" @@ -549,7 +560,7 @@ TEST(BrokerSimpleMessage, SendRecvMsgSynchronous) { BrokerSimpleMessage send_message; send_message.AddDataToMessage(data1, strlen(data1) + 1); BrokerSimpleMessage reply_message; - int returned_fd; + base::ScopedFD returned_fd; ssize_t len = send_message.SendRecvMsgWithFlags( ipc_writer.get(), 0, &returned_fd, &reply_message); @@ -582,7 +593,7 @@ TEST(BrokerSimpleMessage, SendRecvMsgSynchronous) { BrokerSimpleMessage send_message; send_message.AddIntToMessage(int1); BrokerSimpleMessage reply_message; - int returned_fd; + base::ScopedFD returned_fd; ssize_t len = send_message.SendRecvMsgWithFlags( ipc_writer.get(), 0, &returned_fd, &reply_message); @@ -617,7 +628,7 @@ TEST(BrokerSimpleMessage, SendRecvMsgSynchronous) { send_message.AddDataToMessage(data1, strlen(data1) + 1); send_message.AddIntToMessage(int1); BrokerSimpleMessage reply_message; - int returned_fd; + base::ScopedFD returned_fd; ssize_t len = send_message.SendRecvMsgWithFlags( ipc_writer.get(), 0, &returned_fd, &reply_message); @@ -657,7 +668,7 @@ TEST(BrokerSimpleMessage, SendRecvMsgSynchronous) { send_message.AddIntToMessage(int2); send_message.AddDataToMessage(data2, strlen(data2) + 1); BrokerSimpleMessage reply_message; - int returned_fd; + base::ScopedFD returned_fd; ssize_t len = send_message.SendRecvMsgWithFlags( ipc_writer.get(), 0, &returned_fd, &reply_message); @@ -688,7 +699,7 @@ TEST(BrokerSimpleMessage, SendRecvMsgSynchronous) { EXPECT_TRUE(send_message.AddIntToMessage(5)); EXPECT_TRUE(send_message.AddStringToMessage("test")); BrokerSimpleMessage reply_message; - int returned_fd; + base::ScopedFD returned_fd; ssize_t len = send_message.SendRecvMsgWithFlags( ipc_writer.get(), 0, &returned_fd, &reply_message); @@ -698,6 +709,182 @@ TEST(BrokerSimpleMessage, SendRecvMsgSynchronous) { } } +namespace { +// Adds a gtest failure and returns false iff any of the following conditions +// are true: +// 1. |fd1| or |fd2| are invalid fds +// 2. Kcmp fails +// 3. fd1 and fd2 do not compare equal under kcmp. +bool CheckKcmpResult(int fd1, int fd2) { + if (fd1 < 0) { + ADD_FAILURE() << "fd1 invalid"; + return false; + } + if (fd2 < 0) { + ADD_FAILURE() << "fd2 invalid"; + return false; + } + pid_t pid = getpid(); + int ret = syscall(__NR_kcmp, pid, pid, KCMP_FILE, fd1, fd2); + if (ret < 0) { + ADD_FAILURE() << "Kcmp failed, errno = " << errno; + return false; + } + if (ret != 0) { + ADD_FAILURE() << "File description did not compare equal to stdout. Kcmp(" + << fd1 << ", " << fd2 << ") = " << ret; + return false; + } + + return true; +} + +// Receives an fd over |ipc_reader|, and if it does not point to the same +// description as stdout, prints a message and returns false. +// On any other error, also prints a message and returns false. +void ReceiveStdoutDupFd(BrokerChannel::EndPoint* ipc_reader) { + // Receive an fd from |ipc_reader|. + base::ScopedFD recv_fd; + + BrokerSimpleMessage msg; + ssize_t len = msg.RecvMsgWithFlags(ipc_reader->get(), 0, &recv_fd); + ASSERT_GE(len, 0) << "Error on RecvMsgWithFlags, errno = " << errno; + + CheckKcmpResult(STDOUT_FILENO, recv_fd.get()); +} + +void ReceiveTwoDupFds(BrokerChannel::EndPoint* ipc_reader) { + // Receive two fds from |ipc_reader|. + BrokerSimpleMessage msg; + base::ScopedFD recv_fds[2]; + ssize_t len = + msg.RecvMsgWithFlagsMultipleFds(ipc_reader->get(), 0, {recv_fds}); + ASSERT_GE(len, 0) << "Error on RecvMsgWithFlags, errno = " << errno; + + CheckKcmpResult(STDOUT_FILENO, recv_fds[0].get()); + CheckKcmpResult(STDIN_FILENO, recv_fds[1].get()); +} + +void ReceiveThreeFdsSendTwoBack(BrokerChannel::EndPoint* ipc_reader) { + // Receive two fds from |ipc_reader|. + BrokerSimpleMessage msg; + base::ScopedFD recv_fds[3]; + ssize_t len = + msg.RecvMsgWithFlagsMultipleFds(ipc_reader->get(), 0, {recv_fds}); + ASSERT_GE(len, 0) << "Error on RecvMsgWithFlags, errno = " << errno; + ASSERT_TRUE(recv_fds[0].is_valid()); + + if (!CheckKcmpResult(STDOUT_FILENO, recv_fds[1].get()) || + !CheckKcmpResult(STDIN_FILENO, recv_fds[2].get())) { + return; + } + + BrokerSimpleMessage resp; + int send_fds[2]; + send_fds[0] = recv_fds[1].get(); + send_fds[1] = recv_fds[2].get(); + resp.AddIntToMessage(0); // Dummy int to send message + ASSERT_TRUE(resp.SendMsgMultipleFds(recv_fds[0].get(), {send_fds})); +} +} // namespace + +class BrokerSimpleMessageFdTest : public testing::Test { + public: + void SetUp() override { +#if !defined(SANDBOX_USES_BASE_TEST_SUITE) + // TaskEnvironment requires initialized TestTimeouts, which are already + // enabled if using the base test suite. + TestTimeouts::Initialize(); +#endif + task_environment_ = std::make_unique<base::test::TaskEnvironment>(); + } + + bool SkipIfKcmpNotSupported() { + pid_t pid = getpid(); + if (syscall(__NR_kcmp, pid, pid, KCMP_FILE, STDOUT_FILENO, STDOUT_FILENO) < + 0) { + LOG(INFO) << "Skipping test, kcmp not supported."; + return false; + } + return true; + } + + private: + std::unique_ptr<base::test::TaskEnvironment> task_environment_; +}; + +// Passes one fd with RecvMsg, SendMsg. +TEST_F(BrokerSimpleMessageFdTest, PassOneFd) { + if (!SkipIfKcmpNotSupported()) + return; + + BrokerChannel::EndPoint ipc_reader; + BrokerChannel::EndPoint ipc_writer; + BrokerChannel::CreatePair(&ipc_reader, &ipc_writer); + base::RunLoop run_loop; + + base::ThreadPool::PostTaskAndReply( + FROM_HERE, base::BindOnce(&ReceiveStdoutDupFd, &ipc_reader), + run_loop.QuitClosure()); + + BrokerSimpleMessage msg; + msg.AddIntToMessage(0); // Must add a dummy value to send the message. + ASSERT_TRUE(msg.SendMsg(ipc_writer.get(), STDOUT_FILENO)); + + run_loop.Run(); +} + +TEST_F(BrokerSimpleMessageFdTest, PassTwoFds) { + if (!SkipIfKcmpNotSupported()) + return; + + BrokerChannel::EndPoint ipc_reader; + BrokerChannel::EndPoint ipc_writer; + BrokerChannel::CreatePair(&ipc_reader, &ipc_writer); + base::RunLoop run_loop; + + base::ThreadPool::PostTaskAndReply( + FROM_HERE, base::BindOnce(&ReceiveTwoDupFds, &ipc_reader), + run_loop.QuitClosure()); + + BrokerSimpleMessage msg; + msg.AddIntToMessage(0); // Must add a dummy value to send the message. + int send_fds[2]; + send_fds[0] = STDOUT_FILENO; + send_fds[1] = STDIN_FILENO; + ASSERT_TRUE(msg.SendMsgMultipleFds(ipc_writer.get(), {send_fds})); + + run_loop.Run(); +} + +TEST_F(BrokerSimpleMessageFdTest, SynchronousPassTwoFds) { + if (!SkipIfKcmpNotSupported()) + return; + + BrokerChannel::EndPoint ipc_reader; + BrokerChannel::EndPoint ipc_writer; + BrokerChannel::CreatePair(&ipc_reader, &ipc_writer); + base::RunLoop run_loop; + + base::ThreadPool::PostTaskAndReply( + FROM_HERE, base::BindOnce(&ReceiveThreeFdsSendTwoBack, &ipc_reader), + run_loop.QuitClosure()); + + BrokerSimpleMessage msg, reply; + msg.AddIntToMessage(0); // Must add a dummy value to send the message. + int send_fds[2]; + send_fds[0] = STDOUT_FILENO; + send_fds[1] = STDIN_FILENO; + base::ScopedFD result_fds[2]; + msg.SendRecvMsgWithFlagsMultipleFds(ipc_writer.get(), 0, {send_fds}, + {result_fds}, &reply); + + run_loop.Run(); + + ASSERT_TRUE(CheckKcmpResult(STDOUT_FILENO, result_fds[0].get())); + ASSERT_TRUE(CheckKcmpResult(STDIN_FILENO, result_fds[1].get())); +} + } // namespace syscall_broker } // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/remote_syscall_arg_handler.cc b/chromium/sandbox/linux/syscall_broker/remote_syscall_arg_handler.cc new file mode 100644 index 00000000000..f73113c771a --- /dev/null +++ b/chromium/sandbox/linux/syscall_broker/remote_syscall_arg_handler.cc @@ -0,0 +1,146 @@ +// Copyright 2020 The Chromium 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/remote_syscall_arg_handler.h" + +#include <string.h> +#include <sys/ioctl.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> + +#include "base/bits.h" +#include "base/check_op.h" +#include "base/containers/span.h" +#include "base/logging.h" +#include "base/process/process_metrics.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +#if defined(MEMORY_SANITIZER) +#include <sanitizer/msan_interface.h> +#endif + +namespace sandbox { +namespace syscall_broker { + +RemoteProcessIOResult WriteRemoteData(pid_t pid, + uintptr_t remote_addr, + size_t remote_size, + base::span<char> data) { + CHECK_GE(remote_size, data.size()); + + base::span<char> remote_span(reinterpret_cast<char*>(remote_addr), + remote_size); + struct iovec local_iov = {}; + struct iovec remote_iov = {}; + + while (!data.empty()) { + local_iov.iov_base = data.data(); + local_iov.iov_len = data.size(); + remote_iov.iov_base = remote_span.data(); + remote_iov.iov_len = data.size(); + + ssize_t bytes_written = syscall(__NR_process_vm_writev, pid, &local_iov, + 1ul, &remote_iov, 1ul, 0ul); + if (bytes_written < 0) { + if (errno == EFAULT) + return RemoteProcessIOResult::kRemoteMemoryInvalid; + if (errno == ESRCH) + return RemoteProcessIOResult::kRemoteExited; + PLOG(ERROR) + << "process_vm_writev() failed with unknown error code! Write to pid " + << pid << " at remote address " << remote_iov.iov_base + << " of length " << data.size() << ". "; + return RemoteProcessIOResult::kUnknownError; + } + + remote_span = remote_span.subspan(bytes_written); + data = data.subspan(bytes_written); + } + + return RemoteProcessIOResult::kSuccess; +} + +RemoteProcessIOResult ReadFilePathFromRemoteProcess(pid_t pid, + const void* remote_addr, + std::string* out_str) { + // Most pathnames will be small so avoid copying PATH_MAX bytes every time, + // by reading in chunks and checking if the the string ends within the + // chunk. + char buffer[PATH_MAX]; + base::span<char> buffer_span(buffer); + + struct iovec local_iov = {}; + struct iovec remote_iov = {}; + + uintptr_t remote_ptr = reinterpret_cast<uintptr_t>(remote_addr); + + for (;;) { + uintptr_t bytes_left_in_page = internal::NumBytesLeftInPage(remote_ptr); + + // Read the minimum of the chunk size, remaining local buffer size, and + // the number of bytes left in the remote page. + size_t bytes_to_read = std::min( + {internal::kNumBytesPerChunk, buffer_span.size(), bytes_left_in_page}); + + // Set up the iovecs. + local_iov.iov_base = buffer_span.data(); + local_iov.iov_len = bytes_to_read; + + remote_iov.iov_base = reinterpret_cast<void*>(remote_ptr); + remote_iov.iov_len = bytes_to_read; + + // The arguments below must include the ul suffix since they need to be + // 64-bit values, but syscall() takes varargs and doesn't know to promote + // them from 32-bit to 64-bit. + ssize_t bytes_read = syscall(__NR_process_vm_readv, pid, &local_iov, 1ul, + &remote_iov, 1ul, 0ul); + if (bytes_read < 0) { + if (errno == EFAULT) + return RemoteProcessIOResult::kRemoteMemoryInvalid; + if (errno == ESRCH) + return RemoteProcessIOResult::kRemoteExited; + PLOG(ERROR) + << "process_vm_readv() failed with unknown error code! Read from pid " + << pid << " at remote address " << remote_iov.iov_base + << " of length " << bytes_to_read << ". "; + return RemoteProcessIOResult::kUnknownError; + } + + // We successfully performed a read. +#if defined(MEMORY_SANITIZER) + // Msan does not hook syscall(__NR_process_vm_readv, ...) + __msan_unpoison(local_iov.iov_base, bytes_read); +#endif + remote_ptr += bytes_read; + buffer_span = buffer_span.subspan(bytes_read); + + // Check for null byte. + char* null_byte_ptr = + static_cast<char*>(memchr(local_iov.iov_base, '\0', bytes_read)); + if (null_byte_ptr) { + *out_str = std::string(buffer, null_byte_ptr); + return RemoteProcessIOResult::kSuccess; + } + + if (buffer_span.empty()) { + // If we haven't found a null byte yet and our available buffer space is + // empty, stop. + LOG(ERROR) << "Read PATH_MAX bytes in sandboxed process and did not find " + "expected null byte."; + return RemoteProcessIOResult::kExceededPathMax; + } + } +} + +namespace internal { +uintptr_t NumBytesLeftInPage(uintptr_t addr) { + const uintptr_t page_end = base::bits::Align(addr + 1, base::GetPageSize()); + return page_end - addr; +} +} // namespace internal +} // namespace syscall_broker +} // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/remote_syscall_arg_handler.h b/chromium/sandbox/linux/syscall_broker/remote_syscall_arg_handler.h new file mode 100644 index 00000000000..3c06287b7eb --- /dev/null +++ b/chromium/sandbox/linux/syscall_broker/remote_syscall_arg_handler.h @@ -0,0 +1,52 @@ +// Copyright 2020 The Chromium 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_REMOTE_SYSCALL_ARG_HANDLER_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_REMOTE_SYSCALL_ARG_HANDLER_H_ + +#include <unistd.h> + +#include "base/containers/span.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace syscall_broker { + +enum class RemoteProcessIOResult { + kSuccess, + kRemoteExited, + kExceededPathMax, + kRemoteMemoryInvalid, + kUnknownError +}; + +// Writes |data| at |remote_addr| in |pid|'s address space. Returns the +// appropriate result. +SANDBOX_EXPORT RemoteProcessIOResult WriteRemoteData(pid_t pid, + uintptr_t remote_addr, + size_t remote_size, + base::span<char> data); + +// Reads a filepath from |remote_addr| (which points into process |pid|'s memory +// space) into |*out_str|. Returns the appropriate result. +// Safety checks should occur before usage of any system call arguments read +// from a remote address space, so callers should use RemoteSyscallFilepathArgs +// instead of calling this directly. +SANDBOX_EXPORT RemoteProcessIOResult +ReadFilePathFromRemoteProcess(pid_t pid, + const void* remote_addr, + std::string* out_str); + +namespace internal { +// The number of bytes we read from a remote process at a time when reading a +// remote filepath, to avoid reading PATH_MAX bytes every time. +const size_t kNumBytesPerChunk = 256; + +// Calculates the number of bytes left in a page for a particular address. +uintptr_t NumBytesLeftInPage(uintptr_t addr); +} // namespace internal +} // namespace syscall_broker +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_REMOTE_SYSCALL_ARG_HANDLER_H_ diff --git a/chromium/sandbox/linux/syscall_broker/remote_syscall_arg_handler_unittest.cc b/chromium/sandbox/linux/syscall_broker/remote_syscall_arg_handler_unittest.cc new file mode 100644 index 00000000000..c452d1d02d6 --- /dev/null +++ b/chromium/sandbox/linux/syscall_broker/remote_syscall_arg_handler_unittest.cc @@ -0,0 +1,346 @@ +// Copyright 2020 The Chromium 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/remote_syscall_arg_handler.h" + +#include <sys/mman.h> +#include <sys/types.h> +#include <algorithm> +#include <cstring> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback_forward.h" +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "base/posix/unix_domain_socket.h" +#include "base/process/process_metrics.h" +#include "base/test/bind_test_util.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { +namespace syscall_broker { + +namespace { +const char kPathPart[] = "/i/am/path"; + +void FillBufferWithPath(char* buf, size_t size, bool null_terminate) { + SANDBOX_ASSERT_LE(size, static_cast<size_t>(PATH_MAX)); + size_t str_len = strlen(kPathPart); + size_t len_left_to_write = size; + char* curr_buf_pos = buf; + while (len_left_to_write > 0) { + size_t bytes_to_write = std::min(str_len, len_left_to_write); + memcpy(curr_buf_pos, kPathPart, bytes_to_write); + curr_buf_pos += bytes_to_write; + len_left_to_write -= bytes_to_write; + } + + if (null_terminate) { + buf[size - 1] = '\0'; + } +} + +void VerifyCorrectString(std::string str, size_t size) { + SANDBOX_ASSERT_EQ(str.size(), size); + size_t curr_path_part_pos = 0; + for (char ch : str) { + SANDBOX_ASSERT(ch == kPathPart[curr_path_part_pos]); + curr_path_part_pos++; + curr_path_part_pos %= strlen(kPathPart); + } +} + +void* MapPagesOrDie(size_t num_pages) { + void* addr = mmap(nullptr, num_pages * base::GetPageSize(), + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + PCHECK(addr); + return addr; +} + +void MprotectLastPageOrDie(char* addr, size_t num_pages) { + size_t last_page_offset = (num_pages - 1) * base::GetPageSize(); + PCHECK(mprotect(addr + last_page_offset, base::GetPageSize(), PROT_NONE) >= + 0); +} + +pid_t ForkWaitingChild(base::OnceCallback<void(int)> + after_parent_signals_callback = base::DoNothing(), + base::ScopedFD* parent_sync_fd = nullptr) { + base::ScopedFD parent_sync, child_sync; + base::CreateSocketPair(&parent_sync, &child_sync); + + pid_t pid = fork(); + if (!pid) { + parent_sync.reset(); + char dummy_char = 'a'; + std::vector<base::ScopedFD> empty_fd_vec; + // Wait for parent to exit before exiting ourselves. + base::UnixDomainSocket::RecvMsg(child_sync.get(), &dummy_char, 1, + &empty_fd_vec); + std::move(after_parent_signals_callback).Run(child_sync.get()); + _exit(1); + } + + child_sync.reset(); + + if (parent_sync_fd) + *parent_sync_fd = std::move(parent_sync); + else + ignore_result(parent_sync.release()); // Closes when parent dies. + return pid; +} + +struct ReadTestConfig { + size_t start_at = 0; + size_t total_size = strlen(kPathPart) + 1; + bool include_null_byte = true; + bool last_page_inaccessible = false; + RemoteProcessIOResult result = RemoteProcessIOResult::kSuccess; +}; + +void ReadTest(const ReadTestConfig& test_config) { + // Map exactly the right number of pages for the config parameters. + size_t total_pages = (test_config.start_at + test_config.total_size + + base::GetPageSize() - 1) / + base::GetPageSize(); + char* mmap_addr = static_cast<char*>(MapPagesOrDie(total_pages)); + char* addr = mmap_addr + test_config.start_at; + FillBufferWithPath(addr, test_config.total_size, + test_config.include_null_byte); + + if (test_config.last_page_inaccessible) + MprotectLastPageOrDie(mmap_addr, total_pages); + + pid_t pid = ForkWaitingChild(); + munmap(mmap_addr, base::GetPageSize() * total_pages); + + std::string out_str; + SANDBOX_ASSERT_EQ(ReadFilePathFromRemoteProcess(pid, addr, &out_str), + test_config.result); + if (test_config.result == RemoteProcessIOResult::kSuccess) { + VerifyCorrectString(std::move(out_str), test_config.total_size - 1); + } +} +} // namespace + +// | path + null_byte | +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, BasicRead) { + ReadTest(ReadTestConfig()); +} + +// | zero + path... | ...path + null_byte + zero | +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, MultipageRead) { + ReadTestConfig config; + CHECK(PATH_MAX / 2 <= base::GetPageSize()); + config.start_at = base::GetPageSize() - (PATH_MAX / 2); + config.total_size = PATH_MAX; + + ReadTest(config); +} + +// | path... | ...path | +SANDBOX_TEST_ALLOW_NOISE(BrokerRemoteSyscallArgHandler, ReadExceededPathMax) { + ReadTestConfig config; + config.total_size = PATH_MAX * 2; + config.result = RemoteProcessIOResult::kExceededPathMax; +} +// | path... | null_byte + zero | +SANDBOX_TEST_ALLOW_NOISE(BrokerRemoteSyscallArgHandler, + ReadBarelyExceededPathMax) { + ReadTestConfig config; + config.total_size = PATH_MAX + 1; + config.result = RemoteProcessIOResult::kExceededPathMax; +} + +// | zero + path... | INACCESSIBLE | +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, ReadUnreadablePage) { + ReadTestConfig config; + config.start_at = base::GetPageSize() - (PATH_MAX / 2); + config.total_size = PATH_MAX / 2; + config.last_page_inaccessible = true; + config.include_null_byte = false; + config.result = RemoteProcessIOResult::kRemoteMemoryInvalid; + + ReadTest(config); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, ReadChunkMinus1) { + ReadTestConfig config; + config.total_size = internal::kNumBytesPerChunk - 1; + + ReadTest(config); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, ReadChunk) { + ReadTestConfig config; + config.total_size = internal::kNumBytesPerChunk; + + ReadTest(config); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, ReadChunkPlus1) { + ReadTestConfig config; + config.total_size = internal::kNumBytesPerChunk + 1; + + ReadTest(config); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, ReadChunkEndingAtPage) { + ReadTestConfig config; + config.start_at = base::GetPageSize() - internal::kNumBytesPerChunk; + config.total_size = internal::kNumBytesPerChunk; + + ReadTest(config); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, ReadChunkEndingOnePastPage) { + ReadTestConfig config; + config.start_at = base::GetPageSize() - internal::kNumBytesPerChunk + 1; + config.total_size = internal::kNumBytesPerChunk; + + ReadTest(config); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, ReadChunkPlus1EndingOnePastPage) { + ReadTestConfig config; + config.start_at = base::GetPageSize() - internal::kNumBytesPerChunk; + config.total_size = internal::kNumBytesPerChunk + 1; + + ReadTest(config); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, ReadChildExited) { + void* addr = MapPagesOrDie(1); + FillBufferWithPath(static_cast<char*>(addr), strlen(kPathPart) + 1, true); + + base::ScopedFD parent_sync, child_sync; + base::CreateSocketPair(&parent_sync, &child_sync); + + pid_t pid = fork(); + if (!pid) { + parent_sync.reset(); + _exit(1); + } + + child_sync.reset(); + + // Wait for child to exit before reading memory. + char dummy_char = 'a'; + std::vector<base::ScopedFD> empty_fd_vec; + base::UnixDomainSocket::RecvMsg(parent_sync.get(), &dummy_char, 1, + &empty_fd_vec); + + munmap(addr, base::GetPageSize()); + + std::string out_str; + SANDBOX_ASSERT_EQ(ReadFilePathFromRemoteProcess(pid, addr, &out_str), + RemoteProcessIOResult::kRemoteExited); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, BasicWrite) { + void* read_from = MapPagesOrDie(1); + const size_t write_size = base::GetPageSize(); + FillBufferWithPath(static_cast<char*>(read_from), write_size, false); + char* write_to = static_cast<char*>(MapPagesOrDie(1)); + base::ScopedFD parent_signal_fd; + const std::vector<int> empty_fd_vec; + + pid_t pid = + ForkWaitingChild(base::BindLambdaForTesting([=](int child_sync_fd) { + // Check correct result received and tell parent about + // success. + int res = memcmp(read_from, write_to, write_size); + + base::UnixDomainSocket::SendMsg( + child_sync_fd, &res, sizeof(res), empty_fd_vec); + _exit(1); + }), + &parent_signal_fd); + + RemoteProcessIOResult result = WriteRemoteData( + pid, reinterpret_cast<uintptr_t>(write_to), write_size, + base::span<char>(static_cast<char*>(read_from), write_size)); + SANDBOX_ASSERT_EQ(result, RemoteProcessIOResult::kSuccess); + + // Release child. + char dummy_char = 'a'; + base::UnixDomainSocket::SendMsg(parent_signal_fd.get(), &dummy_char, 1, + empty_fd_vec); + + // Read result of memcmp and assert. + int memcmp_res; + std::vector<base::ScopedFD> dummy_fd_vec; + base::UnixDomainSocket::RecvMsg(parent_signal_fd.get(), &memcmp_res, + sizeof(memcmp_res), &dummy_fd_vec); + SANDBOX_ASSERT_EQ(memcmp_res, 0); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, WriteToInvalidAddress) { + char* write_to = static_cast<char*>(MapPagesOrDie(1)); + MprotectLastPageOrDie(write_to, 1); + base::ScopedFD parent_signal_fd; + const std::vector<int> empty_fd_vec; + + pid_t pid = ForkWaitingChild(); + munmap(write_to, base::GetPageSize()); + + char buf[5]; + memset(buf, 'a', sizeof(buf)); + RemoteProcessIOResult result = + WriteRemoteData(pid, reinterpret_cast<uintptr_t>(write_to), sizeof(buf), + base::span<char>(buf, sizeof(buf))); + SANDBOX_ASSERT_EQ(result, RemoteProcessIOResult::kRemoteMemoryInvalid); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, WritePartiallyToInvalidAddress) { + char* read_from = static_cast<char*>(MapPagesOrDie(2)); + const size_t write_size = base::GetPageSize(); + FillBufferWithPath(static_cast<char*>(read_from), write_size, false); + char* write_to = static_cast<char*>(MapPagesOrDie(2)); + MprotectLastPageOrDie(write_to, 2); + write_to += base::GetPageSize() / 2; + base::ScopedFD parent_signal_fd; + const std::vector<int> empty_fd_vec; + + pid_t pid = ForkWaitingChild(); + munmap(write_to, base::GetPageSize()); + + RemoteProcessIOResult result = + WriteRemoteData(pid, reinterpret_cast<uintptr_t>(write_to), write_size, + base::span<char>(read_from, write_size)); + SANDBOX_ASSERT_EQ(result, RemoteProcessIOResult::kRemoteMemoryInvalid); +} + +SANDBOX_TEST(BrokerRemoteSyscallArgHandler, WriteChildExited) { + char* addr = static_cast<char*>(MapPagesOrDie(1)); + FillBufferWithPath(static_cast<char*>(addr), strlen(kPathPart) + 1, true); + + base::ScopedFD parent_sync, child_sync; + base::CreateSocketPair(&parent_sync, &child_sync); + + pid_t pid = fork(); + if (!pid) { + parent_sync.reset(); + _exit(1); + } + + child_sync.reset(); + + // Wait for child to exit before writing memory. + char dummy_char = 'a'; + std::vector<base::ScopedFD> empty_fd_vec; + base::UnixDomainSocket::RecvMsg(parent_sync.get(), &dummy_char, 1, + &empty_fd_vec); + + std::string out_str; + SANDBOX_ASSERT_EQ( + WriteRemoteData(pid, reinterpret_cast<uintptr_t>(addr), strlen(kPathPart), + base::span<char>(addr, strlen(kPathPart))), + RemoteProcessIOResult::kRemoteExited); +} + +} // namespace syscall_broker +} // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/syscall_dispatcher.cc b/chromium/sandbox/linux/syscall_broker/syscall_dispatcher.cc new file mode 100644 index 00000000000..b9ee93c14ac --- /dev/null +++ b/chromium/sandbox/linux/syscall_broker/syscall_dispatcher.cc @@ -0,0 +1,191 @@ +// Copyright 2020 The Chromium 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/syscall_dispatcher.h" + +#include <fcntl.h> + +#include "base/check.h" +#include "base/logging.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { +namespace syscall_broker { + +#if defined(MEMORY_SANITIZER) +#define BROKER_UNPOISON_STRING(x) __msan_unpoison_string(x) +#else +#define BROKER_UNPOISON_STRING(x) +#endif + +int SyscallDispatcher::PerformStatat(const arch_seccomp_data& args, + bool arch64) { + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + // Only allow the AT_SYMLINK_NOFOLLOW flag which is used by some libc + // implementations for lstat(). + if ((static_cast<int>(args.args[3]) & ~AT_SYMLINK_NOFOLLOW) != 0) + return -EINVAL; + + const bool follow_links = + !(static_cast<int>(args.args[3]) & AT_SYMLINK_NOFOLLOW); + if (arch64) { + return Stat64(reinterpret_cast<const char*>(args.args[1]), follow_links, + reinterpret_cast<struct stat64*>(args.args[2])); + } + + return Stat(reinterpret_cast<const char*>(args.args[1]), follow_links, + reinterpret_cast<struct stat*>(args.args[2])); +} + +int SyscallDispatcher::DispatchSyscall(const arch_seccomp_data& args) { + switch (args.nr) { +#if defined(__NR_access) + case __NR_access: + return Access(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); +#endif +#if defined(__NR_faccessat) + case __NR_faccessat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + return Access(reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); +#endif +#if defined(__NR_mkdir) + case __NR_mkdir: + return Mkdir(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); +#endif +#if defined(__NR_mkdirat) + case __NR_mkdirat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + return Mkdir(reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); +#endif +#if defined(__NR_open) + case __NR_open: + // http://crbug.com/372840 + BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[0])); + return Open(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); +#endif +#if defined(__NR_openat) + case __NR_openat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + // http://crbug.com/372840 + BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[1])); + return Open(reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); +#endif +#if defined(__NR_readlink) + case __NR_readlink: + return Readlink(reinterpret_cast<const char*>(args.args[0]), + reinterpret_cast<char*>(args.args[1]), + static_cast<size_t>(args.args[2])); +#endif +#if defined(__NR_readlinkat) + case __NR_readlinkat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + return Readlink(reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<char*>(args.args[2]), + static_cast<size_t>(args.args[3])); +#endif +#if defined(__NR_rename) + case __NR_rename: + return Rename(reinterpret_cast<const char*>(args.args[0]), + reinterpret_cast<const char*>(args.args[1])); +#endif +#if defined(__NR_renameat) + case __NR_renameat: + if (static_cast<int>(args.args[0]) != AT_FDCWD || + static_cast<int>(args.args[2]) != AT_FDCWD) { + return -EPERM; + } + return Rename(reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<const char*>(args.args[3])); +#endif +#if defined(__NR_renameat2) + case __NR_renameat2: + if (static_cast<int>(args.args[0]) != AT_FDCWD || + static_cast<int>(args.args[2]) != AT_FDCWD) { + return -EPERM; + } + if (static_cast<int>(args.args[4]) != 0) + return -EINVAL; + return Rename(reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<const char*>(args.args[3])); +#endif +#if defined(__NR_rmdir) + case __NR_rmdir: + return Rmdir(reinterpret_cast<const char*>(args.args[0])); +#endif +#if defined(__NR_stat) + case __NR_stat: + return Stat(reinterpret_cast<const char*>(args.args[0]), true, + reinterpret_cast<struct stat*>(args.args[1])); +#endif +#if defined(__NR_stat64) + case __NR_stat64: + return Stat64(reinterpret_cast<const char*>(args.args[0]), true, + reinterpret_cast<struct stat64*>(args.args[1])); +#endif +#if defined(__NR_lstat) + case __NR_lstat: + // See https://crbug.com/847096 + BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[0])); + return Stat(reinterpret_cast<const char*>(args.args[0]), false, + reinterpret_cast<struct stat*>(args.args[1])); +#endif +#if defined(__NR_lstat64) + case __NR_lstat64: + // See https://crbug.com/847096 + BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[0])); + return Stat64(reinterpret_cast<const char*>(args.args[0]), false, + reinterpret_cast<struct stat64*>(args.args[1])); +#endif +#if defined(__NR_fstatat) + case __NR_fstatat: + return PerformStatat(args, /*arch64=*/false); +#endif +#if defined(__NR_fstatat64) + case __NR_fstatat64: + return PerformStatat(args, /*arch64=*/true); +#endif +#if defined(__NR_newfstatat) + case __NR_newfstatat: + return PerformStatat(args, /*arch64=*/false); +#endif +#if defined(__NR_unlink) + case __NR_unlink: + return Unlink(reinterpret_cast<const char*>(args.args[0])); +#endif +#if defined(__NR_unlinkat) + case __NR_unlinkat: { + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + + int flags = static_cast<int>(args.args[2]); + + if (flags == AT_REMOVEDIR) { + return Rmdir(reinterpret_cast<const char*>(args.args[1])); + } + + if (flags != 0) + return -EPERM; + + return Unlink(reinterpret_cast<const char*>(args.args[1])); + } +#endif // defined(__NR_unlinkat) + default: + RAW_CHECK(false); + return -ENOSYS; + } +} + +} // namespace syscall_broker +} // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/syscall_dispatcher.h b/chromium/sandbox/linux/syscall_broker/syscall_dispatcher.h new file mode 100644 index 00000000000..d8b8874ad9c --- /dev/null +++ b/chromium/sandbox/linux/syscall_broker/syscall_dispatcher.h @@ -0,0 +1,70 @@ +// Copyright 2020 The Chromium 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_SYSCALL_DISPATCHER_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_SYSCALL_DISPATCHER_H_ + +#include <sys/stat.h> +#include <cstddef> + +#include "sandbox/linux/system_headers/linux_seccomp.h" + +namespace sandbox { +namespace syscall_broker { + +// An abstract class that defines all the system calls we perform for the +// sandboxed process. +class SyscallDispatcher { + public: + // Emulates access()/faccessat(). + // X_OK will always return an error in practice since the broker process + // doesn't support execute permissions. + virtual int Access(const char* pathname, int mode) const = 0; + + // Emulates mkdir()/mkdirat. + virtual int Mkdir(const char* path, int mode) const = 0; + + // Emulates open()/openat(). + // The implementation only supports certain white listed flags and will + // return -EPERM on other flags. + virtual int Open(const char* pathname, int flags) const = 0; + + // Emulates readlink()/readlinkat(). + virtual int Readlink(const char* path, char* buf, size_t bufsize) const = 0; + + // Emulates rename()/renameat()/renameat2(). + virtual int Rename(const char* oldpath, const char* newpath) const = 0; + + // Emulates rmdir(). + virtual int Rmdir(const char* path) const = 0; + + // Emulates stat()/stat64()/lstat()/lstat64()/fstatat()/newfstatat(). + virtual int Stat(const char* pathname, + bool follow_links, + struct stat* sb) const = 0; + virtual int Stat64(const char* pathname, + bool follow_links, + struct stat64* sb) const = 0; + + // Emulates unlink()/unlinkat(). + virtual int Unlink(const char* unlink) const = 0; + + // Validates the args passed to a *statat*() syscall and performs the syscall + // using Stat() or Stat64(). + int PerformStatat(const arch_seccomp_data& args, bool arch64); + + // Reads the syscall number and arguments, imposes some policy (e.g. the *at() + // system calls must only allow AT_FDCWD as the first argument), and + // dispatches to the correct method from above. + // Async-signal-safe since this might be called in a signal handler. + int DispatchSyscall(const arch_seccomp_data& args); + + protected: + virtual ~SyscallDispatcher() = default; +}; + +} // namespace syscall_broker +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_SYSCALL_DISPATCHER_H_ diff --git a/chromium/sandbox/linux/system_headers/linux_prctl.h b/chromium/sandbox/linux/system_headers/linux_prctl.h new file mode 100644 index 00000000000..50f639b0fef --- /dev/null +++ b/chromium/sandbox/linux/system_headers/linux_prctl.h @@ -0,0 +1,35 @@ +// Copyright 2020 The Chromium 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_PRCTL_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_PRCTL_H_ + +#include "build/build_config.h" + +#if !defined(PR_SET_PDEATHSIG) +#define PR_SET_PDEATHSIG 1 +#endif + +#if !defined(PR_SET_TIMERSLACK) +#define PR_SET_TIMERSLACK 29 +#endif + +#if defined(OS_ANDROID) + +// https://android.googlesource.com/platform/bionic/+/lollipop-release/libc/private/bionic_prctl.h +#if !defined(PR_SET_VMA) +#define PR_SET_VMA 0x53564d41 +#endif + +#endif // defined(OS_ANDROID) + +#if !defined(PR_SET_PTRACER) +#define PR_SET_PTRACER 0x59616d61 +#endif + +#if !defined(PR_SET_PTRACER_ANY) +#define PR_SET_PTRACER_ANY ((unsigned long)-1) +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_PRCTL_H_ diff --git a/chromium/sandbox/linux/system_headers/linux_seccomp.h b/chromium/sandbox/linux/system_headers/linux_seccomp.h index ab1d1fc2b5d..1fa47ed09ff 100644 --- a/chromium/sandbox/linux/system_headers/linux_seccomp.h +++ b/chromium/sandbox/linux/system_headers/linux_seccomp.h @@ -5,6 +5,14 @@ #ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SECCOMP_H_ #define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SECCOMP_H_ +#include <stdint.h> + +#include "build/build_config.h" + +#if !defined(OS_NACL_NONSFI) +#include <sys/ioctl.h> +#endif + // The Seccomp2 kernel ABI is not part of older versions of glibc. // As we can't break compilation with these versions of the library, // we explicitly define all missing symbols. @@ -17,15 +25,19 @@ #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 @@ -33,24 +45,31 @@ #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_MIPSEL64 #define AUDIT_ARCH_MIPSEL64 (EM_MIPS|__AUDIT_ARCH_64BIT|__AUDIT_ARCH_LE) #endif + #ifndef AUDIT_ARCH_AARCH64 #define AUDIT_ARCH_AARCH64 (EM_AARCH64 | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE) #endif @@ -60,23 +79,29 @@ #define PR_SET_SECCOMP 22 #define PR_GET_SECCOMP 21 #endif + #ifndef PR_SET_NO_NEW_PRIVS #define PR_SET_NO_NEW_PRIVS 38 #define PR_GET_NO_NEW_PRIVS 39 #endif + #ifndef IPC_64 #define IPC_64 0x0100 #endif + #ifndef PR_SET_SPECULATION_CTRL #define PR_SET_SPECULATION_CTRL 53 #define PR_GET_SPECULATION_CTRL 52 #endif + #ifndef PR_SPEC_INDIRECT_BRANCH #define PR_SPEC_INDIRECT_BRANCH 1 #endif + #ifndef PR_SPEC_PRCTL #define PR_SPEC_PRCTL (1UL << 0) #endif + #ifndef PR_SPEC_FORCE_DISABLE #define PR_SPEC_FORCE_DISABLE (1UL << 3) #endif @@ -96,25 +121,101 @@ #ifndef SECCOMP_SET_MODE_FILTER #define SECCOMP_SET_MODE_FILTER 1 #endif +#ifndef SECCOMP_GET_NOTIF_SIZES +#define SECCOMP_GET_NOTIF_SIZES 3 +#endif + #ifndef SECCOMP_FILTER_FLAG_TSYNC #define SECCOMP_FILTER_FLAG_TSYNC 1 #endif + #ifndef SECCOMP_FILTER_FLAG_SPEC_ALLOW #define SECCOMP_FILTER_FLAG_SPEC_ALLOW (1UL << 2) #endif +#ifndef SECCOMP_FILTER_FLAG_NEW_LISTENER +#define SECCOMP_FILTER_FLAG_NEW_LISTENER (1UL << 3) +#endif + +#ifndef SECCOMP_FILTER_FLAG_TSYNC_ESRCH +#define SECCOMP_FILTER_FLAG_TSYNC_ESRCH (1UL << 4) +#endif + +#ifndef SECCOMP_ADDFD_FLAG_SETFD +#define SECCOMP_ADDFD_FLAG_SETFD (1UL << 0) +#endif + +// In the future, if we add fields to these structs and then access them, they +// might be out-of-bounds on an older kernel. So before adding to these structs, +// make sure to annotate them with a comment that it may be unsafe to access +// those fields on older kernels. +struct arch_seccomp_data { + int nr; + uint32_t arch; + uint64_t instruction_pointer; + uint64_t args[6]; +}; + +struct seccomp_notif_sizes { + uint16_t seccomp_notif; + uint16_t seccomp_notif_resp; + uint16_t seccomp_data; +}; + +struct seccomp_notif { + uint64_t id; + uint32_t pid; + uint32_t flags; + struct arch_seccomp_data data; +}; + +struct seccomp_notif_resp { + uint64_t id; + int64_t val; + int32_t error; + uint32_t flags; +}; + +struct seccomp_notif_addfd { + uint64_t id; + uint32_t flags; + uint32_t srcfd; + uint32_t newfd; + uint32_t newfd_flags; +}; + +// sys/ioctl.h is not available in pnacl toolchain. +#if !defined(OS_NACL_NONSFI) +#define SECCOMP_IOC_MAGIC '!' +#define SECCOMP_IO(nr) _IO(SECCOMP_IOC_MAGIC, nr) +#define SECCOMP_IOR(nr, type) _IOR(SECCOMP_IOC_MAGIC, nr, type) +#define SECCOMP_IOW(nr, type) _IOW(SECCOMP_IOC_MAGIC, nr, type) +#define SECCOMP_IOWR(nr, type) _IOWR(SECCOMP_IOC_MAGIC, nr, type) + +// Flags for seccomp notification fd ioctl. +#define SECCOMP_IOCTL_NOTIF_RECV SECCOMP_IOWR(0, struct seccomp_notif) +#define SECCOMP_IOCTL_NOTIF_SEND SECCOMP_IOWR(1, struct seccomp_notif_resp) +// Note: SECCOMP_IOCTL_NOTIF_ID_VALID is now defined with SECCOMP_IOW, but +// kernels are expected to support the (now incorrect) ioctl number for the +// foreseeable future. +#define SECCOMP_IOCTL_NOTIF_ID_VALID SECCOMP_IOR(2, uint64_t) +// On success, the return value is the remote process's added fd number +#define SECCOMP_IOCTL_NOTIF_ADDFD SECCOMP_IOW(3, struct seccomp_notif_addfd) +#endif // !defined(OS_NACL_NONSFI) + #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 +#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_USER_NOTIF 0x7fc00000U // Notifies userspace +#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 diff --git a/chromium/sandbox/policy/BUILD.gn b/chromium/sandbox/policy/BUILD.gn index 6a148bb4c81..ccb1bd1fd8d 100644 --- a/chromium/sandbox/policy/BUILD.gn +++ b/chromium/sandbox/policy/BUILD.gn @@ -21,12 +21,12 @@ component("policy") { "switches.h", ] defines = [ "SANDBOX_POLICY_IMPL" ] - public_deps = [ "//services/service_manager/embedder:embedder_switches" ] deps = [ ":sanitizer_buildflags", "//base", "//sandbox:common", ] + public_deps = [] if (is_linux || is_chromeos) { sources += [ "linux/bpf_audio_policy_linux.cc", @@ -159,7 +159,10 @@ source_set("tests") { ] if (is_win) { - sources += [ "win/sandbox_win_unittest.cc" ] + sources += [ + "win/mf_cdm_sandbox_type_unittest.cc", + "win/sandbox_win_unittest.cc", + ] deps += [ "//sandbox/win:sandbox" ] data = [ "//base/test/data/pe_image/pe_image_test_32.dll", diff --git a/chromium/sandbox/policy/DEPS b/chromium/sandbox/policy/DEPS index 0f5bfa1df86..804a308655f 100644 --- a/chromium/sandbox/policy/DEPS +++ b/chromium/sandbox/policy/DEPS @@ -1,5 +1,4 @@ include_rules = [ "+sandbox/constants.h", "+sandbox", - "+services/service_manager/embedder/switches.h", ] diff --git a/chromium/sandbox/policy/features.cc b/chromium/sandbox/policy/features.cc index 7930f18b027..bbb78b3b23d 100644 --- a/chromium/sandbox/policy/features.cc +++ b/chromium/sandbox/policy/features.cc @@ -36,20 +36,6 @@ const base::Feature kGpuLPAC{"GpuLPAC", base::FEATURE_ENABLED_BY_DEFAULT}; const base::Feature kXRSandbox{"XRSandbox", base::FEATURE_ENABLED_BY_DEFAULT}; #endif // !defined(OS_ANDROID) -#if defined(OS_CHROMEOS) -// Controls whether the Spectre variant 2 mitigation is enabled. We use a USE -// flag on some Chrome OS boards to disable the mitigation by disabling this -// feature in exchange for system performance. -const base::Feature kSpectreVariant2Mitigation{ - "SpectreVariant2Mitigation", base::FEATURE_ENABLED_BY_DEFAULT}; - -// An override for the Spectre variant 2 default behavior. Security sensitive -// users can enable this feature to ensure that the mitigation is always -// enabled. -const base::Feature kForceSpectreVariant2Mitigation{ - "ForceSpectreVariant2Mitigation", base::FEATURE_DISABLED_BY_DEFAULT}; -#endif // defined(OS_CHROMEOS) - } // namespace features } // namespace policy } // namespace sandbox diff --git a/chromium/sandbox/policy/features.h b/chromium/sandbox/policy/features.h index b86d0938b61..cc9b5e68162 100644 --- a/chromium/sandbox/policy/features.h +++ b/chromium/sandbox/policy/features.h @@ -30,12 +30,6 @@ SANDBOX_POLICY_EXPORT extern const base::Feature kGpuLPAC; SANDBOX_POLICY_EXPORT extern const base::Feature kXRSandbox; #endif // !defined(OS_ANDROID) -#if defined(OS_CHROMEOS) -SANDBOX_POLICY_EXPORT extern const base::Feature kSpectreVariant2Mitigation; -SANDBOX_POLICY_EXPORT extern const base::Feature - kForceSpectreVariant2Mitigation; -#endif // defined(OS_CHROMEOS) - } // namespace features } // namespace policy } // namespace sandbox diff --git a/chromium/sandbox/policy/linux/bpf_audio_policy_linux.cc b/chromium/sandbox/policy/linux/bpf_audio_policy_linux.cc index fba1b9eb089..d7737303d9b 100644 --- a/chromium/sandbox/policy/linux/bpf_audio_policy_linux.cc +++ b/chromium/sandbox/policy/linux/bpf_audio_policy_linux.cc @@ -128,9 +128,9 @@ ResultExpr AudioProcessPolicy::EvaluateSyscall(int system_call_number) const { return Allow(); #endif - auto* broker_process = SandboxLinux::GetInstance()->broker_process(); - if (broker_process->IsSyscallAllowed(system_call_number)) - return Trap(BrokerProcess::SIGSYS_Handler, broker_process); + auto* sandbox_linux = SandboxLinux::GetInstance(); + if (sandbox_linux->ShouldBrokerHandleSyscall(system_call_number)) + return sandbox_linux->HandleViaBroker(); return BPFBasePolicy::EvaluateSyscall(system_call_number); } diff --git a/chromium/sandbox/policy/linux/bpf_broker_policy_linux.h b/chromium/sandbox/policy/linux/bpf_broker_policy_linux.h index 0fe3515a985..1d394c551a5 100644 --- a/chromium/sandbox/policy/linux/bpf_broker_policy_linux.h +++ b/chromium/sandbox/policy/linux/bpf_broker_policy_linux.h @@ -14,7 +14,7 @@ namespace sandbox { namespace policy { // A broker policy is one for a privileged syscall broker that allows -// access, open, openat, and (in the non-Chrome OS case) unlink. +// a limited set of filesystem calls. class SANDBOX_POLICY_EXPORT BrokerProcessPolicy : public BPFBasePolicy { public: explicit BrokerProcessPolicy( diff --git a/chromium/sandbox/policy/linux/bpf_gpu_policy_linux.cc b/chromium/sandbox/policy/linux/bpf_gpu_policy_linux.cc index 127989715b3..24081195c4a 100644 --- a/chromium/sandbox/policy/linux/bpf_gpu_policy_linux.cc +++ b/chromium/sandbox/policy/linux/bpf_gpu_policy_linux.cc @@ -100,10 +100,9 @@ ResultExpr GpuProcessPolicy::EvaluateSyscall(int sysno) const { return Allow(); #endif - auto* broker_process = SandboxLinux::GetInstance()->broker_process(); - if (broker_process->IsSyscallAllowed(sysno)) { - return Trap(BrokerProcess::SIGSYS_Handler, broker_process); - } + auto* sandbox_linux = SandboxLinux::GetInstance(); + if (sandbox_linux->ShouldBrokerHandleSyscall(sysno)) + return sandbox_linux->HandleViaBroker(); // Default on the baseline policy. return BPFBasePolicy::EvaluateSyscall(sysno); diff --git a/chromium/sandbox/policy/linux/bpf_ime_policy_linux.cc b/chromium/sandbox/policy/linux/bpf_ime_policy_linux.cc index 4d6147a2ebd..3fcdbcc188c 100644 --- a/chromium/sandbox/policy/linux/bpf_ime_policy_linux.cc +++ b/chromium/sandbox/policy/linux/bpf_ime_policy_linux.cc @@ -39,9 +39,9 @@ ResultExpr ImeProcessPolicy::EvaluateSyscall(int sysno) const { return RestrictGetrusage(); #endif default: - auto* broker_process = SandboxLinux::GetInstance()->broker_process(); - if (broker_process->IsSyscallAllowed(sysno)) - return Trap(BrokerProcess::SIGSYS_Handler, broker_process); + auto* sandbox_linux = SandboxLinux::GetInstance(); + if (sandbox_linux->ShouldBrokerHandleSyscall(sysno)) + return sandbox_linux->HandleViaBroker(); return BPFBasePolicy::EvaluateSyscall(sysno); } diff --git a/chromium/sandbox/policy/linux/bpf_network_policy_linux.cc b/chromium/sandbox/policy/linux/bpf_network_policy_linux.cc index 2cdcc797d8e..56e2b4a8518 100644 --- a/chromium/sandbox/policy/linux/bpf_network_policy_linux.cc +++ b/chromium/sandbox/policy/linux/bpf_network_policy_linux.cc @@ -33,12 +33,11 @@ NetworkProcessPolicy::NetworkProcessPolicy() {} NetworkProcessPolicy::~NetworkProcessPolicy() {} ResultExpr NetworkProcessPolicy::EvaluateSyscall(int sysno) const { - auto* broker_process = SandboxLinux::GetInstance()->broker_process(); - if (broker_process->IsSyscallAllowed(sysno)) { - return Trap(BrokerProcess::SIGSYS_Handler, broker_process); - } + auto* sandbox_linux = SandboxLinux::GetInstance(); + if (sandbox_linux->ShouldBrokerHandleSyscall(sysno)) + return sandbox_linux->HandleViaBroker(); - // TODO(tsepez): FIX this. + // TODO(mpdenton): FIX this. return Allow(); } diff --git a/chromium/sandbox/policy/linux/bpf_speech_recognition_policy_linux.cc b/chromium/sandbox/policy/linux/bpf_speech_recognition_policy_linux.cc index d02983ca53f..4e62fadfafc 100644 --- a/chromium/sandbox/policy/linux/bpf_speech_recognition_policy_linux.cc +++ b/chromium/sandbox/policy/linux/bpf_speech_recognition_policy_linux.cc @@ -23,10 +23,20 @@ SpeechRecognitionProcessPolicy::~SpeechRecognitionProcessPolicy() = default; ResultExpr SpeechRecognitionProcessPolicy::EvaluateSyscall( int system_call_number) const { switch (system_call_number) { + // Required by the Speech On-Device API (SODA) binary to find the + // appropriate configuration file to use within a language pack directory. +#if defined(__NR_getdents64) + case __NR_getdents64: + return Allow(); +#endif +#if defined(__NR_getdents) + case __NR_getdents: + return Allow(); +#endif default: - auto* broker_process = SandboxLinux::GetInstance()->broker_process(); - if (broker_process->IsSyscallAllowed(system_call_number)) - return Trap(BrokerProcess::SIGSYS_Handler, broker_process); + auto* sandbox_linux = SandboxLinux::GetInstance(); + if (sandbox_linux->ShouldBrokerHandleSyscall(system_call_number)) + return sandbox_linux->HandleViaBroker(); // Default on the content baseline policy. return BPFBasePolicy::EvaluateSyscall(system_call_number); diff --git a/chromium/sandbox/policy/linux/bpf_tts_policy_linux.cc b/chromium/sandbox/policy/linux/bpf_tts_policy_linux.cc index f39a05de9f3..07a80f18ad0 100644 --- a/chromium/sandbox/policy/linux/bpf_tts_policy_linux.cc +++ b/chromium/sandbox/policy/linux/bpf_tts_policy_linux.cc @@ -25,9 +25,16 @@ TtsProcessPolicy::TtsProcessPolicy() {} TtsProcessPolicy::~TtsProcessPolicy() {} ResultExpr TtsProcessPolicy::EvaluateSyscall(int sysno) const { - auto* broker_process = SandboxLinux::GetInstance()->broker_process(); - if (broker_process->IsSyscallAllowed(sysno)) - return Trap(BrokerProcess::SIGSYS_Handler, broker_process); + switch (sysno) { + case __NR_sched_setscheduler: + return RestrictSchedTarget(GetPolicyPid(), sysno); + default: + break; + } + + auto* sandbox_linux = SandboxLinux::GetInstance(); + if (sandbox_linux->ShouldBrokerHandleSyscall(sysno)) + return sandbox_linux->HandleViaBroker(); return BPFBasePolicy::EvaluateSyscall(sysno); } diff --git a/chromium/sandbox/policy/linux/sandbox_linux.cc b/chromium/sandbox/policy/linux/sandbox_linux.cc index a2f1150a0b6..c4231d0b964 100644 --- a/chromium/sandbox/policy/linux/sandbox_linux.cc +++ b/chromium/sandbox/policy/linux/sandbox_linux.cc @@ -40,6 +40,7 @@ #include "sandbox/linux/services/thread_helpers.h" #include "sandbox/linux/services/yama.h" #include "sandbox/linux/suid/client/setuid_sandbox_client.h" +#include "sandbox/linux/syscall_broker/broker_client.h" #include "sandbox/linux/syscall_broker/broker_command.h" #include "sandbox/linux/syscall_broker/broker_process.h" #include "sandbox/policy/linux/bpf_broker_policy_linux.h" @@ -61,7 +62,7 @@ namespace { void LogSandboxStarted(const std::string& sandbox_name) { const std::string process_type = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( - service_manager::switches::kProcessType); + switches::kProcessType); const std::string activated_sandbox = "Activated " + sandbox_name + " sandbox for process type: " + process_type + "."; @@ -106,8 +107,8 @@ bool UpdateProcessTypeAndEnableSandbox( base::CommandLine::ForCurrentProcess()->InitFromArgv(exec); base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - std::string new_process_type = command_line->GetSwitchValueASCII( - service_manager::switches::kProcessType); + std::string new_process_type = + command_line->GetSwitchValueASCII(switches::kProcessType); if (!new_process_type.empty()) { new_process_type.append("-broker"); } else { @@ -116,8 +117,7 @@ bool UpdateProcessTypeAndEnableSandbox( VLOG(3) << "UpdateProcessTypeAndEnableSandbox: Updating process type to " << new_process_type; - command_line->AppendSwitchASCII(service_manager::switches::kProcessType, - new_process_type); + command_line->AppendSwitchASCII(switches::kProcessType, new_process_type); if (broker_side_hook) CHECK(std::move(broker_side_hook).Run(options)); @@ -323,8 +323,8 @@ bool SandboxLinux::InitializeSandbox(SandboxType sandbox_type, initialize_sandbox_ran_ = true; base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); - const std::string process_type = command_line->GetSwitchValueASCII( - service_manager::switches::kProcessType); + const std::string process_type = + command_line->GetSwitchValueASCII(switches::kProcessType); // We need to make absolutely sure that our sandbox is "sealed" before // returning. @@ -491,7 +491,8 @@ void SandboxLinux::StartBrokerProcess( const Options& options) { // Leaked at shutdown, so use bare |new|. broker_process_ = new syscall_broker::BrokerProcess( - BPFBasePolicy::GetFSDeniedErrno(), allowed_command_set, permissions); + BPFBasePolicy::GetFSDeniedErrno(), allowed_command_set, permissions, + syscall_broker::BrokerProcess::BrokerType::SIGNAL_BASED); // The initialization callback will perform generic initialization and then // call broker_sandboxer_callback. @@ -500,6 +501,16 @@ void SandboxLinux::StartBrokerProcess( options, allowed_command_set))); } +bool SandboxLinux::ShouldBrokerHandleSyscall(int sysno) const { + return broker_process_->IsSyscallAllowed(sysno); +} + +sandbox::bpf_dsl::ResultExpr SandboxLinux::HandleViaBroker() const { + return sandbox::bpf_dsl::Trap( + sandbox::syscall_broker::BrokerClient::SIGSYS_Handler, + broker_process_->GetBrokerClientSignalBased()); +} + bool SandboxLinux::HasOpenDirectories() const { return ProcUtil::HasOpenDirectory(proc_fd_); } diff --git a/chromium/sandbox/policy/linux/sandbox_linux.h b/chromium/sandbox/policy/linux/sandbox_linux.h index 3f818b9ad3a..4b32dc48c27 100644 --- a/chromium/sandbox/policy/linux/sandbox_linux.h +++ b/chromium/sandbox/policy/linux/sandbox_linux.h @@ -233,9 +233,16 @@ class SANDBOX_POLICY_EXPORT SandboxLinux { PreSandboxHook broker_side_hook, const Options& options); - syscall_broker::BrokerProcess* broker_process() const { - return broker_process_; - } + // Returns true if the broker should handle a particular syscall indicated by + // |sysno|. This will typically return true for system calls that take + // filepaths as arguments. + bool ShouldBrokerHandleSyscall(int sysno) const; + + // Returns an expression that indicates the syscall in question should be + // handled transparently by the broker process. This is useful for file + // syscalls that take pathnames, so we can enforce pathname whitelisting. + // Only usable if StartBrokerProcess() was already called. + bpf_dsl::ResultExpr HandleViaBroker() const; private: friend struct base::DefaultSingletonTraits<SandboxLinux>; diff --git a/chromium/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc b/chromium/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc index ab45dc7766a..cf9bd192105 100644 --- a/chromium/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc +++ b/chromium/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc @@ -53,7 +53,6 @@ #endif // !defined(OS_NACL_NONSFI) #if defined(OS_CHROMEOS) -#include "sandbox/policy/features.h" #include "sandbox/policy/linux/bpf_ime_policy_linux.h" #include "sandbox/policy/linux/bpf_tts_policy_linux.h" #endif // defined(OS_CHROMEOS) @@ -260,14 +259,7 @@ bool SandboxSeccompBPF::StartSandboxWithExternalPolicy( // doing so does not stop the sandbox. SandboxBPF sandbox(std::move(policy)); sandbox.SetProcFd(std::move(proc_fd)); - bool enable_ibpb = true; -#if defined(OS_CHROMEOS) - enable_ibpb = - base::FeatureList::IsEnabled( - features::kForceSpectreVariant2Mitigation) || - base::FeatureList::IsEnabled(features::kSpectreVariant2Mitigation); -#endif // defined(OS_CHROMEOS) - CHECK(sandbox.StartSandbox(seccomp_level, enable_ibpb)); + CHECK(sandbox.StartSandbox(seccomp_level)); return true; } #endif // BUILDFLAG(USE_SECCOMP_BPF) diff --git a/chromium/sandbox/policy/mac/BUILD.gn b/chromium/sandbox/policy/mac/BUILD.gn index c3d9df3d6a6..373b7e0f7b0 100644 --- a/chromium/sandbox/policy/mac/BUILD.gn +++ b/chromium/sandbox/policy/mac/BUILD.gn @@ -15,6 +15,7 @@ action_foreach("package_sb_files") { "ppapi.sb", "print_compositor.sb", "renderer.sb", + "speech_recognition.sb", "utility.sb", ] outputs = [ diff --git a/chromium/sandbox/policy/mac/common.sb b/chromium/sandbox/policy/mac/common.sb index 27b5a183d0e..349fc080221 100644 --- a/chromium/sandbox/policy/mac/common.sb +++ b/chromium/sandbox/policy/mac/common.sb @@ -199,10 +199,12 @@ (sysctl-name "hw.vectorunit") (sysctl-name "kern.hostname") (sysctl-name "kern.maxfilesperproc") + (sysctl-name "kern.osproductversion") (sysctl-name "kern.osrelease") (sysctl-name "kern.ostype") (sysctl-name "kern.osvariant_status") (sysctl-name "kern.osversion") + (sysctl-name "kern.secure_kernel") (sysctl-name "kern.usrstack64") (sysctl-name "kern.version") (sysctl-name "sysctl.proc_cputype") diff --git a/chromium/sandbox/policy/mac/gpu_v2.sb b/chromium/sandbox/policy/mac/gpu_v2.sb index 543a2724a1f..4d7222c548d 100644 --- a/chromium/sandbox/policy/mac/gpu_v2.sb +++ b/chromium/sandbox/policy/mac/gpu_v2.sb @@ -27,6 +27,7 @@ (global-name "com.apple.PowerManagement.control") (global-name "com.apple.SecurityServer") (global-name "com.apple.system.notification_center") + (global-name "com.apple.system.opendirectoryd.membership") ; https://crbug.com/1126350#c5 (global-name "com.apple.tsm.uiserver") (global-name "com.apple.windowserver.active") ) diff --git a/chromium/sandbox/policy/mac/sandbox_mac.h b/chromium/sandbox/policy/mac/sandbox_mac.h index 37fe74b86fa..864825b43d2 100644 --- a/chromium/sandbox/policy/mac/sandbox_mac.h +++ b/chromium/sandbox/policy/mac/sandbox_mac.h @@ -46,6 +46,8 @@ class SANDBOX_POLICY_EXPORT SandboxMac { static const char* kSandboxBrowserPID; static const char* kSandboxBundlePath; static const char* kSandboxChromeBundleId; + static const char* kSandboxSodaComponentPath; + static const char* kSandboxSodaLanguagePackPath; static const char* kSandboxComponentPath; static const char* kSandboxDisableDenialLogging; static const char* kSandboxEnableLogging; diff --git a/chromium/sandbox/policy/mac/sandbox_mac.mm b/chromium/sandbox/policy/mac/sandbox_mac.mm index 0decae680c2..9a175edb74b 100644 --- a/chromium/sandbox/policy/mac/sandbox_mac.mm +++ b/chromium/sandbox/policy/mac/sandbox_mac.mm @@ -48,6 +48,7 @@ #include "sandbox/policy/mac/ppapi.sb.h" #include "sandbox/policy/mac/print_compositor.sb.h" #include "sandbox/policy/mac/renderer.sb.h" +#include "sandbox/policy/mac/speech_recognition.sb.h" #include "sandbox/policy/mac/utility.sb.h" #include "sandbox/policy/sandbox_type.h" #include "sandbox/policy/switches.h" @@ -59,6 +60,9 @@ namespace policy { const char* SandboxMac::kSandboxBrowserPID = "BROWSER_PID"; const char* SandboxMac::kSandboxBundlePath = "BUNDLE_PATH"; const char* SandboxMac::kSandboxChromeBundleId = "BUNDLE_ID"; +const char* SandboxMac::kSandboxSodaComponentPath = "SODA_COMPONENT_PATH"; +const char* SandboxMac::kSandboxSodaLanguagePackPath = + "SODA_LANGUAGE_PACK_PATH"; const char* SandboxMac::kSandboxComponentPath = "COMPONENT_PATH"; const char* SandboxMac::kSandboxDisableDenialLogging = "DISABLE_SANDBOX_DENIAL_LOGGING"; @@ -254,6 +258,9 @@ std::string SandboxMac::GetSandboxProfile(SandboxType sandbox_type) { case SandboxType::kPrintCompositor: profile += kSeatbeltPolicyString_print_compositor; break; + case SandboxType::kSpeechRecognition: + profile += kSeatbeltPolicyString_speech_recognition; + break; case SandboxType::kUtility: profile += kSeatbeltPolicyString_utility; break; @@ -262,7 +269,6 @@ std::string SandboxMac::GetSandboxProfile(SandboxType sandbox_type) { break; case SandboxType::kNoSandbox: case SandboxType::kVideoCapture: - case SandboxType::kSpeechRecognition: CHECK(false); break; } diff --git a/chromium/sandbox/policy/mac/speech_recognition.sb b/chromium/sandbox/policy/mac/speech_recognition.sb new file mode 100644 index 00000000000..799c2be3c9b --- /dev/null +++ b/chromium/sandbox/policy/mac/speech_recognition.sb @@ -0,0 +1,15 @@ +; Copyright 2020 The Chromium 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 contents of common.sb implicitly included here. --- + +; Required to load the libsoda.so binary downloaded by the component +; updater. +(define soda-component-path "SODA_COMPONENT_PATH") +(allow file-read* (subpath (param soda-component-path))) + +; Required to load the language pack files used by the Speech On-Device +; API (SODA). +(define soda-language-pack-path "SODA_LANGUAGE_PACK_PATH") +(allow file-read* (subpath (param soda-language-pack-path)))
\ No newline at end of file diff --git a/chromium/sandbox/policy/sandbox.cc b/chromium/sandbox/policy/sandbox.cc index ff135818c72..590ecc9cf3f 100644 --- a/chromium/sandbox/policy/sandbox.cc +++ b/chromium/sandbox/policy/sandbox.cc @@ -81,8 +81,7 @@ bool Sandbox::Initialize(SandboxType sandbox_type, // static bool Sandbox::IsProcessSandboxed() { auto* command_line = base::CommandLine::ForCurrentProcess(); - bool is_browser = - !command_line->HasSwitch(service_manager::switches::kProcessType); + bool is_browser = !command_line->HasSwitch(switches::kProcessType); if (!is_browser && base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoSandbox)) { diff --git a/chromium/sandbox/policy/sandbox_type.cc b/chromium/sandbox/policy/sandbox_type.cc index bbd13f05991..c96b8713198 100644 --- a/chromium/sandbox/policy/sandbox_type.cc +++ b/chromium/sandbox/policy/sandbox_type.cc @@ -28,6 +28,7 @@ bool IsUnsandboxedSandboxType(SandboxType sandbox_type) { case SandboxType::kProxyResolver: case SandboxType::kPdfConversion: case SandboxType::kIconReader: + case SandboxType::kMediaFoundationCdm: return false; #endif case SandboxType::kAudio: @@ -75,8 +76,7 @@ void SetCommandLineFlagsForSandboxType(base::CommandLine* command_line, SandboxType sandbox_type) { switch (sandbox_type) { case SandboxType::kNoSandbox: - if (command_line->GetSwitchValueASCII( - service_manager::switches::kProcessType) == + if (command_line->GetSwitchValueASCII(switches::kProcessType) == switches::kUtilityProcess) { DCHECK(!command_line->HasSwitch(switches::kServiceSandboxType)); command_line->AppendSwitchASCII( @@ -92,24 +92,20 @@ void SetCommandLineFlagsForSandboxType(base::CommandLine* command_line, break; #endif case SandboxType::kRenderer: - DCHECK(command_line->GetSwitchValueASCII( - service_manager::switches::kProcessType) == + DCHECK(command_line->GetSwitchValueASCII(switches::kProcessType) == switches::kRendererProcess); break; case SandboxType::kGpu: - DCHECK(command_line->GetSwitchValueASCII( - service_manager::switches::kProcessType) == + DCHECK(command_line->GetSwitchValueASCII(switches::kProcessType) == switches::kGpuProcess); break; case SandboxType::kPpapi: - if (command_line->GetSwitchValueASCII( - service_manager::switches::kProcessType) == + if (command_line->GetSwitchValueASCII(switches::kProcessType) == switches::kUtilityProcess) { command_line->AppendSwitchASCII(switches::kServiceSandboxType, switches::kPpapiSandbox); } else { - DCHECK(command_line->GetSwitchValueASCII( - service_manager::switches::kProcessType) == + DCHECK(command_line->GetSwitchValueASCII(switches::kProcessType) == switches::kPpapiPluginProcess); } break; @@ -124,6 +120,7 @@ void SetCommandLineFlagsForSandboxType(base::CommandLine* command_line, case SandboxType::kProxyResolver: case SandboxType::kPdfConversion: case SandboxType::kIconReader: + case SandboxType::kMediaFoundationCdm: #endif // defined(OS_WIN) #if defined(OS_CHROMEOS) case SandboxType::kIme: @@ -133,8 +130,7 @@ void SetCommandLineFlagsForSandboxType(base::CommandLine* command_line, case SandboxType::kSharingService: #endif case SandboxType::kSpeechRecognition: - DCHECK(command_line->GetSwitchValueASCII( - service_manager::switches::kProcessType) == + DCHECK(command_line->GetSwitchValueASCII(switches::kProcessType) == switches::kUtilityProcess); DCHECK(!command_line->HasSwitch(switches::kServiceSandboxType)); command_line->AppendSwitchASCII( @@ -166,7 +162,7 @@ SandboxType SandboxTypeFromCommandLine(const base::CommandLine& command_line) { #endif std::string process_type = - command_line.GetSwitchValueASCII(service_manager::switches::kProcessType); + command_line.GetSwitchValueASCII(switches::kProcessType); if (process_type.empty()) return SandboxType::kNoSandbox; @@ -249,6 +245,8 @@ std::string StringFromUtilitySandboxType(SandboxType sandbox_type) { return switches::kPdfConversionSandbox; case SandboxType::kIconReader: return switches::kIconReaderSandbox; + case SandboxType::kMediaFoundationCdm: + return switches::kMediaFoundationCdmSandbox; #endif // defined(OS_WIN) #if defined(OS_CHROMEOS) case SandboxType::kIme: @@ -303,6 +301,8 @@ SandboxType UtilitySandboxTypeFromString(const std::string& sandbox_string) { return SandboxType::kPdfConversion; if (sandbox_string == switches::kIconReaderSandbox) return SandboxType::kIconReader; + if (sandbox_string == switches::kMediaFoundationCdmSandbox) + return SandboxType::kMediaFoundationCdm; #endif if (sandbox_string == switches::kAudioSandbox) return SandboxType::kAudio; diff --git a/chromium/sandbox/policy/sandbox_type.h b/chromium/sandbox/policy/sandbox_type.h index b133d3ab5eb..00e23062366 100644 --- a/chromium/sandbox/policy/sandbox_type.h +++ b/chromium/sandbox/policy/sandbox_type.h @@ -34,6 +34,9 @@ enum class SandboxType { // The icon reader service. kIconReader, + + // The MediaFoundation CDM service process. + kMediaFoundationCdm, #endif #if defined(OS_FUCHSIA) diff --git a/chromium/sandbox/policy/sandbox_type_unittest.cc b/chromium/sandbox/policy/sandbox_type_unittest.cc index 51a470663f6..363475740df 100644 --- a/chromium/sandbox/policy/sandbox_type_unittest.cc +++ b/chromium/sandbox/policy/sandbox_type_unittest.cc @@ -36,7 +36,7 @@ TEST(SandboxTypeTest, Empty) { TEST(SandboxTypeTest, Renderer) { base::CommandLine command_line(base::CommandLine::NO_PROGRAM); - command_line.AppendSwitchASCII(service_manager::switches::kProcessType, + command_line.AppendSwitchASCII(switches::kProcessType, switches::kRendererProcess); EXPECT_EQ(SandboxType::kRenderer, SandboxTypeFromCommandLine(command_line)); @@ -51,7 +51,7 @@ TEST(SandboxTypeTest, Renderer) { TEST(SandboxTypeTest, Utility) { base::CommandLine command_line(base::CommandLine::NO_PROGRAM); - command_line.AppendSwitchASCII(service_manager::switches::kProcessType, + command_line.AppendSwitchASCII(switches::kProcessType, switches::kUtilityProcess); EXPECT_EQ(SandboxType::kUtility, SandboxTypeFromCommandLine(command_line)); @@ -123,8 +123,7 @@ TEST(SandboxTypeTest, Utility) { TEST(SandboxTypeTest, GPU) { base::CommandLine command_line(base::CommandLine::NO_PROGRAM); - command_line.AppendSwitchASCII(service_manager::switches::kProcessType, - switches::kGpuProcess); + command_line.AppendSwitchASCII(switches::kProcessType, switches::kGpuProcess); SetCommandLineFlagsForSandboxType(&command_line, SandboxType::kGpu); EXPECT_EQ(SandboxType::kGpu, SandboxTypeFromCommandLine(command_line)); @@ -137,7 +136,7 @@ TEST(SandboxTypeTest, GPU) { TEST(SandboxTypeTest, PPAPIBroker) { base::CommandLine command_line(base::CommandLine::NO_PROGRAM); - command_line.AppendSwitchASCII(service_manager::switches::kProcessType, + command_line.AppendSwitchASCII(switches::kProcessType, switches::kPpapiBrokerProcess); EXPECT_EQ(SandboxType::kNoSandbox, SandboxTypeFromCommandLine(command_line)); @@ -150,7 +149,7 @@ TEST(SandboxTypeTest, PPAPIBroker) { TEST(SandboxTypeTest, PPAPIPlugin) { base::CommandLine command_line(base::CommandLine::NO_PROGRAM); - command_line.AppendSwitchASCII(service_manager::switches::kProcessType, + command_line.AppendSwitchASCII(switches::kProcessType, switches::kPpapiPluginProcess); SetCommandLineFlagsForSandboxType(&command_line, SandboxType::kPpapi); EXPECT_EQ(SandboxType::kPpapi, SandboxTypeFromCommandLine(command_line)); @@ -164,8 +163,7 @@ TEST(SandboxTypeTest, PPAPIPlugin) { TEST(SandboxTypeTest, Nonesuch) { base::CommandLine command_line(base::CommandLine::NO_PROGRAM); - command_line.AppendSwitchASCII(service_manager::switches::kProcessType, - "nonesuch"); + command_line.AppendSwitchASCII(switches::kProcessType, "nonesuch"); // If tested here would CHECK. command_line.AppendSwitchASCII(switches::kServiceSandboxType, "network"); diff --git a/chromium/sandbox/policy/switches.cc b/chromium/sandbox/policy/switches.cc index e8e567d55b8..3afb8768843 100644 --- a/chromium/sandbox/policy/switches.cc +++ b/chromium/sandbox/policy/switches.cc @@ -38,6 +38,7 @@ const char kPdfConversionSandbox[] = "pdf_conversion"; const char kProxyResolverSandbox[] = "proxy_resolver"; const char kXrCompositingSandbox[] = "xr_compositing"; const char kIconReaderSandbox[] = "icon_reader"; +const char kMediaFoundationCdmSandbox[] = "mf_cdm"; #endif // OS_WIN #if defined(OS_CHROMEOS) @@ -109,6 +110,7 @@ const char kEnableSandboxLogging[] = "enable-sandbox-logging"; #endif // Flags spied upon from other layers. +const char kProcessType[] = "type"; const char kGpuProcess[] = "gpu-process"; const char kNaClBrokerProcess[] = "nacl-broker"; const char kNaClLoaderProcess[] = "nacl-loader"; diff --git a/chromium/sandbox/policy/switches.h b/chromium/sandbox/policy/switches.h index 5fc71c805e1..e096e96308a 100644 --- a/chromium/sandbox/policy/switches.h +++ b/chromium/sandbox/policy/switches.h @@ -7,7 +7,6 @@ #include "build/build_config.h" #include "sandbox/policy/export.h" -#include "services/service_manager/embedder/switches.h" namespace sandbox { namespace policy { @@ -36,6 +35,7 @@ SANDBOX_POLICY_EXPORT extern const char kPdfConversionSandbox[]; SANDBOX_POLICY_EXPORT extern const char kProxyResolverSandbox[]; SANDBOX_POLICY_EXPORT extern const char kXrCompositingSandbox[]; SANDBOX_POLICY_EXPORT extern const char kIconReaderSandbox[]; +SANDBOX_POLICY_EXPORT extern const char kMediaFoundationCdmSandbox[]; #endif // OS_WIN #if defined(OS_CHROMEOS) @@ -67,6 +67,7 @@ SANDBOX_POLICY_EXPORT extern const char kEnableSandboxLogging[]; #endif // Flags spied upon from other layers. +SANDBOX_POLICY_EXPORT extern const char kProcessType[]; SANDBOX_POLICY_EXPORT extern const char kGpuProcess[]; SANDBOX_POLICY_EXPORT extern const char kNaClBrokerProcess[]; SANDBOX_POLICY_EXPORT extern const char kNaClLoaderProcess[]; diff --git a/chromium/sandbox/policy/win/mf_cdm_sandbox_type_unittest.cc b/chromium/sandbox/policy/win/mf_cdm_sandbox_type_unittest.cc new file mode 100644 index 00000000000..b2988859fbb --- /dev/null +++ b/chromium/sandbox/policy/win/mf_cdm_sandbox_type_unittest.cc @@ -0,0 +1,28 @@ +// Copyright 2020 The Chromium 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/policy/sandbox_type.h" + +#include "base/command_line.h" +#include "sandbox/policy/switches.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +TEST(SandboxTypeTest, Utility) { + // Setup to have '--type=utility' first. + base::CommandLine command_line(base::CommandLine::NO_PROGRAM); + command_line.AppendSwitchASCII(sandbox::policy::switches::kProcessType, + sandbox::policy::switches::kUtilityProcess); + EXPECT_EQ(sandbox::policy::SandboxType::kUtility, + sandbox::policy::SandboxTypeFromCommandLine(command_line)); + + base::CommandLine command_line2(command_line); + SetCommandLineFlagsForSandboxType( + &command_line2, sandbox::policy::SandboxType::kMediaFoundationCdm); + EXPECT_EQ(sandbox::policy::SandboxType::kMediaFoundationCdm, + sandbox::policy::SandboxTypeFromCommandLine(command_line2)); +} + +} // namespace media diff --git a/chromium/sandbox/policy/win/sandbox_win.cc b/chromium/sandbox/policy/win/sandbox_win.cc index d63d942c7e3..8c1e3aa58b6 100644 --- a/chromium/sandbox/policy/win/sandbox_win.cc +++ b/chromium/sandbox/policy/win/sandbox_win.cc @@ -621,12 +621,22 @@ ResultCode SetJobMemoryLimit(const base::CommandLine& cmd_line, // isn't a security concern. base::string16 GetAppContainerProfileName(const std::string& appcontainer_id, SandboxType sandbox_type) { - DCHECK(sandbox_type == SandboxType::kGpu || - sandbox_type == SandboxType::kXrCompositing); + std::string sandbox_base_name; + switch (sandbox_type) { + case SandboxType::kXrCompositing: + sandbox_base_name = std::string("cr.sb.xr"); + break; + case SandboxType::kGpu: + sandbox_base_name = std::string("cr.sb.gpu"); + break; + case SandboxType::kMediaFoundationCdm: + sandbox_base_name = std::string("cr.sb.cdm"); + break; + default: + DCHECK(0); + } + auto sha1 = base::SHA1HashString(appcontainer_id); - std::string sandbox_base_name = (sandbox_type == SandboxType::kXrCompositing) - ? std::string("cr.sb.xr") - : std::string("cr.sb.gpu"); std::string profile_name = base::StrCat( {sandbox_base_name, base::HexEncode(sha1.data(), sha1.size())}); // CreateAppContainerProfile requires that the profile name is at most 64 @@ -640,7 +650,8 @@ base::string16 GetAppContainerProfileName(const std::string& appcontainer_id, ResultCode SetupAppContainerProfile(AppContainerProfile* profile, const base::CommandLine& command_line, SandboxType sandbox_type) { - if (sandbox_type != SandboxType::kGpu && + if (sandbox_type != SandboxType::kMediaFoundationCdm && + sandbox_type != SandboxType::kGpu && sandbox_type != SandboxType::kXrCompositing) return SBOX_ERROR_UNSUPPORTED; @@ -666,6 +677,36 @@ ResultCode SetupAppContainerProfile(AppContainerProfile* profile, return SBOX_ERROR_CREATE_APPCONTAINER_PROFILE_CAPABILITY; } + if (sandbox_type == SandboxType::kMediaFoundationCdm) { + // Please refer to the following design doc on why we add the capabilities: + // https://docs.google.com/document/d/19Y4Js5v3BlzA5uSuiVTvcvPNIOwmxcMSFJWtuc1A-w8/edit#heading=h.iqvhsrml3gl9 + if (!profile->AddCapability( + sandbox::WellKnownCapabilities::kPrivateNetworkClientServer) || + !profile->AddCapability( + sandbox::WellKnownCapabilities::kInternetClient)) { + DLOG(ERROR) + << "AppContainerProfile::AddCapability() - " + << "SandboxType::kMediaFoundationCdm internet capabilities failed"; + return sandbox::SBOX_ERROR_CREATE_APPCONTAINER_PROFILE_CAPABILITY; + } + + if (!profile->AddCapability(L"lpacCom") || + !profile->AddCapability(L"lpacIdentityServices") || + !profile->AddCapability(L"lpacMedia") || + !profile->AddCapability(L"lpacPnPNotifications") || + !profile->AddCapability(L"lpacServicesManagement") || + !profile->AddCapability(L"lpacSessionManagement") || + !profile->AddCapability(L"lpacAppExperience") || + !profile->AddCapability(L"lpacAppServices") || + !profile->AddCapability(L"lpacCryptoServices") || + !profile->AddCapability(L"lpacEnterprisePolicyChangeNotifications")) { + DLOG(ERROR) + << "AppContainerProfile::AddCapability() - " + << "SandboxType::kMediaFoundationCdm lpac capabilities failed"; + return sandbox::SBOX_ERROR_CREATE_APPCONTAINER_PROFILE_CAPABILITY; + } + } + std::vector<base::string16> base_caps = { L"lpacChromeInstallFiles", L"registryRead", @@ -698,6 +739,9 @@ ResultCode SetupAppContainerProfile(AppContainerProfile* profile, profile->SetEnableLowPrivilegeAppContainer(true); } + if (sandbox_type == SandboxType::kMediaFoundationCdm) + profile->SetEnableLowPrivilegeAppContainer(true); + return SBOX_ALL_OK; } @@ -813,10 +857,14 @@ ResultCode SandboxWin::AddAppContainerProfileToPolicy( bool SandboxWin::IsAppContainerEnabledForSandbox( const base::CommandLine& command_line, SandboxType sandbox_type) { - if (sandbox_type != SandboxType::kGpu) - return false; if (base::win::GetVersion() < base::win::Version::WIN10_RS1) return false; + + if (sandbox_type == SandboxType::kMediaFoundationCdm) + return true; + + if (sandbox_type != SandboxType::kGpu) + return false; return base::FeatureList::IsEnabled(features::kGpuAppContainer); } @@ -1016,6 +1064,15 @@ ResultCode SandboxWin::StartSandboxedProcess( } } + if (sandbox_type == SandboxType::kMediaFoundationCdm) { + // Set a policy that would normally allow for process creation. This allows + // the mf cdm process to launch the protected media pipeline process + // (mfpmp.exe) without process interception. + result = policy->SetJobLevel(JOB_INTERACTIVE, 0); + if (result != SBOX_ALL_OK) + return result; + } + #if !defined(OFFICIAL_BUILD) // If stdout/stderr point to a Windows console, these calls will // have no effect. These calls can fail with SBOX_ERROR_BAD_PARAMS. @@ -1120,6 +1177,8 @@ std::string SandboxWin::GetSandboxTypeInEnglish(SandboxType sandbox_type) { return "Proxy Resolver"; case SandboxType::kPdfConversion: return "PDF Conversion"; + case SandboxType::kMediaFoundationCdm: + return "Media Foundation CDM"; case SandboxType::kSharingService: return "Sharing"; case SandboxType::kVideoCapture: diff --git a/chromium/sandbox/win/src/broker_services.cc b/chromium/sandbox/win/src/broker_services.cc index a00d1b15600..9f3d5395834 100644 --- a/chromium/sandbox/win/src/broker_services.cc +++ b/chromium/sandbox/win/src/broker_services.cc @@ -18,6 +18,7 @@ #include "base/win/scoped_handle.h" #include "base/win/scoped_process_information.h" #include "base/win/windows_version.h" +#include "build/build_config.h" #include "sandbox/win/src/app_container_profile.h" #include "sandbox/win/src/process_mitigations.h" #include "sandbox/win/src/sandbox.h" @@ -138,8 +139,17 @@ ResultCode BrokerServicesBase::Init() { no_targets_.Set(::CreateEventW(nullptr, true, false, nullptr)); - job_thread_.Set(::CreateThread(nullptr, 0, // Default security and stack. - TargetEventsThread, this, 0, nullptr)); +#if defined(ARCH_CPU_32_BITS) + // Conserve address space in 32-bit Chrome. This thread uses a small and + // consistent amount and doesn't need the default of 1.5 MiB. + constexpr unsigned flags = STACK_SIZE_PARAM_IS_A_RESERVATION; + constexpr size_t stack_size = 128 * 1024; +#else + constexpr unsigned int flags = 0; + constexpr size_t stack_size = 0; +#endif + job_thread_.Set(::CreateThread(nullptr, stack_size, // Default security. + TargetEventsThread, this, flags, nullptr)); if (!job_thread_.IsValid()) return SBOX_ERROR_CANNOT_INIT_BROKERSERVICES; @@ -328,12 +338,13 @@ DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) { ::UnregisterWait(tracker->wait_handle); tracker->wait_handle = INVALID_HANDLE_VALUE; - + // Copy process_id so that we can legally reference it even after we have + // found the ProcessTracker object to delete. + const DWORD process_id = tracker->process_id; // PID is unique until the process handle is closed in dtor. processes.erase(std::remove_if(processes.begin(), processes.end(), [&](auto&& p) -> bool { - return p->process_id == - tracker->process_id; + return p->process_id == process_id; }), processes.end()); |