blob: 5a2d3903e66b9b1354ea8acf6b4700d289548e35 [file] [log] [blame]
// Copyright 2019 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 "vm_tools/concierge/plugin_vm_helper.h"
#include <sys/mount.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/strings/stringprintf.h>
#include <base/logging.h>
#include <chromeos/scoped_minijail.h>
namespace vm_tools {
namespace concierge {
namespace pvm {
namespace helper {
namespace {
constexpr char kVmHelperCommand[] = "/opt/pita/pvm_helper";
constexpr char kVmHelperPolicyPath[] = "/usr/share/policy/pvm_helper.policy";
constexpr char kDispatcherSocketPath[] = "/run/pvm/vmplugin_dispatcher.socket";
// Minimal set of devices needed by the helpers.
constexpr const char* kDeviceNames[] = {"full", "null", "urandom", "zero"};
ScopedMinijail SetupSandbox(const std::string& policy_file) {
ScopedMinijail jail(minijail_new());
if (!jail) {
LOG(ERROR) << "Unable to create minijail";
return ScopedMinijail();
}
minijail_namespace_pids(jail.get());
minijail_namespace_user(jail.get());
minijail_namespace_vfs(jail.get());
minijail_namespace_ipc(jail.get());
minijail_namespace_net(jail.get());
minijail_namespace_cgroups(jail.get());
minijail_namespace_uts(jail.get());
std::string uid_map = base::StringPrintf("0 %d 1", geteuid());
std::string gid_map = base::StringPrintf("0 %d 1", getegid());
minijail_uidmap(jail.get(), uid_map.c_str());
minijail_gidmap(jail.get(), gid_map.c_str());
// Use a seccomp filter.
minijail_log_seccomp_filter_failures(jail.get());
minijail_parse_seccomp_filters(jail.get(), policy_file.c_str());
minijail_use_seccomp_filter(jail.get());
// We will manage this process's lifetime.
minijail_run_as_init(jail.get());
// The helpers do not require any capabilities.
minijail_no_new_privs(jail.get());
minijail_use_caps(jail.get(), 0);
if (minijail_enter_pivot_root(jail.get(), "/mnt/empty") < 0) {
LOG(ERROR) << "Failed to pivot root to /mnt/empty";
return ScopedMinijail();
}
// Set up minimal set of mounts for the helpers to run.
if (minijail_mount_with_data(jail.get(), "none", "/", "tmpfs",
MS_NOSUID | MS_NODEV | MS_NOEXEC,
"size=67108864") < 0) {
LOG(ERROR) << "Failed to mount root tmpfs";
return ScopedMinijail();
}
if (minijail_bind(jail.get(), "/opt/pita/", "/opt/pita", 0)) {
LOG(ERROR) << "Failed to bind-mount /opt/pita";
return ScopedMinijail();
}
if (minijail_bind(jail.get(), "/run/pvm", "/run/pvm", 1)) {
LOG(ERROR) << "Failed to bind-mount /run/pvm";
return ScopedMinijail();
}
// Create a minimal /dev with a very restricted set of device nodes.
// We can't use minijail_mount_dev() because Chrome OS LSM module
// does not allow unprivileged users mount filesystems other than
// tmpfs.
for (auto dev : kDeviceNames) {
base::FilePath path(base::FilePath("/dev").Append(dev));
if (minijail_bind(jail.get(), path.value().c_str(), path.value().c_str(),
true /* writeable */) < 0) {
LOG(ERROR) << "Failed to bind-mount " << path.value();
return ScopedMinijail();
}
}
// Close all file descriptors we may have.
minijail_close_open_fds(jail.get());
return jail;
}
bool ConsumeFileDescriptor(int fd, std::string* contents) {
base::FilePath path(base::StringPrintf("/proc/self/fd/%d", fd));
return base::ReadFileToString(path, contents);
}
bool ExecutePvmHelper(const std::string& owner_id,
std::vector<std::string> params,
std::string* stdout_str = nullptr,
std::string* stderr_str = nullptr) {
ScopedMinijail jail = SetupSandbox(kVmHelperPolicyPath);
if (!jail)
return false;
std::vector<std::string> args;
args.emplace_back(kVmHelperCommand);
for (auto& param : params)
args.emplace_back(std::move(param));
args.emplace_back("--socket-path");
args.emplace_back(kDispatcherSocketPath);
args.emplace_back("--user-identity");
args.emplace_back(owner_id);
// Convert args to array of pointers. Must be nullptr terminated.
std::vector<char*> args_ptr;
for (const auto& arg : args)
args_ptr.emplace_back(const_cast<char*>(arg.c_str()));
args_ptr.emplace_back(nullptr);
pid_t pid = -1;
int child_stdout = -1, child_stderr = -1;
int ret =
minijail_run_pid_pipes(jail.get(), args_ptr[0], args_ptr.data(), &pid,
nullptr, stdout_str ? &child_stdout : nullptr,
stderr_str ? &child_stderr : nullptr);
if (ret != 0) {
LOG(ERROR) << "failed to execute helper in minijail: " << ret;
return false;
}
if (stdout_str) {
if (child_stdout >= 0) {
ConsumeFileDescriptor(child_stdout, stdout_str);
close(child_stdout);
}
}
if (stderr_str) {
if (child_stderr >= 0) {
ConsumeFileDescriptor(child_stderr, stderr_str);
close(child_stderr);
}
}
// Always call minijail_wait(), otherwise exit code is never
// queried and the process is left dangling.
int exit_code = minijail_wait(jail.get());
jail.reset();
switch (exit_code) {
case 0:
return true;
case MINIJAIL_ERR_JAIL:
LOG(ERROR) << "helper failed because seccomp blocked a system call";
return false;
default:
LOG(ERROR) << "helper for '" << args[1]
<< "' failed with error: " << exit_code;
return false;
}
}
} // namespace
bool CreateVm(const VmId& vm_id, std::vector<std::string> params) {
std::vector<std::string> args = {
"create",
vm_id.name(),
};
std::move(params.begin(), params.end(), std::back_inserter(args));
return ExecutePvmHelper(vm_id.owner_id(), std::move(args));
}
bool AttachIso(const VmId& vm_id, const std::string& iso_name) {
std::vector<std::string> args = {
"set", vm_id.name(),
"--device-add", "cdrom",
"--image", base::FilePath("/iso").Append(iso_name).value(),
"--connect",
};
return ExecutePvmHelper(vm_id.owner_id(), std::move(args));
}
bool DeleteVm(const VmId& vm_id) {
return ExecutePvmHelper(vm_id.owner_id(), {"delete", vm_id.name()});
}
} // namespace helper
} // namespace pvm
} // namespace concierge
} // namespace vm_tools