blob: 9da9c6891142849376c10929d2cb89102c50a3ba [file] [log] [blame]
// 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