| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "build/build_config.h" |
| #include "sandbox/linux/bpf_dsl/bpf_dsl.h" |
| #include "sandbox/linux/bpf_dsl/policy.h" |
| #include "sandbox/linux/bpf_dsl/seccomp_macros.h" |
| #include "sandbox/linux/seccomp-bpf/bpf_tests.h" |
| #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" |
| #include "sandbox/linux/syscall_broker/broker_file_permission.h" |
| #include "sandbox/linux/syscall_broker/broker_process.h" |
| #include "sandbox/linux/system_headers/linux_syscalls.h" |
| #include "sandbox/linux/tests/unit_tests.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace sandbox { |
| |
| namespace { |
| |
| using bpf_dsl::Allow; |
| using bpf_dsl::ResultExpr; |
| using bpf_dsl::Trap; |
| |
| bool NoOpCallback() { |
| return true; |
| } |
| |
| // Test a trap handler that makes use of a broker process to open(). |
| |
| class InitializedOpenBroker { |
| public: |
| InitializedOpenBroker() : initialized_(false) { |
| std::vector<syscall_broker::BrokerFilePermission> permissions; |
| permissions.push_back( |
| syscall_broker::BrokerFilePermission::ReadOnly("/proc/allowed")); |
| permissions.push_back( |
| syscall_broker::BrokerFilePermission::ReadOnly("/proc/cpuinfo")); |
| |
| broker_process_.reset( |
| new syscall_broker::BrokerProcess(EPERM, permissions)); |
| BPF_ASSERT(broker_process() != NULL); |
| BPF_ASSERT(broker_process_->Init(base::Bind(&NoOpCallback))); |
| |
| initialized_ = true; |
| } |
| bool initialized() { return initialized_; } |
| class syscall_broker::BrokerProcess* broker_process() { |
| return broker_process_.get(); |
| } |
| |
| private: |
| bool initialized_; |
| scoped_ptr<class syscall_broker::BrokerProcess> broker_process_; |
| DISALLOW_COPY_AND_ASSIGN(InitializedOpenBroker); |
| }; |
| |
| intptr_t BrokerOpenTrapHandler(const struct arch_seccomp_data& args, |
| void* aux) { |
| BPF_ASSERT(aux); |
| syscall_broker::BrokerProcess* broker_process = |
| static_cast<syscall_broker::BrokerProcess*>(aux); |
| switch (args.nr) { |
| case __NR_faccessat: // access is a wrapper of faccessat in android |
| BPF_ASSERT(static_cast<int>(args.args[0]) == AT_FDCWD); |
| return broker_process->Access(reinterpret_cast<const char*>(args.args[1]), |
| static_cast<int>(args.args[2])); |
| #if defined(__NR_access) |
| case __NR_access: |
| return broker_process->Access(reinterpret_cast<const char*>(args.args[0]), |
| static_cast<int>(args.args[1])); |
| #endif |
| #if defined(__NR_open) |
| case __NR_open: |
| return broker_process->Open(reinterpret_cast<const char*>(args.args[0]), |
| static_cast<int>(args.args[1])); |
| #endif |
| case __NR_openat: |
| // We only call open() so if we arrive here, it's because glibc uses |
| // the openat() system call. |
| BPF_ASSERT(static_cast<int>(args.args[0]) == AT_FDCWD); |
| return broker_process->Open(reinterpret_cast<const char*>(args.args[1]), |
| static_cast<int>(args.args[2])); |
| default: |
| BPF_ASSERT(false); |
| return -ENOSYS; |
| } |
| } |
| |
| class DenyOpenPolicy : public bpf_dsl::Policy { |
| public: |
| explicit DenyOpenPolicy(InitializedOpenBroker* iob) : iob_(iob) {} |
| ~DenyOpenPolicy() override {} |
| |
| ResultExpr EvaluateSyscall(int sysno) const override { |
| DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); |
| |
| switch (sysno) { |
| case __NR_faccessat: |
| #if defined(__NR_access) |
| case __NR_access: |
| #endif |
| #if defined(__NR_open) |
| case __NR_open: |
| #endif |
| case __NR_openat: |
| // We get a InitializedOpenBroker class, but our trap handler wants |
| // the syscall_broker::BrokerProcess object. |
| return Trap(BrokerOpenTrapHandler, iob_->broker_process()); |
| default: |
| return Allow(); |
| } |
| } |
| |
| private: |
| InitializedOpenBroker* iob_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DenyOpenPolicy); |
| }; |
| |
| // We use a InitializedOpenBroker class, so that we can run unsandboxed |
| // code in its constructor, which is the only way to do so in a BPF_TEST. |
| BPF_TEST(SandboxBPF, |
| UseOpenBroker, |
| DenyOpenPolicy, |
| InitializedOpenBroker /* (*BPF_AUX) */) { |
| BPF_ASSERT(BPF_AUX->initialized()); |
| syscall_broker::BrokerProcess* broker_process = BPF_AUX->broker_process(); |
| BPF_ASSERT(broker_process != NULL); |
| |
| // First, use the broker "manually" |
| BPF_ASSERT(broker_process->Open("/proc/denied", O_RDONLY) == -EPERM); |
| BPF_ASSERT(broker_process->Access("/proc/denied", R_OK) == -EPERM); |
| BPF_ASSERT(broker_process->Open("/proc/allowed", O_RDONLY) == -ENOENT); |
| BPF_ASSERT(broker_process->Access("/proc/allowed", R_OK) == -ENOENT); |
| |
| // Now use glibc's open() as an external library would. |
| BPF_ASSERT(open("/proc/denied", O_RDONLY) == -1); |
| BPF_ASSERT(errno == EPERM); |
| |
| BPF_ASSERT(open("/proc/allowed", O_RDONLY) == -1); |
| BPF_ASSERT(errno == ENOENT); |
| |
| // Also test glibc's openat(), some versions of libc use it transparently |
| // instead of open(). |
| BPF_ASSERT(openat(AT_FDCWD, "/proc/denied", O_RDONLY) == -1); |
| BPF_ASSERT(errno == EPERM); |
| |
| BPF_ASSERT(openat(AT_FDCWD, "/proc/allowed", O_RDONLY) == -1); |
| BPF_ASSERT(errno == ENOENT); |
| |
| // And test glibc's access(). |
| BPF_ASSERT(access("/proc/denied", R_OK) == -1); |
| BPF_ASSERT(errno == EPERM); |
| |
| BPF_ASSERT(access("/proc/allowed", R_OK) == -1); |
| BPF_ASSERT(errno == ENOENT); |
| |
| // This is also white listed and does exist. |
| int cpu_info_access = access("/proc/cpuinfo", R_OK); |
| BPF_ASSERT(cpu_info_access == 0); |
| int cpu_info_fd = open("/proc/cpuinfo", O_RDONLY); |
| BPF_ASSERT(cpu_info_fd >= 0); |
| char buf[1024]; |
| BPF_ASSERT(read(cpu_info_fd, buf, sizeof(buf)) > 0); |
| } |
| |
| } // namespace |
| |
| } // namespace sandbox |