blob: 22db1b416a6488ed6b9906d1e9656f1aee32c889 [file] [log] [blame]
// Copyright 2019 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 "cryptohome/storage/mount_helper.h"
#include <sys/stat.h>
#include <memory>
#include <vector>
#include <base/bind.h>
#include <base/callback_helpers.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <brillo/cryptohome.h>
#include <brillo/secure_blob.h>
#include "cryptohome/cryptohome_common.h"
#include "cryptohome/filesystem_layout.h"
#include "cryptohome/storage/homedirs.h"
#include "cryptohome/storage/mount_constants.h"
using base::FilePath;
using base::StringPrintf;
using brillo::cryptohome::home::GetRootPath;
using brillo::cryptohome::home::GetUserPath;
using brillo::cryptohome::home::SanitizeUserName;
using brillo::cryptohome::home::SanitizeUserNameWithSalt;
namespace cryptohome {
const char kEphemeralCryptohomeRootContext[] =
"u:object_r:cros_home_shadow_uid:s0";
}
namespace {
constexpr uid_t kMountOwnerUid = 0;
constexpr gid_t kMountOwnerGid = 0;
constexpr gid_t kDaemonStoreGid = 400;
const int kDefaultEcryptfsKeySize = CRYPTOHOME_AES_KEY_BYTES;
FilePath GetUserEphemeralMountDirectory(
const std::string& obfuscated_username) {
return FilePath(cryptohome::kEphemeralCryptohomeDir)
.Append(cryptohome::kEphemeralMountDir)
.Append(obfuscated_username);
}
FilePath GetMountedEphemeralRootHomePath(
const std::string& obfuscated_username) {
return GetUserEphemeralMountDirectory(obfuscated_username)
.Append(cryptohome::kRootHomeSuffix);
}
FilePath GetMountedEphemeralUserHomePath(
const std::string& obfuscated_username) {
return GetUserEphemeralMountDirectory(obfuscated_username)
.Append(cryptohome::kUserHomeSuffix);
}
FilePath VaultPathToUserPath(const FilePath& vault) {
return vault.Append(cryptohome::kUserHomeSuffix);
}
FilePath VaultPathToRootPath(const FilePath& vault) {
return vault.Append(cryptohome::kRootHomeSuffix);
}
// Sets up the SELinux context for a freshly mounted ephemeral cryptohome.
bool SetUpSELinuxContextForEphemeralCryptohome(cryptohome::Platform* platform,
const FilePath& source_path) {
// Note that this is needed because the newly mounted ephemeral cryptohome is
// a new file system, and thus the SELinux context that applies to the
// mountpoint will not apply to the new root directory in the filesystem.
return platform->SetSELinuxContext(
source_path, cryptohome::kEphemeralCryptohomeRootContext);
}
constexpr mode_t kSkeletonSubDirMode = S_IRWXU | S_IRGRP | S_IXGRP;
constexpr mode_t kUserMountPointMode = S_IRWXU | S_IRGRP | S_IXGRP;
constexpr mode_t kRootMountPointMode = S_IRWXU;
constexpr mode_t kAccessMode = S_IRWXU | S_IRGRP | S_IXGRP;
constexpr mode_t kRootDirMode = S_IRWXU | S_IRWXG | S_ISVTX;
constexpr mode_t kTrackedDirMode = S_IRWXU;
constexpr mode_t kPathComponentDirMode = S_IRWXU;
constexpr mode_t kGroupExecAccess = S_IXGRP;
constexpr mode_t kGroupWriteAccess = S_IWGRP;
} // namespace
namespace cryptohome {
const char kDefaultHomeDir[] = "/home/chronos/user";
std::vector<DirectoryACL> MountHelper::GetCommonSubdirectories(
uid_t uid, gid_t gid, gid_t access_gid) {
return std::vector<DirectoryACL>{
{FilePath(kUserHomeSuffix).Append(kDownloadsDir), kAccessMode, uid,
access_gid},
{FilePath(kUserHomeSuffix).Append(kMyFilesDir), kAccessMode, uid,
access_gid},
{FilePath(kUserHomeSuffix).Append(kMyFilesDir).Append(kDownloadsDir),
kAccessMode, uid, access_gid},
};
}
std::vector<DirectoryACL> MountHelper::GetCacheSubdirectories(
uid_t uid, gid_t gid, gid_t access_gid) {
return std::vector<DirectoryACL>{
{FilePath(kUserHomeSuffix).Append(kGCacheDir), kAccessMode, uid,
access_gid},
{FilePath(kUserHomeSuffix).Append(kCacheDir), kTrackedDirMode, uid, gid},
};
}
std::vector<DirectoryACL> MountHelper::GetGCacheSubdirectories(uid_t uid,
gid_t gid,
gid_t access_gid,
bool v1_dirs) {
DirectoryACL gcache_v2_subdir = {
FilePath(kUserHomeSuffix).Append(kGCacheDir).Append(kGCacheVersion2Dir),
kAccessMode | kGroupWriteAccess, uid, access_gid};
std::vector<DirectoryACL> gcache_v1_subdirs = {
{FilePath(kUserHomeSuffix).Append(kGCacheDir).Append(kGCacheVersion1Dir),
kAccessMode, uid, access_gid},
{FilePath(kUserHomeSuffix)
.Append(kGCacheDir)
.Append(kGCacheVersion1Dir)
.Append(kGCacheBlobsDir),
kTrackedDirMode, uid, gid},
{FilePath(kUserHomeSuffix)
.Append(kGCacheDir)
.Append(kGCacheVersion1Dir)
.Append(kGCacheTmpDir),
kTrackedDirMode, uid, gid},
};
if (v1_dirs) {
gcache_v1_subdirs.push_back(gcache_v2_subdir);
return gcache_v1_subdirs;
}
return {gcache_v2_subdir};
}
std::vector<DirectoryACL> MountHelper::GetTrackedSubdirectories(
uid_t uid, gid_t gid, gid_t access_gid) {
std::vector<DirectoryACL> durable_only_subdirs{
{FilePath(kRootHomeSuffix), kRootDirMode, kMountOwnerUid,
kDaemonStoreGid},
{FilePath(kUserHomeSuffix), kTrackedDirMode | kGroupExecAccess, uid,
access_gid},
};
std::vector<DirectoryACL> common_subdirs =
GetCommonSubdirectories(uid, gid, access_gid);
std::vector<DirectoryACL> cache_subdirs =
GetCacheSubdirectories(uid, gid, access_gid);
std::vector<DirectoryACL> gcache_subdirs =
GetGCacheSubdirectories(uid, gid, access_gid, /*v1_dirs=*/true);
auto result = durable_only_subdirs;
result.insert(result.end(), common_subdirs.begin(), common_subdirs.end());
result.insert(result.end(), cache_subdirs.begin(), cache_subdirs.end());
result.insert(result.end(), gcache_subdirs.begin(), gcache_subdirs.end());
return result;
}
// static
FilePath MountHelper::GetNewUserPath(const std::string& username) {
std::string sanitized = SanitizeUserName(username);
std::string user_dir = StringPrintf("u-%s", sanitized.c_str());
return FilePath("/home")
.Append(cryptohome::kDefaultSharedUser)
.Append(user_dir);
}
// static
FilePath MountHelper::GetEphemeralSparseFile(
const std::string& obfuscated_username) {
return FilePath(cryptohome::kEphemeralCryptohomeDir)
.Append(kSparseFileDir)
.Append(obfuscated_username);
}
FilePath MountHelper::GetMountedUserHomePath(
const std::string& obfuscated_username) const {
return GetUserMountDirectory(obfuscated_username).Append(kUserHomeSuffix);
}
FilePath MountHelper::GetMountedRootHomePath(
const std::string& obfuscated_username) const {
return GetUserMountDirectory(obfuscated_username).Append(kRootHomeSuffix);
}
bool MountHelper::EnsurePathComponent(const FilePath& check_path,
uid_t uid,
gid_t gid) const {
base::stat_wrapper_t st;
if (!platform_->Stat(check_path, &st)) {
// Dirent not there, so create and set ownership.
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
check_path, kPathComponentDirMode, uid, gid)) {
PLOG(ERROR) << "Can't create: " << check_path.value();
return false;
}
} else {
// Dirent there; make sure it's acceptable.
if (!S_ISDIR(st.st_mode)) {
LOG(ERROR) << "Non-directory path: " << check_path.value();
return false;
}
if (st.st_uid != uid) {
LOG(ERROR) << "Owner mismatch: " << check_path.value() << " " << st.st_uid
<< " != " << uid;
return false;
}
if (st.st_gid != gid) {
LOG(ERROR) << "Group mismatch: " << check_path.value() << " " << st.st_gid
<< " != " << gid;
return false;
}
if (st.st_mode & S_IWOTH) {
LOG(ERROR) << "Permissions too lenient: " << check_path.value() << " has "
<< std::oct << st.st_mode;
return false;
}
}
return true;
}
void MountHelper::CreateHomeSubdirectories(const FilePath& vault_path) const {
FilePath user_path(VaultPathToUserPath(vault_path));
FilePath root_path(VaultPathToRootPath(vault_path));
base::stat_wrapper_t st;
// This check makes the creation idempotent; if we completed creation,
// root_path will exist and we're done, and if we didn't complete it, we can
// finish it.
if (platform_->Stat(root_path, &st) && S_ISDIR(st.st_mode) &&
st.st_mode & S_ISVTX && st.st_uid == kMountOwnerUid &&
st.st_gid == kDaemonStoreGid) {
return;
}
// There are three ways to get here:
// 1) the Stat() call above succeeded, but what we saw was not a root-owned
// directory.
// 2) the Stat() call above failed with -ENOENT
// 3) the Stat() call above failed for some other reason
// In any of these cases, it is safe for us to rm root_path, since the only
// way it could have gotten there is if someone undertook some funny business
// as root.
platform_->DeletePathRecursively(root_path);
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
user_path, kTrackedDirMode | kGroupExecAccess, default_uid_,
default_access_gid_)) {
PLOG(ERROR) << "SafeCreateDirAndSetOwnershipAndPermissions() failed: "
<< user_path.value();
return;
}
// Create root_path at the end as a sentinel for migration.
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
root_path, kRootDirMode, kMountOwnerUid, kDaemonStoreGid)) {
PLOG(ERROR) << "SafeCreateDirAndSetOwnershipAndPermissions() failed: "
<< root_path.value();
return;
}
LOG(INFO) << "Created user directory: " << vault_path.value();
}
bool MountHelper::EnsureMountPointPath(const FilePath& dir) const {
std::vector<std::string> path_parts;
dir.GetComponents(&path_parts);
FilePath check_path(path_parts[0]);
if (path_parts[0] != "/") {
return false;
}
for (size_t i = 1; i < path_parts.size(); i++) {
check_path = check_path.Append(path_parts[i]);
if (!EnsurePathComponent(check_path, kMountOwnerUid, kMountOwnerGid)) {
return false;
}
}
return true;
}
bool MountHelper::EnsureUserMountPoints(const std::string& username) const {
FilePath multi_home_user = GetUserPath(username);
FilePath multi_home_root = GetRootPath(username);
FilePath new_user_path = GetNewUserPath(username);
if (platform_->DirectoryExists(multi_home_user) &&
(platform_->IsDirectoryMounted(multi_home_user) ||
!platform_->DeleteFile(multi_home_user))) {
LOG(ERROR) << "Failed to remove mount point: " << multi_home_user.value();
return false;
}
if (platform_->DirectoryExists(multi_home_root) &&
(platform_->IsDirectoryMounted(multi_home_root) ||
!platform_->DeleteFile(multi_home_root))) {
LOG(ERROR) << "Failed to remove mount point: " << multi_home_root.value();
return false;
}
if (platform_->DirectoryExists(new_user_path) &&
(platform_->IsDirectoryMounted(new_user_path) ||
!platform_->DeleteFile(new_user_path))) {
LOG(ERROR) << "Failed to remove mount point: " << new_user_path.value();
return false;
}
if (!EnsureMountPointPath(multi_home_user.DirName()) ||
!EnsureMountPointPath(multi_home_root.DirName()) ||
!EnsureMountPointPath(new_user_path.DirName().DirName()) ||
!EnsurePathComponent(new_user_path.DirName(), default_uid_,
default_gid_)) {
LOG(ERROR) << "The paths to mountpoints are inconsistent";
return false;
}
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
multi_home_user, kUserMountPointMode, default_uid_,
default_access_gid_)) {
PLOG(ERROR) << "Can't create: " << multi_home_user;
return false;
}
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
new_user_path, kUserMountPointMode, default_uid_,
default_access_gid_)) {
PLOG(ERROR) << "Can't create: " << new_user_path;
return false;
}
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
multi_home_root, kRootMountPointMode, kMountOwnerUid,
kMountOwnerGid)) {
PLOG(ERROR) << "Can't create: " << multi_home_root;
return false;
}
return true;
}
void MountHelper::RecursiveCopy(const FilePath& source,
const FilePath& destination) const {
std::unique_ptr<cryptohome::FileEnumerator> file_enumerator(
platform_->GetFileEnumerator(source, false, base::FileEnumerator::FILES));
FilePath next_path;
while (!(next_path = file_enumerator->Next()).empty()) {
FilePath file_name = next_path.BaseName();
FilePath destination_file = destination.Append(file_name);
if (!platform_->Copy(next_path, destination_file) ||
!platform_->SetOwnership(destination_file, default_uid_, default_gid_,
false)) {
LOG(ERROR) << "Couldn't change owner (" << default_uid_ << ":"
<< default_gid_
<< ") of destination path: " << destination_file.value();
}
}
std::unique_ptr<cryptohome::FileEnumerator> dir_enumerator(
platform_->GetFileEnumerator(source, false,
base::FileEnumerator::DIRECTORIES));
while (!(next_path = dir_enumerator->Next()).empty()) {
FilePath dir_name = FilePath(next_path).BaseName();
FilePath destination_dir = destination.Append(dir_name);
VLOG(1) << "RecursiveCopy: " << destination_dir.value();
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
destination_dir, kSkeletonSubDirMode, default_uid_, default_gid_)) {
LOG(ERROR) << "SafeCreateDirAndSetOwnership() failed: "
<< destination_dir.value();
}
RecursiveCopy(FilePath(next_path), destination_dir);
}
}
void MountHelper::CopySkeleton(const FilePath& destination) const {
RecursiveCopy(SkelDir(), destination);
}
std::vector<DirectoryACL> MountHelper::GetEphemeralSubdirectories(
uid_t uid, gid_t gid, gid_t access_gid) {
std::vector<DirectoryACL> common_subdirs =
GetCommonSubdirectories(uid, gid, access_gid);
std::vector<DirectoryACL> cache_subdirs =
GetCacheSubdirectories(uid, gid, access_gid);
std::vector<DirectoryACL> gcache_subdirs =
GetGCacheSubdirectories(uid, gid, access_gid, /*v1_dirs=*/false);
auto result = common_subdirs;
result.insert(result.end(), cache_subdirs.begin(), cache_subdirs.end());
result.insert(result.end(), gcache_subdirs.begin(), gcache_subdirs.end());
return result;
}
bool MountHelper::SetUpEphemeralCryptohome(const FilePath& source_path) {
FilePath user_home = source_path.Append(kUserHomeSuffix);
CopySkeleton(user_home);
const auto subdirs = GetEphemeralSubdirectories(default_uid_, default_gid_,
default_access_gid_);
for (const auto& subdir : subdirs) {
FilePath path = FilePath(source_path).Append(subdir.path);
if (platform_->DirectoryExists(path))
continue;
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
path, subdir.mode, subdir.uid, subdir.gid)) {
LOG(ERROR) << "Couldn't create user path directory: " << path.value();
return false;
}
}
return true;
}
bool MountHelper::MountLegacyHome(const FilePath& from) {
VLOG(1) << "MountLegacyHome from " << from.value();
// Multiple mounts can't live on the legacy mountpoint.
if (platform_->IsDirectoryMounted(FilePath(kDefaultHomeDir))) {
LOG(INFO) << "Skipping binding to /home/chronos/user";
return true;
}
if (!BindAndPush(from, FilePath(kDefaultHomeDir), true /*is_shared*/))
return false;
return true;
}
bool MountHelper::BindMyFilesDownloads(const base::FilePath& user_home) {
if (!platform_->DirectoryExists(user_home)) {
LOG(ERROR) << "Failed to bind MyFiles/Downloads, missing directory: "
<< user_home.value();
return false;
}
const FilePath downloads = user_home.Append(kDownloadsDir);
if (!platform_->DirectoryExists(downloads)) {
LOG(ERROR) << "Failed to bind MyFiles/Downloads, missing directory: "
<< downloads.value();
return false;
}
const FilePath downloads_in_myfiles =
user_home.Append(kMyFilesDir).Append(kDownloadsDir);
if (!platform_->DirectoryExists(downloads_in_myfiles)) {
LOG(ERROR) << "Failed to bind MyFiles/Downloads, missing directory: "
<< downloads_in_myfiles.value();
return false;
}
/*
* User could have saved files in MyFiles/Downloads in case cryptohome
* crashed and bind mounts were removed by error. See crbug.com/1080730.
* Move the files back to Download unless a file already exits.
*/
MigrateDirectory(downloads, downloads_in_myfiles);
if (!BindAndPush(downloads, downloads_in_myfiles, true /*is_shared*/))
return false;
return true;
}
bool MountHelper::MountAndPush(const base::FilePath& src,
const base::FilePath& dest,
const std::string& type,
const std::string& options) {
if (!platform_->Mount(src, dest, type, kDefaultMountFlags, options)) {
PLOG(ERROR) << "Mount failed: " << src.value() << " -> " << dest.value();
return false;
}
stack_.Push(src, dest);
return true;
}
bool MountHelper::BindAndPush(const FilePath& src,
const FilePath& dest,
bool is_shared) {
if (!platform_->Bind(src, dest, is_shared, /*nosymfollow=*/true)) {
PLOG(ERROR) << "Bind mount failed: " << src.value() << " -> "
<< dest.value() << " is_shared: " << std::boolalpha
<< is_shared;
return false;
}
stack_.Push(src, dest);
return true;
}
bool MountHelper::MountDaemonStoreDirectories(
const FilePath& root_home, const std::string& obfuscated_username) {
// Iterate over all directories in /etc/daemon-store. This list is on rootfs,
// so it's tamper-proof and nobody can sneak in additional directories that we
// blindly mount. The actual mounts happen on /run/daemon-store, though.
std::unique_ptr<cryptohome::FileEnumerator> file_enumerator(
platform_->GetFileEnumerator(FilePath(kEtcDaemonStoreBaseDir),
false /* recursive */,
base::FileEnumerator::DIRECTORIES));
// /etc/daemon-store/<daemon-name>
FilePath etc_daemon_store_path;
while (!(etc_daemon_store_path = file_enumerator->Next()).empty()) {
const FilePath& daemon_name = etc_daemon_store_path.BaseName();
// /run/daemon-store/<daemon-name>
FilePath run_daemon_store_path =
FilePath(kRunDaemonStoreBaseDir).Append(daemon_name);
if (!platform_->DirectoryExists(run_daemon_store_path)) {
// The chromeos_startup script should make sure this exist.
PLOG(ERROR) << "Daemon store directory does not exist: "
<< run_daemon_store_path.value();
return false;
}
// /home/.shadow/<user_hash>/mount/root/<daemon-name>
const FilePath mount_source = root_home.Append(daemon_name);
// /run/daemon-store/<daemon-name>/<user_hash>
const FilePath mount_target =
run_daemon_store_path.Append(obfuscated_username);
// Copy ownership from |etc_daemon_store_path| to |mount_source|. After the
// bind operation, this guarantees that ownership for |mount_target| is the
// same as for |etc_daemon_store_path| (usually
// <daemon_user>:<daemon_group>), which is what the daemon intended.
// Otherwise, it would end up being root-owned.
base::stat_wrapper_t etc_daemon_path_stat =
file_enumerator->GetInfo().stat();
// TODO(dlunev): add some reporting when we see ACL mismatch.
if (!platform_->DirectoryExists(mount_source) &&
!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
mount_source, etc_daemon_path_stat.st_mode,
etc_daemon_path_stat.st_uid, etc_daemon_path_stat.st_gid)) {
LOG(ERROR) << "Failed to create directory " << mount_source.value();
return false;
}
// The target directory's parent exists in the root mount namespace so the
// directory itself can be created in the root mount namespace and it will
// be visible in all namespaces.
if (!platform_->CreateDirectory(mount_target)) {
PLOG(ERROR) << "Failed to create directory " << mount_target.value();
return false;
}
// Assuming that |run_daemon_store_path| is a shared mount and the daemon
// runs in a file system namespace with |run_daemon_store_path| mounted as
// secondary, this mount event propagates into the daemon.
if (!BindAndPush(mount_source, mount_target))
return false;
}
return true;
}
void MountHelper::MigrateDirectory(const base::FilePath& dst,
const base::FilePath& src) const {
VLOG(1) << "Migrating directory " << src << " -> " << dst;
std::unique_ptr<cryptohome::FileEnumerator> enumerator(
platform_->GetFileEnumerator(
src, false /* recursive */,
base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES));
for (base::FilePath src_obj = enumerator->Next(); !src_obj.empty();
src_obj = enumerator->Next()) {
base::FilePath dst_obj = dst.Append(src_obj.BaseName());
// If the destination file exists, or rename failed for whatever reason,
// then log a warning and delete the source file.
if (platform_->FileExists(dst_obj) ||
!platform_->Rename(src_obj, dst_obj)) {
LOG(WARNING) << "Failed to migrate " << src_obj << " : deleting";
platform_->DeletePathRecursively(src_obj);
}
}
}
bool MountHelper::MountHomesAndDaemonStores(
const std::string& username,
const std::string& obfuscated_username,
const FilePath& user_home,
const FilePath& root_home) {
// Bind mount user directory as a shared bind mount.
// This allows us to set up user mounts as shared mounts without needing to
// replicate that across multiple mount points.
if (!BindAndPush(user_home, user_home, true /*is_shared*/))
return false;
// Mount /home/chronos/user.
if (legacy_mount_ && !MountLegacyHome(user_home))
return false;
// Mount /home/chronos/u-<user_hash>
const FilePath new_user_path = GetNewUserPath(username);
if (!BindAndPush(user_home, new_user_path))
return false;
// Mount /home/user/<user_hash>.
const FilePath user_multi_home = GetUserPath(username);
if (!BindAndPush(user_home, user_multi_home))
return false;
// Mount /home/root/<user_hash>.
const FilePath root_multi_home = GetRootPath(username);
if (!BindAndPush(root_home, root_multi_home))
return false;
if (bind_mount_downloads_) {
// Mount Downloads to MyFiles/Downloads in:
// - /home/chronos/u-<user_hash>
if (!BindMyFilesDownloads(user_multi_home)) {
return false;
}
}
// Mount directories used by daemons to store per-user data.
if (!MountDaemonStoreDirectories(root_home, obfuscated_username))
return false;
return true;
}
bool MountHelper::CreateTrackedSubdirectories(
const std::string& obfuscated_username, const MountType& mount_type) const {
// Add the subdirectories if they do not exist.
const FilePath dest_dir(mount_type == MountType::ECRYPTFS
? GetEcryptfsUserVaultPath(obfuscated_username)
: GetUserMountDirectory(obfuscated_username));
if (!platform_->DirectoryExists(dest_dir)) {
LOG(ERROR) << "Can't create tracked subdirectories for a missing user.";
return false;
}
const FilePath mount_dir(GetUserMountDirectory(obfuscated_username));
// The call is allowed to partially fail if directory creation fails, but we
// want to have as many of the specified tracked directories created as
// possible.
bool result = true;
for (const auto& tracked_dir : GetTrackedSubdirectories(
default_uid_, default_gid_, default_access_gid_)) {
const FilePath tracked_dir_path = dest_dir.Append(tracked_dir.path);
if (mount_type == MountType::ECRYPTFS) {
const FilePath userside_dir = mount_dir.Append(tracked_dir.path);
// If non-pass-through dir with the same name existed - delete it
// to prevent duplication.
if (platform_->DirectoryExists(userside_dir) &&
!platform_->DirectoryExists(tracked_dir_path)) {
platform_->DeletePathRecursively(userside_dir);
}
}
// Create pass-through directory.
if (!platform_->DirectoryExists(tracked_dir_path)) {
// Delete the existing file or symbolic link if any.
platform_->DeleteFile(tracked_dir_path);
VLOG(1) << "Creating pass-through directory " << tracked_dir_path.value();
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
tracked_dir_path, tracked_dir.mode, tracked_dir.uid,
tracked_dir.gid)) {
PLOG(ERROR) << "Couldn't create directory: "
<< tracked_dir_path.value();
platform_->DeletePathRecursively(tracked_dir_path);
result = false;
continue;
}
} else {
// We make the mode for chronos-access accessible directories more
// permissive, thus we need to change mode. it is unfortunate we need
// to do it explicitly, unlike with mountpoints which we could just
// recreate, but we must preservce user data while doing so.
if (!platform_->SafeDirChmod(tracked_dir_path, tracked_dir.mode)) {
PLOG(ERROR) << "Couldn't change directory's mode: "
<< tracked_dir_path.value();
}
}
if (mount_type == MountType::DIR_CRYPTO) {
// Set xattr to make this directory trackable.
std::string name = tracked_dir_path.BaseName().value();
if (!platform_->SetExtendedFileAttribute(tracked_dir_path,
kTrackedDirectoryNameAttribute,
name.data(), name.length())) {
PLOG(ERROR) << "Unable to set xattr on " << tracked_dir_path.value();
result = false;
continue;
}
}
}
if (!bind_mount_downloads_) {
// If we are not doing the downloads bind mount, move the content of the
// Downloads to MyFiles/Downloads. Doing it file by file in case there is
// a content in the MyFiles/Downloads already.
auto downloads = dest_dir.Append(kUserHomeSuffix).Append(kDownloadsDir);
auto downloads_in_myfiles = dest_dir.Append(kUserHomeSuffix)
.Append(kMyFilesDir)
.Append(kDownloadsDir);
MigrateDirectory(downloads_in_myfiles, downloads);
}
return result;
}
std::vector<DirectoryACL> MountHelper::GetDmcryptSubdirectories(
uid_t uid, gid_t gid, gid_t access_gid) {
auto common_subdirs = GetCommonSubdirectories(uid, gid, access_gid);
auto cache_subdirs = GetCacheSubdirectories(uid, gid, access_gid);
auto gcache_subdirs =
GetGCacheSubdirectories(uid, gid, access_gid, /*v1_dirs=*/true);
// Construct data volume subdirectories.
std::vector<DirectoryACL> data_volume_subdirs;
data_volume_subdirs.insert(data_volume_subdirs.end(), common_subdirs.begin(),
common_subdirs.end());
data_volume_subdirs.insert(data_volume_subdirs.end(), cache_subdirs.begin(),
cache_subdirs.end());
for (auto& subdir : data_volume_subdirs) {
subdir.path = FilePath(kMountDir).Append(subdir.path);
}
// Construct cache volume subdirectories.
auto cache_volume_subdirs = cache_subdirs;
cache_volume_subdirs.insert(cache_volume_subdirs.end(),
gcache_subdirs.begin(), gcache_subdirs.end());
for (auto& subdir : cache_volume_subdirs) {
subdir.path = FilePath(kDmcryptCacheDir).Append(subdir.path);
}
auto result = cache_volume_subdirs;
result.insert(result.end(), data_volume_subdirs.begin(),
data_volume_subdirs.end());
return result;
}
bool MountHelper::CreateDmcryptSubdirectories(
const std::string& obfuscated_username) {
FilePath user_shadow_dir = ShadowRoot().Append(obfuscated_username);
const std::vector<DirectoryACL> dmcrypt_subdirs =
GetDmcryptSubdirectories(default_uid_, default_gid_, default_access_gid_);
// Set up directories.
for (const auto& subdir : dmcrypt_subdirs) {
FilePath dir = user_shadow_dir.Append(subdir.path);
// Ensure that the directory exists.
if (!platform_->DirectoryExists(dir)) {
// Delete the existing file or symbolic link if any.
platform_->DeletePathRecursively(dir);
VLOG(1) << "Creating directory " << dir.value();
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
dir, subdir.mode, subdir.uid, subdir.gid)) {
PLOG(ERROR) << "SafeCreateDirAndSetOwnershipAndPermissions() failed: "
<< dir.value();
platform_->DeletePathRecursively(dir);
return false;
}
}
}
return true;
}
bool MountHelper::MountCacheSubdirectories(
const std::string& obfuscated_username) {
FilePath cache_directory = GetDmcryptUserCacheDirectory(obfuscated_username);
FilePath data_directory = GetUserMountDirectory(obfuscated_username);
const FilePath tracked_subdir_paths[] = {
FilePath(kUserHomeSuffix).Append(kCacheDir),
FilePath(kUserHomeSuffix).Append(kGCacheDir)};
for (const auto& tracked_dir : tracked_subdir_paths) {
FilePath src_dir = cache_directory.Append(tracked_dir);
FilePath dst_dir = data_directory.Append(tracked_dir);
if (!BindAndPush(src_dir, dst_dir, true)) {
LOG(ERROR) << "Failed to bind mount " << src_dir;
return false;
}
}
return true;
}
// The eCryptfs mount is mounted from vault/ --> mount/ except in case of
// migration where the mount point is a temporary directory.
bool MountHelper::SetUpEcryptfsMount(const std::string& obfuscated_username,
const std::string& fek_signature,
const std::string& fnek_signature,
bool should_migrate) {
const FilePath vault_path = GetEcryptfsUserVaultPath(obfuscated_username);
const FilePath mount_point =
should_migrate ? GetUserTemporaryMountDirectory(obfuscated_username)
: GetUserMountDirectory(obfuscated_username);
// Specify the ecryptfs options for mounting the user's cryptohome.
std::string ecryptfs_options = StringPrintf(
"ecryptfs_cipher=aes"
",ecryptfs_key_bytes=%d"
",ecryptfs_fnek_sig=%s"
",ecryptfs_sig=%s"
",ecryptfs_unlink_sigs",
kDefaultEcryptfsKeySize, fnek_signature.c_str(), fek_signature.c_str());
// Create <vault_path>/user and <vault_path>/root.
CreateHomeSubdirectories(vault_path);
// Move the tracked subdirectories from <mount_point_>/user to <vault_path>
// as passthrough directories.
CreateTrackedSubdirectories(obfuscated_username, MountType::ECRYPTFS);
// b/115997660: Mount eCryptfs after creating the tracked subdirectories.
if (!MountAndPush(vault_path, mount_point, "ecryptfs", ecryptfs_options))
return false;
return true;
}
void MountHelper::SetUpDircryptoMount(const std::string& obfuscated_username) {
const FilePath mount_point = GetUserMountDirectory(obfuscated_username);
CreateHomeSubdirectories(mount_point);
CreateTrackedSubdirectories(obfuscated_username, MountType::DIR_CRYPTO);
}
bool MountHelper::SetUpDmcryptMount(const std::string& obfuscated_username) {
const FilePath dmcrypt_data_volume =
GetDmcryptDataVolume(obfuscated_username);
const FilePath dmcrypt_cache_volume =
GetDmcryptCacheVolume(obfuscated_username);
const FilePath data_mount_point = GetUserMountDirectory(obfuscated_username);
const FilePath cache_mount_point =
GetDmcryptUserCacheDirectory(obfuscated_username);
// Mount the data volume at <vault>/mount and the cache volume at
// <vault>/cache. The directories are set up by the creation code.
if (!MountAndPush(dmcrypt_data_volume, data_mount_point,
kDmcryptContainerMountType,
kDmcryptContainerMountOptions)) {
LOG(ERROR) << "Failed to mount dmcrypt data volume";
return false;
}
if (!MountAndPush(dmcrypt_cache_volume, cache_mount_point,
kDmcryptContainerMountType,
kDmcryptContainerMountOptions)) {
LOG(ERROR) << "Failed to mount dmcrypt cache volume";
return false;
}
CreateHomeSubdirectories(data_mount_point);
CreateDmcryptSubdirectories(obfuscated_username);
return true;
}
bool MountHelper::PerformMount(const Options& mount_opts,
const std::string& username,
const std::string& fek_signature,
const std::string& fnek_signature,
bool is_pristine,
MountError* error) {
const std::string obfuscated_username = SanitizeUserName(username);
bool should_mount_ecryptfs = mount_opts.type == MountType::ECRYPTFS ||
mount_opts.to_migrate_from_ecryptfs;
if (should_mount_ecryptfs &&
!SetUpEcryptfsMount(obfuscated_username, fek_signature, fnek_signature,
mount_opts.to_migrate_from_ecryptfs)) {
LOG(ERROR) << "eCryptfs mount failed";
*error = MOUNT_ERROR_MOUNT_ECRYPTFS_FAILED;
return false;
}
if (mount_opts.type == MountType::DIR_CRYPTO)
SetUpDircryptoMount(obfuscated_username);
if (mount_opts.type == MountType::DMCRYPT &&
!SetUpDmcryptMount(obfuscated_username)) {
LOG(ERROR) << "Dm-crypt mount failed";
*error = MOUNT_ERROR_MOUNT_DMCRYPT_FAILED;
return false;
}
const FilePath user_home = GetMountedUserHomePath(obfuscated_username);
const FilePath root_home = GetMountedRootHomePath(obfuscated_username);
if (is_pristine)
CopySkeleton(user_home);
// When migrating, it's better to avoid exposing the new ext4 crypto dir.
if (!mount_opts.to_migrate_from_ecryptfs &&
!MountHomesAndDaemonStores(username, obfuscated_username, user_home,
root_home)) {
*error = MOUNT_ERROR_MOUNT_HOMES_AND_DAEMON_STORES_FAILED;
return false;
}
// Mount tracked subdirectories from the cache volume.
if (mount_opts.type == MountType::DMCRYPT &&
!MountCacheSubdirectories(obfuscated_username)) {
LOG(ERROR)
<< "Failed to mount tracked subdirectories from the cache volume";
*error = MOUNT_ERROR_MOUNT_DMCRYPT_FAILED;
return false;
}
return true;
}
bool MountHelper::PrepareEphemeralDevice(
const std::string& obfuscated_username) {
// Underlying sparse file will be created in a temporary directory in RAM.
const FilePath ephemeral_root(kEphemeralCryptohomeDir);
// Determine ephemeral cryptohome size.
struct statvfs vfs;
if (!platform_->StatVFS(ephemeral_root, &vfs)) {
PLOG(ERROR) << "Can't determine ephemeral cryptohome size";
return false;
}
const int64_t sparse_size = static_cast<int64_t>(vfs.f_blocks * vfs.f_frsize);
// Create underlying sparse file.
const FilePath sparse_file = GetEphemeralSparseFile(obfuscated_username);
if (!platform_->CreateDirectory(sparse_file.DirName())) {
LOG(ERROR) << "Can't create directory for ephemeral sparse files";
return false;
}
// Remember the file to clean up if an error happens during file creation.
ephemeral_file_path_ = sparse_file;
if (!platform_->CreateSparseFile(sparse_file, sparse_size)) {
LOG(ERROR) << "Can't create ephemeral sparse file";
return false;
}
// Format the sparse file as ext4.
if (!platform_->FormatExt4(sparse_file, kDefaultExt4FormatOpts, 0)) {
LOG(ERROR) << "Can't format ephemeral sparse file as ext4";
return false;
}
// Create a loop device based on the sparse file.
const FilePath loop_device = platform_->AttachLoop(sparse_file);
if (loop_device.empty()) {
LOG(ERROR) << "Can't create loop device";
return false;
}
// Remember the loop device to clean up if an error happens.
ephemeral_loop_device_ = loop_device;
return true;
}
bool MountHelper::PerformEphemeralMount(const std::string& username) {
const std::string obfuscated_username =
SanitizeUserNameWithSalt(username, system_salt_);
if (!PrepareEphemeralDevice(obfuscated_username)) {
LOG(ERROR) << "Can't prepare ephemeral device";
return false;
}
const FilePath mount_point =
GetUserEphemeralMountDirectory(obfuscated_username);
if (!platform_->CreateDirectory(mount_point)) {
PLOG(ERROR) << "Directory creation failed for " << mount_point.value();
return false;
}
if (!MountAndPush(ephemeral_loop_device_, mount_point, kEphemeralMountType,
kEphemeralMountOptions)) {
LOG(ERROR) << "Can't mount ephemeral mount point";
return false;
}
// Set SELinux context first, so that the created user & root directory have
// the correct context.
if (!::SetUpSELinuxContextForEphemeralCryptohome(platform_, mount_point)) {
// Logging already done in SetUpSELinuxContextForEphemeralCryptohome.
return false;
}
// Create user & root directories.
CreateHomeSubdirectories(mount_point);
if (!EnsureUserMountPoints(username)) {
return false;
}
const FilePath user_home =
GetMountedEphemeralUserHomePath(obfuscated_username);
const FilePath root_home =
GetMountedEphemeralRootHomePath(obfuscated_username);
if (!SetUpEphemeralCryptohome(mount_point)) {
return false;
}
if (!MountHomesAndDaemonStores(username, obfuscated_username, user_home,
root_home)) {
return false;
}
return true;
}
bool MountHelper::TearDownEphemeralMount() {
UnmountAll();
return CleanUpEphemeral();
}
void MountHelper::TearDownNonEphemeralMount() {
UnmountAll();
}
void MountHelper::UnmountAll() {
FilePath src, dest;
const FilePath ephemeral_mount_path =
FilePath(kEphemeralCryptohomeDir).Append(kEphemeralMountDir);
while (stack_.Pop(&src, &dest)) {
ForceUnmount(src, dest);
// Clean up destination directory for ephemeral loop device mounts.
if (ephemeral_mount_path == dest.DirName())
platform_->DeletePathRecursively(dest);
}
}
bool MountHelper::CleanUpEphemeral() {
bool success = true;
if (!ephemeral_loop_device_.empty()) {
if (!platform_->DetachLoop(ephemeral_loop_device_)) {
PLOG(ERROR) << "Can't detach loop device '"
<< ephemeral_loop_device_.value() << "'";
success = false;
}
ephemeral_loop_device_.clear();
}
if (!ephemeral_file_path_.empty()) {
if (!platform_->DeleteFile(ephemeral_file_path_)) {
PLOG(ERROR) << "Failed to clean up ephemeral sparse file '"
<< ephemeral_file_path_.value() << "'";
success = false;
}
ephemeral_file_path_.clear();
}
return success;
}
void MountHelper::ForceUnmount(const FilePath& src, const FilePath& dest) {
// Try an immediate unmount.
bool was_busy;
if (!platform_->Unmount(dest, false, &was_busy)) {
LOG(ERROR) << "Couldn't unmount '" << dest.value()
<< "' immediately, was_busy=" << std::boolalpha << was_busy;
// Failed to unmount immediately, do a lazy unmount. If |was_busy| we also
// want to sync before the unmount to help prevent data loss.
if (was_busy)
platform_->SyncDirectory(dest);
platform_->LazyUnmount(dest);
platform_->SyncDirectory(src);
}
}
bool MountHelper::CanPerformEphemeralMount() const {
return ephemeral_file_path_.empty() && ephemeral_loop_device_.empty();
}
bool MountHelper::MountPerformed() const {
return stack_.size() > 0;
}
bool MountHelper::IsPathMounted(const base::FilePath& path) const {
return stack_.ContainsDest(path);
}
std::vector<base::FilePath> MountHelper::MountedPaths() const {
return stack_.MountDestinations();
}
} // namespace cryptohome