blob: 6ff7fb30ae06be9ece01dd284641f9f957a1881b [file] [log] [blame] [edit]
// Copyright 2019 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cryptohome/namespace_mounter/mounter.h"
#include <sys/mount.h>
#include <sys/stat.h>
#include <climits>
#include <memory>
#include <ostream>
#include <string_view>
#include <tuple>
#include <unordered_set>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/functional/callback_helpers.h>
#include <base/location.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <brillo/cryptohome.h>
#include <brillo/secure_blob.h>
#include <libstorage/platform/platform.h>
#include "base/check.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "cryptohome/cryptohome_common.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/filesystem_layout.h"
#include "cryptohome/storage/homedirs.h"
#include "cryptohome/storage/mount_constants.h"
namespace cryptohome {
namespace {
using base::FilePath;
using base::StringPrintf;
using brillo::cryptohome::home::GetRootPath;
using brillo::cryptohome::home::GetUserPath;
using brillo::cryptohome::home::SanitizeUserName;
const char kEphemeralCryptohomeRootContext[] =
"u:object_r:cros_home_shadow_uid:s0";
const int kDefaultEcryptfsKeySize = kCryptohomeAesKeyBytes;
FilePath GetUserEphemeralMountDirectory(
const ObfuscatedUsername& obfuscated_username) {
return FilePath(kEphemeralCryptohomeDir)
.Append(kEphemeralMountDir)
.Append(*obfuscated_username);
}
FilePath GetMountedEphemeralRootHomePath(
const ObfuscatedUsername& obfuscated_username) {
return GetUserEphemeralMountDirectory(obfuscated_username)
.Append(kRootHomeSuffix);
}
FilePath GetMountedEphemeralUserHomePath(
const ObfuscatedUsername& obfuscated_username) {
return GetUserEphemeralMountDirectory(obfuscated_username)
.Append(kUserHomeSuffix);
}
// Sets up the SELinux context for a freshly mounted ephemeral cryptohome.
bool SetUpSELinuxContextForEphemeralCryptohome(libstorage::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,
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 kGroupWriteAccess = S_IWGRP;
struct DirectoryACL {
FilePath path;
mode_t mode;
uid_t uid;
gid_t gid;
};
std::vector<DirectoryACL> GetCacheSubdirectories(const FilePath& dir) {
return std::vector<DirectoryACL>{
{dir.Append(kUserHomeSuffix).Append(kGCacheDir), kAccessMode,
libstorage::kChronosUid, libstorage::kChronosAccessGid},
{dir.Append(kUserHomeSuffix).Append(kCacheDir), kTrackedDirMode,
libstorage::kChronosUid, libstorage::kChronosGid},
{dir.Append(kUserHomeSuffix)
.Append(kGCacheDir)
.Append(kGCacheVersion2Dir),
kAccessMode | kGroupWriteAccess, libstorage::kChronosUid,
libstorage::kChronosAccessGid},
{dir.Append(kRootHomeSuffix).Append(kDaemonStoreCacheDir),
kAccessMode | kGroupWriteAccess, libstorage::kRootUid,
libstorage::kDaemonStoreGid},
};
}
std::vector<DirectoryACL> GetCommonSubdirectories(const FilePath& dir,
bool bind_mount_downloads) {
auto result = std::vector<DirectoryACL>{
{dir.Append(kRootHomeSuffix), kRootDirMode, libstorage::kRootUid,
libstorage::kDaemonStoreGid},
{dir.Append(kUserHomeSuffix), kAccessMode, libstorage::kChronosUid,
libstorage::kChronosAccessGid},
{dir.Append(kUserHomeSuffix).Append(kMyFilesDir), kAccessMode,
libstorage::kChronosUid, libstorage::kChronosAccessGid},
{dir.Append(kUserHomeSuffix).Append(kMyFilesDir).Append(kDownloadsDir),
kAccessMode, libstorage::kChronosUid, libstorage::kChronosAccessGid},
};
if (bind_mount_downloads) {
result.push_back({dir.Append(kUserHomeSuffix).Append(kDownloadsDir),
kAccessMode, libstorage::kChronosUid,
libstorage::kChronosAccessGid});
}
auto cache_subdirs = GetCacheSubdirectories(dir);
result.insert(result.end(), cache_subdirs.begin(), cache_subdirs.end());
return result;
}
std::vector<DirectoryACL> GetDmcryptSubdirectories(const FilePath& dir,
bool bind_mount_downloads) {
auto data_volume_subdirs =
GetCommonSubdirectories(dir.Append(kMountDir), bind_mount_downloads);
auto cache_volume_subdirs =
GetCacheSubdirectories(dir.Append(kDmcryptCacheDir));
auto result = cache_volume_subdirs;
result.insert(result.end(), data_volume_subdirs.begin(),
data_volume_subdirs.end());
return result;
}
// Returns true if the directory should be root owned, but is missing or has
// wrong attributes.
bool IsRootDirectoryAndTampered(libstorage::Platform* platform,
DirectoryACL dir) {
if (dir.uid != libstorage::kRootUid) {
// Shouldn't be owned by root - ignore.
return false;
}
base::stat_wrapper_t st;
if (!platform->Stat(dir.path, &st)) {
// Couldn't stat it, which means something is wrong, consider tampered.
return true;
}
const mode_t st_mode = st.st_mode & 01777;
if (S_ISDIR(st.st_mode) && st_mode == dir.mode && st.st_uid == dir.uid &&
st.st_gid == dir.gid) {
// Attributes are correct, not tampered
return false;
}
LOG(ERROR) << "Root owned directory was tampered with, will be recreated.";
return true;
}
void MaybeCorrectUserDirectoryAttrs(libstorage::Platform* platform,
DirectoryACL dir) {
// Ignore root owned directories - those are recreated if they have wrong
// attributes.
if (dir.uid == libstorage::kRootUid) {
return;
}
// The check is intended to correct, report and fix a group mismatch for the
// <vault> directories. It is initially required for crbug.com/1205308, but
// since we are doing the chown anyway, there is no drama to do it for all
// user directories.
if (!platform->SafeDirChown(dir.path, dir.uid, dir.gid)) {
LOG(ERROR) << "Failed to fix ownership of path directory" << dir.path;
}
// 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 preserve user data while doing so.
if (!platform->SafeDirChmod(dir.path, dir.mode)) {
PLOG(ERROR) << "Failed to fix mode of path directory: " << dir.path;
}
}
bool CreateVaultDirectoryStructure(
libstorage::Platform* platform,
const std::vector<DirectoryACL>& directories) {
bool success = true;
for (const auto& subdir : directories) {
if (platform->DirectoryExists(subdir.path) &&
!IsRootDirectoryAndTampered(platform, subdir)) {
MaybeCorrectUserDirectoryAttrs(platform, subdir);
continue;
}
if (!platform->DeletePathRecursively(subdir.path)) {
LOG(ERROR) << "Couldn't cleanup path element: " << subdir.path;
success = false;
continue;
}
if (!platform->SafeCreateDirAndSetOwnershipAndPermissions(
subdir.path, subdir.mode, subdir.uid, subdir.gid)) {
LOG(ERROR) << "Couldn't create path directory: " << subdir.path;
std::ignore = platform->DeletePathRecursively(subdir.path);
success = false;
continue;
}
LOG(INFO) << "Created vault subdirectory: " << subdir.path;
}
return success;
}
bool SetTrackingXattr(libstorage::Platform* platform,
const std::vector<DirectoryACL>& directories) {
bool success = true;
for (const auto& subdir : directories) {
std::string name = subdir.path.BaseName().value();
if (!platform->SetExtendedFileAttribute(subdir.path,
kTrackedDirectoryNameAttribute,
name.data(), name.length())) {
PLOG(ERROR) << "Unable to set xattr on " << subdir.path;
success = false;
continue;
}
}
return success;
}
// Identifies the pre-migration and post-migration stages of the ~/Downloads
// bind mount migration.
enum class MigrationStage { kUnknown, kMigrating, kMigrated };
// Converts MigrationStage to string.
std::string_view ToString(const MigrationStage stage) {
switch (stage) {
case MigrationStage::kUnknown:
return "unknown";
case MigrationStage::kMigrating:
return kMigrating;
case MigrationStage::kMigrated:
return kMigrated;
}
NOTREACHED_NORETURN() << "Unexpected MigrationStage: "
<< static_cast<int>(stage);
}
// Output operator for logging.
std::ostream& operator<<(std::ostream& out, const MigrationStage stage) {
return out << ToString(stage);
}
MigrationStage GetDownloadsMigrationXattr(libstorage::Platform* const platform,
const FilePath& path) {
std::string xattr;
DCHECK(platform);
if (!platform->GetExtendedFileAttributeAsString(path, kMigrationXattrName,
&xattr)) {
PLOG(ERROR) << "Cannot get xattr " << kMigrationXattrName << " of path '"
<< path << "'";
return MigrationStage::kUnknown;
}
if (xattr == kMigrating) {
return MigrationStage::kMigrating;
}
if (xattr == kMigrated) {
return MigrationStage::kMigrated;
}
LOG(ERROR) << "Unexpected value '" << xattr << "' for xattr "
<< kMigrationXattrName << " of path '" << path << "'";
return MigrationStage::kUnknown;
}
bool SetDownloadsMigrationXattr(libstorage::Platform* const platform,
const FilePath& path,
const MigrationStage stage) {
DCHECK_NE(stage, MigrationStage::kUnknown);
const auto xattr = ToString(stage);
const bool ok = platform->SetExtendedFileAttribute(
path, kMigrationXattrName, xattr.data(), xattr.size());
PLOG_IF(ERROR, !ok) << "Cannot set xattr " << kMigrationXattrName << " on '"
<< path << "' to '" << xattr << "'";
return ok;
}
// Convert |mount_type| to a string for logging.
const char* MountTypeToString(MountType mount_type) {
switch (mount_type) {
case MountType::NONE:
return "NONE";
case MountType::ECRYPTFS:
return "ECRYPTFS";
case MountType::DIR_CRYPTO:
return "DIR_CRYPTO";
case MountType::DMCRYPT:
return "DMCRYPT";
case MountType::EPHEMERAL:
return "EPHEMERAL";
case MountType::ECRYPTFS_TO_DIR_CRYPTO:
return "ECRYPTFS_TO_DIR_CRYPTO";
case MountType::ECRYPTFS_TO_DMCRYPT:
return "ECRYPTFS_TO_DMCRYPT";
case MountType::DIR_CRYPTO_TO_DMCRYPT:
return "DIR_CRYPTO_TO_DMCRYPT";
}
}
class SyncGuard {
public:
explicit SyncGuard(libstorage::Platform* const platform)
: platform_(platform) {
DCHECK(platform_);
}
~SyncGuard() {
platform_->Sync();
LOG(INFO) << "Sync'ed filesystems";
}
private:
libstorage::Platform* const platform_;
};
// Truncates the given string `s` to the maximum length of `max_bytes`. Avoids
// cutting a multibyte UTF-8 sequence. Avoids cutting after a zero-width joiner.
std::string_view TruncateUtf8(std::string_view s, size_t max_bytes) {
if (s.size() <= max_bytes) {
// Nothing to truncate.
return s;
}
// Remove the trailing bytes at the truncation point.
while (max_bytes > 0 && (static_cast<std::uint8_t>(s[max_bytes]) &
0b1100'0000u) == 0b1000'0000u) {
max_bytes--;
}
s = s.substr(0, max_bytes);
// Remove the zero-width joiner if the truncated string would end with one.
if (const std::string_view zwj = "\u200D"; s.ends_with(zwj)) {
s.remove_suffix(zwj.size());
}
return s;
}
// Removes the numeric suffix at the end of the given string `s`. Does nothing
// if the string does not end with a numeric suffix. A numeric suffix is a
// decimal number between parentheses and preceded by a space, like:
// * " (1)" or
// * " (142857)".
void RemoveNumericSuffix(std::string& s) {
size_t i = s.size();
if (i == 0 || s[--i] != ')') {
return;
}
if (i == 0 || !base::IsAsciiDigit(s[--i])) {
return;
}
while (i > 0 && base::IsAsciiDigit(s[i - 1])) {
--i;
}
if (i == 0 || s[--i] != '(') {
return;
}
if (i == 0 || s[--i] != ' ') {
return;
}
s.resize(i);
}
} // namespace
const char kDefaultHomeDir[] = "/home/chronos/user";
// The extended attribute name used to designate the ~/Downloads folder pre and
// post migration.
constexpr char kMigrationXattrName[] = "user.BindMountMigration";
// Prior to moving ~/Downloads to ~/MyFiles/Downloads set the xattr above to
// this value.
constexpr char kMigrating[] = "migrating";
// After moving ~/Downloads to ~/MyFiles/Downloads set the xattr to this value.
constexpr char kMigrated[] = "migrated";
Mounter::Mounter(bool legacy_mount,
bool bind_mount_downloads,
libstorage::Platform* platform)
: legacy_mount_(legacy_mount),
bind_mount_downloads_(bind_mount_downloads),
platform_(platform) {}
// static
FilePath Mounter::GetNewUserPath(const Username& username) {
ObfuscatedUsername sanitized = SanitizeUserName(username);
std::string user_dir = StringPrintf("u-%s", sanitized->c_str());
return FilePath("/home").Append(kDefaultSharedUser).Append(user_dir);
}
FilePath Mounter::GetMountedUserHomePath(
const ObfuscatedUsername& obfuscated_username) const {
return GetUserMountDirectory(obfuscated_username).Append(kUserHomeSuffix);
}
FilePath Mounter::GetMountedRootHomePath(
const ObfuscatedUsername& obfuscated_username) const {
return GetUserMountDirectory(obfuscated_username).Append(kRootHomeSuffix);
}
bool Mounter::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;
}
bool Mounter::EnsureMountPointPath(const FilePath& dir) const {
std::vector<std::string> path_parts = dir.GetComponents();
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, libstorage::kRootUid,
libstorage::kRootGid)) {
return false;
}
}
return true;
}
bool Mounter::EnsureUserMountPoints(const Username& 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_->DeletePathRecursively(multi_home_user))) {
PLOG(ERROR) << "Failed to remove mount point: " << multi_home_user.value();
return false;
}
if (platform_->DirectoryExists(multi_home_root) &&
(platform_->IsDirectoryMounted(multi_home_root) ||
!platform_->DeletePathRecursively(multi_home_root))) {
PLOG(ERROR) << "Failed to remove mount point: " << multi_home_root.value();
return false;
}
if (platform_->DirectoryExists(new_user_path) &&
(platform_->IsDirectoryMounted(new_user_path) ||
!platform_->DeletePathRecursively(new_user_path))) {
PLOG(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(), libstorage::kChronosUid,
libstorage::kChronosGid)) {
LOG(ERROR) << "The paths to mountpoints are inconsistent";
return false;
}
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
multi_home_user, kUserMountPointMode, libstorage::kChronosUid,
libstorage::kChronosAccessGid)) {
PLOG(ERROR) << "Can't create: " << multi_home_user;
return false;
}
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
new_user_path, kUserMountPointMode, libstorage::kChronosUid,
libstorage::kChronosAccessGid)) {
PLOG(ERROR) << "Can't create: " << new_user_path;
return false;
}
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
multi_home_root, kRootMountPointMode, libstorage::kRootUid,
libstorage::kRootGid)) {
PLOG(ERROR) << "Can't create: " << multi_home_root;
return false;
}
// TODO(b/300839936): Temporary verbose log.
LOG(INFO) << "Finished ensuring user mount points";
return true;
}
void Mounter::RecursiveCopy(const FilePath& source,
const FilePath& destination) const {
std::unique_ptr<libstorage::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, libstorage::kChronosUid,
libstorage::kChronosGid, false)) {
LOG(ERROR) << "Couldn't change owner (" << libstorage::kChronosUid << ":"
<< libstorage::kChronosGid
<< ") of destination path: " << destination_file.value();
}
}
std::unique_ptr<libstorage::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, libstorage::kChronosUid,
libstorage::kChronosGid)) {
LOG(ERROR) << "SafeCreateDirAndSetOwnership() failed: "
<< destination_dir.value();
}
RecursiveCopy(FilePath(next_path), destination_dir);
}
}
void Mounter::CopySkeleton(const FilePath& destination) const {
RecursiveCopy(SkelDir(), destination);
}
bool Mounter::IsFirstMountComplete(
const ObfuscatedUsername& obfuscated_username) const {
// TODO(b/300839936): Temporary verbose log.
LOG(INFO) << "Checking if there has been a successful mount in the past";
const FilePath mount_point = GetUserMountDirectory(obfuscated_username);
const FilePath user_home = GetMountedUserHomePath(obfuscated_username);
// Generate the set of the top level nodes that a mount creates.
std::unordered_set<FilePath> initial_nodes;
for (const auto& dir :
GetCommonSubdirectories(mount_point, bind_mount_downloads_)) {
initial_nodes.insert(dir.path);
}
std::unique_ptr<libstorage::FileEnumerator> skel_enumerator(
platform_->GetFileEnumerator(
SkelDir(), false,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES));
for (FilePath next = skel_enumerator->Next(); !next.empty();
next = skel_enumerator->Next()) {
initial_nodes.insert(user_home.Append(next.BaseName()));
}
// If we have any nodes within the vault that are not in the set created
// above - it means we have successfully entered a user session prior.
std::unique_ptr<libstorage::FileEnumerator> vault_enumerator(
platform_->GetFileEnumerator(
user_home, false,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES));
for (FilePath next = vault_enumerator->Next(); !next.empty();
next = vault_enumerator->Next()) {
if (initial_nodes.count(next) == 0) {
// Found a file not from initial list, first mount was completed.
// Log the file name to debug in case we ever see problems with
// something racing the vault creation.
LOG(INFO) << "Not a first mount, since found: " << next;
return true;
}
}
return false;
}
bool Mounter::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),
libstorage::RemountOption::kMountsFlowIn))
return false;
return true;
}
bool Mounter::HandleMyFilesDownloads(const FilePath& user_home) {
// If the flag to not bind mount ~/Downloads to ~/MyFiles/Downloads is
// enabled, then attempt to (one-time) migrate the folder. In the event this
// fails, fallback to the bind mount logic and try again on the next mount.
if (!bind_mount_downloads_ && MoveDownloadsToMyFiles(user_home)) {
return true;
}
const FilePath downloads = user_home.Append(kDownloadsDir);
const FilePath downloads_in_my_files =
user_home.Append(kMyFilesDir).Append(kDownloadsDir);
// See b/172341309. User could have saved files in ~/MyFiles/Downloads in case
// cryptohome crashed and bind-mounts were removed by error. Move the files
// from ~/MyFiles/Downloads to ~/Downloads. In case the ~/Downloads folder had
// been moved to ~/MyFiles/Downloads previously, this also acts as a "reverse"
// migration.
MoveDirectoryContents(downloads_in_my_files, downloads);
// We also need to remove the xattr if it exists. This will allow the next
// future migration of ~/Downloads to ~/MyFiles/Downloads to succeed again,
// when the time comes.
if (platform_->RemoveExtendedFileAttribute(downloads_in_my_files,
kMigrationXattrName)) {
LOG(INFO) << "Removed xattr '" << kMigrationXattrName << "' from '"
<< downloads_in_my_files << "'";
}
if (!BindAndPush(downloads, downloads_in_my_files)) {
return false;
}
return true;
}
bool Mounter::MoveDownloadsToMyFiles(const FilePath& user_home) {
using enum DownloadsMigrationStatus;
const FilePath downloads_in_my_files =
user_home.Append(kMyFilesDir).Append(kDownloadsDir);
const FilePath downloads = user_home.Append(kDownloadsDir);
// Check if the migration has successfully completed on a prior run.
const MigrationStage stage =
GetDownloadsMigrationXattr(platform_, downloads_in_my_files);
bool ok;
if (stage == MigrationStage::kMigrated) {
LOG(INFO) << "The 'Downloads' folder is already marked as 'migrated'";
ReportDownloadsMigrationStatus(kAlreadyMigrated);
// Clean up the ~/Downloads folder if it reappeared after the migration.
if (platform_->DirectoryExists(downloads)) {
LOG(WARNING) << "The ~/Downloads folder reappeared after it was migrated "
"to ~/MyFiles/Downloads";
ReportDownloadsMigrationStatus(kReappeared);
MoveDirectoryContents(downloads, downloads_in_my_files);
ok = platform_->DeleteFile(downloads);
ReportDownloadsMigrationOperation("RemoveReappearedDownloads", ok);
PLOG_IF(ERROR, !ok) << "Cannot remove the reappeared ~/Downloads folder";
LOG_IF(INFO, ok) << "Removed the reappeared ~/Downloads folder";
}
// Clean up the old ~/Downloads-backup folder if it is still there.
if (const FilePath downloads_backup = user_home.Append(kDownloadsBackupDir);
platform_->DirectoryExists(downloads_backup)) {
MoveDirectoryContents(downloads_backup, downloads_in_my_files);
ok = platform_->DeleteFile(downloads_backup);
ReportDownloadsMigrationOperation("CleanUp", ok);
PLOG_IF(ERROR, !ok) << "Cannot delete the old ~/Downloads-backup folder";
LOG_IF(INFO, ok) << "Deleted the old ~/Downloads-backup folder";
}
return true;
}
// Ensure that the filesystems will be sync'ed.
const SyncGuard sync_guard(platform_);
// If ~/Downloads doesn't exist and ~/MyFiles/Downloads does exist, this might
// be a freshly set-up cryptohome or the previous xattr setting failed. Update
// the xattr accordingly and, even if this fails, cryptohome is still in a
// usable state so return true.
if (!platform_->FileExists(downloads) &&
platform_->FileExists(downloads_in_my_files)) {
LOG(INFO) << "The 'Downloads' folder is already in ~/MyFiles/Downloads, "
"but its xattr is still marked as '"
<< stage << "'";
if (stage == MigrationStage::kMigrating) {
ok = SetDownloadsMigrationXattr(platform_, downloads_in_my_files,
MigrationStage::kMigrated);
ReportDownloadsMigrationOperation("FixXattr", ok);
ReportDownloadsMigrationStatus(kFixXattr);
} else {
DCHECK_EQ(stage, MigrationStage::kUnknown);
LOG(INFO) << "It looks like a new cryptohome";
ok = SetDownloadsMigrationXattr(platform_, downloads_in_my_files,
MigrationStage::kMigrated);
ReportDownloadsMigrationOperation("SetXattrForNewCryptoHome", ok);
ReportDownloadsMigrationStatus(kSetXattrForNewCryptoHome);
}
if (!ok) {
ReportDownloadsMigrationStatus(kCannotSetXattrToMigrated);
}
return true;
}
// Move all files from ~/MyFiles/Downloads to ~/Downloads to ensure there's
// none left in ~/MyFiles/Downloads before migration.
MoveDirectoryContents(downloads_in_my_files, downloads);
// Set the xattr for the ~/Downloads directory to be "migrating". If this
// fails, don't continue as the filesystem is in a good state to continue with
// the bind-mount and a migration can be done at a later stage.
ok = SetDownloadsMigrationXattr(platform_, downloads,
MigrationStage::kMigrating);
ReportDownloadsMigrationOperation("SetXattrToMigrating", ok);
if (!ok) {
ReportDownloadsMigrationStatus(kCannotSetXattrToMigrating);
return false;
}
// Exchange ~/Downloads to ~/MyFiles/Downloads.
ok = platform_->Exchange(downloads, downloads_in_my_files);
ReportDownloadsMigrationOperation("Exchange", ok);
if (!ok) {
PLOG(ERROR) << "Cannot exchange ~/Downloads and ~/MyFiles/Downloads";
ReportDownloadsMigrationStatus(kCannotMoveToMyFiles);
return false;
}
LOG(INFO) << "Moved ~/Downloads into ~/MyFiles";
// Remove the old Downloads folder.
ok = platform_->DeleteFile(downloads);
ReportDownloadsMigrationOperation("CleanUp", ok);
PLOG_IF(ERROR, !ok) << "Cannot delete old ~/Downloads folder";
LOG_IF(INFO, ok) << "Deleted old ~/Downloads folder";
// The migration has completed successfully, to ensure no further migrations
// occur, set the xattr to "migrated". If this fails, the cryptohome is usable
// and, the next time this migration logic runs, it will try and update the
// xattr again.
ok = SetDownloadsMigrationXattr(platform_, downloads_in_my_files,
MigrationStage::kMigrated);
ReportDownloadsMigrationOperation("SetXattrToMigrated", ok);
if (!ok) {
ReportDownloadsMigrationStatus(kCannotSetXattrToMigrated);
return true;
}
// This is considered the point of no return. The migration has, for all
// intents and purposes, successfully completed.
ReportDownloadsMigrationStatus(kSuccess);
LOG(INFO) << "The ~/Downloads folder was successfully migrated to "
"~/MyFiles/Downloads and marked as 'migrated'";
return true;
}
bool Mounter::MountAndPush(const FilePath& src,
const FilePath& dest,
const std::string& type,
const std::string& options) {
uint32_t mount_flags = libstorage::kDefaultMountFlags | MS_NOSYMFOLLOW;
if (!platform_->Mount(src, dest, type, mount_flags, options)) {
PLOG(ERROR) << "Mount failed: " << src.value() << " -> " << dest.value();
return false;
}
stack_.Push(src, dest);
return true;
}
bool Mounter::BindAndPush(const FilePath& src,
const FilePath& dest,
libstorage::RemountOption remount) {
if (!platform_->Bind(src, dest, remount, /*nosymfollow=*/true)) {
std::string remount_strs[] = {"kNoRemount", "kPrivate", "kShared",
"kMountsFlowIn", "kUnbindable"};
PLOG(ERROR) << "Bind mount failed: " << src.value() << " -> "
<< dest.value()
<< " remount: " << remount_strs[static_cast<int>(remount)];
return false;
}
stack_.Push(src, dest);
return true;
}
bool Mounter::MountDaemonStoreCacheDirectories(
const FilePath& root_home, const ObfuscatedUsername& obfuscated_username) {
return InternalMountDaemonStoreDirectories(
root_home.Append(kDaemonStoreCacheDir), obfuscated_username,
kEtcDaemonStoreBaseDir, kRunDaemonStoreCacheBaseDir);
}
bool Mounter::MountDaemonStoreDirectories(
const FilePath& root_home, const ObfuscatedUsername& obfuscated_username) {
return InternalMountDaemonStoreDirectories(root_home, obfuscated_username,
kEtcDaemonStoreBaseDir,
kRunDaemonStoreBaseDir);
}
bool Mounter::InternalMountDaemonStoreDirectories(
const FilePath& root_home,
const ObfuscatedUsername& obfuscated_username,
const char* etc_daemon_store_base_dir,
const char* run_daemon_store_base_dir) {
// Iterate over all directories in |etc_daemon_store_base_dir|. 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_base_dir|, though.
std::unique_ptr<libstorage::FileEnumerator> file_enumerator(
platform_->GetFileEnumerator(FilePath(etc_daemon_store_base_dir),
false /* recursive */,
base::FileEnumerator::DIRECTORIES));
// |etc_daemon_store_base_dir|/<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_base_dir|/<daemon-name>
FilePath run_daemon_store_path =
FilePath(run_daemon_store_base_dir).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;
}
// Typically, one of:
// /home/.shadow/<user_hash>/mount/root/<daemon-name>
// /home/.shadow/<user_hash>/mount/root/.cache/<daemon-name>
const FilePath mount_source = root_home.Append(daemon_name);
// |run_daemon_store_base_dir|/<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)) {
if (!platform_->SafeDirChmod(mount_source,
etc_daemon_path_stat.st_mode)) {
LOG(ERROR) << "Failed to chmod directory " << mount_source.value();
return false;
}
} else {
if (!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;
}
bool Mounter::MoveWithConflictResolution(const FilePath& from,
const FilePath& to_dir,
ProbeCounts& probe_counts) const {
DCHECK(from.IsAbsolute()) << from;
DCHECK(to_dir.IsAbsolute()) << to_dir;
std::string name = from.BaseName().value();
DCHECK(!name.empty());
DCHECK(!name.starts_with('/')) << " for " << name;
// Try to move the item without renaming it.
{
const FilePath to = to_dir.Append(name);
if (platform_->RenameNoReplace(from, to)) {
// Successfully moved the item.
return true;
}
// Item cannot be moved. Check the reason.
if (errno != EEXIST) {
PLOG(ERROR) << "Cannot move '" << from << "' to '" << to << "'";
return false;
}
}
// There was a name collision in the destination directory. Get the filename
// extension if the source item is a file (and not a directory).
std::string ext;
if (base::stat_wrapper_t st; !name.ends_with('.') &&
platform_->Stat(from, &st) &&
S_ISREG(st.st_mode)) {
ext = FilePath(name).Extension();
// See b/333986056. Work around some of the limitations of FilePath.
if (ext.size() == name.size() || ext.size() > 12 ||
ext.find(' ') != std::string::npos) {
ext = FilePath(name).FinalExtension();
if (ext.size() == name.size() || ext.size() > 6 ||
ext.find(' ') != std::string::npos) {
ext.clear();
}
}
if (!ext.empty()) {
name.resize(name.size() - ext.size());
DCHECK(!name.empty());
}
}
RemoveNumericSuffix(name);
for (int& i = probe_counts[base::StrCat({name, ext})]; ++i < INT_MAX;) {
const std::string suffix =
base::StrCat({" (", base::NumberToString(i), ")", ext});
// Try to move and rename the item at the same time.
const FilePath to = to_dir.Append(
base::StrCat({TruncateUtf8(name, NAME_MAX - suffix.size()), suffix}));
if (platform_->RenameNoReplace(from, to)) {
// Successfully moved and renamed the item.
return true;
}
// Item cannot be moved. Check the reason.
if (errno != EEXIST) {
PLOG(ERROR) << "Cannot move '" << from << "' to '" << to << "'";
return false;
}
}
LOG(ERROR) << "Cannot move '" << from << "' to dir '" << to_dir
<< "': Too many collisions";
return false;
}
void Mounter::MoveDirectoryContents(const FilePath& from_dir,
const FilePath& to_dir) const {
VLOG(1) << "Migrating items from '" << from_dir << "' to '" << to_dir << "'";
const std::unique_ptr<libstorage::FileEnumerator> enumerator(
platform_->GetFileEnumerator(
from_dir, false /* recursive */,
base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES));
DCHECK(enumerator);
int num_items = 0;
int num_moved = 0;
ProbeCounts probe_counts;
for (FilePath from = enumerator->Next(); !from.empty();
from = enumerator->Next()) {
num_items++;
const bool ok = MoveWithConflictResolution(from, to_dir, probe_counts);
ReportDownloadsMigrationOperation("UnmaskItem", ok);
num_moved += ok;
}
LOG_IF(INFO, num_moved != 0) << "Moved " << num_moved << " items from '"
<< from_dir << "' to '" << to_dir << "'";
ReportMaskedDownloadsItems(num_items);
}
bool Mounter::MountHomesAndDaemonStores(
const Username& username,
const ObfuscatedUsername& 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 subsidiary mounts without needing
// to replicate that across multiple mount points.
if (!BindAndPush(user_home, user_home, libstorage::RemountOption::kShared))
return false;
// Same as above for |root_home|, to ensure submounts are propagated
// correctly.
if (!BindAndPush(root_home, root_home, libstorage::RemountOption::kShared))
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,
libstorage::RemountOption::kMountsFlowIn))
return false;
// Mount /home/user/<user_hash>.
const FilePath user_multi_home = GetUserPath(username);
if (!BindAndPush(user_home, user_multi_home,
libstorage::RemountOption::kMountsFlowIn))
return false;
// Mount /home/root/<user_hash>.
const FilePath root_multi_home = GetRootPath(username);
if (!BindAndPush(root_home, root_multi_home,
libstorage::RemountOption::kMountsFlowIn))
return false;
// Mount Downloads to MyFiles/Downloads in the user shadow directory.
if (!HandleMyFilesDownloads(user_home)) {
return false;
}
// Mount directories used by daemons to store per-user data.
if (!MountDaemonStoreDirectories(root_home, obfuscated_username))
return false;
return true;
}
bool Mounter::MountCacheSubdirectories(
const ObfuscatedUsername& obfuscated_username,
const FilePath& data_directory) {
FilePath cache_directory = GetDmcryptUserCacheDirectory(obfuscated_username);
const FilePath tracked_subdir_paths[] = {
FilePath(kUserHomeSuffix).Append(kCacheDir),
FilePath(kUserHomeSuffix).Append(kGCacheDir),
FilePath(kRootHomeSuffix).Append(kDaemonStoreCacheDir),
};
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,
libstorage::RemountOption::kMountsFlowIn)) {
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 Mounter::SetUpEcryptfsMount(const ObfuscatedUsername& obfuscated_username,
const std::string& fek_signature,
const std::string& fnek_signature,
const FilePath& mount_point) {
const FilePath vault_path = GetEcryptfsUserVaultPath(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.
std::ignore = CreateVaultDirectoryStructure(
platform_, GetCommonSubdirectories(vault_path, bind_mount_downloads_));
// b/115997660: Mount eCryptfs after creating the tracked subdirectories.
if (!MountAndPush(vault_path, mount_point, "ecryptfs", ecryptfs_options)) {
LOG(ERROR) << "eCryptfs mount failed";
return false;
}
return true;
}
void Mounter::SetUpDircryptoMount(
const ObfuscatedUsername& obfuscated_username) {
// TODO(b/300839936): Temporary verbose logging.
LOG(INFO) << "Setting up dircrypto mount";
const FilePath mount_point = GetUserMountDirectory(obfuscated_username);
LOG(INFO) << "Creating vault directory structure";
std::ignore = CreateVaultDirectoryStructure(
platform_, GetCommonSubdirectories(mount_point, bind_mount_downloads_));
LOG(INFO) << "Setting tracking xattr";
std::ignore = SetTrackingXattr(
platform_, GetCommonSubdirectories(mount_point, bind_mount_downloads_));
LOG(INFO) << "Finished setting up dircrypto mount";
}
bool Mounter::SetUpDmcryptMount(const ObfuscatedUsername& obfuscated_username,
const FilePath& data_mount_point) {
const FilePath dmcrypt_data_volume =
GetDmcryptDataVolume(obfuscated_username);
const FilePath dmcrypt_cache_volume =
GetDmcryptCacheVolume(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;
}
std::ignore = CreateVaultDirectoryStructure(
platform_, GetDmcryptSubdirectories(UserPath(obfuscated_username),
bind_mount_downloads_));
return true;
}
StorageStatus Mounter::PerformMount(MountType mount_type,
const Username& username,
const std::string& fek_signature,
const std::string& fnek_signature) {
LOG(INFO) << "Performing mount of type " << MountTypeToString(mount_type);
const ObfuscatedUsername obfuscated_username = SanitizeUserName(username);
if (!EnsureUserMountPoints(username)) {
return StorageStatus::Make(FROM_HERE, "Error creating mountpoints",
MOUNT_ERROR_CREATE_CRYPTOHOME_FAILED);
}
// Since Service::Mount cleans up stale mounts, we should only reach
// this point if someone attempts to re-mount an in-use mount point.
if (platform_->IsDirectoryMounted(
GetUserMountDirectory(obfuscated_username))) {
return StorageStatus::Make(
FROM_HERE,
std::string("Mount point is busy: ") +
GetUserMountDirectory(obfuscated_username).value(),
MOUNT_ERROR_FATAL);
}
const FilePath user_home = GetMountedUserHomePath(obfuscated_username);
const FilePath root_home = GetMountedRootHomePath(obfuscated_username);
switch (mount_type) {
case MountType::ECRYPTFS:
if (!SetUpEcryptfsMount(obfuscated_username, fek_signature,
fnek_signature,
GetUserMountDirectory(obfuscated_username))) {
return StorageStatus::Make(FROM_HERE, "Can't setup ecryptfs",
MOUNT_ERROR_MOUNT_ECRYPTFS_FAILED);
}
break;
case MountType::ECRYPTFS_TO_DIR_CRYPTO:
if (!SetUpEcryptfsMount(
obfuscated_username, fek_signature, fnek_signature,
GetUserTemporaryMountDirectory(obfuscated_username))) {
return StorageStatus::Make(
FROM_HERE, "Can't setup ecryptfs for migration to fscrypt",
MOUNT_ERROR_MOUNT_ECRYPTFS_FAILED);
}
SetUpDircryptoMount(obfuscated_username);
return StorageStatus::Ok();
case MountType::ECRYPTFS_TO_DMCRYPT:
if (!SetUpEcryptfsMount(
obfuscated_username, fek_signature, fnek_signature,
GetUserTemporaryMountDirectory(obfuscated_username))) {
return StorageStatus::Make(
FROM_HERE, "Can't setup ecryptfs for migration to dmcrypt",
MOUNT_ERROR_MOUNT_ECRYPTFS_FAILED);
}
if (!SetUpDmcryptMount(obfuscated_username,
GetUserMountDirectory(obfuscated_username))) {
return StorageStatus::Make(
FROM_HERE, "Can't setup dmcrypt to migrate from ecryptfs",
MOUNT_ERROR_MOUNT_DMCRYPT_FAILED);
}
if (!MountCacheSubdirectories(
obfuscated_username,
GetUserMountDirectory(obfuscated_username))) {
return StorageStatus::Make(
FROM_HERE, "Can't setup dmcrypt cache to migrate from ecryptfs",
MOUNT_ERROR_MOUNT_DMCRYPT_FAILED);
}
if (!MountDaemonStoreCacheDirectories(root_home, obfuscated_username)) {
return StorageStatus::Make(FROM_HERE, "Can't mount daemon-store-cache",
MOUNT_ERROR_MOUNT_DMCRYPT_FAILED);
}
return StorageStatus::Ok();
case MountType::DIR_CRYPTO:
SetUpDircryptoMount(obfuscated_username);
break;
case MountType::DIR_CRYPTO_TO_DMCRYPT:
SetUpDircryptoMount(obfuscated_username);
if (!SetUpDmcryptMount(
obfuscated_username,
GetUserTemporaryMountDirectory(obfuscated_username))) {
return StorageStatus::Make(
FROM_HERE, "Can't setup dmcrypt to migrate from fscrypt",
MOUNT_ERROR_MOUNT_DMCRYPT_FAILED);
}
if (!MountCacheSubdirectories(
obfuscated_username,
GetUserTemporaryMountDirectory(obfuscated_username))) {
return StorageStatus::Make(
FROM_HERE, "Can't setup dmcrypt cache to migrate from fscrypt",
MOUNT_ERROR_MOUNT_DMCRYPT_FAILED);
}
if (!MountDaemonStoreCacheDirectories(root_home, obfuscated_username)) {
return StorageStatus::Make(FROM_HERE, "Can't mount daemon-store-cache",
MOUNT_ERROR_MOUNT_DMCRYPT_FAILED);
}
return StorageStatus::Ok();
case MountType::DMCRYPT:
if (!SetUpDmcryptMount(obfuscated_username,
GetUserMountDirectory(obfuscated_username))) {
return StorageStatus::Make(FROM_HERE, "Dm-crypt mount failed",
MOUNT_ERROR_MOUNT_DMCRYPT_FAILED);
}
break;
case MountType::EPHEMERAL:
case MountType::NONE:
NOTREACHED();
}
if (!IsFirstMountComplete(obfuscated_username)) {
CopySkeleton(user_home);
}
// When migrating, it's better to avoid exposing the new ext4 crypto dir.
if (!MountHomesAndDaemonStores(username, obfuscated_username, user_home,
root_home)) {
return StorageStatus::Make(
FROM_HERE, "Can't mount home or daemonstore",
MOUNT_ERROR_MOUNT_HOMES_AND_DAEMON_STORES_FAILED);
}
// TODO(sarthakkukreti): This can't be moved due to child mount propagation
// issues. Figure out how to make it propagate properly to move to the switch
// above.
if (mount_type == MountType::DMCRYPT &&
!MountCacheSubdirectories(obfuscated_username,
GetUserMountDirectory(obfuscated_username))) {
return StorageStatus::Make(
FROM_HERE,
"Failed to mount tracked subdirectories from the cache volume",
MOUNT_ERROR_MOUNT_DMCRYPT_FAILED);
}
// Mount daemon store cache directories from .cache into /run/daemon-store.
if (!MountDaemonStoreCacheDirectories(root_home, obfuscated_username)) {
return StorageStatus::Make(FROM_HERE, "Can't mount daemon-store-cache",
MOUNT_ERROR_MOUNT_DMCRYPT_FAILED);
}
return StorageStatus::Ok();
}
// TODO(dlunev): make specific errors returned. MOUNT_ERROR_FATAL for now
// to preserve the existing expectations..
StorageStatus Mounter::PerformEphemeralMount(
const Username& username, const FilePath& ephemeral_loop_device) {
const ObfuscatedUsername obfuscated_username = SanitizeUserName(username);
const FilePath mount_point =
GetUserEphemeralMountDirectory(obfuscated_username);
LOG(ERROR) << "Directory is" << mount_point.value();
if (!platform_->CreateDirectory(mount_point)) {
return StorageStatus::Make(
FROM_HERE, "Directory creation failed for " + mount_point.value(),
MOUNT_ERROR_FATAL);
}
if (!MountAndPush(ephemeral_loop_device, mount_point, kEphemeralMountType,
kEphemeralMountOptions)) {
return StorageStatus::Make(FROM_HERE, "Can't mount ephemeral",
MOUNT_ERROR_FATAL);
}
// Set SELinux context first, so that the created user & root directory have
// the correct context.
if (!SetUpSELinuxContextForEphemeralCryptohome(platform_, mount_point)) {
return StorageStatus::Make(FROM_HERE,
"Can't setup SELinux context for ephemeral",
MOUNT_ERROR_FATAL);
}
if (!EnsureUserMountPoints(username)) {
return StorageStatus::Make(
FROM_HERE, "Can't ensure mountpoints for ephemeral", MOUNT_ERROR_FATAL);
}
const FilePath user_home =
GetMountedEphemeralUserHomePath(obfuscated_username);
const FilePath root_home =
GetMountedEphemeralRootHomePath(obfuscated_username);
if (!CreateVaultDirectoryStructure(
platform_,
GetCommonSubdirectories(mount_point, bind_mount_downloads_))) {
return StorageStatus::Make(FROM_HERE,
"Can't create vault structure for ephemeral",
MOUNT_ERROR_FATAL);
}
CopySkeleton(user_home);
if (!MountHomesAndDaemonStores(username, obfuscated_username, user_home,
root_home)) {
return StorageStatus::Make(FROM_HERE,
"Can't mount home and daemonstore for ephemeral",
MOUNT_ERROR_FATAL);
}
if (!MountDaemonStoreCacheDirectories(root_home, obfuscated_username)) {
return StorageStatus::Make(
FROM_HERE, "Can't mount home and daemon-store-cache for ephemeral",
MOUNT_ERROR_FATAL);
}
return StorageStatus::Ok();
}
void Mounter::UnmountAll() {
FilePath src, dest;
while (stack_.Pop(&src, &dest)) {
ForceUnmount(src, dest);
}
// Clean up destination directory for ephemeral loop device mounts.
const FilePath ephemeral_mount_path =
FilePath(kEphemeralCryptohomeDir).Append(kEphemeralMountDir);
platform_->DeletePathRecursively(ephemeral_mount_path);
}
void Mounter::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 Mounter::CanPerformEphemeralMount() const {
return !MountPerformed();
}
bool Mounter::MountPerformed() const {
return stack_.size() > 0;
}
bool Mounter::IsPathMounted(const FilePath& path) const {
return stack_.ContainsDest(path);
}
std::vector<FilePath> Mounter::MountedPaths() const {
return stack_.MountDestinations();
}
} // namespace cryptohome