blob: e7a23bcd8a1e84533b10995317af8f7191c71d37 [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/archive_manager.h"
#include <utility>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <brillo/cryptohome.h>
#include "cros-disks/archive_mounter.h"
#include "cros-disks/platform.h"
#include "cros-disks/quote.h"
#include "cros-disks/rar_mounter.h"
#include "cros-disks/user.h"
namespace cros_disks {
ArchiveManager::ArchiveManager(const std::string& mount_root,
Platform* platform,
Metrics* metrics,
brillo::ProcessReaper* process_reaper)
: MountManager(mount_root, platform, metrics, process_reaper) {}
ArchiveManager::~ArchiveManager() = default;
bool ArchiveManager::Initialize() {
if (!MountManager::Initialize())
return false;
{
SandboxedExecutable executable = {
base::FilePath("/usr/bin/mount-zip"),
base::FilePath("/usr/share/policy/mount-zip-seccomp.policy")};
auto sandbox_factory =
CreateSandboxFactory(std::move(executable), "fuse-zip");
std::vector<int> password_needed_exit_codes = {
23, // ZIP_ER_BASE + ZIP_ER_ZLIB
36, // ZIP_ER_BASE + ZIP_ER_NOPASSWD
37}; // ZIP_ER_BASE + ZIP_ER_WRONGPASSWD
std::vector<std::string> opts = {"-o", "nosymlinks,nospecials"};
if (LOG_IS_ON(INFO)) {
opts.push_back("--verbose");
} else {
opts.push_back("--redact");
}
mounters_.push_back(std::make_unique<ArchiveMounter>(
platform(), process_reaper(), "zip", metrics(), "FuseZip",
std::move(password_needed_exit_codes), std::move(sandbox_factory),
std::move(opts)));
}
{
SandboxedExecutable executable = {
base::FilePath("/usr/bin/rar2fs"),
base::FilePath("/usr/share/policy/rar2fs-seccomp.policy")};
auto sandbox_factory =
CreateSandboxFactory(std::move(executable), "fuse-rar2fs");
mounters_.push_back(std::make_unique<RarMounter>(
platform(), process_reaper(), metrics(), std::move(sandbox_factory)));
}
// TODO(nigeltao): refactor the ArchiveMounter C++ class (and superclasses)
// to break the "1 instance = 1 file extension" assumption. That would let us
// add just 1 element to mounters_, not 1 per file extension.
//
// The variable name (and, later, the policy filename) is called
// "archivemount_etc" because, historically, we executed the archivemount
// program, not the fuse-archive program. More recently, we use fuse-archive
// which is a drop-in replacement, featurewise, but is faster.
const char* const archivemount_extensions[] = {
// The empty // comments make clang-format place one entry per line.
"7z", //
"bz2", //
"crx", //
"gz", //
"iso", //
"tar", //
"tbz", //
"tbz2", //
"tgz", //
"xz", //
};
for (const char* const ext : archivemount_extensions) {
SandboxedExecutable executable = {
base::FilePath("/usr/bin/fuse-archive"),
base::FilePath("/usr/share/policy/archivemount-seccomp.policy")};
auto sandbox_factory =
CreateSandboxFactory(std::move(executable), "fuse-archivemount");
// These fuse-archive exit codes are defined at
// https://github.com/google/fuse-archive/blob/a9df9f76c99b4b127e217f91b220ffe96bd0052f/src/main.cc#L82-L84
std::vector<int> password_needed_exit_codes = {
20, // EXIT_CODE_PASSPHRASE_REQUIRED
21, // EXIT_CODE_PASSPHRASE_INCORRECT
// We don't add 22 (EXIT_CODE_PASSPHRASE_NOT_SUPPORTED) here. That exit
// code means that the archive file has a password, but libarchive (and
// thus the fuse-archive program) does not support decrypting that file
// format. There's no point prompting the user for the password. We
// couldn't use the password even if it's correct.
};
std::vector<std::string> opts = {"--passphrase"};
if (!LOG_IS_ON(INFO))
opts.push_back("--redact");
mounters_.push_back(std::make_unique<ArchiveMounter>(
platform(), process_reaper(), ext, metrics(), "FuseArchive",
std::move(password_needed_exit_codes), std::move(sandbox_factory),
std::move(opts)));
}
return true;
}
bool ArchiveManager::ResolvePath(const std::string& path,
std::string* real_path) {
std::unique_ptr<brillo::ScopedMountNamespace> mount_ns;
if (!platform()->PathExists(path)) {
// Try to locate the file in Chrome's mount namespace.
mount_ns = brillo::ScopedMountNamespace::CreateFromPath(
base::FilePath(ArchiveMounter::kChromeNamespace));
if (!mount_ns) {
PLOG(ERROR) << "Cannot find archive " << redact(path)
<< " in mount namespace "
<< quote(ArchiveMounter::kChromeNamespace);
return false;
}
}
return platform()->GetRealPath(path, real_path);
}
bool ArchiveManager::IsInAllowedFolder(const std::string& source_path) {
std::vector<std::string> parts;
base::FilePath(source_path).GetComponents(&parts);
if (parts.size() < 2 || parts[0] != "/")
return false;
if (parts[1] == "home")
return parts.size() > 5 && parts[2] == "chronos" &&
base::StartsWith(parts[3], "u-", base::CompareCase::SENSITIVE) &&
brillo::cryptohome::home::IsSanitizedUserName(parts[3].substr(2)) &&
parts[4] == "MyFiles";
if (parts[1] == "media")
return parts.size() > 4 && (parts[2] == "archive" || parts[2] == "fuse" ||
parts[2] == "removable");
if (parts[1] == "run")
return parts.size() > 8 && parts[2] == "arc" && parts[3] == "sdcard" &&
parts[4] == "write" && parts[5] == "emulated" && parts[6] == "0";
return false;
}
std::string ArchiveManager::SuggestMountPath(
const std::string& source_path) const {
// Use the archive name to name the mount directory.
base::FilePath base_name = base::FilePath(source_path).BaseName();
return mount_root().Append(base_name).value();
}
std::vector<gid_t> ArchiveManager::GetSupplementaryGroups() const {
std::vector<gid_t> groups;
// To access Play Files.
gid_t gid;
if (platform()->GetGroupId("android-everybody", &gid))
groups.push_back(gid);
return groups;
}
bool ArchiveManager::CanMount(const std::string& source_path) const {
if (IsInAllowedFolder(source_path)) {
base::FilePath name;
for (const auto& m : mounters_) {
if (m->CanMount(source_path, {}, &name)) {
return true;
}
}
}
return false;
}
std::unique_ptr<MountPoint> ArchiveManager::DoMount(
const std::string& source_path,
const std::string& filesystem_type,
const std::vector<std::string>& options,
const base::FilePath& mount_path,
MountErrorType* error) {
// Here source_path is already resolved and free from symlinks and '..' by
// the base class.
if (!IsInAllowedFolder(source_path)) {
LOG(ERROR) << "Source path " << redact(source_path) << " is not allowed";
*error = MOUNT_ERROR_INVALID_DEVICE_PATH;
return nullptr;
}
base::FilePath name;
for (const auto& m : mounters_) {
if (m->CanMount(source_path, {}, &name)) {
return m->Mount(source_path, mount_path, options, error);
}
}
LOG(ERROR) << "Cannot find mounter for archive " << redact(source_path)
<< " of type " << quote(filesystem_type);
*error = MOUNT_ERROR_UNKNOWN_FILESYSTEM;
return nullptr;
}
std::unique_ptr<FUSESandboxedProcessFactory>
ArchiveManager::CreateSandboxFactory(SandboxedExecutable executable,
const std::string& user_name) const {
// To access Play Files.
std::vector<gid_t> groups;
gid_t gid;
if (platform()->GetGroupId("android-everybody", &gid))
groups.push_back(gid);
OwnerUser run_as;
if (!platform()->GetUserAndGroupId(user_name, &run_as.uid, &run_as.gid)) {
PLOG(ERROR) << "Cannot resolve required user " << quote(user_name);
return nullptr;
}
// Archivers need to run in chronos-access group to be able to access
// user's files.
run_as.gid = kChronosAccessGID;
return std::make_unique<FUSESandboxedProcessFactory>(
platform(), std::move(executable), std::move(run_as),
/* has_network_access= */ false, std::move(groups));
}
} // namespace cros_disks