| // Copyright 2014 The Chromium Authors |
| // 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 <limits.h> |
| #include <stddef.h> |
| #include <sys/inotify.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/syscall.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <array> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/files/scoped_file.h" |
| #include "base/logging.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "sandbox/linux/services/syscall_wrappers.h" |
| #include "sandbox/linux/syscall_broker/broker_command.h" |
| #include "sandbox/linux/syscall_broker/broker_permission_list.h" |
| #include "sandbox/linux/syscall_broker/broker_simple_message.h" |
| #include "sandbox/linux/system_headers/linux_stat.h" |
| #include "sandbox/linux/system_headers/linux_syscalls.h" |
| |
| namespace sandbox { |
| namespace syscall_broker { |
| |
| namespace { |
| |
| const char kProcSelf[] = "/proc/self/"; |
| const size_t kProcSelfNumChars = sizeof(kProcSelf) - 1; |
| |
| // 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 = (flags & O_CREAT) ? 0600 : 0; |
| return syscall(__NR_openat, AT_FDCWD, pathname, flags, mode); |
| } |
| |
| } // namespace |
| |
| // Applies a rewrite from /proc/self/ to /proc/[pid of sandboxed process]/. |
| // Returns either a rewritten or the original pathname. |
| absl::optional<std::string> BrokerHost::RewritePathname(const char* pathname) { |
| if (base::StartsWith(pathname, kProcSelf)) { |
| return base::StringPrintf("/proc/%d/%s", sandboxed_process_pid_, |
| pathname + kProcSelfNumChars); |
| } |
| |
| return absl::nullopt; |
| } |
| |
| absl::optional<std::pair<const char*, int>> BrokerHost::GetPathAndFlags( |
| BrokerSimpleMessage* message) { |
| const char* pathname; |
| int flags; |
| if (!message->ReadString(&pathname) || !message->ReadInt(&flags)) { |
| return absl::nullopt; |
| } |
| return {{pathname, flags}}; |
| } |
| |
| // Perform access(2) on |requested_filename| with mode |mode| if allowed by our |
| // permission_list. Write the syscall return value (-errno) to |reply|. |
| void BrokerHost::AccessFileForIPC(const char* requested_filename, |
| int mode, |
| BrokerSimpleMessage* reply) { |
| const char* file_to_access = |
| CommandAccessIsSafe(policy_->allowed_command_set, |
| *policy_->file_permissions, requested_filename, mode); |
| if (!file_to_access) { |
| RAW_CHECK( |
| reply->AddIntToMessage(-policy_->file_permissions->denied_errno())); |
| return; |
| } |
| |
| absl::optional<std::string> rewritten_filename = |
| RewritePathname(file_to_access); |
| if (rewritten_filename.has_value()) { |
| file_to_access = rewritten_filename.value().c_str(); |
| } |
| |
| if (access(file_to_access, mode) < 0) { |
| RAW_CHECK(reply->AddIntToMessage(-errno)); |
| return; |
| } |
| |
| RAW_CHECK(reply->AddIntToMessage(0)); |
| } |
| |
| // Performs mkdir(2) on |requested_filename| with mode |mode| if allowed by our |
| // permission_list. Write the syscall return value (-errno) to |reply|. |
| void BrokerHost::MkdirFileForIPC(const char* requested_filename, |
| int mode, |
| BrokerSimpleMessage* reply) { |
| const char* file_to_access = |
| CommandMkdirIsSafe(policy_->allowed_command_set, |
| *policy_->file_permissions, requested_filename); |
| if (!file_to_access) { |
| RAW_CHECK( |
| reply->AddIntToMessage(-policy_->file_permissions->denied_errno())); |
| return; |
| } |
| |
| absl::optional<std::string> rewritten_filename = |
| RewritePathname(file_to_access); |
| if (rewritten_filename.has_value()) { |
| file_to_access = rewritten_filename.value().c_str(); |
| } |
| |
| if (mkdir(file_to_access, mode) < 0) { |
| RAW_CHECK(reply->AddIntToMessage(-errno)); |
| return; |
| } |
| RAW_CHECK(reply->AddIntToMessage(0)); |
| } |
| |
| // Open |requested_filename| with |flags| if allowed by our permission list. |
| // Write the syscall return value (-errno) to |reply| and return the |
| // file descriptor in the |opened_file| if relevant. |
| void BrokerHost::OpenFileForIPC(const char* requested_filename, |
| int flags, |
| BrokerSimpleMessage* reply, |
| base::ScopedFD* opened_file) { |
| const char* file_to_open = nullptr; |
| bool unlink_after_open = false; |
| std::tie(file_to_open, unlink_after_open) = |
| CommandOpenIsSafe(policy_->allowed_command_set, |
| *policy_->file_permissions, requested_filename, flags); |
| if (!file_to_open) { |
| RAW_CHECK( |
| reply->AddIntToMessage(-policy_->file_permissions->denied_errno())); |
| return; |
| } |
| |
| absl::optional<std::string> rewritten_filename = |
| RewritePathname(file_to_open); |
| if (rewritten_filename.has_value()) { |
| file_to_open = rewritten_filename.value().c_str(); |
| } |
| |
| opened_file->reset(sys_open(file_to_open, flags)); |
| if (!opened_file->is_valid()) { |
| RAW_CHECK(reply->AddIntToMessage(-errno)); |
| return; |
| } |
| |
| if (unlink_after_open) |
| unlink(file_to_open); |
| |
| RAW_CHECK(reply->AddIntToMessage(0)); |
| } |
| |
| // Perform rename(2) on |old_filename| to |new_filename| and write the |
| // result to |return_val|. |
| void BrokerHost::RenameFileForIPC(const char* old_filename, |
| const char* new_filename, |
| BrokerSimpleMessage* reply) { |
| const char* old_file_to_access = nullptr; |
| const char* new_file_to_access = nullptr; |
| std::tie(old_file_to_access, new_file_to_access) = CommandRenameIsSafe( |
| policy_->allowed_command_set, *policy_->file_permissions, old_filename, |
| new_filename); |
| if (!old_file_to_access || !new_file_to_access) { |
| RAW_CHECK( |
| reply->AddIntToMessage(-policy_->file_permissions->denied_errno())); |
| return; |
| } |
| |
| absl::optional<std::string> old_rewritten_filename = |
| RewritePathname(old_file_to_access); |
| if (old_rewritten_filename) { |
| old_file_to_access = old_rewritten_filename.value().c_str(); |
| } |
| |
| absl::optional<std::string> new_rewritten_filename = |
| RewritePathname(new_file_to_access); |
| if (new_rewritten_filename) { |
| new_file_to_access = new_rewritten_filename.value().c_str(); |
| } |
| |
| if (rename(old_file_to_access, new_file_to_access) < 0) { |
| RAW_CHECK(reply->AddIntToMessage(-errno)); |
| return; |
| } |
| RAW_CHECK(reply->AddIntToMessage(0)); |
| } |
| |
| // Perform readlink(2) on |filename| using a buffer of MAX_PATH bytes. |
| void BrokerHost::ReadlinkFileForIPC(const char* requested_filename, |
| BrokerSimpleMessage* reply) { |
| const char* file_to_access = |
| CommandReadlinkIsSafe(policy_->allowed_command_set, |
| *policy_->file_permissions, requested_filename); |
| if (!file_to_access) { |
| RAW_CHECK( |
| reply->AddIntToMessage(-policy_->file_permissions->denied_errno())); |
| return; |
| } |
| |
| absl::optional<std::string> rewritten_filename = |
| RewritePathname(file_to_access); |
| if (rewritten_filename.has_value()) { |
| file_to_access = rewritten_filename.value().c_str(); |
| } |
| |
| char buf[PATH_MAX]; |
| ssize_t result = readlink(file_to_access, buf, sizeof(buf)); |
| if (result < 0) { |
| RAW_CHECK(reply->AddIntToMessage(-errno)); |
| return; |
| } |
| RAW_CHECK(reply->AddIntToMessage(result)); |
| RAW_CHECK(reply->AddDataToMessage(buf, result)); |
| } |
| |
| void BrokerHost::RmdirFileForIPC(const char* requested_filename, |
| BrokerSimpleMessage* reply) { |
| const char* file_to_access = |
| CommandRmdirIsSafe(policy_->allowed_command_set, |
| *policy_->file_permissions, requested_filename); |
| if (!file_to_access) { |
| RAW_CHECK( |
| reply->AddIntToMessage(-policy_->file_permissions->denied_errno())); |
| return; |
| } |
| |
| absl::optional<std::string> rewritten_filename = |
| RewritePathname(file_to_access); |
| if (rewritten_filename.has_value()) { |
| file_to_access = rewritten_filename.value().c_str(); |
| } |
| |
| if (rmdir(file_to_access) < 0) { |
| RAW_CHECK(reply->AddIntToMessage(-errno)); |
| return; |
| } |
| RAW_CHECK(reply->AddIntToMessage(0)); |
| } |
| |
| // Perform stat(2) on |requested_filename| and write the result to |
| // |return_val|. |
| void BrokerHost::StatFileForIPC(BrokerCommand command_type, |
| |
| const char* requested_filename, |
| bool follow_links, |
| BrokerSimpleMessage* reply) { |
| const char* file_to_access = |
| CommandStatIsSafe(policy_->allowed_command_set, |
| *policy_->file_permissions, requested_filename); |
| if (!file_to_access) { |
| RAW_CHECK( |
| reply->AddIntToMessage(-policy_->file_permissions->denied_errno())); |
| return; |
| } |
| |
| absl::optional<std::string> rewritten_filename = |
| RewritePathname(file_to_access); |
| if (rewritten_filename.has_value()) { |
| file_to_access = rewritten_filename.value().c_str(); |
| } |
| |
| if (command_type == COMMAND_STAT) { |
| struct kernel_stat sb; |
| |
| int sts = follow_links ? sandbox::sys_stat(file_to_access, &sb) |
| : sandbox::sys_lstat(file_to_access, &sb); |
| if (sts < 0) { |
| RAW_CHECK(reply->AddIntToMessage(-errno)); |
| return; |
| } |
| RAW_CHECK(reply->AddIntToMessage(0)); |
| RAW_CHECK( |
| reply->AddDataToMessage(reinterpret_cast<char*>(&sb), sizeof(sb))); |
| } else { |
| #if defined(__NR_fstatat64) |
| DCHECK(command_type == COMMAND_STAT64); |
| struct kernel_stat64 sb; |
| |
| int sts = sandbox::sys_fstatat64(AT_FDCWD, file_to_access, &sb, |
| follow_links ? 0 : AT_SYMLINK_NOFOLLOW); |
| if (sts < 0) { |
| RAW_CHECK(reply->AddIntToMessage(-errno)); |
| return; |
| } |
| RAW_CHECK(reply->AddIntToMessage(0)); |
| RAW_CHECK( |
| reply->AddDataToMessage(reinterpret_cast<char*>(&sb), sizeof(sb))); |
| #else // defined(__NR_fstatat64) |
| // We should not reach here on 64-bit systems, as the *stat*64() are only |
| // necessary on 32-bit. |
| RAW_CHECK(false); |
| #endif |
| } |
| } |
| |
| void BrokerHost::UnlinkFileForIPC(const char* requested_filename, |
| BrokerSimpleMessage* reply) { |
| const char* file_to_access = |
| CommandUnlinkIsSafe(policy_->allowed_command_set, |
| *policy_->file_permissions, requested_filename); |
| if (!file_to_access) { |
| RAW_CHECK( |
| reply->AddIntToMessage(-policy_->file_permissions->denied_errno())); |
| return; |
| } |
| |
| absl::optional<std::string> rewritten_filename = |
| RewritePathname(file_to_access); |
| if (rewritten_filename.has_value()) { |
| file_to_access = rewritten_filename.value().c_str(); |
| } |
| |
| if (unlink(file_to_access) < 0) { |
| RAW_CHECK(reply->AddIntToMessage(-errno)); |
| return; |
| } |
| RAW_CHECK(reply->AddIntToMessage(0)); |
| } |
| |
| void BrokerHost::InotifyAddWatchForIPC(base::ScopedFD inotify_fd, |
| const char* requested_filename, |
| uint32_t mask, |
| BrokerSimpleMessage* message) { |
| const char* file_to_access = CommandInotifyAddWatchIsSafe( |
| policy_->allowed_command_set, *policy_->file_permissions, |
| requested_filename, mask); |
| if (!file_to_access) { |
| RAW_CHECK( |
| message->AddIntToMessage(-policy_->file_permissions->denied_errno())); |
| return; |
| } |
| |
| absl::optional<std::string> rewritten_filename = |
| RewritePathname(file_to_access); |
| if (rewritten_filename.has_value()) { |
| file_to_access = rewritten_filename.value().c_str(); |
| } |
| |
| int wd = inotify_add_watch(inotify_fd.get(), file_to_access, mask); |
| if (wd < 0) { |
| RAW_CHECK(message->AddIntToMessage(-errno)); |
| return; |
| } |
| RAW_CHECK(message->AddIntToMessage(wd)); |
| } |
| |
| // Handle a |command_type| request contained in |iter| and write the reply |
| // to |reply|. |
| bool BrokerHost::HandleRemoteCommand(BrokerSimpleMessage* message, |
| base::span<base::ScopedFD> recv_fds, |
| BrokerSimpleMessage* reply, |
| base::ScopedFD* opened_file) { |
| // Message structure: |
| // int: command type |
| // char[]: pathname |
| // int: flags |
| int command_type; |
| if (!message->ReadInt(&command_type)) |
| return false; |
| |
| switch (command_type) { |
| case COMMAND_ACCESS: { |
| const char* requested_filename; |
| int flags = 0; |
| auto result = GetPathAndFlags(message); |
| if (!result) { |
| return false; |
| } |
| std::tie(requested_filename, flags) = *result; |
| AccessFileForIPC(requested_filename, flags, reply); |
| break; |
| } |
| case COMMAND_MKDIR: { |
| const char* requested_filename; |
| int mode = 0; |
| auto result = GetPathAndFlags(message); |
| if (!result) { |
| return false; |
| } |
| std::tie(requested_filename, mode) = *result; |
| MkdirFileForIPC(requested_filename, mode, reply); |
| break; |
| } |
| case COMMAND_OPEN: { |
| const char* requested_filename; |
| int flags = 0; |
| auto result = GetPathAndFlags(message); |
| if (!result) { |
| return false; |
| } |
| std::tie(requested_filename, flags) = *result; |
| OpenFileForIPC(requested_filename, flags, reply, opened_file); |
| break; |
| } |
| case COMMAND_READLINK: { |
| const char* filename; |
| if (!message->ReadString(&filename)) { |
| return false; |
| } |
| |
| ReadlinkFileForIPC(filename, reply); |
| break; |
| } |
| case COMMAND_RENAME: { |
| const char* old_filename; |
| if (!message->ReadString(&old_filename)) { |
| return false; |
| } |
| |
| const char* new_filename; |
| if (!message->ReadString(&new_filename)) { |
| return false; |
| } |
| |
| RenameFileForIPC(old_filename, new_filename, reply); |
| break; |
| } |
| case COMMAND_RMDIR: { |
| const char* requested_filename; |
| if (!message->ReadString(&requested_filename)) { |
| return false; |
| } |
| RmdirFileForIPC(requested_filename, reply); |
| break; |
| } |
| case COMMAND_STAT: |
| case COMMAND_STAT64: { |
| const char* requested_filename; |
| if (!message->ReadString(&requested_filename)) { |
| return false; |
| } |
| int follow_links; |
| if (!message->ReadInt(&follow_links)) { |
| return false; |
| } |
| StatFileForIPC(static_cast<BrokerCommand>(command_type), |
| requested_filename, !!follow_links, reply); |
| break; |
| } |
| case COMMAND_UNLINK: { |
| const char* requested_filename; |
| if (!message->ReadString(&requested_filename)) { |
| return false; |
| } |
| UnlinkFileForIPC(requested_filename, reply); |
| break; |
| } |
| case COMMAND_INOTIFY_ADD_WATCH: { |
| const char* requested_filename; |
| if (!message->ReadString(&requested_filename)) { |
| return false; |
| } |
| int mask; |
| if (!message->ReadInt(&mask)) { |
| return false; |
| } |
| if (!recv_fds[0].is_valid()) { |
| return false; |
| } |
| InotifyAddWatchForIPC(std::move(recv_fds[0]), requested_filename, mask, |
| reply); |
| break; |
| } |
| default: |
| LOG(ERROR) << "Invalid IPC command"; |
| return false; |
| } |
| return true; |
| } |
| |
| BrokerHost::BrokerHost(const BrokerSandboxConfig& policy, |
| BrokerChannel::EndPoint ipc_channel, |
| pid_t sandboxed_process_pid) |
| : policy_(policy), |
| ipc_channel_(std::move(ipc_channel)), |
| sandboxed_process_pid_(sandboxed_process_pid) {} |
| |
| BrokerHost::~BrokerHost() = default; |
| |
| // 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. |
| void BrokerHost::LoopAndHandleRequests() { |
| for (;;) { |
| BrokerSimpleMessage message; |
| errno = 0; |
| base::ScopedFD temporary_ipc; |
| std::array<base::ScopedFD, 2> recv_fds_arr; |
| base::span<base::ScopedFD> recv_fds(recv_fds_arr); |
| const ssize_t msg_len = |
| message.RecvMsgWithFlagsMultipleFds(ipc_channel_.get(), 0, recv_fds); |
| |
| if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) { |
| // EOF from the client, or the client died, we should finish looping. |
| return; |
| } |
| |
| // This indicates an error occurred in IPC. For example, too many fds were |
| // sent along with the message. |
| if (msg_len < 0 || !recv_fds[0].is_valid()) { |
| if (!recv_fds[0].is_valid()) { |
| errno = EBADF; |
| } |
| PLOG(ERROR) << "Error reading message from the client"; |
| continue; |
| } |
| |
| temporary_ipc = std::move(recv_fds[0]); |
| |
| BrokerSimpleMessage reply; |
| base::ScopedFD opened_file; |
| if (!HandleRemoteCommand(&message, recv_fds.subspan(1), &reply, |
| &opened_file)) { |
| // Does not exit if we received a malformed message. |
| LOG(ERROR) << "Received malformed message from the client"; |
| continue; |
| } |
| |
| ssize_t sent = reply.SendMsg(temporary_ipc.get(), opened_file.get()); |
| if (sent < 0) |
| LOG(ERROR) << "sent failed"; |
| } |
| } |
| |
| } // namespace syscall_broker |
| |
| } // namespace sandbox |