diff options
author | jln@chromium.org <jln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-14 03:18:25 +0000 |
---|---|---|
committer | jln@chromium.org <jln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-14 03:18:25 +0000 |
commit | 9205431d0e8ee82b0d9adcbeb33ef4db481b13d1 (patch) | |
tree | 1c3c58b31fdc3a26b13939d94a05afd5aa4fba71 /sandbox | |
parent | 3d6a895ab78ba4fbd121e786163ff118aaf76e11 (diff) | |
download | chromium_src-9205431d0e8ee82b0d9adcbeb33ef4db481b13d1.zip chromium_src-9205431d0e8ee82b0d9adcbeb33ef4db481b13d1.tar.gz chromium_src-9205431d0e8ee82b0d9adcbeb33ef4db481b13d1.tar.bz2 |
Linux sandbox: add a new low-level broker process mechanism.
We add a new low-level broker process mechanism that can be
async signal safe and is suitable for use in the seccomp-bpf sandbox.
Also fix UnixDomainSocket::SendMsg() to never generate a SIGPIPE.
BUG=165837
NOTRY=true
Review URL: https://chromiumcodereview.appspot.com/11557025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@173064 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sandbox')
-rw-r--r-- | sandbox/linux/sandbox_linux.gypi | 17 | ||||
-rw-r--r-- | sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc | 100 | ||||
-rw-r--r-- | sandbox/linux/services/broker_process.cc | 350 | ||||
-rw-r--r-- | sandbox/linux/services/broker_process.h | 71 | ||||
-rw-r--r-- | sandbox/linux/services/broker_process_unittest.cc | 271 |
5 files changed, 809 insertions, 0 deletions
diff --git a/sandbox/linux/sandbox_linux.gypi b/sandbox/linux/sandbox_linux.gypi index 535fb89..9283d6a 100644 --- a/sandbox/linux/sandbox_linux.gypi +++ b/sandbox/linux/sandbox_linux.gypi @@ -13,6 +13,7 @@ 'type': 'none', 'dependencies': [ 'suid_sandbox_client', + 'sandbox_services', ], 'conditions': [ # Only compile in the seccomp mode 1 code for the flag combination @@ -45,6 +46,7 @@ 'tests/unit_tests.cc', 'tests/unit_tests.h', 'suid/client/setuid_sandbox_client_unittest.cc', + 'services/broker_process_unittest.cc', ], 'include_dirs': [ '../..', @@ -113,7 +115,22 @@ '../..', ], }, + { 'target_name': 'sandbox_services', + 'type': 'static_library', + 'sources': [ + 'services/broker_process.cc', + 'services/broker_process.h', + ], + 'dependencies': [ + '../base/base.gyp:base', + ], + 'include_dirs': [ + '..', + ], + }, { + # We make this its own target so that it does not interfere + # with our tests. 'target_name': 'libc_urandom_override', 'type': 'static_library', 'sources': [ diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc index 365092e..a5e3b25 100644 --- a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc @@ -7,11 +7,14 @@ #include <ostream> +#include "base/memory/scoped_ptr.h" #include "sandbox/linux/seccomp-bpf/bpf_tests.h" #include "sandbox/linux/seccomp-bpf/verifier.h" +#include "sandbox/linux/services/broker_process.h" #include "testing/gtest/include/gtest/gtest.h" using namespace playground2; +using sandbox::BrokerProcess; namespace { @@ -480,4 +483,101 @@ BPF_TEST(SandboxBpf, UnsafeTrapWithErrno, RedirectAllSyscallsPolicy) { BPF_ASSERT(errno == 0); } +// Test a trap handler that makes use of a broker process to open(). + +class InitializedOpenBroker { + public: + InitializedOpenBroker() : initialized_(false) { + std::vector<std::string> allowed_files; + allowed_files.push_back("/proc/allowed"); + allowed_files.push_back("/proc/cpuinfo"); + + broker_process_.reset(new BrokerProcess(allowed_files, + std::vector<std::string>())); + BPF_ASSERT(broker_process() != NULL); + BPF_ASSERT(broker_process_->Init(NULL)); + + initialized_ = true; + } + bool initialized() { return initialized_; } + class BrokerProcess* broker_process() { return broker_process_.get(); } + private: + bool initialized_; + scoped_ptr<class 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_open: + return broker_process->Open(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); + 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; + } +} + +ErrorCode DenyOpenPolicy(int sysno, void *aux) { + InitializedOpenBroker* iob = static_cast<InitializedOpenBroker*>(aux); + if (!Sandbox::isValidSyscallNumber(sysno)) { + return ErrorCode(ENOSYS); + } + + switch (sysno) { + case __NR_open: + case __NR_openat: + // We get a InitializedOpenBroker class, but our trap handler wants + // the BrokerProcess object. + return ErrorCode(Sandbox::Trap(BrokerOpenTrapHandler, + iob->broker_process())); + default: + return ErrorCode(ErrorCode::ERR_ALLOWED); + } +} + +// 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()); + 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->Open("/proc/allowed", O_RDONLY) == -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); + + + // This is also white listed and does exist. + 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 diff --git a/sandbox/linux/services/broker_process.cc b/sandbox/linux/services/broker_process.cc new file mode 100644 index 0000000..7c70118 --- /dev/null +++ b/sandbox/linux/services/broker_process.cc @@ -0,0 +1,350 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/broker_process.h" + +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket.h" + +namespace { + +static const int kCommandOpen = 'O'; +static const size_t kMaxMessageLength = 4096; + +// Some flags will need special treatment on the client side and are not +// supported for now. +int ForCurrentProcessFlagsMask() { + return O_CLOEXEC | O_NONBLOCK; +} + +// Check whether |requested_filename| is in |allowed_file_names|. +// See GetFileNameIfAllowedAccess() for an explaination of |file_to_open|. +// async signal safe if |file_to_open| is NULL. +// TODO(jln): assert signal safety. +bool GetFileNameInWhitelist(const std::vector<std::string>& allowed_file_names, + const std::string& requested_filename, + const char** file_to_open) { + if (file_to_open && *file_to_open) { + // Make sure that callers never pass a non-empty string. In case callers + // wrongly forget to check the return value and look at the string + // instead, this could catch bugs. + RAW_LOG(FATAL, "*file_to_open should be NULL"); + return false; + } + std::vector<std::string>::const_iterator it; + it = std::find(allowed_file_names.begin(), allowed_file_names.end(), + requested_filename); + if (it < allowed_file_names.end()) { // requested_filename was found? + if (file_to_open) + *file_to_open = it->c_str(); + return true; + } + return false; +} + +// We maintain a list of flags that have been reviewed for "sanity" and that +// we're ok to allow in the broker. +// I.e. here is where we wouldn't add O_RESET_FILE_SYSTEM. +bool IsAllowedOpenFlags(int flags) { + // First, check the access mode + const int access_mode = flags & O_ACCMODE; + if (access_mode != O_RDONLY && access_mode != O_WRONLY && + access_mode != O_RDWR) { + return false; + } + + // Some flags affect the behavior of the current process. We don't support + // them and don't allow them for now. + if (flags & ForCurrentProcessFlagsMask()) { + return false; + } + + // Now check that all the flags are known to us. + const int creation_and_status_flags = flags & ~O_ACCMODE; + + const int known_flags = + O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT | + O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME | O_NOCTTY | + O_NOFOLLOW | O_NONBLOCK | O_NDELAY | O_SYNC | O_TRUNC; + + const int unknown_flags = ~known_flags; + const bool has_unknown_flags = creation_and_status_flags & unknown_flags; + return !has_unknown_flags; +} + +} // namespace + +namespace sandbox { + +BrokerProcess::BrokerProcess(const std::vector<std::string>& allowed_r_files, + const std::vector<std::string>& allowed_w_files, + bool fast_check_in_client, + bool quiet_failures_for_tests) + : initialized_(false), + is_child_(false), + fast_check_in_client_(fast_check_in_client), + quiet_failures_for_tests_(quiet_failures_for_tests), + broker_pid_(-1), + allowed_r_files_(allowed_r_files), + allowed_w_files_(allowed_w_files), + ipc_socketpair_(-1) { +} + +BrokerProcess::~BrokerProcess() { + if (initialized_ && ipc_socketpair_ != -1) { + void (HANDLE_EINTR(close(ipc_socketpair_))); + } +} + +bool BrokerProcess::Init(void* sandbox_callback) { + CHECK(!initialized_); + CHECK_EQ(sandbox_callback, (void*) NULL) << + "sandbox_callback is not implemented"; + int socket_pair[2]; + // Use SOCK_SEQPACKET, because we need to preserve message boundaries + // but we also want to be notified (recvmsg should return and not block) + // when the connection has been broken (one of the processes died). + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socket_pair)) { + LOG(ERROR) << "Failed to create socketpair"; + return false; + } + + int child_pid = fork(); + if (child_pid == -1) { + (void) HANDLE_EINTR(close(socket_pair[0])); + (void) HANDLE_EINTR(close(socket_pair[1])); + return false; + } + if (child_pid) { + // We are the parent and we have just forked our broker process. + (void) HANDLE_EINTR(close(socket_pair[0])); + // We should only be able to write to the IPC channel. We'll always send + // a new file descriptor to receive the reply on. + shutdown(socket_pair[1], SHUT_RD); + ipc_socketpair_ = socket_pair[1]; + is_child_ = false; + broker_pid_ = child_pid; + initialized_ = true; + return true; + } else { + // We are the broker. + (void) HANDLE_EINTR(close(socket_pair[1])); + // We should only be able to read from this IPC channel. We will send our + // replies on a new file descriptor attached to the requests. + shutdown(socket_pair[0], SHUT_WR); + ipc_socketpair_ = socket_pair[0]; + is_child_ = true; + // TODO(jln): activate a sandbox here. + initialized_ = true; + for (;;) { + HandleRequest(); + } + _exit(1); + } + NOTREACHED(); +} + +// This function needs to be async signal safe. +int BrokerProcess::Open(const char* pathname, int flags) const { + RAW_CHECK(initialized_); // async signal safe CHECK(). + if (!pathname) + return -EFAULT; + // There is no point in forwarding a request that we know will be denied. + // Of course, the real security check needs to be on the other side of the + // IPC. + if (fast_check_in_client_) { + if (!GetFileNameIfAllowedAccess(pathname, flags, NULL)) + return -EPERM; + } + + Pickle write_pickle; + write_pickle.WriteInt(kCommandOpen); + write_pickle.WriteString(pathname); + write_pickle.WriteInt(flags); + RAW_CHECK(write_pickle.size() <= kMaxMessageLength); + + int returned_fd = -1; + uint8_t reply_buf[kMaxMessageLength]; + // Send a request (in write_pickle) as well that will include a new + // temporary socketpair (created internally by SendRecvMsg()). + // Then read the reply on this new socketpair in reply_buf and put an + // eventual attached file descriptor in |returned_fd|. + // TODO(jln): this API needs some rewriting and documentation. + ssize_t msg_len = UnixDomainSocket::SendRecvMsg(ipc_socketpair_, + reply_buf, + sizeof(reply_buf), + &returned_fd, + write_pickle); + if (msg_len <= 0) { + if (!quiet_failures_for_tests_) + RAW_LOG(ERROR, "Could not make request to broker process"); + return -ENOMEM; + } + + Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); + PickleIterator iter(read_pickle); + int return_value = -1; + // Now deserialize the return value and eventually return the file + // descriptor. + if (read_pickle.ReadInt(&iter, &return_value)) { + if (return_value < 0) { + RAW_CHECK(returned_fd == -1); + return return_value; + } else { + // We have a real file descriptor to return. + RAW_CHECK(returned_fd >= 0); + return returned_fd; + } + } else { + RAW_LOG(ERROR, "Could not read pickle"); + return -1; + } +} + +// Handle a request on the IPC channel ipc_socketpair_. +// A request should have a file descriptor attached on which we will reply and +// that we will then close. +// A request should start with an int that will be used as the command type. +bool BrokerProcess::HandleRequest() const { + + std::vector<int> fds; + char buf[kMaxMessageLength]; + errno = 0; + const ssize_t msg_len = UnixDomainSocket::RecvMsg(ipc_socketpair_, buf, + sizeof(buf), &fds); + + if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) { + // EOF from our parent, or our parent died, we should die. + _exit(0); + } + + // The parent should send exactly one file descriptor, on which we + // will write the reply. + if (msg_len < 0 || fds.size() != 1 || fds.at(0) < 0) { + PLOG(ERROR) << "Error reading message from the client"; + return false; + } + + const int temporary_ipc = fds.at(0); + + Pickle pickle(buf, msg_len); + PickleIterator iter(pickle); + int command_type; + if (pickle.ReadInt(&iter, &command_type)) { + bool r = false; + // Go through all the possible IPC messages. + switch (command_type) { + case kCommandOpen: + // We reply on the file descriptor sent to us via the IPC channel. + r = HandleOpenRequest(temporary_ipc, pickle, iter); + (void) HANDLE_EINTR(close(temporary_ipc)); + return r; + default: + NOTREACHED(); + return false; + } + } + + LOG(ERROR) << "Error parsing IPC request"; + return false; +} + +// Handle an open request contained in |read_pickle| and send the reply +// on |reply_ipc|. +bool BrokerProcess::HandleOpenRequest(int reply_ipc, + const Pickle& read_pickle, + PickleIterator iter) const { + std::string requested_filename; + int flags = 0; + if (!read_pickle.ReadString(&iter, &requested_filename) || + !read_pickle.ReadInt(&iter, &flags)) { + return -1; + } + + Pickle write_pickle; + std::vector<int> opened_files; + + const char* file_to_open = NULL; + const bool safe_to_open_file = GetFileNameIfAllowedAccess( + requested_filename.c_str(), flags, &file_to_open); + + if (safe_to_open_file) { + CHECK(file_to_open); + // O_CLOEXEC doesn't hurt (even though we won't execve()), and this + // property won't be passed to the client. + // We may want to think about O_NONBLOCK as well. + int opened_fd = open(file_to_open, flags | O_CLOEXEC); + if (opened_fd < 0) { + write_pickle.WriteInt(-errno); + } else { + // Success. + opened_files.push_back(opened_fd); + write_pickle.WriteInt(0); + } + } else { + write_pickle.WriteInt(-EPERM); + } + + CHECK_LE(write_pickle.size(), kMaxMessageLength); + ssize_t sent = UnixDomainSocket::SendMsg(reply_ipc, write_pickle.data(), + write_pickle.size(), opened_files); + + // Close anything we have opened in this process. + for (std::vector<int>::iterator it = opened_files.begin(); + it < opened_files.end(); ++it) { + (void) HANDLE_EINTR(close(*it)); + } + + if (sent <= 0) { + LOG(ERROR) << "Could not send IPC reply"; + return false; + } + return true; +} + +// For paranoia, if |file_to_open| is not NULL, we will return the matching +// string from the white list. +// Async signal safe only if |file_to_open| is NULL. +// Even if an attacker managed to fool the string comparison mechanism, we +// would not open an attacker-controlled file name. +// Return true if access should be allowed, false otherwise. +bool BrokerProcess::GetFileNameIfAllowedAccess(const char* requested_filename, + int requested_flags, const char** file_to_open) const { + if (!IsAllowedOpenFlags(requested_flags)) { + return false; + } + switch (requested_flags & O_ACCMODE) { + case O_RDONLY: + return GetFileNameInWhitelist(allowed_r_files_, requested_filename, + file_to_open); + case O_WRONLY: + return GetFileNameInWhitelist(allowed_w_files_, requested_filename, + file_to_open); + case O_RDWR: + { + bool allowed_for_read_and_write = + GetFileNameInWhitelist(allowed_r_files_, requested_filename, NULL) && + GetFileNameInWhitelist(allowed_w_files_, requested_filename, + file_to_open); + return allowed_for_read_and_write; + } + default: + return false; + } +} + +} // namespace sandbox. diff --git a/sandbox/linux/services/broker_process.h b/sandbox/linux/services/broker_process.h new file mode 100644 index 0000000..8498239 --- /dev/null +++ b/sandbox/linux/services/broker_process.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ +#define SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/pickle.h" +#include "base/process.h" + +namespace sandbox { + +// Create a new "broker" process to which we can send requests via an IPC +// channel. +// This is a low level IPC mechanism that is suitable to be called from a +// signal handler. +// A process would typically create a broker process before entering +// sandboxing. +// 1. BrokerProcess open_broker(file_whitelist); +// 2. CHECK(open_broker.Init(NULL)); +// 3. Enable sandbox. +// 4. Use open_broker.Open() to open files. +class BrokerProcess { + public: + // |allowed_file_names| is a white list of files that can be opened later via + // the Open() API. + // |fast_check_in_client| and |quiet_failures_for_tests| are reserved for + // unit tests, don't use it. + explicit BrokerProcess(const std::vector<std::string>& allowed_r_files_, + const std::vector<std::string>& allowed_w_files_, + bool fast_check_in_client = true, + bool quiet_failures_for_tests = false); + ~BrokerProcess(); + // Will initialize the broker process. There should be no threads at this + // point, since we need to fork(). + // sandbox_callback should be NULL as this feature is not implemented yet. + bool Init(void* sandbox_callback); + + // Can be used in place of open(). Will be async signal safe. + // The implementation only supports certain white listed flags and will + // return -EPERM on other flags. + // It's similar to the open() system call and will return -errno on errors. + int Open(const char* pathname, int flags) const; + + int broker_pid() const { return broker_pid_; } + + private: + bool HandleRequest() const; + bool HandleOpenRequest(int reply_ipc, const Pickle& read_pickle, + PickleIterator iter) const; + bool GetFileNameIfAllowedAccess(const char*, int, const char**) const; + bool initialized_; // Whether we've been through Init() yet. + bool is_child_; // Whether we're the child (broker process). + bool fast_check_in_client_; // Whether to forward a request that we know + // will be denied to the broker. + bool quiet_failures_for_tests_; // Disable certain error message when + // testing for failures. + pid_t broker_pid_; // The PID of the broker (child). + const std::vector<std::string> allowed_r_files_; // Files allowed for read. + const std::vector<std::string> allowed_w_files_; // Files allowed for write. + int ipc_socketpair_; // Our communication channel to parent or child. + DISALLOW_IMPLICIT_CONSTRUCTORS(BrokerProcess); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ diff --git a/sandbox/linux/services/broker_process_unittest.cc b/sandbox/linux/services/broker_process_unittest.cc new file mode 100644 index 0000000..879fe66 --- /dev/null +++ b/sandbox/linux/services/broker_process_unittest.cc @@ -0,0 +1,271 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/broker_process.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <string> +#include <vector> + +#include "base/logging.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +TEST(BrokerProcess, CreateAndDestroy) { + std::vector<std::string> read_whitelist; + read_whitelist.push_back("/proc/cpuinfo"); + + BrokerProcess* open_broker = new BrokerProcess(read_whitelist, + std::vector<std::string>()); + ASSERT_TRUE(open_broker->Init(NULL)); + pid_t broker_pid = open_broker->broker_pid(); + delete(open_broker); + + // Now we check that the broker has exited properly. + int status = 0; + EXPECT_EQ(waitpid(broker_pid, &status, 0), broker_pid); + EXPECT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), 0); +} + +TEST(BrokerProcess, TestOpenNull) { + const std::vector<std::string> empty; + BrokerProcess open_broker(empty, empty); + ASSERT_TRUE(open_broker.Init(NULL)); + + int fd = open_broker.Open(NULL, O_RDONLY); + EXPECT_EQ(fd, -EFAULT); +} + +void TestOpenFilePerms(bool fast_check_in_client) { + const char kR_WhiteListed[] = "/proc/DOESNOTEXIST1"; + const char kW_WhiteListed[] = "/proc/DOESNOTEXIST2"; + const char kRW_WhiteListed[] = "/proc/DOESNOTEXIST3"; + const char k_NotWhitelisted[] = "/proc/DOESNOTEXIST4"; + + std::vector<std::string> read_whitelist; + read_whitelist.push_back(kR_WhiteListed); + read_whitelist.push_back(kRW_WhiteListed); + + std::vector<std::string> write_whitelist; + write_whitelist.push_back(kW_WhiteListed); + write_whitelist.push_back(kRW_WhiteListed); + + BrokerProcess open_broker(read_whitelist, + write_whitelist, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(NULL)); + + int fd = -1; + fd = open_broker.Open(kR_WhiteListed, O_RDONLY); + EXPECT_EQ(fd, -ENOENT); + fd = open_broker.Open(kR_WhiteListed, O_WRONLY); + EXPECT_EQ(fd, -EPERM); + fd = open_broker.Open(kR_WhiteListed, O_RDWR); + EXPECT_EQ(fd, -EPERM); + + fd = open_broker.Open(kW_WhiteListed, O_RDONLY); + EXPECT_EQ(fd, -EPERM); + fd = open_broker.Open(kW_WhiteListed, O_WRONLY); + EXPECT_EQ(fd, -ENOENT); + fd = open_broker.Open(kW_WhiteListed, O_RDWR); + EXPECT_EQ(fd, -EPERM); + + fd = open_broker.Open(kRW_WhiteListed, O_RDONLY); + EXPECT_EQ(fd, -ENOENT); + fd = open_broker.Open(kRW_WhiteListed, O_WRONLY); + EXPECT_EQ(fd, -ENOENT); + fd = open_broker.Open(kRW_WhiteListed, O_RDWR); + EXPECT_EQ(fd, -ENOENT); + + fd = open_broker.Open(k_NotWhitelisted, O_RDONLY); + EXPECT_EQ(fd, -EPERM); + fd = open_broker.Open(k_NotWhitelisted, O_WRONLY); + EXPECT_EQ(fd, -EPERM); + fd = open_broker.Open(k_NotWhitelisted, O_RDWR); + EXPECT_EQ(fd, -EPERM); + + // We have some extra sanity check for clearly wrong values. + fd = open_broker.Open(kRW_WhiteListed, O_RDONLY|O_WRONLY|O_RDWR); + EXPECT_EQ(fd, -EPERM); +} + +// Run the same thing twice. The second time, we make sure that no security +// check is performed on the client. +TEST(BrokerProcess, OpenFilePermsWithClientCheck) { + TestOpenFilePerms(true /* fast_check_in_client */); +} + +TEST(BrokerProcess, OpenOpenFilePermsNoClientCheck) { + TestOpenFilePerms(false /* fast_check_in_client */); +} + + +void TestOpenCpuinfo(bool fast_check_in_client) { + const char kFileCpuInfo[] = "/proc/cpuinfo"; + std::vector<std::string> read_whitelist; + read_whitelist.push_back(kFileCpuInfo); + + BrokerProcess* open_broker = new BrokerProcess(read_whitelist, + std::vector<std::string>(), + fast_check_in_client); + ASSERT_TRUE(open_broker->Init(NULL)); + pid_t broker_pid = open_broker->broker_pid(); + + int fd = -1; + fd = open_broker->Open(kFileCpuInfo, O_RDWR); + EXPECT_EQ(fd, -EPERM); + + // Open cpuinfo via the broker. + int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); + ASSERT_GE(cpuinfo_fd, 0); + char buf[3]; + memset(buf, 0, sizeof(buf)); + int read_len1 = read(cpuinfo_fd, buf, sizeof(buf)); + EXPECT_GT(read_len1, 0); + + // Open cpuinfo directly. + int cpuinfo_fd2 = open(kFileCpuInfo, O_RDONLY); + ASSERT_GE(cpuinfo_fd2, 0); + char buf2[3]; + memset(buf2, 1, sizeof(buf2)); + int read_len2 = read(cpuinfo_fd2, buf2, sizeof(buf2)); + EXPECT_GT(read_len1, 0); + + // The following is not guaranteed true, but will be in practice. + EXPECT_EQ(read_len1, read_len2); + // Compare the cpuinfo as returned by the broker with the one we opened + // ourselves. + EXPECT_EQ(memcmp(buf, buf2, read_len1), 0); + + if (fd >= 0) + close(fd); + if (cpuinfo_fd >= 0) + close(cpuinfo_fd); + if (cpuinfo_fd2 >= 0) + close(cpuinfo_fd); + + delete(open_broker); + + // Now we check that the broker has exited properly. + int status = 0; + EXPECT_EQ(waitpid(broker_pid, &status, 0), broker_pid); + EXPECT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), 0); +} + +// Run the same thing twice. The second time, we make sure that no security +// check is performed on the client. +TEST(BrokerProcess, OpenCpuinfoWithClientCheck) { + TestOpenCpuinfo(true /* fast_check_in_client */); +} + +TEST(BrokerProcess, OpenCpuinfoNoClientCheck) { + TestOpenCpuinfo(false /* fast_check_in_client */); +} + +TEST(BrokerProcess, OpenFileRW) { + char templatename[] = "BrokerProcessXXXXXX"; + int tempfile = mkstemp(templatename); + ASSERT_GE(tempfile, 0); + char tempfile_name[2048]; + int written = snprintf(tempfile_name, sizeof(tempfile_name), + "/proc/self/fd/%d", tempfile); + ASSERT_LT(written, static_cast<int>(sizeof(tempfile_name))); + + std::vector<std::string> whitelist; + whitelist.push_back(tempfile_name); + + BrokerProcess open_broker(whitelist, whitelist); + ASSERT_TRUE(open_broker.Init(NULL)); + + int tempfile2 = -1; + tempfile2 = open_broker.Open(tempfile_name, O_RDWR); + ASSERT_GE(tempfile2, 0); + + // Write to the descriptor opened by the broker. + char test_text[] = "TESTTESTTEST"; + ssize_t len = write(tempfile2, test_text, sizeof(test_text)); + 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 = read(tempfile, buf, sizeof(buf)); + + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(test_text))); + ASSERT_EQ(memcmp(test_text, buf, sizeof(test_text)), 0); + + // Cleanup the temporary file. + char tempfile_full_path[2048]; + // Make sure tempfile_full_path will terminate with a 0. + memset(tempfile_full_path, 0, sizeof(tempfile_full_path)); + ssize_t ret = readlink(tempfile_name, tempfile_full_path, + sizeof(tempfile_full_path)); + ASSERT_GT(ret, 0); + // Make sure we still have a trailing zero in tempfile_full_path. + ASSERT_LT(ret, static_cast<ssize_t>(sizeof(tempfile_full_path))); + ASSERT_EQ(unlink(tempfile_full_path), 0); + + EXPECT_EQ(close(tempfile), 0); + EXPECT_EQ(close(tempfile2), 0); +} + +// Sandbox test because we could get a SIGPIPE. +SANDBOX_TEST(BrokerProcess, BrokerDied) { + std::vector<std::string> read_whitelist; + read_whitelist.push_back("/proc/cpuinfo"); + + BrokerProcess open_broker(read_whitelist, + std::vector<std::string>(), + true /* fast_check_in_client */, + true /* quiet_failures_for_tests */); + SANDBOX_ASSERT(open_broker.Init(NULL)); + pid_t broker_pid = open_broker.broker_pid(); + SANDBOX_ASSERT(kill(broker_pid, SIGKILL) == 0); + + // Now we check that the broker has exited properly. + int status = 0; + SANDBOX_ASSERT(waitpid(broker_pid, &status, 0) == broker_pid); + SANDBOX_ASSERT(WIFSIGNALED(status)); + SANDBOX_ASSERT(WTERMSIG(status) == SIGKILL); + // Hopefully doing Open with a dead broker won't SIGPIPE us. + SANDBOX_ASSERT(open_broker.Open("/proc/cpuinfo", O_RDONLY) == -ENOMEM); +} + +void TestComplexFlags(bool fast_check_in_client) { + std::vector<std::string> whitelist; + whitelist.push_back("/proc/cpuinfo"); + + BrokerProcess open_broker(whitelist, + whitelist, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(NULL)); + // Test that we do the right thing for O_CLOEXEC and O_NONBLOCK. + // Presently, the right thing is to always deny them since they are not + // supported. + int fd = -1; + fd = open_broker.Open("/proc/cpuinfo", O_RDONLY); + ASSERT_GE(fd, 0); + ASSERT_EQ(close(fd), 0); + + ASSERT_EQ(open_broker.Open("/proc/cpuinfo", O_RDONLY | O_CLOEXEC), -EPERM); + ASSERT_EQ(open_broker.Open("/proc/cpuinfo", O_RDONLY | O_NONBLOCK), -EPERM); +} + +TEST(BrokerProcess, ComplexFlagsWithClientCheck) { + TestComplexFlags(true /* fast_check_in_client */); +} + +TEST(BrokerProcess, ComplexFlagsNoClientCheck) { + TestComplexFlags(false /* fast_check_in_client */); +} + +} // namespace sandbox |