blob: b2178bdc1d485a142ffceca53d00882694bde7b0 [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 "cryptohome/out_of_process_mount_helper.h"
#include <poll.h>
#include <sys/stat.h>
#include <sysexits.h>
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/callback_helpers.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <brillo/cryptohome.h>
#include <brillo/process/process.h>
#include <brillo/secure_blob.h>
#include "cryptohome/cryptohome_common.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/mount_constants.h"
#include "cryptohome/mount_utils.h"
#include "cryptohome/namespace_mounter_ipc.pb.h"
using base::FilePath;
using base::StringPrintf;
namespace {
// Wait up to three seconds for the ephemeral mount to be performed.
// Normally, setting up a full ephemeral mount takes about 300 ms, so
// give ourselves a healthy 10x margin.
constexpr base::TimeDelta kOutOfProcessHelperMountTimeout =
base::TimeDelta::FromSeconds(3);
// Wait one second for the helper to exit and be reaped.
// The brillo::Process::Kill() function that takes this timeout does not allow
// for sub-second granularity, and waiting more than one second for the helper
// to exit makes little sense: the helper is designed to clean up and exit
// quickly: it takes about 100 ms to clean up ephemeral mounts.
constexpr base::TimeDelta kOutOfProcessHelperReapTimeout =
base::TimeDelta::FromSeconds(1);
bool WaitForHelper(int read_from_helper, const base::TimeDelta& timeout) {
struct pollfd poll_fd = {};
poll_fd.fd = read_from_helper;
poll_fd.events = POLLIN;
// While HANDLE_EINTR will restart the timeout, this happening repeatedly
// should be exceedingly rare.
int ret = HANDLE_EINTR(poll(&poll_fd, 1U, timeout.InMilliseconds()));
if (ret < 0) {
PLOG(ERROR) << "poll(read_from_helper) failed";
return false;
}
if (ret == 0) {
LOG(ERROR) << "WaitForHelper timed out";
return false;
}
return (poll_fd.revents & POLLIN) == POLLIN;
}
std::map<cryptohome::MountType, cryptohome::OutOfProcessMountRequest_MountType>
mount_type = {
// Not mounted.
{cryptohome::MountType::NONE,
cryptohome::OutOfProcessMountRequest_MountType_NONE},
// Encrypted with ecryptfs.
{cryptohome::MountType::ECRYPTFS,
cryptohome::OutOfProcessMountRequest_MountType_ECRYPTFS},
// Encrypted with dircrypto.
{cryptohome::MountType::DIR_CRYPTO,
cryptohome::OutOfProcessMountRequest_MountType_DIR_CRYPTO},
// Ephemeral mount.
{cryptohome::MountType::EPHEMERAL,
cryptohome::OutOfProcessMountRequest_MountType_EPHEMERAL},
};
} // namespace
namespace cryptohome {
bool OutOfProcessMountHelper::CanPerformEphemeralMount() const {
return !helper_process_ || helper_process_->pid() == 0;
}
bool OutOfProcessMountHelper::MountPerformed() const {
return helper_process_ && helper_process_->pid() > 0;
}
bool OutOfProcessMountHelper::IsPathMounted(const base::FilePath& path) const {
return mounted_paths_.count(path.value()) > 0;
}
void OutOfProcessMountHelper::KillOutOfProcessHelperIfNecessary() {
if (helper_process_->pid() == 0) {
return;
}
ReportTimerStart(kOOPMountCleanupTimer);
if (helper_process_->Kill(SIGTERM,
kOutOfProcessHelperReapTimeout.InSeconds())) {
ReportTimerStop(kOOPMountCleanupTimer);
ReportOOPMountCleanupResult(OOPMountCleanupResult::kSuccess);
} else {
LOG(ERROR) << "Failed to send SIGTERM to OOP mount helper";
// If the process didn't exit on SIGTERM, attempt SIGKILL.
if (helper_process_->Kill(SIGKILL, 0)) {
// If SIGKILL succeeds (with SIGTERM having failed) log the fact that
// poking failed.
ReportOOPMountCleanupResult(OOPMountCleanupResult::kFailedToPoke);
} else {
LOG(ERROR) << "Failed to kill OOP mount helper";
ReportOOPMountCleanupResult(OOPMountCleanupResult::kFailedToKill);
}
}
// Reset the brillo::Process object to close pipe file descriptors.
helper_process_->Reset(0);
}
bool OutOfProcessMountHelper::PerformEphemeralMount(
const std::string& username) {
OutOfProcessMountRequest request;
request.set_username(username);
request.set_system_salt(SecureBlobToSecureHex(system_salt_).to_string());
request.set_legacy_home(legacy_home_);
request.set_mount_namespace_path(
chrome_mnt_ns_ ? chrome_mnt_ns_->path().value() : "");
request.set_type(cryptohome::OutOfProcessMountRequest_MountType_EPHEMERAL);
OutOfProcessMountResponse response;
if (!LaunchOutOfProcessHelper(request, &response)) {
return false;
}
username_ = request.username();
if (response.paths_size() > 0) {
for (int i = 0; i < response.paths_size(); i++) {
mounted_paths_.insert(response.paths(i));
}
}
return true;
}
bool OutOfProcessMountHelper::LaunchOutOfProcessHelper(
const OutOfProcessMountRequest& request,
OutOfProcessMountResponse* response) {
std::unique_ptr<brillo::Process> mount_helper =
platform_->CreateProcessInstance();
mount_helper->AddArg("/usr/sbin/cryptohome-namespace-mounter");
mount_helper->RedirectUsingPipe(
STDIN_FILENO, true /* is_input, from child's perspective */);
mount_helper->RedirectUsingPipe(
STDOUT_FILENO, false /* is_input, from child's perspective */);
ReportTimerStart(kOOPMountOperationTimer);
if (!mount_helper->Start()) {
LOG(ERROR) << "Failed to start OOP mount helper";
ReportOOPMountOperationResult(OOPMountOperationResult::kFailedToStart);
return false;
}
helper_process_ = std::move(mount_helper);
write_to_helper_ = helper_process_->GetPipe(STDIN_FILENO);
int read_from_helper = helper_process_->GetPipe(STDOUT_FILENO);
base::ScopedClosureRunner kill_runner(
base::Bind(&OutOfProcessMountHelper::KillOutOfProcessHelperIfNecessary,
base::Unretained(this)));
if (!WriteProtobuf(write_to_helper_, request)) {
LOG(ERROR) << "Failed to write request protobuf";
ReportOOPMountOperationResult(
OOPMountOperationResult::kFailedToWriteRequestProtobuf);
return false;
}
// Avoid blocking forever in the read(2) call below by poll(2)-ing the file
// descriptor with a |kOutOfProcessHelperMountTimeout| long timeout.
if (!WaitForHelper(read_from_helper, kOutOfProcessHelperMountTimeout)) {
LOG(ERROR) << "OOP mount helper did not respond in time";
ReportOOPMountOperationResult(
OOPMountOperationResult::kHelperProcessTimedOut);
return false;
}
if (!ReadProtobuf(read_from_helper, response)) {
LOG(ERROR) << "Failed to read response protobuf";
ReportOOPMountOperationResult(
OOPMountOperationResult::kFailedToReadResponseProtobuf);
return false;
}
// OOP mount helper started successfully, report elapsed time since process
// was started.
ReportTimerStop(kOOPMountOperationTimer);
// OOP mount helper started successfully, release the clean-up closure.
ignore_result(kill_runner.Release());
LOG(INFO) << "OOP mount helper started successfully";
ReportOOPMountOperationResult(OOPMountOperationResult::kSuccess);
return true;
}
bool OutOfProcessMountHelper::TearDownEphemeralMount() {
return TearDownExistingMount();
}
void OutOfProcessMountHelper::TearDownNonEphemeralMount() {
TearDownExistingMount();
}
bool OutOfProcessMountHelper::TearDownExistingMount() {
if (!helper_process_) {
LOG(WARNING) << "Can't tear down mount, OOP mount helper is not running";
return false;
}
// While currently a MountHelper instance is not used for more than one
// cryptohome mount operation, this function should ensure that the
// MountHelper instance is left in a state suited to perform subsequent
// mounts.
KillOutOfProcessHelperIfNecessary();
mounted_paths_.clear();
username_.clear();
return true;
}
bool OutOfProcessMountHelper::PerformMount(const Options& mount_opts,
const std::string& username,
const std::string& fek_signature,
const std::string& fnek_signature,
bool is_pristine,
MountError* error) {
OutOfProcessMountRequest request;
request.set_username(username);
request.set_system_salt(SecureBlobToSecureHex(system_salt_).to_string());
request.set_legacy_home(legacy_home_);
request.set_mount_namespace_path(chrome_mnt_ns_ && IsolateUserSession()
? chrome_mnt_ns_->path().value()
: "");
request.set_type(mount_type[mount_opts.type]);
request.set_to_migrate_from_ecryptfs(mount_opts.to_migrate_from_ecryptfs);
request.set_shadow_only(mount_opts.shadow_only);
request.set_fek_signature(fek_signature);
request.set_fnek_signature(fnek_signature);
request.set_is_pristine(is_pristine);
OutOfProcessMountResponse response;
if (!LaunchOutOfProcessHelper(request, &response)) {
return false;
}
username_ = request.username();
if (response.paths_size() > 0) {
for (int i = 0; i < response.paths_size(); i++) {
mounted_paths_.insert(response.paths(i));
}
}
*error = static_cast<MountError>(response.mount_error());
return true;
}
} // namespace cryptohome