chromium/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include <errno.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <memory>
#include <tuple>
#include <type_traits>
#include <vector>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/files/file_path_watcher_inotify.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/posix/eintr_wrapper.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.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_stat.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 {

Allow;
Arg;
Error;
If;
ResultExpr;
Trap;

BrokerProcess;
BrokerType;
BrokerCommandSet;
BrokerFilePermission;

// Test a trap handler that makes use of a broker process to open().

class InitializedOpenBroker {};

intptr_t BrokerOpenTrapHandler(const struct arch_seccomp_data& args,
                               void* aux) {}

class DenyOpenPolicy : public bpf_dsl::Policy {};

// 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) */) {}

// 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 =;

void ConvertKernelStatToLibcStat(default_stat_struct& in_stat,
                                 struct stat& out_stat) {}
}  // 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 {};

class IPCSyscaller : public Syscaller {};

// Only use syscall(...) on x64 to avoid having to reimplement a libc-like
// layer that uses different syscalls on different architectures.
#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \
     BUILDFLAG(IS_ANDROID)) &&                        \
    defined(__x86_64__)
#define DIRECT_SYSCALLER_ENABLED
#endif

#if defined(DIRECT_SYSCALLER_ENABLED)
class DirectSyscaller : public Syscaller {};
#endif  // defined(DIRECT_SYSCALLER_ENABLED)

class LibcSyscaller : public Syscaller {};

enum class SyscallerType {};

// 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 {};

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 {};
}  // namespace syscall_broker

// This implements BPFTesterDelegate to layer the broker integration tests on
// top of BPF_TEST infrastructure.
class BPFTesterBrokerDelegate : public BPFTesterDelegate {};

namespace {
struct BrokerTestConfiguration {};

// Lists out all the broker configurations we want to test.
const std::vector<BrokerTestConfiguration> broker_test_configs =;
}  // namespace

void RunSingleBrokerTest(BrokerTestDelegate* test_delegate,
                         const BrokerTestConfiguration& test_config) {}

template <typename T>
void RunAllBrokerTests() {}

template <typename T>
void RunIPCBrokerTests() {}

// Tests that a SIGNALS_BASED broker responds with -EFAULT when open() or
// access() are called with nullptr.
class TestOpenAccessNullDelegate final : public BrokerTestDelegate {};

TEST(BrokerProcessIntegrationTest, TestOpenAccessNull) {}

// 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 {};

TEST(BrokerProcessIntegrationTest, TestOpenFilePermsEPERM) {}

TEST(BrokerProcessIntegrationTest, TestOpenFilePermsENOENT) {}

class BadPathsDelegate final : public BrokerTestDelegate {};

TEST(BrokerProcessIntegrationTest, BadPaths) {}

template <bool recursive>
class OpenCpuinfoDelegate final : public BrokerTestDelegate {};

TEST(BrokerProcessIntegrationTest, OpenCpuinfoRecursive) {}

TEST(BrokerProcessIntegrationTest, OpenCpuinfoNonRecursive) {}

class OpenFileRWDelegate final : public BrokerTestDelegate {};

TEST(BrokerProcessIntegrationTest, OpenFileRW) {}

class BrokerDiedDelegate final : public BrokerTestDelegate {};

TEST(BrokerProcessIntegrationTest, BrokerDied) {}

class OpenComplexFlagsDelegate final : public BrokerTestDelegate {};

TEST(BrokerProcessIntegrationTest, OpenComplexFlags) {}

class RewriteProcSelfDelegate final : public BrokerTestDelegate {};

TEST(BrokerProcessIntegrationTest, RewriteProcSelf) {}

class CreateFileDelegate final : public BrokerTestDelegate {};

TEST(BrokerProcessIntegrationTest, CreateFile) {}

// StatFileDelegate is the base class for all the Stat() tests.
class StatFileDelegate : public BrokerTestDelegate {};

// Actual file with permissions to see file but command not allowed.
template <bool follow_links>
class StatFileNoCommandDelegate final : public StatFileDelegate {};

TEST(BrokerProcessIntegrationTest, StatFileNoCommandFollowLinks) {}

TEST(BrokerProcessIntegrationTest, StatFileNoCommandNoFollowLinks) {}

// Allows the STAT command without any file permissions.
template <bool follow_links>
class StatFilesNoPermissionDelegate final : public StatFileDelegate {};

TEST(BrokerProcessIntegrationTest, StatFilesNoPermissionFollowLinks) {}

TEST(BrokerProcessIntegrationTest, StatFilesNoPermissionNoFollowLinks) {}

// Nonexistent file with permissions to see file.
template <bool follow_links>
class StatNonexistentFileWithPermissionsDelegate final
    : public StatFileDelegate {};

TEST(BrokerProcessIntegrationTest,
     StatNonexistentFileWithPermissionsFollowLinks) {}

