| // Copyright 2018 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/drivefs_helper.h" |
| |
| #include <stdlib.h> |
| #include <sys/wait.h> |
| #include <sysexits.h> |
| #include <unistd.h> |
| |
| #include <utility> |
| |
| #include <base/check.h> |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_path.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/strings/string_util.h> |
| #include <brillo/files/safe_fd.h> |
| |
| #include "cros-disks/fuse_mounter.h" |
| #include "cros-disks/mount_options.h" |
| #include "cros-disks/platform.h" |
| #include "cros-disks/quote.h" |
| #include "cros-disks/sandboxed_process.h" |
| #include "cros-disks/system_mounter.h" |
| #include "cros-disks/uri.h" |
| |
| namespace cros_disks { |
| namespace { |
| |
| const char kDataDirOptionPrefix[] = "datadir"; |
| const char kIdentityOptionPrefix[] = "identity"; |
| const char kMyFilesOptionPrefix[] = "myfiles"; |
| const char kPathPrefixOptionPrefix[] = "prefix"; |
| |
| const char kHelperTool[] = "/opt/google/drive-file-stream/drivefs"; |
| const char kType[] = "drivefs"; |
| const char kDbusSocketPath[] = "/run/dbus"; |
| const char kHomeBaseDir[] = "/home"; |
| |
| // UID of fuse-drivefs user. |
| constexpr uid_t kOldDriveUID = 304; |
| |
| bool FindPathOption(const std::vector<std::string>& options, |
| const std::string& name, |
| base::FilePath* path) { |
| std::string value; |
| if (!GetParamValue(options, name, &value) || value.empty()) { |
| return false; |
| } |
| *path = base::FilePath(value); |
| return true; |
| } |
| |
| error_t RemoveDirectory(brillo::SafeFD* parent, const base::FilePath& name) { |
| if (setresuid(kOldDriveUID, kOldDriveUID, kOldDriveUID) != 0) { |
| return errno; |
| } |
| |
| // This function accounts for various gotchas like filesystem boundaries, |
| // types, etc. If we were to chown the dir we'd have to replicate that |
| // traversal approach. But for now just nuke the dir. |
| brillo::SafeFD::Error error = parent->Rmdir(name.value(), |
| /*recursive=*/true, |
| /*max_depth=*/7, |
| /*keep_going=*/false); |
| switch (error) { |
| case brillo::SafeFD::Error::kNoError: |
| return 0; |
| case brillo::SafeFD::Error::kIOError: |
| case brillo::SafeFD::Error::kWrongType: |
| return errno; |
| case brillo::SafeFD::Error::kBoundaryDetected: |
| return EXDEV; |
| default: |
| return EINVAL; |
| } |
| } |
| |
| bool FixDirectory(const base::FilePath& path) { |
| brillo::SafeFD::SafeFDResult root = brillo::SafeFD::Root(); |
| if (brillo::SafeFD::IsError(root.second)) { |
| PLOG(ERROR) << "Failed to open root dir: " << static_cast<int>(root.second); |
| return false; |
| } |
| brillo::SafeFD::SafeFDResult parent = root.first.OpenExistingDir( |
| path.DirName(), O_PATH | O_CLOEXEC | O_NONBLOCK | O_NOFOLLOW); |
| if (brillo::SafeFD::IsError(parent.second)) { |
| PLOG(ERROR) << "Failed to open parent dir: " |
| << static_cast<int>(parent.second); |
| return false; |
| } |
| |
| // Permission on the datadir won't allow cros-disks to manipulate it |
| // directly, chowning it based off path is unsafe, and fchown requires |
| // an FD, which we can't obtain because permissions, so instead we fork |
| // and change child's UID to freely navigate the directory tree. |
| pid_t pid = fork(); |
| if (pid == -1) { |
| PLOG(ERROR) << "Failed to fork"; |
| return false; |
| } else if (pid == 0) { |
| _exit(RemoveDirectory(&parent.first, path.BaseName())); |
| } else { |
| int wstatus; |
| PCHECK(pid == waitpid(pid, &wstatus, 0)); |
| int status = EXIT_FAILURE; |
| if (WIFEXITED(wstatus)) { |
| status = WEXITSTATUS(wstatus); |
| errno = status; |
| } |
| if (status != EXIT_SUCCESS) { |
| PLOG(ERROR) << "Cannot remove datadir " << quote(path); |
| return false; |
| } |
| } |
| |
| brillo::SafeFD::SafeFDResult datadir = |
| parent.first.MakeDir(path.BaseName(), S_IRWXU | S_IRWXG | S_ISGID, |
| kChronosUID, kChronosAccessGID); |
| |
| if (brillo::SafeFD::IsError(datadir.second)) { |
| PLOG(ERROR) << "Cannot create datadir " << quote(path); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ValidateDirectory(const Platform* platform, |
| base::FilePath* dir, |
| bool fix_non_compliant) { |
| if (dir->empty() || !dir->IsAbsolute() || dir->ReferencesParent()) { |
| LOG(ERROR) << "Unsafe path " << quote(*dir); |
| return false; |
| } |
| std::string path_string; |
| if (!platform->GetRealPath(dir->value(), &path_string)) { |
| LOG(ERROR) << "Unable to find real path of " << quote(*dir); |
| // TODO(crbug.com/1205308): Remove extra logging when root cause found. |
| LOG(ERROR) << "Checking ancestors:"; |
| for (base::FilePath p = *dir; p != p.DirName(); p = p.DirName()) { |
| if (!platform->GetRealPath(p.value(), &path_string)) { |
| LOG(ERROR) << "Unable to find real path of " << quote(p); |
| } |
| if (!platform->DirectoryExists(p.value())) { |
| LOG(ERROR) << "Dir does not exist " << quote(p); |
| continue; |
| } |
| uid_t uid; |
| gid_t gid; |
| if (platform->GetOwnership(p.value(), &uid, &gid)) { |
| LOG(ERROR) << "Path " << quote(p) << " has owner " << uid << ":" << gid; |
| } |
| mode_t mode; |
| if (platform->GetPermissions(p.value(), &mode)) { |
| LOG(ERROR) << "Path " << quote(p) << " has mode " << std::oct << mode; |
| } |
| } |
| return false; |
| } |
| *dir = base::FilePath(path_string); |
| |
| CHECK(dir->IsAbsolute() && !dir->ReferencesParent()); |
| |
| if (!platform->DirectoryExists(dir->value())) { |
| LOG(ERROR) << "Dir does not exist " << quote(*dir); |
| return false; |
| } |
| |
| uid_t current_uid; |
| if (!platform->GetOwnership(dir->value(), ¤t_uid, nullptr)) { |
| LOG(ERROR) << "Cannot access datadir " << quote(*dir); |
| return false; |
| } |
| |
| if (current_uid != kChronosUID) { |
| if (!fix_non_compliant || current_uid != kOldDriveUID) { |
| LOG(ERROR) << "Wrong owner of datadir: " << current_uid; |
| return false; |
| } |
| |
| LOG(WARNING) << "Unmigrated drivefs datadir detected"; |
| if (!FixDirectory(*dir)) { |
| LOG(ERROR) << "Could not repair drivefs datadir ownership"; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| DrivefsHelper::DrivefsHelper(const Platform* platform, |
| brillo::ProcessReaper* process_reaper) |
| : FUSEMounterHelper(platform, |
| process_reaper, |
| kType, |
| /* nosymfollow= */ false, |
| &sandbox_factory_), |
| sandbox_factory_(platform, |
| SandboxedExecutable{base::FilePath(kHelperTool)}, |
| OwnerUser{kChronosUID, kChronosGID}, |
| /* has_network_access= */ true) {} |
| |
| DrivefsHelper::~DrivefsHelper() = default; |
| |
| bool DrivefsHelper::CanMount(const std::string& source, |
| const std::vector<std::string>& params, |
| base::FilePath* suggested_name) const { |
| const Uri uri = Uri::Parse(source); |
| if (!uri.valid() || uri.scheme() != kType) |
| return false; |
| |
| if (uri.path().empty()) |
| *suggested_name = base::FilePath(kType); |
| else |
| *suggested_name = base::FilePath(uri.path()); |
| return true; |
| } |
| |
| MountErrorType DrivefsHelper::ConfigureSandbox( |
| const std::string& source, |
| const base::FilePath& target_path, |
| std::vector<std::string> params, |
| SandboxedProcess* sandbox) const { |
| const Uri uri = Uri::Parse(source); |
| if (!uri.valid() || uri.scheme() != kType) { |
| LOG(ERROR) << "Invalid source format " << quote(source); |
| return MOUNT_ERROR_INVALID_DEVICE_PATH; |
| } |
| if (uri.path().empty()) { |
| LOG(ERROR) << "Invalid source " << quote(source); |
| return MOUNT_ERROR_INVALID_DEVICE_PATH; |
| } |
| |
| base::FilePath data_dir; |
| if (!FindPathOption(params, kDataDirOptionPrefix, &data_dir)) { |
| LOG(ERROR) << "No data directory provided"; |
| return MOUNT_ERROR_INVALID_MOUNT_OPTIONS; |
| } |
| if (!ValidateDirectory(platform(), &data_dir, true)) { |
| return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS; |
| } |
| |
| const base::FilePath homedir(kHomeBaseDir); |
| if (!homedir.IsParent(data_dir)) { |
| LOG(ERROR) << "Unexpected location of " << quote(data_dir); |
| return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS; |
| } |
| |
| base::FilePath my_files; |
| if (FindPathOption(params, kMyFilesOptionPrefix, &my_files)) { |
| if (!ValidateDirectory(platform(), &my_files, false)) { |
| LOG(ERROR) << "User files inaccessible"; |
| return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS; |
| } |
| if (!homedir.IsParent(my_files)) { |
| LOG(ERROR) << "Unexpected location of " << quote(my_files); |
| return MOUNT_ERROR_INSUFFICIENT_PERMISSIONS; |
| } |
| } |
| |
| // Bind datadir, user files and DBus communication socket into the sandbox. |
| if (!sandbox->Mount("tmpfs", "/home", "tmpfs", "mode=0755,size=1M")) { |
| LOG(ERROR) << "Cannot mount /home"; |
| return MOUNT_ERROR_INTERNAL; |
| } |
| if (!sandbox->BindMount(data_dir.value(), data_dir.value(), true, false)) { |
| LOG(ERROR) << "Cannot bind " << quote(data_dir); |
| return MOUNT_ERROR_INTERNAL; |
| } |
| if (!sandbox->BindMount(kDbusSocketPath, kDbusSocketPath, true, false)) { |
| LOG(ERROR) << "Cannot bind " << quote(kDbusSocketPath); |
| return MOUNT_ERROR_INTERNAL; |
| } |
| if (!my_files.empty()) { |
| if (!sandbox->BindMount(my_files.value(), my_files.value(), true, true)) { |
| LOG(ERROR) << "Cannot bind " << quote(my_files); |
| return MOUNT_ERROR_INTERNAL; |
| } |
| } |
| |
| // Sandboxed processes have their own tmpfs mount, but this mount is too small |
| // for certain sqlite operations that DriveFS does. Tell DriveFS to use the |
| // datadir for sqlite temporary file storage instead. |
| sandbox->AddEnvironmentVariable("SQLITE_TMPDIR", data_dir.value()); |
| |
| std::vector<std::string> args; |
| SetParamValue(&args, "uid", base::NumberToString(kChronosUID)); |
| SetParamValue(&args, "gid", base::NumberToString(kChronosAccessGID)); |
| SetParamValue(&args, kDataDirOptionPrefix, data_dir.value()); |
| SetParamValue(&args, kIdentityOptionPrefix, uri.path()); |
| SetParamValue(&args, kPathPrefixOptionPrefix, target_path.value()); |
| if (!my_files.empty()) { |
| SetParamValue(&args, kMyFilesOptionPrefix, my_files.value()); |
| } |
| std::string options; |
| if (!JoinParamsIntoOptions(args, &options)) { |
| return MOUNT_ERROR_INVALID_MOUNT_OPTIONS; |
| } |
| sandbox->AddArgument("-o"); |
| sandbox->AddArgument(options); |
| |
| return MOUNT_ERROR_NONE; |
| } |
| |
| } // namespace cros_disks |