| // 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.h" | 
 | #include "base/third_party/valgrind/valgrind.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" | 
 |  | 
 | 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 (int fd : opened_files) { | 
 |     int ret = IGNORE_EINTR(close(fd)); | 
 |     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 |