blob: 027656815b2ea807f0995cbeebdc0f376df332b3 [file] [log] [blame]
// 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 <utility>
#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/string_util.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/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 kOldUser[] = "fuse-drivefs";
const char kHelperTool[] = "/opt/google/drive-file-stream/drivefs";
const char kSeccompPolicyFile[] =
"/opt/google/drive-file-stream/drivefs-seccomp.policy";
const char kType[] = "drivefs";
const char kDbusSocketPath[] = "/run/dbus";
// The deepest expected path within a DriveFS datadir is
// {content,thumbnails}_cache/d<number>/d<number>/<number>. Allow one extra
// level just in case.
constexpr int kMaxTraversalDepth = 5;
// Ensures that the datadir has the correct owner. If not, recursively chown the
// contents, skipping directories with the expected owner. During directory
// descent, set group to the eventual group to allow directory traversal to
// allow access to the contents. On ascent, set user to the expected user,
// marking the directory as having the correct ownership.
bool EnsureOwnership(const Platform& platform,
const base::FilePath& path,
uid_t mounter_uid,
gid_t files_gid,
uid_t old_mounter_uid,
int depth = 0) {
if (depth > kMaxTraversalDepth) {
LOG(ERROR) << "Reached maximum traversal depth ensuring drivefs datadir "
"ownership: "
<< path.value();
return false;
}
uid_t current_uid;
gid_t current_gid;
if (!platform.GetOwnership(path.value(), &current_uid, &current_gid)) {
LOG(ERROR) << "Cannot access datadir " << quote(path);
return false;
}
if (current_uid == mounter_uid && current_gid == files_gid) {
return true;
}
if (current_uid != old_mounter_uid) {
LOG(ERROR) << "Unexpected old uid for " << quote(path) << ": Expected "
<< old_mounter_uid << " but found " << current_uid;
return false;
}
// Set group to |files_gid| to ensure the directory is traversable. Keep the
// |current_uid| so this directory isn't treated as having the correct
// ownership in case this operation is interrupted.
if (!platform.SetOwnership(path.value(), current_uid, files_gid)) {
LOG(ERROR) << "Cannot chown " << quote(path) << " to " << current_uid << ":"
<< files_gid;
return false;
}
base::FileEnumerator dirs(path, false, base::FileEnumerator::DIRECTORIES);
for (auto dir_path = dirs.Next(); !dir_path.empty(); dir_path = dirs.Next()) {
if (!EnsureOwnership(platform, dir_path, mounter_uid, files_gid,
old_mounter_uid, depth + 1)) {
return false;
}
}
base::FileEnumerator files(path, false, base::FileEnumerator::FILES);
for (auto file_path = files.Next(); !file_path.empty();
file_path = files.Next()) {
if (!platform.SetOwnership(file_path.value(), mounter_uid, files_gid)) {
LOG(ERROR) << "Cannot chown " << quote(file_path) << " to " << mounter_uid
<< ":" << files_gid;
return false;
}
}
if (!platform.SetOwnership(path.value(), mounter_uid, files_gid)) {
LOG(ERROR) << "Cannot chown " << quote(path) << " to " << mounter_uid << ":"
<< files_gid;
return false;
}
return true;
}
class DrivefsMounter : public FUSEMounter {
public:
DrivefsMounter(std::string filesystem_type,
MountOptions mount_options,
const Platform* platform,
brillo::ProcessReaper* process_reaper,
std::string mount_program,
std::string mount_user,
std::string seccomp_policy,
BindPaths bind_paths)
: FUSEMounter({.bind_paths = std::move(bind_paths),
.filesystem_type = std::move(filesystem_type),
.mount_options = std::move(mount_options),
.mount_program = std::move(mount_program),
.mount_user = std::move(mount_user),
.network_access = true,
.platform = platform,
.process_reaper = process_reaper,
.seccomp_policy = seccomp_policy}) {}
// FUSEMounter overrides:
std::unique_ptr<MountPoint> Mount(const std::string& source,
const base::FilePath& target_path,
std::vector<std::string> options,
MountErrorType* error) const override {
return FUSEMounter::Mount("", target_path, options, error);
}
};
} // namespace
DrivefsHelper::DrivefsHelper(const Platform* platform,
brillo::ProcessReaper* process_reaper)
: FUSEHelper(kType,
platform,
process_reaper,
base::FilePath(kHelperTool),
kFilesUser) {}
DrivefsHelper::~DrivefsHelper() = default;
std::unique_ptr<FUSEMounter> DrivefsHelper::CreateMounter(
const base::FilePath& working_dir,
const Uri& source,
const base::FilePath& target_path,
const std::vector<std::string>& options) const {
const std::string& identity = source.path();
// Enforced by FUSEHelper::CanMount().
DCHECK(!identity.empty());
auto data_dir = GetValidatedDirectory(options, kDataDirOptionPrefix);
if (data_dir.empty()) {
return nullptr;
}
uid_t files_uid;
gid_t files_gid;
if (!platform()->GetUserAndGroupId(kFilesUser, &files_uid, nullptr) ||
!platform()->GetGroupId(kFilesGroup, &files_gid)) {
LOG(ERROR) << "Invalid user configuration.";
return nullptr;
}
auto my_files_path = GetValidatedDirectory(options, kMyFilesOptionPrefix);
if (!my_files_path.empty() && !CheckMyFilesPermissions(my_files_path)) {
return nullptr;
}
if (!SetupDirectoryForFUSEAccess(data_dir)) {
return nullptr;
}
MountOptions mount_options;
mount_options.EnforceOption(kDataDirOptionPrefix + data_dir.value());
mount_options.EnforceOption(kIdentityOptionPrefix + identity);
mount_options.EnforceOption(kPathPrefixOptionPrefix + target_path.value());
if (!my_files_path.empty()) {
mount_options.EnforceOption(kMyFilesOptionPrefix + my_files_path.value());
}
mount_options.Initialize(options, true, base::NumberToString(files_uid),
base::NumberToString(files_gid));
// TODO(crbug.com/859802): Make seccomp mandatory when testing done.
std::string seccomp =
platform()->PathExists(kSeccompPolicyFile) ? kSeccompPolicyFile : "";
// Bind datadir and DBus communication socket into the sandbox.
FUSEMounter::BindPaths paths = {{.path = data_dir.value(), .writable = true},
{.path = kDbusSocketPath, .writable = true}};
if (!my_files_path.empty()) {
paths.push_back(
{.path = my_files_path.value(), .writable = true, .recursive = true});
}
return std::make_unique<DrivefsMounter>(
type(), mount_options, platform(), process_reaper(),
program_path().value(), user(), seccomp, paths);
}
base::FilePath DrivefsHelper::GetValidatedDirectory(
const std::vector<std::string>& options,
const base::StringPiece prefix) const {
for (const auto& option : options) {
if (base::StartsWith(option, prefix, base::CompareCase::SENSITIVE)) {
std::string path_string = option.substr(prefix.size());
base::FilePath data_dir(path_string);
if (data_dir.empty() || !data_dir.IsAbsolute() ||
data_dir.ReferencesParent()) {
LOG(ERROR) << "Invalid DriveFS option " << prefix << path_string;
return {};
}
base::FilePath suffix_component;
// If the datadir doesn't exist, canonicalize the parent directory
// instead, and append the last path component to that path.
if (!platform()->DirectoryExists(data_dir.value())) {
suffix_component = data_dir.BaseName();
data_dir = data_dir.DirName();
}
if (!platform()->GetRealPath(data_dir.value(), &path_string)) {
return {};
}
return base::FilePath(path_string).Append(suffix_component);
}
}
return {};
}
bool DrivefsHelper::SetupDirectoryForFUSEAccess(
const base::FilePath& dir) const {
CHECK(dir.IsAbsolute() && !dir.ReferencesParent())
<< "Unsafe path " << quote(dir);
uid_t mounter_uid, old_mounter_uid;
gid_t files_gid;
if (!platform()->GetUserAndGroupId(user(), &mounter_uid, nullptr) ||
!platform()->GetUserAndGroupId(kOldUser, &old_mounter_uid, nullptr) ||
!platform()->GetGroupId(kFilesGroup, &files_gid)) {
LOG(ERROR) << "Invalid user configuration.";
return false;
}
std::string path = dir.value();
if (platform()->DirectoryExists(path)) {
return EnsureOwnership(*platform(), dir, mounter_uid, files_gid,
old_mounter_uid);
}
if (!platform()->CreateDirectory(path)) {
LOG(ERROR) << "Cannot create datadir " << quote(path);
return false;
}
if (!platform()->SetPermissions(path, 0770)) {
LOG(ERROR) << "Cannot chmod datadir " << quote(path);
return false;
}
if (!platform()->SetOwnership(path, mounter_uid, files_gid)) {
LOG(ERROR) << "Cannot chown datadir " << quote(path);
return false;
}
return true;
}
bool DrivefsHelper::CheckMyFilesPermissions(const base::FilePath& dir) const {
CHECK(dir.IsAbsolute() && !dir.ReferencesParent())
<< "Unsafe 'My Files' path " << quote(dir);
uid_t mounter_uid;
if (!platform()->GetUserAndGroupId(user(), &mounter_uid, nullptr)) {
LOG(ERROR) << "Invalid user configuration.";
return false;
}
std::string path = dir.value();
if (!platform()->DirectoryExists(path)) {
LOG(ERROR) << "My files directory " << quote(path) << " does not exist";
return false;
}
uid_t current_uid;
if (!platform()->GetOwnership(path, &current_uid, nullptr)) {
LOG(WARNING) << "Cannot access my files directory " << quote(path);
return false;
}
if (current_uid != mounter_uid) {
LOG(ERROR) << "Incorrect owner for my files directory " << quote(path);
return false;
}
return true;
}
} // namespace cros_disks