TEST(BrokerProcessIntegrationTest,
     StatNonexistentFileWithPermissionsNoFollowLinks) {}

// Nonexistent file with permissions to create file.
template <bool follow_links>
class StatNonexistentFileWithCreatePermissionsDelegate final
    : public StatFileDelegate {};

TEST(BrokerProcessIntegrationTest,
     StatNonexistentFileWithCreatePermissionsFollowLinks) {}

TEST(BrokerProcessIntegrationTest,
     StatNonexistentFileWithCreatePermissionsNoFollowLinks) {}

// Actual file with permissions to see file.
template <bool follow_links>
class StatFileWithPermissionsDelegate final : public StatFileDelegate {};

TEST(BrokerProcessIntegrationTest, StatFileWithPermissionsFollowLinks) {}

TEST(BrokerProcessIntegrationTest, StatFileWithPermissionsNoFollowLinks) {}

class RenameTestDelegate : public BrokerTestDelegate {};

// Check rename fails with write permissions to both files but command
// itself is not allowed.
class RenameNoCommandDelegate final : public RenameTestDelegate {};

TEST(BrokerProcessIntegrationTest, RenameNoCommand) {}
// Check rename fails when no permission to new file.
class RenameNoPermNewDelegate final : public RenameTestDelegate {};

TEST(BrokerProcessIntegrationTest, RenameNoPermNew) {}
// Check rename fails when no permission to old file.
class RenameNoPermOldDelegate final : public RenameTestDelegate {};

TEST(BrokerProcessIntegrationTest, RenameNoPermOld) {}

// Check rename fails when only read permission to first file.
class RenameReadPermNewDelegate final : public RenameTestDelegate {};

TEST(BrokerProcessIntegrationTest, RenameReadPermNew) {}

// Check rename fails when only read permission to second file.
class RenameReadPermOldDelegate final : public RenameTestDelegate {};

TEST(BrokerProcessIntegrationTest, RenameReadPermOld) {}

// Check rename passes with write permissions to both files.
class RenameWritePermsBothDelegate final : public RenameTestDelegate {};

TEST(BrokerProcessIntegrationTest, RenameWritePermsBoth) {}

// The base class of all the Readlink() tests.
class ReadlinkTestDelegate : public BrokerTestDelegate {};

// Actual file with permissions to see file but command itself not allowed.
class ReadlinkNoCommandDelegate final : public ReadlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, ReadlinkNoCommand) {}

void ReadlinkNoCommandDelegate::RunTestInSandboxedChild(Syscaller* syscaller) {}

// Nonexistent file with no permissions to see file.
class ReadlinkNonexistentNoPermissionsDelegate final
    : public ReadlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, ReadlinkNonexistentNoPermissions) {}

// Actual file with no permissions to see file.
class ReadlinkNoPermissionsDelegate final : public ReadlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, ReadlinkNoPermissions) {}

// Nonexistent file with permissions to see file.
class ReadlinkNonexistentWithPermissionsDelegate final
    : public ReadlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, ReadlinkNonexistentWithPermissions) {}

// Actual file with permissions to see file.
class ReadlinkFileWithPermissionsDelegate final : public ReadlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, ReadlinkFileWithPermissions) {}

// Actual file with permissions to see file, but too small a buffer.
class ReadlinkFileWithPermissionsSmallBufferDelegate final
    : public ReadlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, ReadlinkFileWithPermissionsSmallBuffer) {}

// The base class of all the Mkdir() tests.
class MkdirTestDelegate : public BrokerTestDelegate {};

// Actual file with permissions to use but command itself not allowed.
class MkdirNoCommandDelegate final : public MkdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, MkdirNoCommand) {}

// Nonexistent file with no permissions to see file.
class MkdirNonexistentNoPermissionsDelegate final : public MkdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, MkdirNonexistentNoPermissions) {}

// Actual file with no permissions to see file.
class MkdirFileNoPermissionsDelegate final : public MkdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, MkdirFileNoPermissions) {}

// Nonexistent file with insufficient permissions to see file.
class MkdirNonexistentROPermissionsDelegate final : public MkdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, MkdirNonexistentROPermissions) {}

// Actual file with insufficient permissions to see file.
class MkdirFileROPermissionsDelegate final : public MkdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, MkdirFileROPermissions) {}

// Nonexistent file with insufficient permissions to see file, case 2.
class MkdirNonExistentRWPermissionsDelegate final : public MkdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, MkdirNonExistentRWPermissions) {}

// Actual file with insufficient permissions to see file, case 2.
class MkdirFileRWPermissionsDelegate final : public MkdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, MkdirFileRWPermissions) {}

// Nonexistent file with permissions to see file.
class MkdirNonExistentRWCPermissionsDelegate final : public MkdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, MkdirNonExistentRWCPermissions) {}

