blob: 8d1e96cb84dc1b87180dda7acc063ac64e5a8446 [file] [log] [blame]
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +11001// Copyright 2020 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "cros-disks/archive_mounter.h"
6
7#include <utility>
8
9#include <base/strings/stringprintf.h>
10#include <base/strings/string_util.h>
11#include <brillo/scoped_mount_namespace.h>
12
13#include "cros-disks/quote.h"
14
15namespace cros_disks {
16
17namespace {
18constexpr char kOptionPassword[] = "password";
19} // namespace
20
21ArchiveMounter::ArchiveMounter(
22 const Platform* platform,
23 brillo::ProcessReaper* process_reaper,
24 std::string archive_type,
25 Metrics* metrics,
26 std::string metrics_name,
27 std::vector<int> password_needed_exit_codes,
28 std::unique_ptr<SandboxedProcessFactory> sandbox_factory)
29 : FUSEMounter(
30 platform, process_reaper, archive_type + "fs", {.read_only = true}),
31 archive_type_(archive_type),
32 extension_("." + archive_type),
33 metrics_(metrics),
34 metrics_name_(std::move(metrics_name)),
35 password_needed_exit_codes_(std::move(password_needed_exit_codes)),
36 sandbox_factory_(std::move(sandbox_factory)) {}
37
38ArchiveMounter::~ArchiveMounter() = default;
39
40bool ArchiveMounter::CanMount(const std::string& source,
41 const std::vector<std::string>& /*params*/,
42 base::FilePath* suggested_dir_name) const {
43 base::FilePath path(source);
44 if (path.IsAbsolute() &&
45 base::CompareCaseInsensitiveASCII(path.Extension(), extension_) == 0) {
46 *suggested_dir_name = path.BaseName();
47 return true;
48 }
49 return false;
50}
51
52MountErrorType ArchiveMounter::InterpretReturnCode(int return_code) const {
53 if (metrics_ && !metrics_name_.empty())
54 metrics_->RecordFuseMounterErrorCode(metrics_name_, return_code);
55
56 if (base::Contains(password_needed_exit_codes_, return_code))
57 return MOUNT_ERROR_NEED_PASSWORD;
58 return FUSEMounter::InterpretReturnCode(return_code);
59}
60
61std::unique_ptr<SandboxedProcess> ArchiveMounter::PrepareSandbox(
62 const std::string& source,
63 const base::FilePath& /*target_path*/,
64 std::vector<std::string> params,
65 MountErrorType* error) const {
66 metrics_->RecordArchiveType(archive_type_);
67
68 base::FilePath path(source);
69 if (!path.IsAbsolute() || path.ReferencesParent()) {
François Degrosca450862021-01-14 14:40:02 +110070 LOG(ERROR) << "Invalid archive path " << redact(path);
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +110071 *error = MOUNT_ERROR_INVALID_ARGUMENT;
72 return nullptr;
73 }
74
75 auto sandbox = sandbox_factory_->CreateSandboxedProcess();
76
77 std::unique_ptr<brillo::ScopedMountNamespace> mount_ns;
78 if (!platform()->PathExists(path.value())) {
79 // Try to locate the file in Chrome's mount namespace.
80 mount_ns = brillo::ScopedMountNamespace::CreateFromPath(
81 base::FilePath(kChromeNamespace));
82 if (!mount_ns) {
François Degrosca450862021-01-14 14:40:02 +110083 PLOG(ERROR) << "Cannot find archive " << redact(path)
84 << " in mount namespace " << quote(kChromeNamespace);
85
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +110086 // TODO(dats): These probably should be MOUNT_ERROR_INVALID_DEVICE_PATH or
87 // something like that, but tast tests expect
88 // MOUNT_ERROR_MOUNT_PROGRAM_FAILED.
89 *error = MOUNT_ERROR_MOUNT_PROGRAM_FAILED;
90 return nullptr;
91 }
92 if (!platform()->PathExists(path.value())) {
François Degrosca450862021-01-14 14:40:02 +110093 PLOG(ERROR) << "Cannot find archive " << redact(path);
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +110094 *error = MOUNT_ERROR_MOUNT_PROGRAM_FAILED;
95 return nullptr;
96 }
97 }
98
99 // Archives are typically under /home, /media or /run. To bind-mount the
100 // source those directories must be writable, but by default only /run is.
101 for (const char* const dir : {"/home", "/media"}) {
102 if (!sandbox->Mount("tmpfs", dir, "tmpfs", "mode=0755,size=1M")) {
103 LOG(ERROR) << "Cannot mount " << quote(dir);
104 *error = MOUNT_ERROR_INTERNAL;
105 return nullptr;
106 }
107 }
108
109 // Is the process "password-aware"?
110 if (!password_needed_exit_codes_.empty()) {
111 std::string password;
112 if (GetParamValue(params, kOptionPassword, &password)) {
113 sandbox->SetStdIn(password);
114 }
115 }
116
117 *error = FormatInvocationCommand(path, std::move(params), sandbox.get());
118 if (*error != MOUNT_ERROR_NONE) {
119 return nullptr;
120 }
121
122 if (mount_ns) {
123 // Sandbox will need to enter Chrome's namespace too to access files.
124 mount_ns.reset();
125 sandbox->EnterExistingMountNamespace(kChromeNamespace);
126 }
127
128 return sandbox;
129}
130
131MountErrorType ArchiveMounter::FormatInvocationCommand(
132 const base::FilePath& archive,
133 std::vector<std::string> /*params*/,
134 SandboxedProcess* sandbox) const {
135 // Make the source available in the sandbox.
136 if (!sandbox->BindMount(archive.value(), archive.value(),
137 /* writeable= */ false,
138 /* recursive= */ false)) {
François Degrosca450862021-01-14 14:40:02 +1100139 LOG(ERROR) << "Cannot bind-mount archive " << redact(archive);
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +1100140 return MOUNT_ERROR_INTERNAL;
141 }
142
143 std::vector<std::string> opts = {
Sergei Datsenko3928f782020-12-31 09:14:04 +1100144 "ro", "umask=0222", base::StringPrintf("uid=%d", kChronosUID),
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +1100145 base::StringPrintf("gid=%d", kChronosAccessGID)};
146
Sergei Datsenko1bb0bdc2021-02-17 11:26:43 +1100147 std::string options;
148 if (!JoinParamsIntoOptions(opts, &options)) {
149 return MOUNT_ERROR_INVALID_MOUNT_OPTIONS;
150 }
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +1100151 sandbox->AddArgument("-o");
Sergei Datsenko1bb0bdc2021-02-17 11:26:43 +1100152 sandbox->AddArgument(options);
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +1100153 sandbox->AddArgument(archive.value());
154
155 return MOUNT_ERROR_NONE;
156}
157
158} // namespace cros_disks