blob: e6aac6c7f3e3edb0572df465bac32a7b05abdb5a [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/storage/out_of_process_mount_helper.h"
#include <poll.h>
#include <signal.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 <chromeos/constants/cryptohome.h>
#include "cryptohome/cryptohome_common.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/storage/mount_constants.h"
#include "cryptohome/storage/mount_utils.h"
#include "cryptohome/namespace_mounter_ipc.pb.h"
using base::FilePath;
using base::StringPrintf;
namespace {
// How long to wait for the out-of-process helper to perform a mount.
//
// 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 40x margin.
//
// Certain boards can be very slow on mount operations. Extend the timeout in
// this case to 120s.
constexpr base::TimeDelta kOutOfProcessHelperMountTimeout =
base::Seconds(USE_SLOW_MOUNT ? 120 : 12);
// How long to wait for the out-of-process helper to exit and be reaped.
//
// 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.
//
// Certain boards can be very slow on mount operations. Extend the timeout in
// this case to 120s.
constexpr base::TimeDelta kOutOfProcessHelperReapTimeout =
base::Seconds(USE_SLOW_MOUNT ? 120 : 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>
kProtobufMountType = {
// 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},
// Encrypted with dmcrpyt.
{cryptohome::MountType::DMCRYPT,
cryptohome::OutOfProcessMountRequest_MountType_DMCRYPT},
// Ephemeral mount.
{cryptohome::MountType::EPHEMERAL,
cryptohome::OutOfProcessMountRequest_MountType_EPHEMERAL},
// Vault Migration.
{cryptohome::MountType::ECRYPTFS_TO_DIR_CRYPTO,
cryptohome::OutOfProcessMountRequest_MountType_ECRYPTFS_TO_DIR_CRYPTO},
{cryptohome::MountType::ECRYPTFS_TO_DMCRYPT,
cryptohome::OutOfProcessMountRequest_MountType_ECRYPTFS_TO_DMCRYPT},
{cryptohome::MountType::DIR_CRYPTO_TO_DMCRYPT,
cryptohome::OutOfProcessMountRequest_MountType_DIR_CRYPTO_TO_DMCRYPT},
};
} // namespace
namespace cryptohome {
// cryptohome_namespace_mounter enters the Chrome mount namespace and mounts
// the user cryptohome in that mount namespace if the flags are enabled.
// Chrome mount namespace is created by session_manager. cryptohome knows
// the path at which this mount namespace is created and uses that path to
// enter it.
OutOfProcessMountHelper::OutOfProcessMountHelper(bool legacy_home,
bool bind_mount_downloads,
Platform* platform)
: legacy_home_(legacy_home),
bind_mount_downloads_(bind_mount_downloads),
platform_(platform),
username_(),
write_to_helper_(-1) {}
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;
}
if (helper_process_->Kill(SIGTERM,
kOutOfProcessHelperReapTimeout.InSeconds())) {
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);
}
MountError OutOfProcessMountHelper::PerformEphemeralMount(
const std::string& username, const base::FilePath& ephemeral_loop_device) {
OutOfProcessMountRequest request;
request.set_username(username);
request.set_legacy_home(legacy_home_);
request.set_bind_mount_downloads(bind_mount_downloads_);
request.set_mount_namespace_path(
username == brillo::cryptohome::home::kGuestUserName
? kUserSessionMountNamespacePath
: "");
request.set_type(cryptohome::OutOfProcessMountRequest_MountType_EPHEMERAL);
request.set_ephemeral_loop_device(ephemeral_loop_device.value());
OutOfProcessMountResponse response;
if (!LaunchOutOfProcessHelper(request, &response)) {
return MOUNT_ERROR_FATAL;
}
username_ = request.username();
if (response.paths_size() > 0) {
for (int i = 0; i < response.paths_size(); i++) {
mounted_paths_.insert(response.paths(i));
}
}
return static_cast<MountError>(response.mount_error());
}
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 */);
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::BindOnce(
&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, release the clean-up closure.
kill_runner.ReplaceClosure(base::DoNothing());
LOG(INFO) << "OOP mount helper started successfully";
ReportOOPMountOperationResult(OOPMountOperationResult::kSuccess);
return true;
}
void OutOfProcessMountHelper::UnmountAll() {
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;
}
MountError OutOfProcessMountHelper::PerformMount(
MountType mount_type,
const std::string& username,
const std::string& fek_signature,
const std::string& fnek_signature) {
OutOfProcessMountRequest request;
request.set_username(username);
request.set_bind_mount_downloads(bind_mount_downloads_);
request.set_legacy_home(legacy_home_);
request.set_mount_namespace_path(
IsolateUserSession() ? kUserSessionMountNamespacePath : "");
request.set_type(kProtobufMountType[mount_type]);
request.set_fek_signature(fek_signature);
request.set_fnek_signature(fnek_signature);
OutOfProcessMountResponse response;
if (!LaunchOutOfProcessHelper(request, &response)) {
return MOUNT_ERROR_FATAL;
}
username_ = request.username();
if (response.paths_size() > 0) {
for (int i = 0; i < response.paths_size(); i++) {
mounted_paths_.insert(response.paths(i));
}
}
return static_cast<MountError>(response.mount_error());
}
} // namespace cryptohome