| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #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.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 { |
| |
| 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: |
| 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<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; }))); |
| } |
| |
| BrokerProcess* broker_process() const { return broker_process_.get(); } |
| |
| private: |
| 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); |
| 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->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->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->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->GetBrokerClientSignalBased()->Open( |
| reinterpret_cast<const char*>(args.args[1]), |
| static_cast<int>(args.args[2])); |
| default: |
| BPF_ASSERT(false); |
| return -ENOSYS; |
| } |
| } |
| |
| class DenyOpenPolicy : public bpf_dsl::Policy { |
| public: |
| explicit DenyOpenPolicy(InitializedOpenBroker* iob) : iob_(iob) {} |
| ~DenyOpenPolicy() override {} |
| |
| ResultExpr EvaluateSyscall(int sysno) const override { |
| DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); |
| |
| switch (sysno) { |
| case __NR_faccessat: |
| #if defined(__NR_access) |
| case __NR_access: |
| #endif |
| #if defined(__NR_open) |
| case __NR_open: |
| #endif |
| case __NR_openat: |
| // We get a InitializedOpenBroker class, but our trap handler wants |
| // the BrokerProcess object. |
| return Trap(BrokerOpenTrapHandler, iob_->broker_process()); |
| default: |
| return Allow(); |
| } |
| } |
| |
| private: |
| InitializedOpenBroker* iob_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DenyOpenPolicy); |
| }; |
| |
| // We use a InitializedOpenBroker class, so that we can run unsandboxed |
| // code in its constructor, which is the only way to do so in a BPF_TEST. |
| BPF_TEST(SandboxBPF, |
| UseOpenBroker, |
| DenyOpenPolicy, |
| InitializedOpenBroker /* (*BPF_AUX) */) { |
| BrokerProcess* broker_process = BPF_AUX->broker_process(); |
| BPF_ASSERT(broker_process != nullptr); |
| |
| // First, use the broker "manually" |
| 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); |
| BPF_ASSERT(errno == EPERM); |
| |
| BPF_ASSERT(open("/proc/allowed", O_RDONLY) == -1); |
| BPF_ASSERT(errno == ENOENT); |
| |
| // Also test glibc's openat(), some versions of libc use it transparently |
| // instead of open(). |
| BPF_ASSERT(openat(AT_FDCWD, "/proc/denied", O_RDONLY) == -1); |
| BPF_ASSERT(errno == EPERM); |
| |
| BPF_ASSERT(openat(AT_FDCWD, "/proc/allowed", O_RDONLY) == -1); |
| BPF_ASSERT(errno == ENOENT); |
| |
| // And test glibc's access(). |
| BPF_ASSERT(access("/proc/denied", R_OK) == -1); |
| BPF_ASSERT(errno == EPERM); |
| |
| BPF_ASSERT(access("/proc/allowed", R_OK) == -1); |
| BPF_ASSERT(errno == ENOENT); |
| |
| // This is also white listed and does exist. |
| int cpu_info_access = access("/proc/cpuinfo", R_OK); |
| BPF_ASSERT(cpu_info_access == 0); |
| int cpu_info_fd = open("/proc/cpuinfo", O_RDONLY); |
| BPF_ASSERT(cpu_info_fd >= 0); |
| char buf[1024]; |
| BPF_ASSERT(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 |