| // Copyright (c) 2013 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. |
| |
| // Contains the implementation of class Mount. |
| |
| #include "cryptohome/storage/mount.h" |
| |
| #include <errno.h> |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/callback_helpers.h> |
| #include <base/check.h> |
| #include <base/files/file_path.h> |
| #include <base/logging.h> |
| #include <base/hash/sha1.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 <google/protobuf/util/message_differencer.h> |
| |
| #include "cryptohome/crypto/secure_blob_util.h" |
| #include "cryptohome/cryptohome_common.h" |
| #include "cryptohome/cryptohome_metrics.h" |
| #include "cryptohome/dircrypto_data_migrator/migration_helper.h" |
| #include "cryptohome/dircrypto_util.h" |
| #include "cryptohome/filesystem_layout.h" |
| #include "cryptohome/platform.h" |
| #include "cryptohome/storage/homedirs.h" |
| #include "cryptohome/storage/mount_utils.h" |
| #include "cryptohome/tpm.h" |
| #include "cryptohome/vault_keyset.h" |
| #include "cryptohome/vault_keyset.pb.h" |
| |
| using base::FilePath; |
| using base::StringPrintf; |
| using brillo::BlobToString; |
| using brillo::SecureBlob; |
| using brillo::cryptohome::home::GetRootPath; |
| using brillo::cryptohome::home::GetUserPath; |
| using brillo::cryptohome::home::IsSanitizedUserName; |
| using brillo::cryptohome::home::kGuestUserName; |
| using brillo::cryptohome::home::SanitizeUserName; |
| using brillo::cryptohome::home::SanitizeUserNameWithSalt; |
| using google::protobuf::util::MessageDifferencer; |
| |
| namespace { |
| constexpr bool __attribute__((unused)) MountUserSessionOOP() { |
| return USE_MOUNT_OOP; |
| } |
| |
| } // namespace |
| |
| namespace cryptohome { |
| |
| const char kChapsUserName[] = "chaps"; |
| const char kDefaultSharedAccessGroup[] = "chronos-access"; |
| |
| void StartUserFileAttrsCleanerService(cryptohome::Platform* platform, |
| const std::string& username) { |
| std::unique_ptr<brillo::Process> file_attrs = |
| platform->CreateProcessInstance(); |
| |
| file_attrs->AddArg("/sbin/initctl"); |
| file_attrs->AddArg("start"); |
| file_attrs->AddArg("--no-wait"); |
| file_attrs->AddArg("file_attrs_cleaner_tool"); |
| file_attrs->AddArg( |
| base::StringPrintf("OBFUSCATED_USERNAME=%s", username.c_str())); |
| |
| if (file_attrs->Run() != 0) |
| PLOG(WARNING) << "Error while running file_attrs_cleaner_tool"; |
| } |
| |
| Mount::Mount(Platform* platform, HomeDirs* homedirs) |
| : default_user_(-1), |
| chaps_user_(-1), |
| default_group_(-1), |
| default_access_group_(-1), |
| system_salt_(), |
| platform_(platform), |
| homedirs_(homedirs), |
| legacy_mount_(true), |
| bind_mount_downloads_(true), |
| mount_type_(MountType::NONE), |
| dircrypto_migration_stopped_condition_(&active_dircrypto_migrator_lock_) { |
| } |
| |
| Mount::Mount() : Mount(nullptr, nullptr) {} |
| |
| Mount::~Mount() { |
| if (IsMounted()) |
| UnmountCryptohome(); |
| } |
| |
| bool Mount::Init(bool use_init_namespace) { |
| bool result = true; |
| |
| // Get the user id and group id of the default user |
| if (!platform_->GetUserId(kDefaultSharedUser, &default_user_, |
| &default_group_)) { |
| result = false; |
| } |
| |
| // Get the user id of the chaps user. |
| gid_t not_used; |
| if (!platform_->GetUserId(kChapsUserName, &chaps_user_, ¬_used)) { |
| result = false; |
| } |
| |
| // Get the group id of the default shared access group. |
| if (!platform_->GetGroupId(kDefaultSharedAccessGroup, |
| &default_access_group_)) { |
| result = false; |
| } |
| |
| // One-time load of the global system salt (used in generating username |
| // hashes) |
| if (!homedirs_->GetSystemSalt(&system_salt_)) { |
| LOG(ERROR) << "Failed to load or create the system salt"; |
| result = false; |
| } |
| |
| mounter_.reset(new MountHelper( |
| default_user_, default_group_, default_access_group_, system_salt_, |
| legacy_mount_, bind_mount_downloads_, platform_)); |
| active_mounter_ = mounter_.get(); |
| |
| // cryptohome_namespace_mounter enters the Chrome mount namespace and mounts |
| // the user cryptohome in that mount namespace if the flags are enabled. |
| // Chrome mount namespace is created by session_manager. cryptohome knows |
| // the path at which this mount namespace is created and uses that path to |
| // enter it. |
| if (!use_init_namespace) { |
| std::unique_ptr<MountNamespace> chrome_mnt_ns = |
| std::make_unique<MountNamespace>( |
| base::FilePath(kUserSessionMountNamespacePath), platform_); |
| |
| out_of_process_mounter_.reset(new OutOfProcessMountHelper( |
| system_salt_, std::move(chrome_mnt_ns), legacy_mount_, |
| bind_mount_downloads_, platform_)); |
| active_mounter_ = out_of_process_mounter_.get(); |
| } |
| |
| return result; |
| } |
| |
| MountError Mount::MountEphemeralCryptohome(const std::string& username) { |
| username_ = username; |
| |
| base::ScopedClosureRunner cleanup_runner(base::BindOnce( |
| base::IgnoreResult(&MountHelperInterface::TearDownEphemeralMount), |
| base::Unretained(active_mounter_))); |
| |
| // Ephemeral cryptohome can't be mounted twice. |
| CHECK(active_mounter_->CanPerformEphemeralMount()); |
| |
| if (!active_mounter_->PerformEphemeralMount(username)) { |
| LOG(ERROR) << "PerformEphemeralMount() failed, aborting ephemeral mount"; |
| return MOUNT_ERROR_FATAL; |
| } |
| |
| // Mount succeeded, move the clean-up closure to the instance variable. |
| mount_cleanup_ = cleanup_runner.Release(); |
| |
| mount_type_ = MountType::EPHEMERAL; |
| |
| return MOUNT_ERROR_NONE; |
| } |
| |
| bool Mount::MountCryptohome(const std::string& username, |
| const FileSystemKeyset& file_system_keyset, |
| const Mount::MountArgs& mount_args, |
| bool is_pristine, |
| MountError* mount_error) { |
| username_ = username; |
| std::string obfuscated_username = |
| SanitizeUserNameWithSalt(username_, system_salt_); |
| |
| if (!mounter_->EnsureUserMountPoints(username_)) { |
| LOG(ERROR) << "Error creating mountpoint."; |
| *mount_error = MOUNT_ERROR_CREATE_CRYPTOHOME_FAILED; |
| return false; |
| } |
| |
| CryptohomeVault::Options vault_options; |
| if (mount_args.force_dircrypto) { |
| // If dircrypto is forced, it's an error to mount ecryptfs home unless |
| // we are migrating from ecryptfs. |
| vault_options.block_ecryptfs = true; |
| } else if (mount_args.create_as_ecryptfs) { |
| vault_options.force_type = EncryptedContainerType::kEcryptfs; |
| } |
| |
| vault_options.migrate = mount_args.to_migrate_from_ecryptfs; |
| |
| user_cryptohome_vault_ = homedirs_->GenerateCryptohomeVault( |
| obfuscated_username, file_system_keyset.KeyReference(), vault_options, |
| is_pristine, mount_error); |
| if (*mount_error != MOUNT_ERROR_NONE) { |
| return false; |
| } |
| |
| mount_type_ = user_cryptohome_vault_->GetMountType(); |
| |
| if (mount_type_ == 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. |
| *mount_error = MOUNT_ERROR_CREATE_CRYPTOHOME_FAILED; |
| return false; |
| } |
| |
| // Set up the cryptohome vault for mount. |
| *mount_error = |
| user_cryptohome_vault_->Setup(file_system_keyset.Key(), is_pristine); |
| if (*mount_error != MOUNT_ERROR_NONE) { |
| return false; |
| } |
| |
| // Ensure we don't leave any mounts hanging on intermediate errors. |
| // The closure won't outlive the class so |this| will always be valid. |
| // |out_of_process_mounter_|/|mounter_| will always be valid since this |
| // callback runs in the destructor at the latest. |
| base::ScopedClosureRunner unmount_and_drop_keys_runner(base::BindOnce( |
| &Mount::UnmountAndDropKeys, base::Unretained(this), |
| base::BindOnce(&MountHelperInterface::TearDownNonEphemeralMount, |
| base::Unretained(active_mounter_)))); |
| |
| // Mount cryptohome |
| // /home/.shadow: owned by root |
| // /home/.shadow/$hash: owned by root |
| // /home/.shadow/$hash/vault: owned by root |
| // /home/.shadow/$hash/mount: owned by root |
| // /home/.shadow/$hash/mount/root: owned by root |
| // /home/.shadow/$hash/mount/user: owned by chronos |
| // /home/chronos: owned by chronos |
| // /home/chronos/user: owned by chronos |
| // /home/user/$hash: owned by chronos |
| // /home/root/$hash: owned by root |
| |
| mount_point_ = GetUserMountDirectory(obfuscated_username); |
| // 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(mount_point_)) { |
| LOG(ERROR) << "Mount point is busy: " << mount_point_.value(); |
| *mount_error = MOUNT_ERROR_FATAL; |
| return false; |
| } |
| |
| std::string key_signature = |
| SecureBlobToHex(file_system_keyset.KeyReference().fek_sig); |
| std::string fnek_signature = |
| SecureBlobToHex(file_system_keyset.KeyReference().fnek_sig); |
| |
| MountHelper::Options mount_opts = {mount_type_, |
| mount_args.to_migrate_from_ecryptfs}; |
| |
| cryptohome::ReportTimerStart(cryptohome::kPerformMountTimer); |
| if (!active_mounter_->PerformMount(mount_opts, username_, key_signature, |
| fnek_signature, is_pristine, |
| mount_error)) { |
| LOG(ERROR) << "MountHelper::PerformMount failed, error = " << *mount_error; |
| return false; |
| } |
| |
| cryptohome::ReportTimerStop(cryptohome::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. |
| ignore_result(user_cryptohome_vault_->SetLazyTeardownWhenUnused()); |
| |
| // At this point we're done mounting so move the clean-up closure to the |
| // instance variable. |
| mount_cleanup_ = unmount_and_drop_keys_runner.Release(); |
| |
| *mount_error = MOUNT_ERROR_NONE; |
| |
| user_cryptohome_vault_->ReportVaultEncryptionType(); |
| |
| // Start file attribute cleaner service. |
| StartUserFileAttrsCleanerService(platform_, obfuscated_username); |
| |
| // 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. |
| if (platform_->RestoreSELinuxContexts( |
| GetUserDirectoryForUser(obfuscated_username), true /*recursive*/)) { |
| ReportRestoreSELinuxContextResultForHomeDir(true); |
| } else { |
| ReportRestoreSELinuxContextResultForHomeDir(false); |
| LOG(ERROR) << "RestoreSELinuxContexts(" |
| << GetUserDirectoryForUser(obfuscated_username) << ") failed."; |
| } |
| |
| return true; |
| } |
| |
| void Mount::UnmountAndDropKeys(base::OnceClosure unmounter) { |
| std::move(unmounter).Run(); |
| |
| // Resetting the vault teardowns the enclosed containers if setup succeeded. |
| user_cryptohome_vault_.reset(); |
| |
| mount_type_ = MountType::NONE; |
| } |
| |
| bool Mount::UnmountCryptohome() { |
| // There should be no file access when unmounting. |
| // Stop dircrypto migration if in progress. |
| MaybeCancelActiveDircryptoMigrationAndWait(); |
| |
| if (!mount_cleanup_.is_null()) { |
| std::move(mount_cleanup_).Run(); |
| } |
| |
| // Resetting the vault teardowns the enclosed containers if setup succeeded. |
| user_cryptohome_vault_.reset(); |
| |
| mount_type_ = MountType::NONE; |
| |
| return true; |
| } |
| |
| bool Mount::IsMounted() const { |
| return (mounter_ && mounter_->MountPerformed()) || |
| (out_of_process_mounter_ && out_of_process_mounter_->MountPerformed()); |
| } |
| |
| bool Mount::IsEphemeral() const { |
| return mount_type_ == MountType::EPHEMERAL; |
| } |
| |
| bool Mount::IsNonEphemeralMounted() const { |
| return IsMounted() && !IsEphemeral(); |
| } |
| |
| bool Mount::OwnsMountPoint(const FilePath& path) const { |
| return (mounter_ && mounter_->IsPathMounted(path)) || |
| (out_of_process_mounter_ && |
| out_of_process_mounter_->IsPathMounted(path)); |
| } |
| |
| bool Mount::CreateTrackedSubdirectories(const std::string& username) const { |
| std::string obfuscated_username = |
| SanitizeUserNameWithSalt(username, system_salt_); |
| return mounter_->CreateTrackedSubdirectories(obfuscated_username, |
| mount_type_); |
| } |
| |
| FilePath Mount::GetUserDirectoryForUser( |
| const std::string& obfuscated_username) const { |
| return ShadowRoot().Append(obfuscated_username); |
| } |
| |
| bool Mount::SetupChapsDirectory(const FilePath& dir) { |
| // If the Chaps database directory does not exist, create it. |
| if (!platform_->DirectoryExists(dir)) { |
| if (!platform_->SafeCreateDirAndSetOwnershipAndPermissions( |
| dir, S_IRWXU | S_IRGRP | S_IXGRP, chaps_user_, |
| default_access_group_)) { |
| LOG(ERROR) << "Failed to create " << dir.value(); |
| return false; |
| } |
| return true; |
| } |
| return true; |
| } |
| |
| std::string Mount::GetMountTypeString() const { |
| switch (mount_type_) { |
| case MountType::NONE: |
| return "none"; |
| case MountType::ECRYPTFS: |
| return "ecryptfs"; |
| case MountType::DIR_CRYPTO: |
| return "dircrypto"; |
| case MountType::EPHEMERAL: |
| return "ephemeral"; |
| case MountType::DMCRYPT: |
| return "dmcrypt"; |
| } |
| return ""; |
| } |
| |
| bool Mount::MigrateToDircrypto( |
| const dircrypto_data_migrator::MigrationHelper::ProgressCallback& callback, |
| MigrationType migration_type) { |
| std::string obfuscated_username = |
| SanitizeUserNameWithSalt(username_, system_salt_); |
| FilePath temporary_mount = |
| GetUserTemporaryMountDirectory(obfuscated_username); |
| if (!IsMounted() || mount_type_ != MountType::DIR_CRYPTO || |
| !platform_->DirectoryExists(temporary_mount) || |
| !OwnsMountPoint(temporary_mount)) { |
| LOG(ERROR) << "Not mounted for eCryptfs->dircrypto migration."; |
| return false; |
| } |
| // Do migration. |
| constexpr uint64_t kMaxChunkSize = 128 * 1024 * 1024; |
| dircrypto_data_migrator::MigrationHelper migrator( |
| platform_, temporary_mount, mount_point_, |
| GetUserDirectoryForUser(obfuscated_username), kMaxChunkSize, |
| migration_type); |
| { // 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); |
| |
| UnmountAndDropKeys( |
| base::BindOnce(&MountHelperInterface::TearDownNonEphemeralMount, |
| base::Unretained(active_mounter_))); |
| { // 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; |
| } |
| // Clean up. |
| FilePath vault_path = GetEcryptfsUserVaultPath(obfuscated_username); |
| if (!platform_->DeletePathRecursively(temporary_mount) || |
| !platform_->DeletePathRecursively(vault_path)) { |
| LOG(ERROR) << "Failed to delete the old vault."; |
| return false; |
| } |
| return true; |
| } |
| |
| void Mount::MaybeCancelActiveDircryptoMigrationAndWait() { |
| 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 |