blob: 93782a0bc4f612bd310287b2dff0858501476c8a [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 <tuple>
#include <unordered_set>
#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/cryptohome_metrics.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;
namespace cryptohome {
namespace {
const char kEphemeralCryptohomeRootContext[] =
"u:object_r:cros_home_shadow_uid:s0";
const int kDefaultEcryptfsKeySize = CRYPTOHOME_AES_KEY_BYTES;
FilePath GetUserEphemeralMountDirectory(
const std::string& obfuscated_username) {
return FilePath(kEphemeralCryptohomeDir)
.Append(kEphemeralMountDir)
.Append(obfuscated_username);
}
FilePath GetMountedEphemeralRootHomePath(
const std::string& obfuscated_username) {
return GetUserEphemeralMountDirectory(obfuscated_username)
.Append(kRootHomeSuffix);
}
FilePath GetMountedEphemeralUserHomePath(
const std::string& obfuscated_username) {
return GetUserEphemeralMountDirectory(obfuscated_username)
.Append(kUserHomeSuffix);
}
// 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,
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 {
base::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, kChronosUid,
kChronosAccessGid},
{dir.Append(kUserHomeSuffix).Append(kCacheDir), kTrackedDirMode,
kChronosUid, kChronosGid},
{dir.Append(kUserHomeSuffix)
.Append(kGCacheDir)
.Append(kGCacheVersion2Dir),
kAccessMode | kGroupWriteAccess, kChronosUid, kChronosAccessGid}};
}
std::vector<DirectoryACL> GetCommonSubdirectories(const FilePath& dir) {
auto result = std::vector<DirectoryACL>{
{dir.Append(kRootHomeSuffix), kRootDirMode, kRootUid, kDaemonStoreGid},
{dir.Append(kUserHomeSuffix), kAccessMode, kChronosUid,
kChronosAccessGid},
{dir.Append(kUserHomeSuffix).Append(kDownloadsDir), kAccessMode,
kChronosUid, kChronosAccessGid},
{dir.Append(kUserHomeSuffix).Append(kMyFilesDir), kAccessMode,
kChronosUid, kChronosAccessGid},
{dir.Append(kUserHomeSuffix).Append(kMyFilesDir).Append(kDownloadsDir),
kAccessMode, kChronosUid, 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) {
auto data_volume_subdirs = GetCommonSubdirectories(dir.Append(kMountDir));
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(Platform* platform, DirectoryACL dir) {
if (dir.uid != 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(Platform* platform, DirectoryACL dir) {
// Ignore root owned directories - those are recreated if they have wrong
// attributes.
if (dir.uid == 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(
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(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;
}
} // namespace
const char kDefaultHomeDir[] = "/home/chronos/user";
MountHelper::MountHelper(bool legacy_mount,
bool bind_mount_downloads,
Platform* platform)
: legacy_mount_(legacy_mount),
bind_mount_downloads_(bind_mount_downloads),
platform_(platform) {}
// 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);
}
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;
}
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, kRootUid, kRootGid)) {
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_->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(), kChronosUid, kChronosGid)) {
LOG(ERROR) << "The paths to mountpoints are inconsistent";
return false;
}
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
multi_home_user, kUserMountPointMode, kChronosUid,
kChronosAccessGid)) {
PLOG(ERROR) << "Can't create: " << multi_home_user;
return false;
}
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
new_user_path, kUserMountPointMode, kChronosUid, kChronosAccessGid)) {
PLOG(ERROR) << "Can't create: " << new_user_path;
return false;
}
if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions(
multi_home_root, kRootMountPointMode, kRootUid, kRootGid)) {
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, kChronosUid, kChronosGid,
false)) {
LOG(ERROR) << "Couldn't change owner (" << kChronosUid << ":"
<< kChronosGid
<< ") 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, kChronosUid, kChronosGid)) {
LOG(ERROR) << "SafeCreateDirAndSetOwnership() failed: "
<< destination_dir.value();
}
RecursiveCopy(FilePath(next_path), destination_dir);
}
}
void MountHelper::CopySkeleton(const FilePath& destination) const {
RecursiveCopy(SkelDir(), destination);
}
bool MountHelper::IsFirstMountComplete(
const std::string& obfuscated_username) const {
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)) {
initial_nodes.insert(dir.path);
}
std::unique_ptr<cryptohome::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<cryptohome::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 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),
RemountOption::kMountsFlowIn))
return false;
return true;
}
bool MountHelper::HandleMyFilesDownloads(const base::FilePath& user_home) {
const FilePath downloads = user_home.Append(kDownloadsDir);
const FilePath downloads_in_myfiles =
user_home.Append(kMyFilesDir).Append(kDownloadsDir);
if (!bind_mount_downloads_) {
MigrateDirectory(downloads_in_myfiles, downloads);
return true;
}
// 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 exists.
int migrated_items = MigrateDirectory(downloads, downloads_in_myfiles);
ReportMaskedDownloadsItems(migrated_items);
if (!BindAndPush(downloads, downloads_in_myfiles))
return false;
return true;
}
bool MountHelper::MountAndPush(const base::FilePath& src,
const base::FilePath& dest,
const std::string& type,
const std::string& options) {
uint32_t mount_flags = 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 MountHelper::BindAndPush(const FilePath& src,
const FilePath& dest,
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 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;
}
int MountHelper::MigrateDirectory(const base::FilePath& dst,
const base::FilePath& src) const {
VLOG(1) << "Migrating directory " << src << " -> " << dst;
int num_items = 0;
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());
num_items++;
// 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);
}
}
return num_items;
}
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 subsidiary mounts without needing
// to replicate that across multiple mount points.
if (!BindAndPush(user_home, user_home, 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, RemountOption::kMountsFlowIn))
return false;
// Mount /home/user/<user_hash>.
const FilePath user_multi_home = GetUserPath(username);
if (!BindAndPush(user_home, user_multi_home, RemountOption::kMountsFlowIn))
return false;
// Mount /home/root/<user_hash>.
const FilePath root_multi_home = GetRootPath(username);
if (!BindAndPush(root_home, root_multi_home, 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 MountHelper::MountCacheSubdirectories(
const std::string& obfuscated_username,
const base::FilePath& data_directory) {
FilePath cache_directory = GetDmcryptUserCacheDirectory(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, 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 MountHelper::SetUpEcryptfsMount(const std::string& 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));
// 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 MountHelper::SetUpDircryptoMount(const std::string& obfuscated_username) {
const FilePath mount_point = GetUserMountDirectory(obfuscated_username);
std::ignore = CreateVaultDirectoryStructure(
platform_, GetCommonSubdirectories(mount_point));
std::ignore =
SetTrackingXattr(platform_, GetCommonSubdirectories(mount_point));
}
bool MountHelper::SetUpDmcryptMount(const std::string& obfuscated_username,
const base::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)));
return true;
}
MountError MountHelper::PerformMount(MountType mount_type,
const std::string& username,
const std::string& fek_signature,
const std::string& fnek_signature) {
const std::string obfuscated_username = SanitizeUserName(username);
if (!EnsureUserMountPoints(username)) {
LOG(ERROR) << "Error creating mountpoint.";
return 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))) {
LOG(ERROR) << "Mount point is busy: "
<< GetUserMountDirectory(obfuscated_username);
return MOUNT_ERROR_FATAL;
}
switch (mount_type) {
case MountType::ECRYPTFS:
if (!SetUpEcryptfsMount(obfuscated_username, fek_signature,
fnek_signature,
GetUserMountDirectory(obfuscated_username))) {
return MOUNT_ERROR_MOUNT_ECRYPTFS_FAILED;
}
break;
case MountType::ECRYPTFS_TO_DIR_CRYPTO:
if (!SetUpEcryptfsMount(
obfuscated_username, fek_signature, fnek_signature,
GetUserTemporaryMountDirectory(obfuscated_username))) {
return MOUNT_ERROR_MOUNT_ECRYPTFS_FAILED;
}
SetUpDircryptoMount(obfuscated_username);
return MOUNT_ERROR_NONE;
case MountType::ECRYPTFS_TO_DMCRYPT:
if (!SetUpEcryptfsMount(
obfuscated_username, fek_signature, fnek_signature,
GetUserTemporaryMountDirectory(obfuscated_username))) {
return MOUNT_ERROR_MOUNT_ECRYPTFS_FAILED;
}
if (!SetUpDmcryptMount(obfuscated_username,
GetUserMountDirectory(obfuscated_username)) ||
!MountCacheSubdirectories(
obfuscated_username,
GetUserMountDirectory(obfuscated_username))) {
LOG(ERROR) << "Dm-crypt mount failed";
return MOUNT_ERROR_MOUNT_DMCRYPT_FAILED;
}
return MOUNT_ERROR_NONE;
case MountType::DIR_CRYPTO:
SetUpDircryptoMount(obfuscated_username);
break;
case MountType::DIR_CRYPTO_TO_DMCRYPT:
SetUpDircryptoMount(obfuscated_username);
if (!SetUpDmcryptMount(
obfuscated_username,
GetUserTemporaryMountDirectory(obfuscated_username)) ||
!MountCacheSubdirectories(
obfuscated_username,
GetUserTemporaryMountDirectory(obfuscated_username))) {
LOG(ERROR) << "Dm-crypt mount failed";
return MOUNT_ERROR_MOUNT_DMCRYPT_FAILED;
}
return MOUNT_ERROR_NONE;
case MountType::DMCRYPT:
if (!SetUpDmcryptMount(obfuscated_username,
GetUserMountDirectory(obfuscated_username))) {
LOG(ERROR) << "Dm-crypt mount failed";
return MOUNT_ERROR_MOUNT_DMCRYPT_FAILED;
}
break;
case MountType::EPHEMERAL:
case MountType::NONE:
NOTREACHED();
}
const FilePath user_home = GetMountedUserHomePath(obfuscated_username);
const FilePath root_home = GetMountedRootHomePath(obfuscated_username);
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 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))) {
LOG(ERROR)
<< "Failed to mount tracked subdirectories from the cache volume";
return MOUNT_ERROR_MOUNT_DMCRYPT_FAILED;
}
return MOUNT_ERROR_NONE;
}
// TODO(dlunev): make specific errors returned. MOUNT_ERROR_FATAL for now
// to preserve the existing expectations..
MountError MountHelper::PerformEphemeralMount(
const std::string& username, const FilePath& ephemeral_loop_device) {
const std::string obfuscated_username = SanitizeUserName(username);
const FilePath mount_point =
GetUserEphemeralMountDirectory(obfuscated_username);
LOG(ERROR) << "Directory is" << mount_point.value();
if (!platform_->CreateDirectory(mount_point)) {
PLOG(ERROR) << "Directory creation failed for " << mount_point.value();
return MOUNT_ERROR_FATAL;
}
if (!MountAndPush(ephemeral_loop_device, mount_point, kEphemeralMountType,
kEphemeralMountOptions)) {
LOG(ERROR) << "Can't mount ephemeral mount point";
return MOUNT_ERROR_FATAL;
}
// 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 MOUNT_ERROR_FATAL;
}
if (!EnsureUserMountPoints(username)) {
return MOUNT_ERROR_FATAL;
}
const FilePath user_home =
GetMountedEphemeralUserHomePath(obfuscated_username);
const FilePath root_home =
GetMountedEphemeralRootHomePath(obfuscated_username);
if (!CreateVaultDirectoryStructure(platform_,
GetCommonSubdirectories(mount_point))) {
return MOUNT_ERROR_FATAL;
}
CopySkeleton(user_home);
if (!MountHomesAndDaemonStores(username, obfuscated_username, user_home,
root_home)) {
return MOUNT_ERROR_FATAL;
}
return MOUNT_ERROR_NONE;
}
void MountHelper::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 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 !MountPerformed();
}
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