blob: e90f4d670e0aa26769b1055cacb49c3ae316d8eb [file] [log] [blame]
Ben Chan7e6fea02013-01-17 15:21:01 -08001// Copyright (c) 2013 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
Ben Chan5ccd9fe2013-11-13 18:28:27 -08005#include "cros-disks/fuse_mounter.h"
Ben Chan7e6fea02013-01-17 15:21:01 -08006
Qijiang Fan713061e2021-03-08 15:45:12 +09007#include <base/check.h>
8#include <base/check_op.h>
9
Anand K Mistrya24c75b2020-01-09 17:57:25 +110010// Has to come before linux/fs.h due to conflicting definitions of MS_*
11// constants.
12#include <sys/mount.h>
13
Evan Green9075b842019-08-09 11:33:05 -070014#include <fcntl.h>
Ben Chan7e6fea02013-01-17 15:21:01 -080015#include <linux/capability.h>
Evan Green9075b842019-08-09 11:33:05 -070016#include <linux/fs.h>
17#include <sys/ioctl.h>
Anand K Mistry41a23962019-02-05 11:57:03 +110018#include <sys/stat.h>
19#include <sys/types.h>
20#include <unistd.h>
Ben Chan7e6fea02013-01-17 15:21:01 -080021
François Degros5c6d9cb2020-07-16 13:44:44 +100022#include <algorithm>
Sergei Datsenko6907a132019-04-01 11:26:56 +110023#include <memory>
Ben Chan7e6fea02013-01-17 15:21:01 -080024#include <string>
Sergei Datsenkoa910bba2019-06-18 13:31:59 +100025#include <utility>
Ben Chan7e6fea02013-01-17 15:21:01 -080026
Anand K Mistry41a23962019-02-05 11:57:03 +110027#include <base/bind.h>
28#include <base/callback_helpers.h>
29#include <base/files/file.h>
Ben Chan7e6fea02013-01-17 15:21:01 -080030#include <base/logging.h>
Anand K Mistrycf5135f2020-01-21 10:48:54 +110031#include <base/memory/weak_ptr.h>
François Degrosf926b992020-08-13 18:17:01 +100032#include <base/stl_util.h>
Anand K Mistry238aae12019-01-22 15:02:46 +110033#include <base/strings/string_util.h>
François Degrosf926b992020-08-13 18:17:01 +100034#include <base/strings/stringprintf.h>
Simon Glass2b1da092020-05-21 12:24:16 -060035#include <brillo/process/process_reaper.h>
Ben Chan7e6fea02013-01-17 15:21:01 -080036
François Degrosbf7bb592019-07-12 09:49:52 +100037#include "cros-disks/error_logger.h"
Anand K Mistry9f4611e2019-12-19 16:06:39 +110038#include "cros-disks/mount_point.h"
Ben Chan7e6fea02013-01-17 15:21:01 -080039#include "cros-disks/platform.h"
François Degros899487c2019-07-12 11:57:52 +100040#include "cros-disks/quote.h"
Ben Chan5ccd9fe2013-11-13 18:28:27 -080041#include "cros-disks/sandboxed_process.h"
Sergei Datsenkof5553d12020-11-25 07:51:59 +110042#include "cros-disks/uri.h"
Ben Chan7e6fea02013-01-17 15:21:01 -080043
Anand K Mistry41a23962019-02-05 11:57:03 +110044namespace cros_disks {
45
Ben Chan7e6fea02013-01-17 15:21:01 -080046namespace {
47
Anand K Mistry238aae12019-01-22 15:02:46 +110048const char kFuseDeviceFile[] = "/dev/fuse";
Derek Basehorea057a972021-01-19 18:45:58 -080049const char kBaseFreezerCgroup[] = "/sys/fs/cgroup/freezer";
50const char kCgroupProcsFile[] = "cgroup.procs";
Sergei Datsenko3928f782020-12-31 09:14:04 +110051const int kFUSEMountFlags = MS_NODEV | MS_NOSUID | MS_NOEXEC | MS_DIRSYNC;
Anand K Mistry238aae12019-01-22 15:02:46 +110052
Anand K Mistrya24c75b2020-01-09 17:57:25 +110053class FUSEMountPoint : public MountPoint {
54 public:
Sergei Datsenko0ba12032021-01-07 08:51:14 +110055 using MountPoint::MountPoint;
Anand K Mistrya24c75b2020-01-09 17:57:25 +110056
Anand K Mistrycf5135f2020-01-21 10:48:54 +110057 base::WeakPtr<FUSEMountPoint> GetWeakPtr() {
58 return weak_factory_.GetWeakPtr();
59 }
60
Sergei Datsenko199f4f42020-10-08 10:47:12 +110061 static void CleanUpCallback(const base::FilePath& mount_path,
62 base::WeakPtr<FUSEMountPoint> ptr,
63 const siginfo_t& info) {
64 CHECK_EQ(SIGCHLD, info.si_signo);
65 if (info.si_code != CLD_EXITED) {
François Degrosc309d932021-02-04 15:12:30 +110066 LOG(WARNING) << "FUSE daemon for " << redact(mount_path)
Sergei Datsenko199f4f42020-10-08 10:47:12 +110067 << " crashed with code " << info.si_code << " and status "
68 << info.si_status;
69 } else if (info.si_status != 0) {
François Degrosc309d932021-02-04 15:12:30 +110070 LOG(WARNING) << "FUSE daemon for " << redact(mount_path)
Sergei Datsenko199f4f42020-10-08 10:47:12 +110071 << " exited with status " << info.si_status;
72 } else {
73 LOG(INFO) << "FUSE daemon for " << quote(mount_path)
74 << " exited normally";
75 }
76 if (!ptr) {
77 // If the MountPoint instance has been deleted, it was
78 // already unmounted and cleaned up due to a
79 // request from the browser (or logout). In this
80 // case, there's nothing to do.
81 // TODO(dats): Consolidate this logic into centralized place (likely
82 // MountPoint base class).
83 return;
84 }
85 ptr->CleanUp();
86 }
87
Anand K Mistrya24c75b2020-01-09 17:57:25 +110088 private:
Sergei Datsenko199f4f42020-10-08 10:47:12 +110089 void CleanUp() {
90 MountErrorType unmount_error = Unmount();
91 LOG_IF(ERROR, unmount_error != MOUNT_ERROR_NONE)
François Degrosc309d932021-02-04 15:12:30 +110092 << "Cannot unmount FUSE mount point " << redact(path())
Sergei Datsenko199f4f42020-10-08 10:47:12 +110093 << " after process exit: " << unmount_error;
94
95 if (!platform_->RemoveEmptyDirectory(path().value())) {
François Degrosc309d932021-02-04 15:12:30 +110096 PLOG(ERROR) << "Cannot remove FUSE mount point " << redact(path())
Sergei Datsenko199f4f42020-10-08 10:47:12 +110097 << " after process exit";
98 }
99 }
100
Anand K Mistrycf5135f2020-01-21 10:48:54 +1100101 base::WeakPtrFactory<FUSEMountPoint> weak_factory_{this};
Anand K Mistrya24c75b2020-01-09 17:57:25 +1100102};
103
Evan Green9075b842019-08-09 11:33:05 -0700104bool GetPhysicalBlockSize(const std::string& source, int* size) {
105 base::ScopedFD fd(open(source.c_str(), O_RDONLY | O_CLOEXEC));
106
107 *size = 0;
108 if (!fd.is_valid()) {
109 PLOG(WARNING) << "Couldn't open " << source;
110 return false;
111 }
112
113 if (ioctl(fd.get(), BLKPBSZGET, size) < 0) {
114 PLOG(WARNING) << "Failed to get block size for" << source;
115 return false;
116 }
117
118 return true;
119}
120
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100121} // namespace
122
Sergei Datsenkoadd282e2020-11-16 15:41:24 +1100123FUSESandboxedProcessFactory::FUSESandboxedProcessFactory(
124 const Platform* platform,
125 SandboxedExecutable executable,
126 OwnerUser run_as,
127 bool has_network_access,
128 std::vector<gid_t> supplementary_groups,
129 base::Optional<base::FilePath> mount_namespace)
130 : platform_(platform),
131 executable_(std::move(executable.executable)),
132 seccomp_policy_(std::move(executable.seccomp_policy)),
133 run_as_(std::move(run_as)),
134 has_network_access_(has_network_access),
135 supplementary_groups_(std::move(supplementary_groups)),
136 mount_namespace_(std::move(mount_namespace)) {
137 CHECK(executable_.IsAbsolute());
138 if (seccomp_policy_) {
139 CHECK(seccomp_policy_.value().IsAbsolute());
140 }
141 if (mount_namespace_) {
142 CHECK(mount_namespace_.value().IsAbsolute());
143 }
144}
145
146FUSESandboxedProcessFactory::~FUSESandboxedProcessFactory() = default;
147
148std::unique_ptr<SandboxedProcess>
149FUSESandboxedProcessFactory::CreateSandboxedProcess() const {
150 auto sandbox = std::make_unique<SandboxedProcess>();
151 if (!ConfigureSandbox(sandbox.get()))
152 return nullptr;
153 return sandbox;
154}
155
156bool FUSESandboxedProcessFactory::ConfigureSandbox(
157 SandboxedProcess* sandbox) const {
Derek Basehorea057a972021-01-19 18:45:58 -0800158 base::FilePath cgroup = base::FilePath(kBaseFreezerCgroup)
159 .Append(executable_.BaseName())
160 .Append(kCgroupProcsFile);
Sergei Datsenkoadd282e2020-11-16 15:41:24 +1100161 sandbox->SetCapabilities(0);
162 sandbox->SetNoNewPrivileges();
163
164 // The FUSE mount program is put under a new mount namespace, so mounts
165 // inside that namespace don't normally propagate.
166 sandbox->NewMountNamespace();
167 sandbox->SkipRemountPrivate();
168
169 // TODO(benchan): Re-enable cgroup namespace when either Chrome OS
170 // kernel 3.8 supports it or no more supported devices use kernel
171 // 3.8.
172 // mount_process.NewCgroupNamespace();
173
Derek Basehorea057a972021-01-19 18:45:58 -0800174 // Add the sandboxed process to its cgroup that should be setup. Return an
175 // error if it's not there.
176 if (!platform_->PathExists(cgroup.value())) {
177 LOG(ERROR) << "Freezer cgroup, " << cgroup << " is missing";
178 return MOUNT_ERROR_INTERNAL;
179 }
180
181 if (!sandbox->AddToCgroup(cgroup.value())) {
182 LOG(ERROR) << "Unable to add sandboxed process to cgroup " << cgroup;
183 return MOUNT_ERROR_INTERNAL;
184 }
185
Sergei Datsenkoadd282e2020-11-16 15:41:24 +1100186 sandbox->NewIpcNamespace();
187
188 sandbox->NewPidNamespace();
189
190 // Prepare mounts for pivot_root.
191 if (!sandbox->SetUpMinimalMounts()) {
192 LOG(ERROR) << "Cannot set up minijail mounts";
193 return false;
194 }
195
196 // /run is the place where mutable system configs are being kept.
197 // We don't expose them by default, but to be able to bind them when
198 // needed /run needs to be writeable.
199 if (!sandbox->Mount("tmpfs", "/run", "tmpfs", "mode=0755,size=1M")) {
200 LOG(ERROR) << "Cannot mount /run";
201 return false;
202 }
203
204 if (!has_network_access_) {
205 sandbox->NewNetworkNamespace();
206 } else {
207 // Network DNS configs are in /run/shill.
208 if (!sandbox->BindMount("/run/shill", "/run/shill", false, false)) {
209 LOG(ERROR) << "Cannot bind /run/shill";
210 return false;
211 }
212 // Hardcoded hosts are mounted into /etc/hosts.d when Crostini is enabled.
213 if (platform_->PathExists("/etc/hosts.d") &&
214 !sandbox->BindMount("/etc/hosts.d", "/etc/hosts.d", false, false)) {
215 LOG(ERROR) << "Cannot bind /etc/hosts.d";
216 return false;
217 }
218 }
219
220 if (!sandbox->EnterPivotRoot()) {
221 LOG(ERROR) << "Cannot pivot root";
222 return false;
223 }
224
225 if (seccomp_policy_) {
226 if (!platform_->PathExists(seccomp_policy_.value().value())) {
227 LOG(ERROR) << "Seccomp policy " << quote(seccomp_policy_.value())
228 << " is missing";
229 return false;
230 }
231 sandbox->LoadSeccompFilterPolicy(seccomp_policy_.value().value());
232 }
233
234 sandbox->SetUserId(run_as_.uid);
235 sandbox->SetGroupId(run_as_.gid);
236 if (!supplementary_groups_.empty()) {
237 sandbox->SetSupplementaryGroupIds(supplementary_groups_);
238 }
239
240 // Enter mount namespace in the sandbox if necessary.
241 if (mount_namespace_) {
242 sandbox->EnterExistingMountNamespace(mount_namespace_.value().value());
243 }
244
245 if (!platform_->PathExists(executable_.value())) {
246 LOG(ERROR) << "Cannot find mount program " << quote(executable_);
247 return false;
248 }
249 sandbox->AddArgument(executable_.value());
250
251 return true;
252}
253
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100254FUSEMounter::FUSEMounter(const Platform* platform,
255 brillo::ProcessReaper* process_reaper,
256 std::string filesystem_type,
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +1100257 Config config)
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100258 : platform_(platform),
259 process_reaper_(process_reaper),
260 filesystem_type_(std::move(filesystem_type)),
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +1100261 config_(std::move(config)) {}
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100262
263FUSEMounter::~FUSEMounter() = default;
264
265std::unique_ptr<MountPoint> FUSEMounter::Mount(
266 const std::string& source,
267 const base::FilePath& target_path,
268 std::vector<std::string> params,
269 MountErrorType* error) const {
270 // Read-only is the only parameter that has any effect at this layer.
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +1100271 const bool read_only = config_.read_only || IsReadOnlyMount(params);
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100272
273 const base::File fuse_file = base::File(
274 base::FilePath(kFuseDeviceFile),
275 base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE);
276 if (!fuse_file.IsValid()) {
277 LOG(ERROR) << "Unable to open FUSE device file. Error: "
278 << fuse_file.error_details() << " "
279 << base::File::ErrorToString(fuse_file.error_details());
280 *error = MOUNT_ERROR_INTERNAL;
281 return nullptr;
282 }
283
Sergei Datsenko6907a132019-04-01 11:26:56 +1100284 // Mount options for FUSE:
285 // fd - File descriptor for /dev/fuse.
286 // user_id/group_id - user/group for file access control. Essentially
287 // bypassed due to allow_other, but still required to be set.
288 // allow_other - Allows users other than user_id/group_id to access files
289 // on the file system. By default, FUSE prevents any process other than
290 // ones running under user_id/group_id to access files, regardless of
291 // the file's permissions.
292 // default_permissions - Enforce permission checking.
293 // rootmode - Mode bits for the root inode.
294 std::string fuse_mount_options = base::StringPrintf(
295 "fd=%d,user_id=%u,group_id=%u,allow_other,default_permissions,"
296 "rootmode=%o",
Sergei Datsenkoadd282e2020-11-16 15:41:24 +1100297 fuse_file.GetPlatformFile(), kChronosUID, kChronosAccessGID, S_IFDIR);
Sergei Datsenko6907a132019-04-01 11:26:56 +1100298
Sergei Datsenko6907a132019-04-01 11:26:56 +1100299 std::string fuse_type = "fuse";
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100300 std::string source_descr = source;
Sergei Datsenkoe2d1d0b2020-11-18 12:48:13 +1100301 base::stat_wrapper_t statbuf = {0};
302 if (platform_->Lstat(source, &statbuf) && S_ISBLK(statbuf.st_mode)) {
Evan Green9075b842019-08-09 11:33:05 -0700303 int blksize = 0;
304
305 // TODO(crbug.com/931500): It's possible that specifying a block size equal
306 // to the file system cluster size (which might be larger than the physical
307 // block size) might be more efficient. Data would be needed to see what
308 // kind of performance benefit, if any, could be gained. At the very least,
309 // specify the block size of the underlying device. Without this, UFS cards
310 // with 4k sector size will fail to mount.
311 if (GetPhysicalBlockSize(source, &blksize) && blksize > 0)
312 fuse_mount_options.append(base::StringPrintf(",blksize=%d", blksize));
313
François Degros8b4e31e2019-07-29 11:39:19 +1000314 LOG(INFO) << "Source file " << quote(source)
315 << " is a block device with block size " << blksize;
Evan Green9075b842019-08-09 11:33:05 -0700316
Sergei Datsenko6907a132019-04-01 11:26:56 +1100317 fuse_type = "fuseblk";
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100318 } else {
Sergei Datsenkoe2d1d0b2020-11-18 12:48:13 +1100319 source_descr = "fuse:" + source;
Sergei Datsenko6907a132019-04-01 11:26:56 +1100320 }
François Degros01913712020-09-08 17:33:54 +1000321
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100322 if (!filesystem_type_.empty()) {
Sam McNally38e5c232019-04-18 17:09:10 +1000323 fuse_type += ".";
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100324 fuse_type += filesystem_type_;
Sam McNally38e5c232019-04-18 17:09:10 +1000325 }
Sergei Datsenko6907a132019-04-01 11:26:56 +1100326
Sergei Datsenko0ba12032021-01-07 08:51:14 +1100327 MountPointData data = {
328 .mount_path = target_path,
329 .source = source_descr,
330 .filesystem_type = fuse_type,
331 .flags = kFUSEMountFlags | (read_only ? MS_RDONLY : 0) |
332 (config_.nosymfollow ? MS_NOSYMFOLLOW : 0),
333 .data = fuse_mount_options,
334 };
335 *error = platform_->Mount(data.source, data.mount_path.value(),
336 data.filesystem_type, data.flags, data.data);
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100337 if (*error != MOUNT_ERROR_NONE) {
338 LOG(ERROR) << "Cannot perform unprivileged FUSE mount: " << *error;
339 return nullptr;
340 }
341
342 pid_t pid =
343 StartDaemon(fuse_file, source, target_path, std::move(params), error);
344 if (*error != MOUNT_ERROR_NONE || pid == Process::kInvalidProcessId) {
345 LOG(ERROR) << "FUSE daemon start failure: " << *error;
346 LOG(INFO) << "FUSE cleanup on start failure for " << quote(target_path);
347 MountErrorType unmount_error =
348 platform_->Unmount(target_path.value(), MNT_FORCE | MNT_DETACH);
349 LOG_IF(ERROR, unmount_error != MOUNT_ERROR_NONE)
François Degrosc309d932021-02-04 15:12:30 +1100350 << "Cannot unmount FUSE mount point " << redact(target_path)
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100351 << " after launch failure: " << unmount_error;
352 return nullptr;
353 }
354
355 // At this point, the FUSE daemon has successfully started.
356 std::unique_ptr<FUSEMountPoint> mount_point =
Sergei Datsenko0ba12032021-01-07 08:51:14 +1100357 std::make_unique<FUSEMountPoint>(std::move(data), platform_);
Sergei Datsenko199f4f42020-10-08 10:47:12 +1100358
359 // Add a watcher that cleans up the FUSE mount when the process exits.
360 // This is defined as in-jail "init" process, denoted by pid(),
361 // terminates, which happens only when the last process in the jailed PID
362 // namespace terminates.
363 process_reaper_->WatchForChild(
364 FROM_HERE, pid,
365 base::BindOnce(&FUSEMountPoint::CleanUpCallback, target_path,
366 mount_point->GetWeakPtr()));
367
368 *error = MOUNT_ERROR_NONE;
369 return std::move(mount_point);
Sergei Datsenko6907a132019-04-01 11:26:56 +1100370}
371
Sergei Datsenko88035aa2020-11-15 00:24:01 +1100372pid_t FUSEMounter::StartDaemon(const base::File& fuse_file,
373 const std::string& source,
374 const base::FilePath& target_path,
375 std::vector<std::string> params,
376 MountErrorType* error) const {
377 auto mount_process =
378 PrepareSandbox(source, target_path, std::move(params), error);
379 if (*error != MOUNT_ERROR_NONE) {
380 return Process::kInvalidProcessId;
381 }
382
383 mount_process->AddArgument(
384 base::StringPrintf("/dev/fd/%d", fuse_file.GetPlatformFile()));
385
386 std::vector<std::string> output;
387 const int return_code = mount_process->Run(&output);
388 *error = InterpretReturnCode(return_code);
389
390 if (*error != MOUNT_ERROR_NONE) {
391 const auto& executable = mount_process->arguments()[0];
392 if (!output.empty()) {
393 LOG(ERROR) << "FUSE mount program " << quote(executable) << " outputted "
394 << output.size() << " lines:";
395 for (const std::string& line : output) {
396 LOG(ERROR) << line;
397 }
398 }
399 LOG(ERROR) << "FUSE mount program " << quote(executable)
400 << " returned error code " << return_code;
401 return Process::kInvalidProcessId;
402 }
403
404 return mount_process->pid();
405}
406
407MountErrorType FUSEMounter::InterpretReturnCode(int return_code) const {
408 if (return_code != 0)
409 return MOUNT_ERROR_MOUNT_PROGRAM_FAILED;
410 return MOUNT_ERROR_NONE;
411}
412
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100413FUSEMounterHelper::FUSEMounterHelper(
414 const Platform* platform,
415 brillo::ProcessReaper* process_reaper,
416 std::string filesystem_type,
417 bool nosymfollow,
418 const SandboxedProcessFactory* sandbox_factory)
Sergei Datsenko1d2cbf82020-12-08 21:54:42 +1100419 : FUSEMounter(platform,
420 process_reaper,
421 std::move(filesystem_type),
422 {.nosymfollow = nosymfollow}),
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100423 sandbox_factory_(sandbox_factory) {}
424
425FUSEMounterHelper::~FUSEMounterHelper() = default;
426
427std::unique_ptr<SandboxedProcess> FUSEMounterHelper::PrepareSandbox(
428 const std::string& source,
429 const base::FilePath& target_path,
430 std::vector<std::string> params,
431 MountErrorType* error) const {
432 auto sandbox = sandbox_factory_->CreateSandboxedProcess();
Sergei Datsenkob99344b2020-12-31 08:35:12 +1100433 if (!sandbox) {
434 *error = MOUNT_ERROR_INTERNAL;
435 return nullptr;
436 }
Sergei Datsenkof5553d12020-11-25 07:51:59 +1100437 *error =
438 ConfigureSandbox(source, target_path, std::move(params), sandbox.get());
439 if (*error != MOUNT_ERROR_NONE) {
440 return nullptr;
441 }
442 return sandbox;
443}
444
Ben Chan7e6fea02013-01-17 15:21:01 -0800445} // namespace cros_disks