blob: d4bf17ab1e7172168b1e50177acdaa26fb9ea6cd [file] [log] [blame]
// Copyright 2020 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/archive_mounter.h"
#include <utility>
#include <base/strings/stringprintf.h>
#include <base/strings/string_util.h>
#include <brillo/scoped_mount_namespace.h>
#include "cros-disks/quote.h"
namespace cros_disks {
namespace {
constexpr char kOptionPassword[] = "password";
} // namespace
ArchiveMounter::ArchiveMounter(
const Platform* platform,
brillo::ProcessReaper* process_reaper,
std::string archive_type,
Metrics* metrics,
std::string metrics_name,
std::vector<int> password_needed_exit_codes,
std::unique_ptr<SandboxedProcessFactory> sandbox_factory)
: FUSEMounter(
platform, process_reaper, archive_type + "fs", {.read_only = true}),
archive_type_(archive_type),
extension_("." + archive_type),
metrics_(metrics),
metrics_name_(std::move(metrics_name)),
password_needed_exit_codes_(std::move(password_needed_exit_codes)),
sandbox_factory_(std::move(sandbox_factory)) {}
ArchiveMounter::~ArchiveMounter() = default;
bool ArchiveMounter::CanMount(const std::string& source,
const std::vector<std::string>& /*params*/,
base::FilePath* suggested_dir_name) const {
base::FilePath path(source);
if (path.IsAbsolute() &&
base::CompareCaseInsensitiveASCII(path.Extension(), extension_) == 0) {
*suggested_dir_name = path.BaseName();
return true;
}
return false;
}
MountErrorType ArchiveMounter::InterpretReturnCode(int return_code) const {
if (metrics_ && !metrics_name_.empty())
metrics_->RecordFuseMounterErrorCode(metrics_name_, return_code);
if (base::Contains(password_needed_exit_codes_, return_code))
return MOUNT_ERROR_NEED_PASSWORD;
return FUSEMounter::InterpretReturnCode(return_code);
}
std::unique_ptr<SandboxedProcess> ArchiveMounter::PrepareSandbox(
const std::string& source,
const base::FilePath& /*target_path*/,
std::vector<std::string> params,
MountErrorType* error) const {
metrics_->RecordArchiveType(archive_type_);
base::FilePath path(source);
if (!path.IsAbsolute() || path.ReferencesParent()) {
LOG(ERROR) << "Invalid archive path " << quote(path);
*error = MOUNT_ERROR_INVALID_ARGUMENT;
return nullptr;
}
auto sandbox = sandbox_factory_->CreateSandboxedProcess();
std::unique_ptr<brillo::ScopedMountNamespace> mount_ns;
if (!platform()->PathExists(path.value())) {
// Try to locate the file in Chrome's mount namespace.
mount_ns = brillo::ScopedMountNamespace::CreateFromPath(
base::FilePath(kChromeNamespace));
if (!mount_ns) {
PLOG(ERROR) << "Could not look for archive " << quote(path)
<< " in the Chrome's namespace";
// TODO(dats): These probably should be MOUNT_ERROR_INVALID_DEVICE_PATH or
// something like that, but tast tests expect
// MOUNT_ERROR_MOUNT_PROGRAM_FAILED.
*error = MOUNT_ERROR_MOUNT_PROGRAM_FAILED;
return nullptr;
}
if (!platform()->PathExists(path.value())) {
PLOG(ERROR) << "Could not find archive " << quote(path);
*error = MOUNT_ERROR_MOUNT_PROGRAM_FAILED;
return nullptr;
}
}
// Archives are typically under /home, /media or /run. To bind-mount the
// source those directories must be writable, but by default only /run is.
for (const char* const dir : {"/home", "/media"}) {
if (!sandbox->Mount("tmpfs", dir, "tmpfs", "mode=0755,size=1M")) {
LOG(ERROR) << "Cannot mount " << quote(dir);
*error = MOUNT_ERROR_INTERNAL;
return nullptr;
}
}
// Is the process "password-aware"?
if (!password_needed_exit_codes_.empty()) {
std::string password;
if (GetParamValue(params, kOptionPassword, &password)) {
sandbox->SetStdIn(password);
}
}
*error = FormatInvocationCommand(path, std::move(params), sandbox.get());
if (*error != MOUNT_ERROR_NONE) {
return nullptr;
}
if (mount_ns) {
// Sandbox will need to enter Chrome's namespace too to access files.
mount_ns.reset();
sandbox->EnterExistingMountNamespace(kChromeNamespace);
}
return sandbox;
}
MountErrorType ArchiveMounter::FormatInvocationCommand(
const base::FilePath& archive,
std::vector<std::string> /*params*/,
SandboxedProcess* sandbox) const {
// Make the source available in the sandbox.
if (!sandbox->BindMount(archive.value(), archive.value(),
/* writeable= */ false,
/* recursive= */ false)) {
LOG(ERROR) << "Cannot bind the source archive " << quote(archive);
return MOUNT_ERROR_INTERNAL;
}
std::vector<std::string> opts = {
MountOptions::kOptionReadOnly, "umask=0222",
base::StringPrintf("uid=%d", kChronosUID),
base::StringPrintf("gid=%d", kChronosAccessGID)};
sandbox->AddArgument("-o");
sandbox->AddArgument(base::JoinString(opts, ","));
sandbox->AddArgument(archive.value());
return MOUNT_ERROR_NONE;
}
} // namespace cros_disks