// Copyright 2014 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/syscall_broker/broker_host.h" #include <errno.h> #include <fcntl.h> #include <stddef.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/syscall.h> #include <sys/types.h> #include <unistd.h> #include <string> #include <utility> #include <vector> #include "base/files/scoped_file.h" #include "base/logging.h" #include "base/pickle.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/unix_domain_socket_linux.h" #include "sandbox/linux/syscall_broker/broker_common.h" #include "sandbox/linux/syscall_broker/broker_policy.h" #include "sandbox/linux/system_headers/linux_syscalls.h" #include "third_party/valgrind/valgrind.h" namespace sandbox { namespace syscall_broker { namespace { bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } // A little open(2) wrapper to handle some oddities for us. In the general case // make a direct system call since we want to keep in control of the broker // process' system calls profile to be able to loosely sandbox it. int sys_open(const char* pathname, int flags) { // Hardcode mode to rw------- when creating files. int mode; if (flags & O_CREAT) { mode = 0600; } else { mode = 0; } if (IsRunningOnValgrind()) { // Valgrind does not support AT_FDCWD, just use libc's open() in this case. return open(pathname, flags, mode); } else { return syscall(__NR_openat, AT_FDCWD, pathname, flags, mode); } } // Open |requested_filename| with |flags| if allowed by our policy. // Write the syscall return value (-errno) to |write_pickle| and append // a file descriptor to |opened_files| if relevant. void OpenFileForIPC(const BrokerPolicy& policy, const std::string& requested_filename, int flags, base::Pickle* write_pickle, std::vector<int>* opened_files) { DCHECK(write_pickle); DCHECK(opened_files); const char* file_to_open = NULL; bool unlink_after_open = false; const bool safe_to_open_file = policy.GetFileNameIfAllowedToOpen( requested_filename.c_str(), flags, &file_to_open, &unlink_after_open); if (safe_to_open_file) { CHECK(file_to_open); int opened_fd = sys_open(file_to_open, flags); if (opened_fd < 0) { write_pickle->WriteInt(-errno); } else { // Success. if (unlink_after_open) { unlink(file_to_open); } opened_files->push_back(opened_fd); write_pickle->WriteInt(0); } } else { write_pickle->WriteInt(-policy.denied_errno()); } } // Perform access(2) on |requested_filename| with mode |mode| if allowed by our // policy. Write the syscall return value (-errno) to |write_pickle|. void AccessFileForIPC(const BrokerPolicy& policy, const std::string& requested_filename, int mode, base::Pickle* write_pickle) { DCHECK(write_pickle); const char* file_to_access = NULL; const bool safe_to_access_file = policy.GetFileNameIfAllowedToAccess( requested_filename.c_str(), mode, &file_to_access); if (safe_to_access_file) { CHECK(file_to_access); int access_ret = access(file_to_access, mode); int access_errno = errno; if (!access_ret) write_pickle->WriteInt(0); else write_pickle->WriteInt(-access_errno); } else { write_pickle->WriteInt(-policy.denied_errno()); } } // Handle a |command_type| request contained in |iter| and send the reply // on |reply_ipc|. // Currently COMMAND_OPEN and COMMAND_ACCESS are supported. bool HandleRemoteCommand(const BrokerPolicy& policy, IPCCommand command_type, int reply_ipc, base::PickleIterator iter) { // Currently all commands have two arguments: filename and flags. std::string requested_filename; int flags = 0; if (!iter.ReadString(&requested_filename) || !iter.ReadInt(&flags)) return false; base::Pickle write_pickle; std::vector<int> opened_files; switch (command_type) { case COMMAND_ACCESS: AccessFileForIPC(policy, requested_filename, flags, &write_pickle); break; case COMMAND_OPEN: OpenFileForIPC( policy, requested_filename, flags, &write_pickle, &opened_files); break; default: LOG(ERROR) << "Invalid IPC command"; break; } CHECK_LE(write_pickle.size(), kMaxMessageLength); ssize_t sent = base::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) { int ret = IGNORE_EINTR(close(*it)); DCHECK(!ret) << "Could not close file descriptor"; } if (sent <= 0) { LOG(ERROR) << "Could not send IPC reply"; return false; } return true; } } // namespace BrokerHost::BrokerHost(const BrokerPolicy& broker_policy, BrokerChannel::EndPoint ipc_channel) : broker_policy_(broker_policy), ipc_channel_(std::move(ipc_channel)) {} BrokerHost::~BrokerHost() { } // Handle a request on the IPC channel ipc_channel_. // 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. BrokerHost::RequestStatus BrokerHost::HandleRequest() const { std::vector<base::ScopedFD> fds; char buf[kMaxMessageLength]; errno = 0; const ssize_t msg_len = base::UnixDomainSocket::RecvMsg( ipc_channel_.get(), buf, sizeof(buf), &fds); if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) { // EOF from the client, or the client died, we should die. return RequestStatus::LOST_CLIENT; } // The client should send exactly one file descriptor, on which we // will write the reply. if (msg_len < 0 || fds.size() != 1 || fds[0].get() < 0) { PLOG(ERROR) << "Error reading message from the client"; return RequestStatus::FAILURE; } base::ScopedFD temporary_ipc(std::move(fds[0])); base::Pickle pickle(buf, msg_len); base::PickleIterator iter(pickle); int command_type; if (iter.ReadInt(&command_type)) { bool command_handled = false; // Go through all the possible IPC messages. switch (command_type) { case COMMAND_ACCESS: case COMMAND_OPEN: // We reply on the file descriptor sent to us via the IPC channel. command_handled = HandleRemoteCommand( broker_policy_, static_cast<IPCCommand>(command_type), temporary_ipc.get(), iter); break; default: NOTREACHED(); break; } if (command_handled) { return RequestStatus::SUCCESS; } else { return RequestStatus::FAILURE; } NOTREACHED(); } LOG(ERROR) << "Error parsing IPC request"; return RequestStatus::FAILURE; } } // namespace syscall_broker } // namespace sandbox