blob: 0b47d6064d7904ff710762adedef65b1752fe519 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS 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 "cros-disks/sandboxed_process.h"
#include <utility>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <unistd.h>
#include <base/bind.h>
#include <base/check.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/notreached.h>
#include <base/posix/safe_strerror.h>
#include <chromeos/libminijail.h>
#include "cros-disks/mount_options.h"
#include "cros-disks/quote.h"
namespace cros_disks {
namespace {
int Exec(char* const args[], char* const env[]) {
const char* const path = args[0];
execve(path, args, env);
const int ret =
(errno == ENOENT ? MINIJAIL_ERR_NO_COMMAND : MINIJAIL_ERR_NO_ACCESS);
PLOG(ERROR) << "Cannot exec " << quote(path);
return ret;
}
} // namespace
SandboxedProcess::SandboxedProcess() : jail_(minijail_new()) {
CHECK(jail_) << "Failed to create a process jail";
}
SandboxedProcess::~SandboxedProcess() {
minijail_destroy(jail_);
}
void SandboxedProcess::LoadSeccompFilterPolicy(const std::string& policy_file) {
minijail_parse_seccomp_filters(jail_, policy_file.c_str());
minijail_use_seccomp_filter(jail_);
}
void SandboxedProcess::NewCgroupNamespace() {
minijail_namespace_cgroups(jail_);
}
void SandboxedProcess::NewIpcNamespace() {
minijail_namespace_ipc(jail_);
}
void SandboxedProcess::NewMountNamespace() {
minijail_namespace_vfs(jail_);
}
void SandboxedProcess::EnterExistingMountNamespace(const std::string& ns_path) {
minijail_namespace_enter_vfs(jail_, ns_path.c_str());
}
void SandboxedProcess::NewPidNamespace() {
minijail_namespace_pids(jail_);
minijail_run_as_init(jail_);
minijail_reset_signal_mask(jail_);
minijail_reset_signal_handlers(jail_);
minijail_skip_remount_private(jail_); // crbug.com/1008262
use_pid_namespace_ = true;
}
bool SandboxedProcess::SetUpMinimalMounts() {
if (minijail_bind(jail_, "/", "/", 0))
return false;
if (minijail_bind(jail_, "/proc", "/proc", 0))
return false;
minijail_remount_proc_readonly(jail_);
minijail_mount_tmp_size(jail_, 128 * 1024 * 1024);
// Create a minimal /dev with a very restricted set of device nodes.
minijail_mount_dev(jail_);
if (minijail_bind(jail_, "/dev/log", "/dev/log", 0))
return false;
return true;
}
bool SandboxedProcess::BindMount(const std::string& from,
const std::string& to,
bool writeable,
bool recursive) {
int flags = MS_BIND;
if (!writeable) {
flags |= MS_RDONLY;
}
if (recursive) {
flags |= MS_REC;
}
return minijail_mount(jail_, from.c_str(), to.c_str(), "", flags) == 0;
}
bool SandboxedProcess::Mount(const std::string& src,
const std::string& to,
const std::string& type,
const char* data) {
return minijail_mount_with_data(jail_, src.c_str(), to.c_str(), type.c_str(),
0, data) == 0;
}
bool SandboxedProcess::EnterPivotRoot() {
return minijail_enter_pivot_root(jail_, "/mnt/empty") == 0;
}
void SandboxedProcess::NewNetworkNamespace() {
// As of 2021-08-04, this can log a warning to /var/log/messages:
// "ioctl(SIOCSIFFLAGS) failed: Operation not permitted"
//
// This libminijail message is harmless: https://crbug.com/1226229
minijail_namespace_net(jail_);
}
void SandboxedProcess::SetNoNewPrivileges() {
minijail_no_new_privs(jail_);
}
void SandboxedProcess::SetCapabilities(uint64_t capabilities) {
minijail_use_caps(jail_, capabilities);
}
void SandboxedProcess::SetGroupId(gid_t group_id) {
minijail_change_gid(jail_, group_id);
}
void SandboxedProcess::SetUserId(uid_t user_id) {
minijail_change_uid(jail_, user_id);
}
void SandboxedProcess::SetSupplementaryGroupIds(base::span<const gid_t> gids) {
minijail_set_supplementary_gids(jail_, gids.size(), gids.data());
}
bool SandboxedProcess::AddToCgroup(const std::string& cgroup) {
return minijail_add_to_cgroup(jail_, cgroup.c_str()) == 0;
}
void SandboxedProcess::PreserveFile(int fd) {
if (const int ret = minijail_preserve_fd(jail_, fd, fd)) {
LOG(FATAL) << "Cannot preserve file descriptor " << fd << ": "
<< base::safe_strerror(-ret);
}
}
pid_t SandboxedProcess::StartImpl(base::ScopedFD in_fd, base::ScopedFD out_fd) {
char* const* const args = GetArguments();
DCHECK(args && args[0]);
char* const* const env = GetEnvironment();
DCHECK(env);
pid_t child_pid = kInvalidProcessId;
minijail_close_open_fds(jail_);
// Set up stdin, stdout and stderr to be connected to the matching pipes in
// the jailed process.
CHECK_EQ(minijail_preserve_fd(jail_, in_fd.get(), STDIN_FILENO), 0);
CHECK_EQ(minijail_preserve_fd(jail_, out_fd.get(), STDOUT_FILENO), 0);
CHECK_EQ(minijail_preserve_fd(jail_, out_fd.get(), STDERR_FILENO), 0);
if (!use_pid_namespace_) {
if (const int ret = minijail_run_env_pid_pipes(
jail_, args[0], args, env, &child_pid, nullptr, nullptr, nullptr);
ret < 0) {
errno = -ret;
PLOG(ERROR) << "Cannot start minijail process";
return kInvalidProcessId;
}
} else {
// The sandboxed process will run in a PID namespace.
PreserveFile(launcher_pipe_.child_fd.get());
SubprocessPipe termination_pipe(SubprocessPipe::kParentToChild);
PreserveFile(termination_pipe.child_fd.get());
// Create child 'init' process in the PID namespace.
child_pid = minijail_fork(jail_);
if (child_pid < 0) {
errno = -child_pid;
PLOG(ERROR) << "Cannot run minijail_fork";
return kInvalidProcessId;
}
if (child_pid == 0) {
// In child 'init' process.
SandboxedInit(base::BindOnce(Exec, args, env),
std::move(launcher_pipe_.child_fd),
std::move(termination_pipe.child_fd))
.Run();
NOTREACHED();
} else {
// In parent process.
PCHECK(base::SetNonBlocking(launcher_pipe_.parent_fd.get()));
launcher_pipe_.child_fd.reset();
DCHECK(!termination_fd_.is_valid());
termination_fd_ = std::move(termination_pipe.parent_fd);
DCHECK(termination_fd_.is_valid());
}
}
return child_pid;
}
int SandboxedProcess::WaitImpl() {
if (use_pid_namespace_) {
launcher_watch_.reset();
return SandboxedInit::WaitForLauncher(&launcher_pipe_.parent_fd);
}
while (true) {
const int status = minijail_wait(jail_);
if (status >= 0)
return status;
if (const int err = -status; err != EINTR) {
LOG(ERROR) << "Cannot wait for process " << pid() << ": "
<< base::safe_strerror(err);
return MINIJAIL_ERR_INIT;
}
}
}
int SandboxedProcess::WaitNonBlockingImpl() {
if (use_pid_namespace_) {
const int exit_code =
SandboxedInit::PollLauncher(&launcher_pipe_.parent_fd);
if (exit_code >= 0)
launcher_watch_.reset();
return exit_code;
}
// TODO(chromium:971667) Use Minijail's non-blocking wait once it exists.
int wstatus;
const pid_t child_pid = pid();
const int ret = waitpid(child_pid, &wstatus, WNOHANG);
if (ret < 0) {
PLOG(ERROR) << "Cannot wait for process " << child_pid;
return MINIJAIL_ERR_INIT;
}
if (ret == 0) {
// Process is still running.
return -1;
}
return SandboxedInit::WaitStatusToExitCode(wstatus);
}
int FakeSandboxedProcess::OnProcessLaunch(
const std::vector<std::string>& argv) {
return 0;
}
pid_t FakeSandboxedProcess::StartImpl(base::ScopedFD, base::ScopedFD) {
DCHECK(!ret_code_);
ret_code_ = OnProcessLaunch(arguments());
return 42;
}
int FakeSandboxedProcess::WaitImpl() {
DCHECK(ret_code_);
return ret_code_.value();
}
int FakeSandboxedProcess::WaitNonBlockingImpl() {
if (ret_code_)
return ret_code_.value();
return -1;
}
} // namespace cros_disks