cros-disks: Make a mounter for archives and remove legacy mounter
Move archive-specific bits of the legacy mounter into a dedicated
archive mounter.
BUG=chromium:950442
BUG=chromium:933018
TEST=platform.CrosDisks*
Change-Id: I852a95c7de3ed656e275e5447ef821e780e04ab6
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2580960
Tested-by: Sergei Datsenko <dats@chromium.org>
Reviewed-by: Austin Tankiang <austinct@chromium.org>
Commit-Queue: Sergei Datsenko <dats@chromium.org>
Auto-Submit: Sergei Datsenko <dats@chromium.org>
diff --git a/cros-disks/BUILD.gn b/cros-disks/BUILD.gn
index 35fcd86..64377f4 100644
--- a/cros-disks/BUILD.gn
+++ b/cros-disks/BUILD.gn
@@ -43,6 +43,7 @@
static_library("libdisks") {
sources = [
"archive_manager.cc",
+ "archive_mounter.cc",
"cros_disks_server.cc",
"daemon.cc",
"device_ejector.cc",
@@ -124,6 +125,7 @@
executable("disks_testrunner") {
sources = [
"archive_manager_test.cc",
+ "archive_mounter_test.cc",
"device_event_moderator_test.cc",
"device_event_queue_test.cc",
"disk_manager_test.cc",
diff --git a/cros-disks/archive_mounter.cc b/cros-disks/archive_mounter.cc
new file mode 100644
index 0000000..d4bf17a
--- /dev/null
+++ b/cros-disks/archive_mounter.cc
@@ -0,0 +1,154 @@
+// 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
diff --git a/cros-disks/archive_mounter.h b/cros-disks/archive_mounter.h
new file mode 100644
index 0000000..2c03cb6
--- /dev/null
+++ b/cros-disks/archive_mounter.h
@@ -0,0 +1,67 @@
+// 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.
+
+#ifndef CROS_DISKS_ARCHIVE_MOUNTER_H_
+#define CROS_DISKS_ARCHIVE_MOUNTER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "cros-disks/fuse_mounter.h"
+
+namespace cros_disks {
+
+class Metrics;
+
+// An implementation of FUSEMounter tailored for mounting archives.
+class ArchiveMounter : public FUSEMounter {
+ public:
+ static constexpr char kChromeNamespace[] = "/run/namespaces/mnt_chrome";
+
+ 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);
+ ArchiveMounter(const ArchiveMounter&) = delete;
+ ArchiveMounter& operator=(const ArchiveMounter&) = delete;
+
+ ~ArchiveMounter() override;
+
+ bool CanMount(const std::string& source,
+ const std::vector<std::string>& params,
+ base::FilePath* suggested_dir_name) const override;
+
+ protected:
+ // FUSEMounter overrides:
+ MountErrorType InterpretReturnCode(int return_code) const override;
+
+ std::unique_ptr<SandboxedProcess> PrepareSandbox(
+ const std::string& source,
+ const base::FilePath& target_path,
+ std::vector<std::string> params,
+ MountErrorType* error) const final;
+
+ virtual MountErrorType FormatInvocationCommand(
+ const base::FilePath& archive,
+ std::vector<std::string> params,
+ SandboxedProcess* sandbox) const;
+
+ private:
+ const std::string archive_type_;
+ const std::string extension_;
+ Metrics* const metrics_;
+ const std::string metrics_name_;
+ const std::vector<int> password_needed_exit_codes_;
+ const std::unique_ptr<SandboxedProcessFactory> sandbox_factory_;
+
+ friend class ArchiveMounterTest;
+};
+
+} // namespace cros_disks
+
+#endif // CROS_DISKS_ARCHIVE_MOUNTER_H_
diff --git a/cros-disks/archive_mounter_test.cc b/cros-disks/archive_mounter_test.cc
new file mode 100644
index 0000000..c9e1cfc
--- /dev/null
+++ b/cros-disks/archive_mounter_test.cc
@@ -0,0 +1,193 @@
+// 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/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_util.h>
+#include <base/strings/string_split.h>
+#include <brillo/process/process_reaper.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "cros-disks/mount_point.h"
+#include "cros-disks/platform.h"
+
+namespace cros_disks {
+
+namespace {
+
+using testing::_;
+using testing::ElementsAre;
+using testing::Return;
+using testing::UnorderedElementsAre;
+
+const char kArchiveType[] = "archive";
+const char kSomeSource[] = "/home/user/something.archive";
+const char kMountDir[] = "/mnt";
+const int kPasswordNeededCode = 42;
+
+// Mock Platform implementation for testing.
+class MockFUSEPlatform : public Platform {
+ public:
+ MOCK_METHOD(bool, PathExists, (const std::string&), (const, override));
+};
+
+class FakeSandboxedProcessFactory : public SandboxedProcessFactory {
+ public:
+ std::unique_ptr<SandboxedProcess> CreateSandboxedProcess() const override {
+ return std::make_unique<FakeSandboxedProcess>();
+ }
+};
+
+} // namespace
+
+class ArchiveMounterTest : public ::testing::Test {
+ public:
+ ArchiveMounterTest() {
+ ON_CALL(platform_, PathExists).WillByDefault(Return(true));
+ }
+
+ protected:
+ std::unique_ptr<ArchiveMounter> CreateMounter(
+ std::vector<int> password_needed_codes) {
+ return std::make_unique<ArchiveMounter>(
+ &platform_, &process_reaper_, kArchiveType, &metrics_, "ArchiveMetrics",
+ std::move(password_needed_codes),
+ std::make_unique<FakeSandboxedProcessFactory>());
+ }
+
+ MountErrorType InterpretReturnCode(const ArchiveMounter& mounter,
+ int exit_code) const {
+ return mounter.InterpretReturnCode(exit_code);
+ }
+
+ std::unique_ptr<FakeSandboxedProcess> PrepareSandbox(
+ const ArchiveMounter& mounter,
+ const std::string& source,
+ std::vector<std::string> params,
+ MountErrorType* error) const {
+ auto sandbox = mounter.PrepareSandbox(source, base::FilePath(kMountDir),
+ std::move(params), error);
+ return std::unique_ptr<FakeSandboxedProcess>(
+ static_cast<FakeSandboxedProcess*>(sandbox.release()));
+ }
+
+ MockFUSEPlatform platform_;
+ brillo::ProcessReaper process_reaper_;
+ Metrics metrics_;
+};
+
+TEST_F(ArchiveMounterTest, CanMount) {
+ auto mounter = CreateMounter({});
+ base::FilePath name;
+ EXPECT_TRUE(mounter->CanMount("/foo/bar/baz.archive", {}, &name));
+ EXPECT_EQ("baz.archive", name.value());
+ EXPECT_FALSE(mounter->CanMount("/foo/bar/baz.something", {}, &name));
+}
+
+TEST_F(ArchiveMounterTest, InvalidPathsRejected) {
+ auto mounter = CreateMounter({});
+ MountErrorType error = MOUNT_ERROR_UNKNOWN;
+ auto sandbox = PrepareSandbox(*mounter, "foo.archive", {}, &error);
+ EXPECT_NE(MOUNT_ERROR_NONE, error);
+ EXPECT_FALSE(sandbox);
+ sandbox = PrepareSandbox(*mounter, "/foo/../etc/foo.archive", {}, &error);
+ EXPECT_NE(MOUNT_ERROR_NONE, error);
+ EXPECT_FALSE(sandbox);
+}
+
+TEST_F(ArchiveMounterTest, AppArgs) {
+ auto mounter = CreateMounter({});
+ MountErrorType error = MOUNT_ERROR_UNKNOWN;
+ auto sandbox = PrepareSandbox(*mounter, kSomeSource, {}, &error);
+ EXPECT_EQ(MOUNT_ERROR_NONE, error);
+ ASSERT_TRUE(sandbox);
+ EXPECT_THAT(sandbox->arguments(), ElementsAre("-o", _, kSomeSource));
+ std::vector<std::string> opts =
+ base::SplitString(sandbox->arguments()[1], ",", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ EXPECT_THAT(opts,
+ UnorderedElementsAre("umask=0222", "uid=1000", "gid=1001", "ro"));
+}
+
+TEST_F(ArchiveMounterTest, FileNotFound) {
+ EXPECT_CALL(platform_, PathExists(kSomeSource)).WillRepeatedly(Return(false));
+ auto mounter = CreateMounter({});
+ MountErrorType error = MOUNT_ERROR_UNKNOWN;
+ auto sandbox = PrepareSandbox(*mounter, kSomeSource, {}, &error);
+ EXPECT_NE(MOUNT_ERROR_NONE, error);
+ EXPECT_FALSE(sandbox);
+}
+
+TEST_F(ArchiveMounterTest, AppNeedsPassword) {
+ auto mounter = CreateMounter({kPasswordNeededCode});
+ EXPECT_EQ(MOUNT_ERROR_NEED_PASSWORD,
+ InterpretReturnCode(*mounter, kPasswordNeededCode));
+}
+
+TEST_F(ArchiveMounterTest, WithPassword) {
+ const std::string password = "My Password";
+
+ auto mounter = CreateMounter({kPasswordNeededCode});
+ MountErrorType error = MOUNT_ERROR_UNKNOWN;
+ auto sandbox =
+ PrepareSandbox(*mounter, kSomeSource, {"password=" + password}, &error);
+ EXPECT_EQ(MOUNT_ERROR_NONE, error);
+ ASSERT_TRUE(sandbox);
+ EXPECT_EQ(password, sandbox->input());
+ // Make sure password is not in args.
+ std::vector<std::string> opts =
+ base::SplitString(sandbox->arguments()[1], ",", base::KEEP_WHITESPACE,
+ base::SPLIT_WANT_ALL);
+ EXPECT_THAT(opts,
+ UnorderedElementsAre("umask=0222", "uid=1000", "gid=1001", "ro"));
+}
+
+TEST_F(ArchiveMounterTest, NoPassword) {
+ auto mounter = CreateMounter({kPasswordNeededCode});
+ MountErrorType error = MOUNT_ERROR_UNKNOWN;
+ auto sandbox = PrepareSandbox(*mounter, kSomeSource,
+ {
+ "Password=1", // Options are case sensitive
+ "password =2", // Space is significant
+ " password=3", // Space is significant
+ "password", // Not a valid option
+ },
+ &error);
+ ASSERT_TRUE(sandbox);
+ EXPECT_EQ("", sandbox->input());
+}
+
+TEST_F(ArchiveMounterTest, CopiesPassword) {
+ for (const std::string password : {
+ "",
+ " ",
+ "=",
+ "simple",
+ R"( !@#$%^&*()_-+={[}]|\:;"'<,>.?/ )",
+ }) {
+ auto mounter = CreateMounter({kPasswordNeededCode});
+ MountErrorType error = MOUNT_ERROR_UNKNOWN;
+ auto sandbox =
+ PrepareSandbox(*mounter, kSomeSource, {"password=" + password}, &error);
+ ASSERT_TRUE(sandbox);
+ EXPECT_EQ(password, sandbox->input());
+ }
+}
+
+TEST_F(ArchiveMounterTest, IgnoredIfNotNeeded) {
+ auto mounter = CreateMounter({});
+ MountErrorType error = MOUNT_ERROR_UNKNOWN;
+ auto sandbox =
+ PrepareSandbox(*mounter, kSomeSource, {"password=foo"}, &error);
+ EXPECT_EQ(MOUNT_ERROR_NONE, error);
+ ASSERT_TRUE(sandbox);
+ EXPECT_EQ("", sandbox->input());
+}
+
+} // namespace cros_disks
diff --git a/cros-disks/disk_manager.cc b/cros-disks/disk_manager.cc
index d7cbb9f..cceefc6 100644
--- a/cros-disks/disk_manager.cc
+++ b/cros-disks/disk_manager.cc
@@ -47,10 +47,7 @@
SandboxedExecutable executable,
OwnerUser run_as,
std::vector<std::string> options)
- : FUSEMounter(platform,
- reaper,
- std::move(filesystem_type),
- /* nosymfollow= */ true),
+ : FUSEMounter(platform, reaper, std::move(filesystem_type), {}),
upstream_factory_(upstream_factory),
sandbox_factory_(platform,
std::move(executable),
diff --git a/cros-disks/fuse_mounter.cc b/cros-disks/fuse_mounter.cc
index 5b9995a..5ac6888 100644
--- a/cros-disks/fuse_mounter.cc
+++ b/cros-disks/fuse_mounter.cc
@@ -42,25 +42,8 @@
namespace {
-const mode_t kSourcePathPermissions = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
-
const char kFuseDeviceFile[] = "/dev/fuse";
-template <typename T>
-base::Optional<T> UnsetIfEmpty(T value) {
- if (value.empty())
- return base::Optional<T>();
- return base::Optional<T>(std::move(value));
-}
-
-// TODO(dats): Remove when it's done beforehead by the caller.
-OwnerUser ResolveUserOrDie(const Platform* platform, const std::string& user) {
- OwnerUser result;
- PCHECK(platform->GetUserAndGroupId(user, &result.uid, &result.gid));
- result.gid = kChronosAccessGID;
- return result;
-}
-
class FUSEMountPoint : public MountPoint {
public:
FUSEMountPoint(const base::FilePath& path, const Platform* platform)
@@ -286,11 +269,11 @@
FUSEMounter::FUSEMounter(const Platform* platform,
brillo::ProcessReaper* process_reaper,
std::string filesystem_type,
- bool nosymfollow)
+ Config config)
: platform_(platform),
process_reaper_(process_reaper),
filesystem_type_(std::move(filesystem_type)),
- nosymfollow_(nosymfollow) {}
+ config_(std::move(config)) {}
FUSEMounter::~FUSEMounter() = default;
@@ -300,7 +283,7 @@
std::vector<std::string> params,
MountErrorType* error) const {
// Read-only is the only parameter that has any effect at this layer.
- const bool read_only = IsReadOnlyMount(params);
+ const bool read_only = config_.read_only || IsReadOnlyMount(params);
const base::File fuse_file = base::File(
base::FilePath(kFuseDeviceFile),
@@ -358,7 +341,7 @@
*error = platform_->Mount(source_descr, target_path.value(), fuse_type,
MountOptions::kMountFlags | MS_DIRSYNC |
(read_only ? MS_RDONLY : 0) |
- (nosymfollow_ ? MS_NOSYMFOLLOW : 0),
+ (config_.nosymfollow ? MS_NOSYMFOLLOW : 0),
fuse_mount_options);
if (*error != MOUNT_ERROR_NONE) {
@@ -443,8 +426,10 @@
std::string filesystem_type,
bool nosymfollow,
const SandboxedProcessFactory* sandbox_factory)
- : FUSEMounter(
- platform, process_reaper, std::move(filesystem_type), nosymfollow),
+ : FUSEMounter(platform,
+ process_reaper,
+ std::move(filesystem_type),
+ {.nosymfollow = nosymfollow}),
sandbox_factory_(sandbox_factory) {}
FUSEMounterHelper::~FUSEMounterHelper() = default;
@@ -463,134 +448,4 @@
return sandbox;
}
-FUSEMounterLegacy::FUSEMounterLegacy(Params params)
- : FUSEMounter(params.platform,
- params.process_reaper,
- std::move(params.filesystem_type),
- params.nosymfollow),
- metrics_(params.metrics),
- metrics_name_(std::move(params.metrics_name)),
- seccomp_policy_(),
- bind_paths_(std::move(params.bind_paths)),
- password_needed_codes_(std::move(params.password_needed_codes)),
- mount_options_(std::move(params.mount_options)),
- sandbox_factory_(
- platform(),
- {base::FilePath(std::move(params.mount_program)),
- UnsetIfEmpty(base::FilePath(std::move(params.seccomp_policy)))},
- ResolveUserOrDie(platform(), params.mount_user),
- params.network_access,
- std::move(params.supplementary_groups),
- UnsetIfEmpty(base::FilePath(std::move(params.mount_namespace)))) {}
-
-void FUSEMounterLegacy::CopyPassword(const std::vector<std::string>& options,
- Process* const process) const {
- DCHECK(process);
-
- // Is the process "password-aware"?
- if (password_needed_codes_.empty())
- return;
-
- // Is there a password available in options?
- const base::StringPiece prefix = "password=";
- const auto it = std::find_if(options.cbegin(), options.cend(),
- [prefix](const base::StringPiece s) {
- return base::StartsWith(s, prefix);
- });
- if (it == options.cend())
- return;
-
- // Pass the password via stdin.
- process->SetStdIn(it->substr(prefix.size()));
-}
-
-std::unique_ptr<SandboxedProcess> FUSEMounterLegacy::PrepareSandbox(
- const std::string& source,
- const base::FilePath& target_path,
- std::vector<std::string> params,
- MountErrorType* error) const {
- std::unique_ptr<SandboxedProcess> mount_process = CreateSandboxedProcess();
-
- // TODO(crbug.com/1053778) Only create the necessary tmpfs filesystems.
- for (const char* const dir : {"/home", "/media"}) {
- if (!mount_process->Mount("tmpfs", dir, "tmpfs", "mode=0755,size=10M")) {
- LOG(ERROR) << "Cannot mount " << quote(dir);
- *error = MOUNT_ERROR_INTERNAL;
- return nullptr;
- }
- }
-
- // Data dirs if any are mounted inside /run/fuse.
- if (!mount_process->BindMount("/run/fuse", "/run/fuse", false, false)) {
- LOG(ERROR) << "Can't bind /run/fuse";
- return nullptr;
- }
-
- // If a block device is being mounted, bind mount it into the sandbox.
- if (base::StartsWith(source, "/dev/", base::CompareCase::SENSITIVE)) {
- // Re-own source.
- if (!platform()->SetOwnership(source, getuid(),
- sandbox_factory_.run_as().gid) ||
- !platform()->SetPermissions(source, kSourcePathPermissions)) {
- LOG(ERROR) << "Can't set up permissions on " << quote(source);
- *error = MOUNT_ERROR_INSUFFICIENT_PERMISSIONS;
- return nullptr;
- }
-
- if (!mount_process->BindMount(source, source, true, false)) {
- LOG(ERROR) << "Cannot bind mount device " << quote(source);
- *error = MOUNT_ERROR_INVALID_ARGUMENT;
- return nullptr;
- }
- }
-
- // This is for additional data dirs.
- for (const BindPath& bind_path : bind_paths_) {
- if (!mount_process->BindMount(bind_path.path, bind_path.path,
- bind_path.writable, bind_path.recursive)) {
- LOG(ERROR) << "Cannot bind-mount " << quote(bind_path.path);
- *error = MOUNT_ERROR_INVALID_ARGUMENT;
- return nullptr;
- }
- }
-
- {
- // TODO(dats): This it leaking legacy options implementation. Remove it.
- std::string options_string = mount_options_.ToFuseMounterOptions();
- DCHECK(!options_string.empty());
- mount_process->AddArgument("-o");
- mount_process->AddArgument(std::move(options_string));
- }
-
- if (!source.empty()) {
- mount_process->AddArgument(source);
- }
-
- CopyPassword(params, mount_process.get());
-
- return mount_process;
-}
-
-MountErrorType FUSEMounterLegacy::InterpretReturnCode(int return_code) const {
- if (metrics_ && !metrics_name_.empty())
- metrics_->RecordFuseMounterErrorCode(metrics_name_, return_code);
-
- if (base::Contains(password_needed_codes_, return_code))
- return MOUNT_ERROR_NEED_PASSWORD;
-
- return FUSEMounter::InterpretReturnCode(return_code);
-}
-
-bool FUSEMounterLegacy::CanMount(const std::string& source,
- const std::vector<std::string>& params,
- base::FilePath* suggested_name) const {
- NOTREACHED();
- return true;
-}
-
-std::unique_ptr<SandboxedProcess> FUSEMounterLegacy::CreateSandboxedProcess()
- const {
- return sandbox_factory_.CreateSandboxedProcess();
-}
-
} // namespace cros_disks
diff --git a/cros-disks/fuse_mounter.h b/cros-disks/fuse_mounter.h
index 7da29bd..0001883 100644
--- a/cros-disks/fuse_mounter.h
+++ b/cros-disks/fuse_mounter.h
@@ -80,10 +80,15 @@
// and sandboxing is to be done in a subclass.
class FUSEMounter : public Mounter {
public:
+ struct Config {
+ bool nosymfollow = true;
+ bool read_only = false;
+ };
+
FUSEMounter(const Platform* platform,
brillo::ProcessReaper* process_reaper,
std::string filesystem_type,
- bool nosymfollow);
+ Config config);
FUSEMounter(const FUSEMounter&) = delete;
FUSEMounter& operator=(const FUSEMounter&) = delete;
~FUSEMounter() override;
@@ -129,7 +134,7 @@
const Platform* const platform_;
brillo::ProcessReaper* const process_reaper_;
const std::string filesystem_type_;
- const bool nosymfollow_;
+ const Config config_;
};
// A convenience class to tie FUSE mounter with a sandbox configuration.
@@ -165,123 +170,6 @@
const SandboxedProcessFactory* const sandbox_factory_;
};
-// A class for mounting something using a FUSE mount program.
-// TODO(dats): It contains too much logic used only in some cases but
-// not others. Tear it apart.
-class FUSEMounterLegacy : public FUSEMounter {
- public:
- struct BindPath {
- std::string path;
- bool writable = false;
- bool recursive = false;
- };
-
- using BindPaths = std::vector<BindPath>;
-
- // Parameters passed to FUSEMounter's constructor.
- // Members are kept in alphabetical order.
- struct Params {
- // Paths the FUSE mount program needs to access (beyond basic /proc, /dev,
- // etc).
- BindPaths bind_paths;
-
- // Filesystem type.
- std::string filesystem_type;
-
- // Optional object that collects UMA metrics.
- Metrics* metrics = nullptr;
-
- // Name of the UMA histogram recording the FUSE mount program return code.
- // Not recorded if empty or if metrics is null.
- std::string metrics_name;
-
- // Optional mount namespace where the source path exists.
- std::string mount_namespace;
-
- // FUSE mount options.
- MountOptions mount_options;
-
- // Path of the FUSE mount program.
- std::string mount_program;
-
- // User to run the FUSE mount program as.
- std::string mount_user;
-
- // Whether the FUSE mount program needs to access the network.
- bool network_access = false;
-
- // By default it's mounted with symlinks following disabled.
- bool nosymfollow = true;
-
- // Possible codes returned by the FUSE mount program to ask for a password.
- std::vector<int> password_needed_codes;
-
- // Object that provides platform service.
- const Platform* platform = nullptr;
-
- // Process reaper to monitor FUSE daemons.
- brillo::ProcessReaper* process_reaper = nullptr;
-
- // Optional path to BPF seccomp filter policy.
- std::string seccomp_policy;
-
- // Supplementary groups to run the mount program with.
- std::vector<gid_t> supplementary_groups;
- };
-
- explicit FUSEMounterLegacy(Params params);
- FUSEMounterLegacy(const FUSEMounterLegacy&) = delete;
- FUSEMounterLegacy& operator=(const FUSEMounterLegacy&) = delete;
-
- // If necessary, extracts the password from the given options and sets the
- // standard input of the given process. Does nothing if password_needed_codes
- // is empty. Does nothing if no string starting with "password=" is found in
- // options. If several options start with "password=", only the first one is
- // taken in account and the other ones are ignored.
- void CopyPassword(const std::vector<std::string>& options,
- Process* process) const;
-
- const MountOptions& mount_options() const { return mount_options_; }
-
- protected:
- // FUSEMounter overrides:
- bool CanMount(const std::string& source,
- const std::vector<std::string>& params,
- base::FilePath* suggested_name) const override;
-
- MountErrorType InterpretReturnCode(int return_code) const override;
-
- std::unique_ptr<SandboxedProcess> PrepareSandbox(
- const std::string& source,
- const base::FilePath& target_path,
- std::vector<std::string> params,
- MountErrorType* error) const override;
-
- // Protected for mocking out in testing.
- virtual std::unique_ptr<SandboxedProcess> CreateSandboxedProcess() const;
-
- // An object that collects UMA metrics.
- Metrics* const metrics_;
-
- // Name of the UMA histogram recording the FUSE mount program return code.
- // Not recorded if empty or if metrics is null.
- const std::string metrics_name_;
-
- // If not empty the path to BPF seccomp filter policy.
- const std::string seccomp_policy_;
-
- // Paths the FUSE mount program needs to access (beyond basic /proc, /dev,
- // etc).
- const BindPaths bind_paths_;
-
- // Possible codes returned by the FUSE mount program to ask for a password.
- std::vector<int> password_needed_codes_;
-
- const MountOptions mount_options_;
-
- const FUSESandboxedProcessFactory sandbox_factory_;
-};
-
} // namespace cros_disks
#endif // CROS_DISKS_FUSE_MOUNTER_H_
diff --git a/cros-disks/fuse_mounter_test.cc b/cros-disks/fuse_mounter_test.cc
index e4fc5aa..10f03e6 100644
--- a/cros-disks/fuse_mounter_test.cc
+++ b/cros-disks/fuse_mounter_test.cc
@@ -44,10 +44,8 @@
const gid_t kMountGID = 201;
const char kMountUser[] = "fuse-fuse";
const char kFUSEType[] = "fusefs";
-const char kMountProgram[] = "/bin/dummy";
const char kSomeSource[] = "/dev/dummy";
const char kMountDir[] = "/mnt";
-const int kPasswordNeededCode = 42;
// Mock Platform implementation for testing.
class MockFUSEPlatform : public Platform {
@@ -133,8 +131,7 @@
public:
FUSEMounterForTesting(const Platform* platform,
brillo::ProcessReaper* process_reaper)
- : FUSEMounter(
- platform, process_reaper, kFUSEType, true /* nosymfollow */) {}
+ : FUSEMounter(platform, process_reaper, kFUSEType, {}) {}
MOCK_METHOD(std::unique_ptr<SandboxedProcess>,
PrepareSandbox,
@@ -428,167 +425,4 @@
EXPECT_EQ(MOUNT_ERROR_NONE, mount_point->Unmount());
}
-namespace {
-
-class FUSEMounterLegacyForTesting : public FUSEMounterLegacy {
- public:
- FUSEMounterLegacyForTesting(const Platform* platform,
- brillo::ProcessReaper* process_reaper)
- : FUSEMounterLegacy({.filesystem_type = kFUSEType,
- .mount_program = kMountProgram,
- .mount_user = kMountUser,
- .password_needed_codes = {kPasswordNeededCode},
- .platform = platform,
- .process_reaper = process_reaper}) {}
-
- MOCK_METHOD(int, OnInput, (const std::string&), (const));
- MOCK_METHOD(int, InvokeMountTool, (const std::vector<std::string>&), (const));
-
- mutable std::vector<std::string> environment;
-
- private:
- std::unique_ptr<SandboxedProcess> CreateSandboxedProcess() const override {
- auto mock = std::make_unique<MockSandboxedProcess>();
- const SandboxedProcess* const process = mock.get();
- mock->AddArgument(kMountProgram);
- ON_CALL(*mock, StartImpl(_, _, _)).WillByDefault(Return(123));
- ON_CALL(*mock, WaitNonBlockingImpl())
- .WillByDefault(Invoke([this, process]() {
- const std::string& input = process->input();
- if (!input.empty())
- OnInput(input);
-
- return InvokeMountTool(process->arguments());
- }));
- return mock;
- }
-};
-
-} // namespace
-
-class FUSEMounterLegacyTest : public ::testing::Test {
- public:
- FUSEMounterLegacyTest() : mounter_(&platform_, &process_reaper_) {
- ON_CALL(platform_, Mount(kSomeSource, kMountDir, _, _, _))
- .WillByDefault(Return(MOUNT_ERROR_NONE));
- }
-
- protected:
- // Sets up mock expectations for a successful mount.
- void SetupMountExpectations() {
- EXPECT_CALL(mounter_, InvokeMountTool(ElementsAre(
- kMountProgram, "-o", MountOptions().ToString(),
- kSomeSource, StartsWith("/dev/fd/"))))
- .WillOnce(Return(0));
- EXPECT_CALL(platform_,
- SetOwnership(kSomeSource, getuid(), kChronosAccessGID))
- .WillOnce(Return(true));
- EXPECT_CALL(platform_, SetPermissions(kSomeSource, S_IRUSR | S_IWUSR |
- S_IRGRP | S_IWGRP))
- .WillOnce(Return(true));
- EXPECT_CALL(platform_, SetOwnership(kMountDir, _, _)).Times(0);
- EXPECT_CALL(platform_, SetPermissions(kMountDir, _)).Times(0);
- }
-
- MockFUSEPlatform platform_;
- brillo::ProcessReaper process_reaper_;
- FUSEMounterLegacyForTesting mounter_;
-};
-
-TEST_F(FUSEMounterLegacyTest, AppNeedsPassword) {
- EXPECT_CALL(platform_, Unmount(_, _)).WillOnce(Return(MOUNT_ERROR_NONE));
- EXPECT_CALL(mounter_, InvokeMountTool(_))
- .WillOnce(Return(kPasswordNeededCode));
-
- MountErrorType error = MOUNT_ERROR_UNKNOWN;
- auto mount_point =
- mounter_.Mount(kSomeSource, base::FilePath(kMountDir), {}, &error);
- EXPECT_FALSE(mount_point);
- EXPECT_EQ(MOUNT_ERROR_NEED_PASSWORD, error);
-}
-
-TEST_F(FUSEMounterLegacyTest, WithPassword) {
- const std::string password = "My Password";
-
- SetupMountExpectations();
- EXPECT_CALL(mounter_, OnInput(password)).Times(1);
- // The MountPoint returned by Mount() will unmount when it is destructed.
- EXPECT_CALL(platform_, Unmount(kMountDir, 0))
- .WillOnce(Return(MOUNT_ERROR_NONE));
-
- MountErrorType error = MOUNT_ERROR_UNKNOWN;
- auto mount_point = mounter_.Mount(kSomeSource, base::FilePath(kMountDir),
- {"password=" + password}, &error);
- EXPECT_TRUE(mount_point);
- EXPECT_EQ(MOUNT_ERROR_NONE, error);
-}
-
-TEST(FUSEMounterPasswordTest, NoPassword) {
- MockFUSEPlatform platform;
- const FUSEMounterLegacy mounter({
- .mount_program = kMountProgram,
- .mount_user = kMountUser,
- .password_needed_codes = {kPasswordNeededCode},
- .platform = &platform,
- });
- SandboxedProcess process;
- mounter.CopyPassword(
- {
- "Password=1", // Options are case sensitive
- "password =2", // Space is significant
- " password=3", // Space is significant
- "password", // Not a valid option
- },
- &process);
- EXPECT_EQ(process.input(), "");
-}
-
-TEST(FUSEMounterPasswordTest, CopiesPassword) {
- MockFUSEPlatform platform;
- const FUSEMounterLegacy mounter({
- .mount_program = kMountProgram,
- .mount_user = kMountUser,
- .password_needed_codes = {kPasswordNeededCode},
- .platform = &platform,
- });
- for (const std::string password : {
- "",
- " ",
- "=",
- "simple",
- R"( !@#$%^&*()_-+={[}]|\:;"'<,>.?/ )",
- }) {
- SandboxedProcess process;
- mounter.CopyPassword({"password=" + password}, &process);
- EXPECT_EQ(process.input(), password);
- }
-}
-
-TEST(FUSEMounterPasswordTest, FirstPassword) {
- MockFUSEPlatform platform;
- const FUSEMounterLegacy mounter({
- .mount_program = kMountProgram,
- .mount_user = kMountUser,
- .password_needed_codes = {kPasswordNeededCode},
- .platform = &platform,
- });
- SandboxedProcess process;
- mounter.CopyPassword({"other1=value1", "password=1", "password=2",
- "other2=value2", "password=3"},
- &process);
- EXPECT_EQ(process.input(), "1");
-}
-
-TEST(FUSEMounterPasswordTest, IgnoredIfNotNeeded) {
- MockFUSEPlatform platform;
- const FUSEMounterLegacy mounter({
- .mount_program = kMountProgram,
- .mount_user = kMountUser,
- .platform = &platform,
- });
- SandboxedProcess process;
- mounter.CopyPassword({"password=dummy"}, &process);
- EXPECT_EQ(process.input(), "");
-}
-
} // namespace cros_disks
diff --git a/cros-disks/rar_manager.cc b/cros-disks/rar_manager.cc
index b908bbe..cf89f75 100644
--- a/cros-disks/rar_manager.cc
+++ b/cros-disks/rar_manager.cc
@@ -12,7 +12,9 @@
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include "cros-disks/archive_mounter.h"
#include "cros-disks/error_logger.h"
#include "cros-disks/fuse_mounter.h"
#include "cros-disks/metrics.h"
@@ -24,8 +26,77 @@
const char kExtension[] = ".rar";
+OwnerUser GetRarUserOrDie(const Platform* platform) {
+ OwnerUser run_as;
+ PCHECK(platform->GetUserAndGroupId("fuse-rar2fs", &run_as.uid, &run_as.gid))
+ << "Cannot resolve required user fuse-rar2fs";
+ return run_as;
+}
+
} // namespace
+class RarManager::RarMounter : public ArchiveMounter {
+ public:
+ RarMounter(const Platform* platform,
+ brillo::ProcessReaper* process_reaper,
+ Metrics* metrics,
+ const RarManager* rar_manager)
+ : ArchiveMounter(
+ platform,
+ process_reaper,
+ "rar",
+ metrics,
+ "Rar2fs",
+ {12, // ERAR_BAD_DATA
+ 22, // ERAR_MISSING_PASSWORD
+ 24}, // ERAR_BAD_PASSWORD
+ std::make_unique<FUSESandboxedProcessFactory>(
+ platform,
+ SandboxedExecutable{
+ base::FilePath("/usr/bin/rar2fs"),
+ base::FilePath("/usr/share/policy/rar2fs-seccomp.policy")},
+ GetRarUserOrDie(platform),
+ /* has_network_access= */ false,
+ rar_manager->GetSupplementaryGroups())),
+ rar_manager_(rar_manager) {}
+
+ protected:
+ MountErrorType FormatInvocationCommand(
+ const base::FilePath& archive,
+ std::vector<std::string> params,
+ SandboxedProcess* sandbox) const override {
+ // Bind-mount parts of a multipart archive if any.
+ for (const auto& path : rar_manager_->GetBindPaths(archive.value())) {
+ if (!sandbox->BindMount(path, path, /* writeable= */ false,
+ /* recursive= */ false)) {
+ PLOG(ERROR) << "Could not bind " << quote(path);
+ return MOUNT_ERROR_INTERNAL;
+ }
+ }
+
+ std::vector<std::string> opts = {
+ MountOptions::kOptionReadOnly, "umask=0222", "locale=en_US.UTF8",
+ 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;
+ }
+
+ const RarManager* rar_manager_;
+};
+
+RarManager::RarManager(const std::string& mount_root,
+ Platform* platform,
+ Metrics* metrics,
+ brillo::ProcessReaper* process_reaper)
+ : ArchiveManager(mount_root, platform, metrics, process_reaper),
+ mounter_(std::make_unique<RarMounter>(
+ platform, process_reaper, metrics, this)) {}
+
RarManager::~RarManager() {
UnmountAll();
}
@@ -44,11 +115,7 @@
const base::FilePath& mount_path,
MountOptions* const applied_options,
MountErrorType* const error) {
- DCHECK(applied_options);
DCHECK(error);
-
- metrics()->RecordArchiveType("rar");
-
// MountManager resolves source path to real path before calling DoMount,
// so no symlinks or '..' will be here.
if (!IsInAllowedFolder(source_path)) {
@@ -56,39 +123,7 @@
*error = MOUNT_ERROR_INVALID_DEVICE_PATH;
return nullptr;
}
-
- MountNamespace mount_namespace = GetMountNamespaceFor(source_path);
-
- FUSEMounterLegacy::Params params{
- .bind_paths = GetBindPaths(source_path),
- .filesystem_type = "rarfs",
- .metrics = metrics(),
- .metrics_name = "Rar2fs",
- .mount_namespace = std::move(mount_namespace.name),
- .mount_program = "/usr/bin/rar2fs",
- .mount_user = "fuse-rar2fs",
- .password_needed_codes = {12, // ERAR_BAD_DATA
- 22, // ERAR_MISSING_PASSWORD
- 24}, // ERAR_BAD_PASSWORD
- .platform = platform(),
- .process_reaper = process_reaper(),
- .seccomp_policy = "/usr/share/policy/rar2fs-seccomp.policy",
- .supplementary_groups = GetSupplementaryGroups(),
- };
-
- mount_namespace.guard.reset();
-
- // Prepare FUSE mount options.
- params.mount_options.EnforceOption("locale=en_US.UTF8");
- *error = GetMountOptions(¶ms.mount_options);
- if (*error != MOUNT_ERROR_NONE)
- return nullptr;
-
- *applied_options = params.mount_options;
-
- // Run rar2fs.
- const FUSEMounterLegacy mounter(std::move(params));
- return mounter.Mount(source_path, mount_path, options, error);
+ return mounter_->Mount(source_path, mount_path, options, error);
}
bool RarManager::Increment(const std::string::iterator begin,
@@ -134,7 +169,7 @@
}
void RarManager::AddPathsWithOldNamingScheme(
- FUSEMounterLegacy::BindPaths* const bind_paths,
+ std::vector<std::string>* const bind_paths,
const base::StringPiece original_path) const {
DCHECK(bind_paths);
@@ -155,18 +190,18 @@
if (!platform()->PathExists(candidate_path))
return;
- bind_paths->push_back({candidate_path});
+ bind_paths->push_back(candidate_path);
// Iterate by incrementing the last 3 characters of the extension:
// '.r00' -> '.r01' -> ... -> '.r99' -> '.s00' -> ... -> '.z99'
// or
// '.R00' -> '.R01' -> ... -> '.R99' -> '.S00' -> ... -> '.Z99'
while (Increment(end - 3, end) && platform()->PathExists(candidate_path))
- bind_paths->push_back({candidate_path});
+ bind_paths->push_back(candidate_path);
}
void RarManager::AddPathsWithNewNamingScheme(
- FUSEMounterLegacy::BindPaths* const bind_paths,
+ std::vector<std::string>* const bind_paths,
const base::StringPiece original_path,
const IndexRange& digits) const {
DCHECK(bind_paths);
@@ -186,13 +221,13 @@
// Find all the files making the multipart archive.
while (Increment(begin, end) && platform()->PathExists(candidate_path)) {
if (candidate_path != original_path)
- bind_paths->push_back({candidate_path});
+ bind_paths->push_back(candidate_path);
}
}
-FUSEMounterLegacy::BindPaths RarManager::GetBindPaths(
+std::vector<std::string> RarManager::GetBindPaths(
const base::StringPiece original_path) const {
- FUSEMounterLegacy::BindPaths bind_paths = {{std::string(original_path)}};
+ std::vector<std::string> bind_paths = {std::string(original_path)};
// Delimit the digit range assuming original_path uses the new naming scheme.
const IndexRange digits = ParseDigits(original_path);
diff --git a/cros-disks/rar_manager.h b/cros-disks/rar_manager.h
index 3c4cdb4..4d2cb66 100644
--- a/cros-disks/rar_manager.h
+++ b/cros-disks/rar_manager.h
@@ -17,10 +17,17 @@
namespace cros_disks {
+class ArchiveMounter;
+
// A MountManager mounting RAR archives as virtual filesystems using rar2fs.
class RarManager : public ArchiveManager {
public:
- using ArchiveManager::ArchiveManager;
+ RarManager(const std::string& mount_root,
+ Platform* platform,
+ Metrics* metrics,
+ brillo::ProcessReaper* process_reaper);
+ RarManager(const RarManager&) = delete;
+ RarManager& operator=(const RarManager&) = delete;
~RarManager() override;
@@ -63,11 +70,11 @@
static IndexRange ParseDigits(base::StringPiece path);
// Adds bind paths using old naming scheme.
- void AddPathsWithOldNamingScheme(FUSEMounterLegacy::BindPaths* bind_paths,
+ void AddPathsWithOldNamingScheme(std::vector<std::string>* bind_paths,
base::StringPiece original_path) const;
// Adds bind paths using new naming scheme.
- void AddPathsWithNewNamingScheme(FUSEMounterLegacy::BindPaths* bind_paths,
+ void AddPathsWithNewNamingScheme(std::vector<std::string>* bind_paths,
base::StringPiece original_path,
const IndexRange& digits) const;
@@ -111,8 +118,10 @@
// ...
// basename999.rar
// etc.
- FUSEMounterLegacy::BindPaths GetBindPaths(
- base::StringPiece original_path) const;
+ std::vector<std::string> GetBindPaths(base::StringPiece original_path) const;
+
+ class RarMounter;
+ const std::unique_ptr<ArchiveMounter> mounter_;
FRIEND_TEST(RarManagerTest, CanMount);
FRIEND_TEST(RarManagerTest, SuggestMountPath);
diff --git a/cros-disks/rar_manager_test.cc b/cros-disks/rar_manager_test.cc
index b017952..8f71d4d 100644
--- a/cros-disks/rar_manager_test.cc
+++ b/cros-disks/rar_manager_test.cc
@@ -14,18 +14,6 @@
namespace cros_disks {
-std::ostream& operator<<(std::ostream& out,
- const FUSEMounterLegacy::BindPath& x) {
- return out << "{ path: " << quote(x.path) << ", writable: " << x.writable
- << ", recursive: " << x.recursive << " }";
-}
-
-bool operator==(const FUSEMounterLegacy::BindPath& a,
- const FUSEMounterLegacy::BindPath& b) {
- return a.path == b.path && a.writable == b.writable &&
- a.recursive == b.recursive;
-}
-
namespace {
using ::testing::_;
@@ -221,26 +209,23 @@
TEST_F(RarManagerTest, GetBindPathsWithOldNamingScheme) {
const RarManager& m = manager_;
- EXPECT_THAT(m.GetBindPaths("poi"),
- ElementsAreArray<FUSEMounterLegacy::BindPath>({{"poi"}}));
+ EXPECT_THAT(m.GetBindPaths("poi"), ElementsAreArray<std::string>({"poi"}));
EXPECT_CALL(platform_, PathExists("poi.r00")).WillOnce(Return(false));
EXPECT_THAT(m.GetBindPaths("poi.rar"),
- ElementsAreArray<FUSEMounterLegacy::BindPath>({{"poi.rar"}}));
+ ElementsAreArray<std::string>({"poi.rar"}));
EXPECT_CALL(platform_, PathExists("poi.r00")).WillOnce(Return(true));
EXPECT_CALL(platform_, PathExists("poi.r01")).WillOnce(Return(true));
EXPECT_CALL(platform_, PathExists("poi.r02")).WillOnce(Return(false));
EXPECT_THAT(m.GetBindPaths("poi.rar"),
- ElementsAreArray<FUSEMounterLegacy::BindPath>(
- {{"poi.rar"}, {"poi.r00"}, {"poi.r01"}}));
+ ElementsAreArray<std::string>({"poi.rar", "poi.r00", "poi.r01"}));
EXPECT_CALL(platform_, PathExists("POI.R00")).WillOnce(Return(true));
EXPECT_CALL(platform_, PathExists("POI.R01")).WillOnce(Return(true));
EXPECT_CALL(platform_, PathExists("POI.R02")).WillOnce(Return(false));
EXPECT_THAT(m.GetBindPaths("POI.RAR"),
- ElementsAreArray<FUSEMounterLegacy::BindPath>(
- {{"POI.RAR"}, {"POI.R00"}, {"POI.R01"}}));
+ ElementsAreArray<std::string>({"POI.RAR", "POI.R00", "POI.R01"}));
}
TEST_F(RarManagerTest, GetBindPathsWithNewNamingScheme) {
@@ -248,7 +233,7 @@
EXPECT_CALL(platform_, PathExists("poi1.rar")).WillOnce(Return(false));
EXPECT_THAT(m.GetBindPaths("poi2.rar"),
- ElementsAreArray<FUSEMounterLegacy::BindPath>({{"poi2.rar"}}));
+ ElementsAreArray<std::string>({"poi2.rar"}));
EXPECT_CALL(platform_, PathExists("poi1.rar")).WillOnce(Return(true));
EXPECT_CALL(platform_, PathExists("poi2.rar")).WillOnce(Return(true));
@@ -256,8 +241,8 @@
EXPECT_CALL(platform_, PathExists("poi4.rar")).WillOnce(Return(true));
EXPECT_CALL(platform_, PathExists("poi5.rar")).WillOnce(Return(false));
EXPECT_THAT(m.GetBindPaths("poi2.rar"),
- ElementsAreArray<FUSEMounterLegacy::BindPath>(
- {{"poi2.rar"}, {"poi1.rar"}, {"poi3.rar"}, {"poi4.rar"}}));
+ ElementsAreArray<std::string>(
+ {"poi2.rar", "poi1.rar", "poi3.rar", "poi4.rar"}));
EXPECT_CALL(platform_, PathExists("POI1.RAR")).WillOnce(Return(true));
EXPECT_CALL(platform_, PathExists("POI2.RAR")).WillOnce(Return(true));
@@ -265,8 +250,8 @@
EXPECT_CALL(platform_, PathExists("POI4.RAR")).WillOnce(Return(true));
EXPECT_CALL(platform_, PathExists("POI5.RAR")).WillOnce(Return(false));
EXPECT_THAT(m.GetBindPaths("POI2.RAR"),
- ElementsAreArray<FUSEMounterLegacy::BindPath>(
- {{"POI2.RAR"}, {"POI1.RAR"}, {"POI3.RAR"}, {"POI4.RAR"}}));
+ ElementsAreArray<std::string>(
+ {"POI2.RAR", "POI1.RAR", "POI3.RAR", "POI4.RAR"}));
}
TEST_F(RarManagerTest, GetBindPathsStopsOnOverflow) {
diff --git a/cros-disks/zip_manager.cc b/cros-disks/zip_manager.cc
index 4f14425..d7fb47f 100644
--- a/cros-disks/zip_manager.cc
+++ b/cros-disks/zip_manager.cc
@@ -11,6 +11,7 @@
#include <base/logging.h>
#include <base/strings/string_util.h>
+#include "cros-disks/archive_mounter.h"
#include "cros-disks/error_logger.h"
#include "cros-disks/fuse_mounter.h"
#include "cros-disks/metrics.h"
@@ -19,15 +20,53 @@
namespace cros_disks {
+namespace {
+
+std::unique_ptr<ArchiveMounter> CreateZipMounter(
+ Platform* platform,
+ Metrics* metrics,
+ brillo::ProcessReaper* process_reaper,
+ std::vector<gid_t> supplementary_groups) {
+ OwnerUser run_as;
+ PCHECK(platform->GetUserAndGroupId("fuse-zip", &run_as.uid, &run_as.gid))
+ << "Cannot resolve required user fuse-zip";
+
+ const SandboxedExecutable executable = {
+ base::FilePath("/usr/bin/fuse-zip"),
+ base::FilePath("/usr/share/policy/fuse-zip-seccomp.policy")};
+
+ auto sandbox_factory = std::make_unique<FUSESandboxedProcessFactory>(
+ platform, std::move(executable), std::move(run_as),
+ /* has_network_access= */ false, std::move(supplementary_groups));
+
+ std::vector<int> password_needed_codes = {
+ 23, // ZIP_ER_BASE + ZIP_ER_ZLIB
+ 36, // ZIP_ER_BASE + ZIP_ER_NOPASSWD
+ 37}; // ZIP_ER_BASE + ZIP_ER_WRONGPASSWD
+
+ return std::make_unique<ArchiveMounter>(
+ platform, process_reaper, "zip", metrics, "FuseZip",
+ std::move(password_needed_codes), std::move(sandbox_factory));
+}
+
+} // namespace
+
+ZipManager::ZipManager(const std::string& mount_root,
+ Platform* platform,
+ Metrics* metrics,
+ brillo::ProcessReaper* process_reaper)
+ : ArchiveManager(mount_root, platform, metrics, process_reaper),
+ mounter_(CreateZipMounter(
+ platform, metrics, process_reaper, GetSupplementaryGroups())) {}
+
ZipManager::~ZipManager() {
UnmountAll();
}
bool ZipManager::CanMount(const std::string& source_path) const {
- // Check for expected file extension.
- return base::EndsWith(source_path, ".zip",
- base::CompareCase::INSENSITIVE_ASCII) &&
- IsInAllowedFolder(source_path);
+ base::FilePath name;
+ return IsInAllowedFolder(source_path) &&
+ mounter_->CanMount(source_path, {}, &name);
}
std::unique_ptr<MountPoint> ZipManager::DoMount(
@@ -37,11 +76,7 @@
const base::FilePath& mount_path,
MountOptions* const applied_options,
MountErrorType* const error) {
- DCHECK(applied_options);
DCHECK(error);
-
- metrics()->RecordArchiveType("zip");
-
// MountManager resolves source path to real path before calling DoMount,
// so no symlinks or '..' will be here.
if (!IsInAllowedFolder(source_path)) {
@@ -49,34 +84,7 @@
*error = MOUNT_ERROR_INVALID_DEVICE_PATH;
return nullptr;
}
-
- FUSEMounterLegacy::Params params{
- .bind_paths = {{source_path}},
- .filesystem_type = "zipfs",
- .metrics = metrics(),
- .metrics_name = "FuseZip",
- .mount_namespace = GetMountNamespaceFor(source_path).name,
- .mount_program = "/usr/bin/fuse-zip",
- .mount_user = "fuse-zip",
- .password_needed_codes = {23, // ZIP_ER_BASE + ZIP_ER_ZLIB
- 36, // ZIP_ER_BASE + ZIP_ER_NOPASSWD
- 37}, // ZIP_ER_BASE + ZIP_ER_WRONGPASSWD
- .platform = platform(),
- .process_reaper = process_reaper(),
- .seccomp_policy = "/usr/share/policy/fuse-zip-seccomp.policy",
- .supplementary_groups = GetSupplementaryGroups(),
- };
-
- // Prepare FUSE mount options.
- *error = GetMountOptions(¶ms.mount_options);
- if (*error != MOUNT_ERROR_NONE)
- return nullptr;
-
- *applied_options = params.mount_options;
-
- // Run fuse-zip.
- const FUSEMounterLegacy mounter(std::move(params));
- return mounter.Mount(source_path, mount_path, options, error);
+ return mounter_->Mount(source_path, mount_path, options, error);
}
} // namespace cros_disks
diff --git a/cros-disks/zip_manager.h b/cros-disks/zip_manager.h
index 0d9cecd..18981d2 100644
--- a/cros-disks/zip_manager.h
+++ b/cros-disks/zip_manager.h
@@ -13,10 +13,17 @@
namespace cros_disks {
+class ArchiveMounter;
+
// A MountManager mounting ZIP archives as virtual filesystems using fuse-zip.
class ZipManager : public ArchiveManager {
public:
- using ArchiveManager::ArchiveManager;
+ ZipManager(const std::string& mount_root,
+ Platform* platform,
+ Metrics* metrics,
+ brillo::ProcessReaper* process_reaper);
+ ZipManager(const ZipManager&) = delete;
+ ZipManager& operator=(const ZipManager&) = delete;
~ZipManager() override;
@@ -30,6 +37,8 @@
const base::FilePath& mount_path,
MountOptions* applied_options,
MountErrorType* error) override;
+
+ const std::unique_ptr<ArchiveMounter> mounter_;
};
} // namespace cros_disks
diff --git a/cros-disks/zip_manager_test.cc b/cros-disks/zip_manager_test.cc
index b26ac8b..4517cc2 100644
--- a/cros-disks/zip_manager_test.cc
+++ b/cros-disks/zip_manager_test.cc
@@ -15,12 +15,28 @@
const char kMountRootDirectory[] = "/my_mount_point";
+class MockPlatform : public Platform {
+ public:
+ bool GetUserAndGroupId(const std::string& name,
+ uid_t* uid,
+ gid_t* gid) const override {
+ if (name == "fuse-zip") {
+ if (uid)
+ *uid = 200;
+ if (gid)
+ *gid = 300;
+ return true;
+ }
+ return false;
+ }
+};
+
} // namespace
class ZipManagerTest : public testing::Test {
protected:
Metrics metrics_;
- Platform platform_;
+ MockPlatform platform_;
brillo::ProcessReaper reaper_;
const ZipManager manager_{kMountRootDirectory, &platform_, &metrics_,
&reaper_};