// Actual file with permissions to see file.
class MkdirFileRWCPermissionsDelegate final : public MkdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, MkdirFileRWCPermissions) {}

class RmdirTestDelegate : public BrokerTestDelegate {};

// Actual file with permissions to use but command itself not allowed.
class RmdirNoCommandDelegate final : public RmdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, RmdirNoCommand) {}

// Nonexistent file with no permissions to see file.
class RmdirNonexistentNoPermissionsDelegate final : public RmdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, RmdirNonexistentNoPermissions) {}

// Actual file with no permissions to see file.
class RmdirFileNoPermissionsDelegate final : public RmdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, RmdirFileNoPermissions) {}

// Nonexistent file with insufficient permissions to see file.
class RmdirNonexistentROPermissionsDelegate final : public RmdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, RmdirNonexistentROPermissions) {}

// Actual file with insufficient permissions to see file.
class RmdirFileROPermissionsDelegate final : public RmdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, RmdirFileROPermissions) {}

// Nonexistent file with insufficient permissions to see file, case 2.
class RmdirNonExistentRWPermissionsDelegate final : public RmdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, RmdirNonExistentRWPermissions) {}

// Actual file with insufficient permissions to see file, case 2.
class RmdirFileRWPermissionsDelegate final : public RmdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, RmdirFileRWPermissions) {}

// Nonexistent file with permissions to see file.
class RmdirNonExistentRWCPermissionsDelegate final : public RmdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, RmdirNonExistentRWCPermissions) {}

// Actual file with permissions to see file.
class RmdirFileRWCPermissionsDelegate final : public RmdirTestDelegate {};

TEST(BrokerProcessIntegrationTest, RmdirFileRWCPermissions) {}

// The base class for all the Unlink() tests.
class UnlinkTestDelegate : public BrokerTestDelegate {};

// Actual file with permissions to use but command itself not allowed.
class UnlinkNoCommandDelegate final : public UnlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, UnlinkNoCommand) {}

// Nonexistent file with no permissions to see file.
class UnlinkNonexistentNoPermissionsDelegate final : public UnlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, UnlinkNonexistentNoPermissions) {}

// Actual file with no permissions to see file.
class UnlinkFileNoPermissionsDelegate final : public UnlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, UnlinkFileNoPermissions) {}

// Nonexistent file with insufficient permissions to see file.
class UnlinkNonexistentROPermissionsDelegate final : public UnlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, UnlinkNonexistentROPermissions) {}

// Actual file with insufficient permissions to see file.
class UnlinkFileROPermissionsDelegate final : public UnlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, UnlinkFileROPermissions) {}

// Nonexistent file with insufficient permissions to see file, case 2.
class UnlinkNonExistentRWPermissionsDelegate final : public UnlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, UnlinkNonExistentRWPermissions) {}

// Actual file with insufficient permissions to see file, case 2.
class UnlinkFileRWPermissionsDelegate final : public UnlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, UnlinkFileRWPermissions) {}

// Nonexistent file with permissions to see file.
class UnlinkNonExistentRWCPermissionsDelegate final
    : public UnlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, UnlinkNonExistentRWCPermissions) {}

// Actual file with permissions to see file.
class UnlinkFileRWCPermissionsDelegate final : public UnlinkTestDelegate {};

TEST(BrokerProcessIntegrationTest, UnlinkFileRWCPermissions) {}

// Parent class for the inotify_add_watch() tests.
class InotifyAddWatchDelegate : public BrokerTestDelegate {};

// Try inotify_add_watch() without the relevant broker command.
class InotifyAddWatchNoCommandDelegate final : public InotifyAddWatchDelegate {};

TEST(BrokerProcessIntegrationTest, InotifyAddWatchNoCommand) {}

// Try inotify_add_watch() without the relevant permissions.
class InotifyAddWatchNoPermissionsDelegate final
    : public InotifyAddWatchDelegate {};

TEST(BrokerProcessIntegrationTest, InotifyAddWatchNoPermissions) {}

// Try inotify_add_watch() with a variety of bad arguments.
class InotifyAddWatchBadArgumentsDelegate final
    : public InotifyAddWatchDelegate {};

TEST(BrokerProcessIntegrationTest, InotifyAddWatchBadArguments) {}

// Use inottify_add_watch() successfully and verify it actually works.
class InotifyAddWatchSuccessDelegate final : public InotifyAddWatchDelegate {};

TEST(BrokerProcessIntegrationTest, InotifyAddWatchSuccess) {}

// Tests base::FilePathWatcher which uses inotify on Linux.
// This is used in the network service sandbox.
class BaseFilePathWatcherDelegate final : public InotifyAddWatchDelegate {};

TEST(BrokerProcessIntegrationTest, BaseFilePathWatcherInotifyTest) {}

#endif  // !defined(THREAD_SANITIZER)
}  // namespace sandbox