| // Copyright 2013 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Contains the implementation of class Mount. |
| |
| #include "cryptohome/storage/mount.h" |
| |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| |
| #include <memory> |
| #include <tuple> |
| #include <utility> |
| |
| #include <absl/cleanup/cleanup.h> |
| #include <base/check.h> |
| #include <base/files/file_path.h> |
| #include <base/functional/bind.h> |
| #include <base/functional/callback_helpers.h> |
| #include <base/hash/sha1.h> |
| #include <base/location.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/threading/platform_thread.h> |
| #include <brillo/cryptohome.h> |
| #include <brillo/process/process.h> |
| #include <brillo/scoped_umask.h> |
| #include <brillo/secure_blob.h> |
| #include <chromeos/constants/cryptohome.h> |
| #include <cryptohome/proto_bindings/UserDataAuth.pb.h> |
| #include <libhwsec-foundation/crypto/secure_blob_util.h> |
| #include <libhwsec-foundation/status/status_chain_macros.h> |
| #include <libstorage/platform/platform.h> |
| |
| #include "cryptohome/cryptohome_metrics.h" |
| #include "cryptohome/data_migrator/migration_helper.h" |
| #include "cryptohome/filesystem_layout.h" |
| #include "cryptohome/storage/dircrypto_migration_helper_delegate.h" |
| #include "cryptohome/storage/error.h" |
| #include "cryptohome/storage/homedirs.h" |
| #include "cryptohome/storage/mount_helper_interface.h" |
| #include "cryptohome/storage/out_of_process_mount_helper.h" |
| #include "cryptohome/vault_keyset.h" |
| |
| using base::FilePath; |
| using base::StringPrintf; |
| using brillo::BlobToString; |
| using brillo::SecureBlob; |
| using brillo::cryptohome::home::IsSanitizedUserName; |
| using brillo::cryptohome::home::SanitizeUserName; |
| using cryptohome::data_migrator::MigrationHelper; |
| using hwsec_foundation::SecureBlobToHex; |
| |
| namespace { |
| constexpr bool __attribute__((unused)) MountUserSessionOOP() { |
| return USE_MOUNT_OOP; |
| } |
| |
| } // namespace |
| |
| namespace cryptohome { |
| |
| Mount::Mount(libstorage::Platform* platform, |
| HomeDirs* homedirs, |
| std::unique_ptr<MountHelperInterface> mount_helper) |
| : platform_(platform), |
| homedirs_(homedirs), |
| dircrypto_migration_stopped_condition_(&active_dircrypto_migrator_lock_), |
| active_mounter_(std::move(mount_helper)) {} |
| |
| Mount::Mount() : Mount(nullptr, nullptr, nullptr) {} |
| |
| Mount::~Mount() { |
| if (IsMounted()) |
| UnmountCryptohome(); |
| } |
| |
| StorageStatus Mount::MountEphemeralCryptohome(const Username& username) { |
| username_ = username; |
| ObfuscatedUsername obfuscated_username = SanitizeUserName(username_); |
| |
| absl::Cleanup unmount_on_exit = [this]() { UnmountCryptohome(); }; |
| |
| // Ephemeral cryptohome can't be mounted twice. |
| CHECK(active_mounter_->CanPerformEphemeralMount()); |
| |
| user_cryptohome_vault_ = homedirs_->GetVaultFactory()->Generate( |
| obfuscated_username, libstorage::FileSystemKeyReference(), |
| libstorage::StorageContainerType::kEphemeral); |
| |
| if (!user_cryptohome_vault_) { |
| return StorageStatus::Make(FROM_HERE, "Failed to generate ephemeral vault.", |
| MOUNT_ERROR_FATAL); |
| } |
| |
| RETURN_IF_ERROR(user_cryptohome_vault_->Setup(libstorage::FileSystemKey())) |
| .LogError() |
| << "Failed to setup ephemeral vault"; |
| |
| RETURN_IF_ERROR( |
| active_mounter_->PerformEphemeralMount( |
| username, user_cryptohome_vault_->GetContainerBackingLocation())) |
| .LogError() |
| << "PerformEphemeralMount() failed, aborting ephemeral mount"; |
| |
| std::move(unmount_on_exit).Cancel(); |
| return StorageStatus::Ok(); |
| } |
| |
| StorageStatus Mount::MountCryptohome( |
| const Username& username, |
| const FileSystemKeyset& file_system_keyset, |
| const CryptohomeVault::Options& vault_options) { |
| username_ = username; |
| ObfuscatedUsername obfuscated_username = SanitizeUserName(username_); |
| |
| ReportTimerStart(kVaultSetupTimer); |
| |
| ASSIGN_OR_RETURN(libstorage::StorageContainerType vault_type, |
| homedirs_->PickVaultType(obfuscated_username, vault_options), |
| (_.LogError() << "Could't pick vault type")); |
| |
| user_cryptohome_vault_ = homedirs_->GetVaultFactory()->Generate( |
| obfuscated_username, file_system_keyset.KeyReference(), vault_type, |
| homedirs_->KeylockerForStorageEncryptionEnabled()); |
| |
| if (GetMountType() == MountType::NONE) { |
| // TODO(dlunev): there should be a more proper error code set. CREATE_FAILED |
| // is a temporary returned error to keep the behaviour unchanged while |
| // refactoring. |
| return StorageStatus::Make(FROM_HERE, "Could't generate vault", |
| MOUNT_ERROR_CREATE_CRYPTOHOME_FAILED); |
| } |
| |
| // Ensure we don't leave any mounts hanging on intermediate errors. |
| // The closure won't outlive the class so |this| will always be valid. |
| // |active_mounter_| will always be valid since this callback runs in the |
| // destructor at the latest. |
| absl::Cleanup unmount_on_exit = [this]() { UnmountCryptohome(); }; |
| |
| // Set up the cryptohome vault for mount. |
| RETURN_IF_ERROR(user_cryptohome_vault_->Setup(file_system_keyset.Key())) |
| .LogError() |
| << "Failed to setup persistent vault"; |
| ReportTimerStop(kVaultSetupTimer); |
| |
| std::string key_signature = |
| SecureBlobToHex(file_system_keyset.KeyReference().fek_sig); |
| std::string fnek_signature = |
| SecureBlobToHex(file_system_keyset.KeyReference().fnek_sig); |
| |
| ReportTimerStart(kPerformMountTimer); |
| RETURN_IF_ERROR(active_mounter_->PerformMount(GetMountType(), username_, |
| key_signature, fnek_signature)) |
| .LogError() |
| << "MountHelper::PerformMount failed"; |
| |
| ReportTimerStop(kPerformMountTimer); |
| |
| // Once mount is complete, do a deferred teardown for on the vault. |
| // The teardown occurs when the vault's containers has no references ie. no |
| // mount holds the containers open. |
| // This is useful if cryptohome crashes: on recovery, if cryptohome decides to |
| // cleanup mounts, the underlying devices (in case of dm-crypt cryptohome) |
| // will be automatically torn down. |
| |
| // TODO(sarthakkukreti): remove this in favor of using the session-manager |
| // as the source-of-truth during crash recovery. That would allow us to |
| // reconstruct the run-time state of cryptohome vault(s) at the time of crash. |
| std::ignore = user_cryptohome_vault_->SetLazyTeardownWhenUnused(); |
| |
| // At this point we're done mounting. |
| std::move(unmount_on_exit).Cancel(); |
| |
| user_cryptohome_vault_->ReportVaultEncryptionType(); |
| |
| ReportTimerStart(kSELinuxRelabelTimer); |
| |
| // TODO(fqj,b/116072767) Ignore errors since unlabeled files are currently |
| // still okay during current development progress. |
| // Report the success rate of the restore SELinux context operation for user |
| // directory to decide on the action on failure when we move on to the next |
| // phase in the cryptohome SELinux development, i.e. making cryptohome |
| // enforcing. |
| std::vector<base::FilePath> exclude_path; |
| // The exclusion of android-data is intentional here because the directory and |
| // all its contents are labelled by the Android policy and cryptohome should |
| // not attempt to label them. |
| base::FilePath path_ap = GetUserMountDirectory(obfuscated_username) |
| .Append("root") |
| .Append("android-data") |
| .Append("data"); |
| exclude_path.push_back(path_ap); |
| platform_->AddGlobalSELinuxRestoreconExclusion(exclude_path); |
| |
| // Restore context for UserPath. |
| bool result = platform_->RestoreSELinuxContexts(UserPath(obfuscated_username), |
| true /*recursive*/); |
| |
| // For dm-crypt cryptohomes, cache volumes may be reset as a part of the |
| // cleanup mechanism; the newly created filesystem's root inode will be |
| // unlabeled. Relabel the mounted cache directory unconditionally; this will |
| // be a no-op if the cache directory was already relabelled but if the cache |
| // volume is reset, this will restore the SELinux contexts of the cache |
| // volume. |
| base::FilePath user_cache_dir = |
| GetDmcryptUserCacheDirectory(obfuscated_username); |
| if (base::PathExists(user_cache_dir)) { |
| result &= |
| platform_->RestoreSELinuxContexts(user_cache_dir, true /*recursive*/); |
| } |
| |
| if (!result) { |
| LOG(ERROR) << "RestoreSELinuxContexts(" << UserPath(obfuscated_username) |
| << ") failed."; |
| } |
| ReportTimerStop(kSELinuxRelabelTimer); |
| |
| ReportRestoreSELinuxContextResultForHomeDir(result); |
| |
| return StorageStatus::Ok(); |
| } |
| |
| bool Mount::UnmountCryptohome() { |
| // There should be no file access when unmounting. |
| // Stop dircrypto migration if in progress. |
| MaybeCancelMigrateEncryptionAndWait(); |
| |
| active_mounter_->UnmountAll(); |
| |
| // Resetting the vault teardowns the enclosed containers if setup succeeded. |
| user_cryptohome_vault_.reset(); |
| |
| return true; |
| } |
| |
| void Mount::UnmountCryptohomeFromMigration() { |
| active_mounter_->UnmountAll(); |
| |
| // Resetting the vault teardowns the enclosed containers if setup succeeded. |
| user_cryptohome_vault_.reset(); |
| } |
| |
| bool Mount::IsMounted() const { |
| return active_mounter_ && active_mounter_->MountPerformed(); |
| } |
| |
| bool Mount::IsEphemeral() const { |
| return GetMountType() == MountType::EPHEMERAL; |
| } |
| |
| bool Mount::IsNonEphemeralMounted() const { |
| return IsMounted() && !IsEphemeral(); |
| } |
| |
| bool Mount::OwnsMountPoint(const FilePath& path) const { |
| return active_mounter_ && active_mounter_->IsPathMounted(path); |
| } |
| |
| StorageStatus Mount::EvictCryptohomeKey() { |
| if (user_cryptohome_vault_) { |
| return user_cryptohome_vault_->EvictKey(); |
| } |
| return StorageStatus::Make(FROM_HERE, "Failed to evict cryptohome key.", |
| MOUNT_ERROR_INVALID_ARGS); |
| } |
| |
| StorageStatus Mount::RestoreCryptohomeKey( |
| const FileSystemKeyset& file_system_keyset) { |
| if (!user_cryptohome_vault_) { |
| return StorageStatus::Make(FROM_HERE, "Cryptohome vault has not set up.", |
| MOUNT_ERROR_KEY_RESTORE_FAILED); |
| } |
| if (!IsNonEphemeralMounted()) { |
| return StorageStatus::Make( |
| FROM_HERE, "Non-mounted or ephemeral mount can't restore key.", |
| MOUNT_ERROR_KEY_RESTORE_FAILED); |
| } |
| |
| RETURN_IF_ERROR(user_cryptohome_vault_->RestoreKey(file_system_keyset.Key())) |
| .LogError() |
| << "Failed to restore key of the persistent vault"; |
| return StorageStatus::Ok(); |
| } |
| |
| MountType Mount::GetMountType() const { |
| if (!user_cryptohome_vault_) { |
| return MountType::NONE; |
| } |
| return user_cryptohome_vault_->GetMountType(); |
| } |
| |
| bool Mount::MigrateEncryption(const MigrationCallback& callback, |
| MigrationType migration_type) { |
| if (!IsMounted()) { |
| LOG(ERROR) << "Not mounted."; |
| return false; |
| } |
| |
| auto migration_helper_callback = base::BindRepeating( |
| [](const MigrationCallback& callback, uint64_t current_bytes, |
| uint64_t total_bytes) { |
| user_data_auth::DircryptoMigrationProgress progress; |
| if (total_bytes == 0) { |
| // Since total_bytes and current_bytes in |progress| are discarded by |
| // client unless |progress.status| is DIRCRYPTO_MIGRATION_IN_PROGRESS, |
| // they are left with the default value of 0 here. |
| progress.set_status(user_data_auth::DIRCRYPTO_MIGRATION_INITIALIZING); |
| } else { |
| progress.set_status(user_data_auth::DIRCRYPTO_MIGRATION_IN_PROGRESS); |
| progress.set_current_bytes(current_bytes); |
| progress.set_total_bytes(total_bytes); |
| } |
| callback.Run(progress); |
| }, |
| callback); |
| |
| auto mount_type = GetMountType(); |
| if (mount_type == MountType::ECRYPTFS_TO_DIR_CRYPTO || |
| mount_type == MountType::ECRYPTFS_TO_DMCRYPT) { |
| return MigrateFromEcryptfs(migration_helper_callback, migration_type); |
| } |
| if (mount_type == MountType::DIR_CRYPTO_TO_DMCRYPT) { |
| return MigrateFromDircrypto(migration_helper_callback, migration_type); |
| } |
| LOG(ERROR) << "Not mounted for migration."; |
| return false; |
| } |
| |
| bool Mount::MigrateFromEcryptfs( |
| const MigrationHelper::ProgressCallback& callback, |
| MigrationType migration_type) { |
| ObfuscatedUsername obfuscated_username = SanitizeUserName(username_); |
| FilePath source = GetUserTemporaryMountDirectory(obfuscated_username); |
| FilePath destination = GetUserMountDirectory(obfuscated_username); |
| FilePath status_files_dir = UserPath(obfuscated_username); |
| |
| if (!platform_->DirectoryExists(source) || !OwnsMountPoint(source)) { |
| LOG(ERROR) << "Unexpected ecryptfs source state."; |
| return false; |
| } |
| // Do migration. |
| if (!PerformMigration(callback, source, destination, status_files_dir, |
| migration_type)) { |
| return false; |
| } |
| |
| // Clean up. |
| FilePath vault_path = GetEcryptfsUserVaultPath(obfuscated_username); |
| if (!platform_->DeletePathRecursively(source) || |
| !platform_->DeletePathRecursively(vault_path)) { |
| LOG(ERROR) << "Failed to delete the old vault."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool Mount::MigrateFromDircrypto( |
| const MigrationHelper::ProgressCallback& callback, |
| MigrationType migration_type) { |
| ObfuscatedUsername obfuscated_username = SanitizeUserName(username_); |
| FilePath source = GetUserMountDirectory(obfuscated_username); |
| FilePath destination = GetUserTemporaryMountDirectory(obfuscated_username); |
| FilePath status_files_dir = UserPath(obfuscated_username); |
| |
| if (!platform_->DirectoryExists(destination) || |
| !OwnsMountPoint(destination)) { |
| LOG(ERROR) << "Unexpected dmcrypt destination state."; |
| return false; |
| } |
| // Do migration. |
| if (!PerformMigration(callback, source, destination, status_files_dir, |
| migration_type)) { |
| return false; |
| } |
| |
| // Clean up, in case of dircrypto source is the vault path, and destination |
| // is a temp mount point. |
| FilePath vault_path = GetEcryptfsUserVaultPath(obfuscated_username); |
| if (!platform_->DeletePathRecursively(source) || |
| !platform_->DeletePathRecursively(destination)) { |
| LOG(ERROR) << "Failed to delete the old vault."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool Mount::PerformMigration(const MigrationHelper::ProgressCallback& callback, |
| const base::FilePath& source, |
| const base::FilePath& destination, |
| const base::FilePath& status_files_dir, |
| MigrationType migration_type) { |
| constexpr uint64_t kMaxChunkSize = 128 * 1024 * 1024; |
| DircryptoMigrationHelperDelegate delegate(platform_, destination, |
| migration_type); |
| MigrationHelper migrator(platform_, &delegate, source, destination, |
| status_files_dir, kMaxChunkSize); |
| |
| { // Abort if already cancelled. |
| base::AutoLock lock(active_dircrypto_migrator_lock_); |
| if (is_dircrypto_migration_cancelled_) |
| return false; |
| CHECK(!active_dircrypto_migrator_); |
| active_dircrypto_migrator_ = &migrator; |
| } |
| bool success = migrator.Migrate(callback); |
| |
| UnmountCryptohomeFromMigration(); |
| { // Signal the waiting thread. |
| base::AutoLock lock(active_dircrypto_migrator_lock_); |
| active_dircrypto_migrator_ = nullptr; |
| dircrypto_migration_stopped_condition_.Signal(); |
| } |
| if (!success) { |
| LOG(ERROR) << "Failed to migrate."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool Mount::ResetApplicationContainer(const std::string& application) { |
| if (!user_cryptohome_vault_) { |
| LOG(ERROR) << "No active vault containers to reset."; |
| return false; |
| } |
| |
| return user_cryptohome_vault_->ResetApplicationContainer(application); |
| } |
| |
| void Mount::MaybeCancelMigrateEncryptionAndWait() { |
| base::AutoLock lock(active_dircrypto_migrator_lock_); |
| is_dircrypto_migration_cancelled_ = true; |
| while (active_dircrypto_migrator_) { |
| active_dircrypto_migrator_->Cancel(); |
| LOG(INFO) << "Waiting for dircrypto migration to stop."; |
| dircrypto_migration_stopped_condition_.Wait(); |
| LOG(INFO) << "Dircrypto migration stopped."; |
| } |
| } |
| |
| } // namespace cryptohome |