blob: 59fe1d699383b3dd50161fc60aad8548630a4736 [file] [log] [blame] [edit]
// 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.
// Unit tests for Mount.
#include "cryptohome/storage/mount.h"
#include <linux/magic.h>
#include <sys/statfs.h>
#include <memory>
#include <utility>
#include <base/check.h>
#include <base/check_op.h>
#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/logging.h>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/time/time.h>
#include <brillo/cryptohome.h>
#include <brillo/process/process_mock.h>
#include <brillo/secure_blob.h>
#include <chromeos/constants/cryptohome.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include <libhwsec-foundation/error/testing_helper.h>
#include <libstorage/platform/keyring/fake_keyring.h>
#include <libstorage/platform/mock_platform.h>
#include <libstorage/storage_container/fake_storage_container_factory.h>
#include <libstorage/storage_container/storage_container.h>
#include <policy/libpolicy.h>
#include "cryptohome/fake_platform.h"
#include "cryptohome/filesystem_layout.h"
#include "cryptohome/mock_keyset_management.h"
#include "cryptohome/storage/error_test_helpers.h"
#include "cryptohome/storage/file_system_keyset.h"
#include "cryptohome/storage/homedirs.h"
#include "cryptohome/storage/mock_mount_helper_interface.h"
#include "cryptohome/storage/mount_constants.h"
#include "cryptohome/username.h"
namespace cryptohome {
using base::FilePath;
using brillo::SecureBlob;
using ::cryptohome::storage::testing::IsError;
using ::hwsec_foundation::error::testing::IsOk;
using ::testing::_;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::NiceMock;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StartsWith;
namespace {
constexpr int kEphemeralFSFragmentSize = 1 << 10;
constexpr int kEphemeralFSSize = 1 << 12;
struct Attributes {
mode_t mode;
uid_t uid;
gid_t gid;
};
// TODO(hardigoyal, b:290897808): Cleanup the rest of this file, there is
// duplicated mount_helper_unittest.cc. This would require migrating tests
// to just use MountHelper and not Mount+MountHelper.
constexpr char kEtc[] = "/etc";
constexpr char kEtcSkel[] = "/etc/skel";
constexpr char kEtcDaemonStore[] = "/etc/daemon-store";
constexpr char kRun[] = "/run";
constexpr char kRunCryptohome[] = "/run/cryptohome";
constexpr char kRunDaemonStore[] = "/run/daemon-store";
constexpr char kRunDaemonStoreCache[] = "/run/daemon-store-cache";
constexpr char kHome[] = "/home";
constexpr char kHomeChronos[] = "/home/chronos";
constexpr char kHomeChronosUser[] = "/home/chronos/user";
constexpr char kHomeUser[] = "/home/user";
constexpr char kHomeRoot[] = "/home/root";
constexpr char kDir1[] = "dir1";
constexpr char kFile1[] = "file1";
constexpr char kDir1File2[] = "dir1/file2";
constexpr char kDir1Dir2[] = "dir1/dir2";
constexpr char kDir1Dir2File3[] = "dir1/dir2/file3";
constexpr char kFile1Content[] = "content1";
constexpr char kDir1File2Content[] = "content2";
constexpr char kDir1Dir2File3Content[] = "content3";
constexpr char kSomeDaemon[] = "some_daemon";
constexpr Attributes kSomeDaemonAttributes{01735, 12, 27};
constexpr char kAnotherDaemon[] = "another_daemon";
constexpr Attributes kAnotherDaemonAttributes{0600, 0, 0};
constexpr char kDevLoopPrefix[] = "/dev/loop";
MATCHER_P(DirCryptoReferenceMatcher, reference, "") {
if (reference.reference != arg.reference) {
return false;
}
if (reference.policy_version != arg.policy_version) {
return false;
}
return true;
}
void PrepareDirectoryStructure(libstorage::Platform* platform) {
// Create environment as defined in
// src/platform2/cryptohome/tmpfiles.d/cryptohome.conf
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kRun), 0755, libstorage::kRootUid, libstorage::kRootGid));
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kRunCryptohome), 0700, libstorage::kRootUid,
libstorage::kRootGid));
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kRunDaemonStore), 0755, libstorage::kRootUid,
libstorage::kRootGid));
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kRunDaemonStoreCache), 0755, libstorage::kRootUid,
libstorage::kRootGid));
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kHome), 0755, libstorage::kRootUid, libstorage::kRootGid));
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kHomeChronos), 0755, libstorage::kChronosUid,
libstorage::kChronosGid));
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kHomeChronosUser), 01755, libstorage::kChronosUid,
libstorage::kChronosGid));
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kHomeUser), 0755, libstorage::kRootUid,
libstorage::kRootGid));
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kHomeRoot), 01751, libstorage::kRootUid,
libstorage::kRootGid));
// Setup some skel directories to make sure they are copied over.
// TODO(dlunev): for now setting permissions is useless, for the code
// relies on Copy to copy it over for files, meaning we can't intercept it.
// It can be fixed by setting permissions explicitly in RecursiveCopy.
ASSERT_TRUE(platform->CreateDirectory(base::FilePath(kEtc)));
ASSERT_TRUE(platform->CreateDirectory(base::FilePath(kEtcSkel)));
ASSERT_TRUE(
platform->CreateDirectory(base::FilePath(kEtcSkel).Append(kDir1)));
ASSERT_TRUE(platform->WriteStringToFile(
base::FilePath(kEtcSkel).Append(kFile1), kFile1Content));
ASSERT_TRUE(platform->WriteStringToFile(
base::FilePath(kEtcSkel).Append(kDir1File2), kDir1File2Content));
ASSERT_TRUE(
platform->CreateDirectory(base::FilePath(kEtcSkel).Append(kDir1Dir2)));
ASSERT_TRUE(platform->WriteStringToFile(
base::FilePath(kEtcSkel).Append(kDir1Dir2File3), kDir1Dir2File3Content));
// Setup daemon-store templates
ASSERT_TRUE(platform->CreateDirectory(base::FilePath(kEtcDaemonStore)));
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kEtcDaemonStore).Append(kSomeDaemon),
kSomeDaemonAttributes.mode, kSomeDaemonAttributes.uid,
kSomeDaemonAttributes.gid));
ASSERT_TRUE(platform->SafeCreateDirAndSetOwnershipAndPermissions(
base::FilePath(kEtcDaemonStore).Append(kAnotherDaemon),
kAnotherDaemonAttributes.mode, kAnotherDaemonAttributes.uid,
kAnotherDaemonAttributes.gid));
ASSERT_TRUE(platform->CreateDirectory(
base::FilePath(kRunDaemonStore).Append(kSomeDaemon)));
ASSERT_TRUE(platform->CreateDirectory(
base::FilePath(kRunDaemonStoreCache).Append(kSomeDaemon)));
ASSERT_TRUE(platform->CreateDirectory(
base::FilePath(kRunDaemonStore).Append(kAnotherDaemon)));
ASSERT_TRUE(platform->CreateDirectory(
base::FilePath(kRunDaemonStoreCache).Append(kAnotherDaemon)));
}
} // namespace
// TODO(dlunev): add test ecryptfs blasts "mount".
class PersistentSystemTest : public ::testing::Test {
public:
const Username kUser{"someuser"};
PersistentSystemTest() = default;
void SetUp() {
ASSERT_NO_FATAL_FAILURE(PrepareDirectoryStructure(&platform_));
std::unique_ptr<libstorage::StorageContainerFactory> container_factory =
std::make_unique<libstorage::FakeStorageContainerFactory>(
&platform_, std::make_unique<libstorage::FakeKeyring>());
vault_factory_ = std::make_unique<CryptohomeVaultFactory>(
&platform_, std::move(container_factory));
std::shared_ptr<brillo::LvmCommandRunner> command_runner =
std::make_shared<brillo::MockLvmCommandRunner>();
brillo::VolumeGroup vg("STATEFUL", command_runner);
brillo::Thinpool thinpool("thinpool", "STATEFUL", command_runner);
vault_factory_->CacheLogicalVolumeObjects(vg, thinpool);
homedirs_ = std::make_unique<HomeDirs>(
&platform_, std::make_unique<policy::PolicyProvider>(),
base::BindRepeating([](const ObfuscatedUsername& unused) {}),
vault_factory_.get());
std::unique_ptr<MockMounterHelperInterface> mock_mount_helper_interface =
std::make_unique<MockMounterHelperInterface>();
mock_mount_interface_ = mock_mount_helper_interface.get();
mount_ = new Mount(&platform_, homedirs_.get(),
std::move(mock_mount_helper_interface));
}
protected:
// Protected for trivial access.
NiceMock<libstorage::MockPlatform> platform_;
std::unique_ptr<CryptohomeVaultFactory> vault_factory_;
std::unique_ptr<HomeDirs> homedirs_;
scoped_refptr<Mount> mount_;
MockMounterHelperInterface* mock_mount_interface_;
void MockPreclearKeyring(bool success) {
EXPECT_CALL(platform_, ClearUserKeyring()).WillOnce(Return(success));
}
void MockDircryptoKeyringSetup(const Username& username,
const FileSystemKeyset& keyset,
bool existing_dir,
bool success) {
const ObfuscatedUsername obfuscated_username =
brillo::cryptohome::home::SanitizeUserName(username);
const base::FilePath backing_dir =
GetUserMountDirectory(obfuscated_username);
const dircrypto::KeyReference reference = {
.policy_version = FSCRYPT_POLICY_V1,
.reference = keyset.KeyReference().fek_sig,
};
EXPECT_CALL(platform_, GetDirectoryPolicyVersion(backing_dir))
.WillRepeatedly(Return(existing_dir ? FSCRYPT_POLICY_V1 : -1));
EXPECT_CALL(platform_, GetDirCryptoKeyState(ShadowRoot()))
.WillRepeatedly(Return(dircrypto::KeyState::NO_KEY));
EXPECT_CALL(platform_, GetDirCryptoKeyState(backing_dir))
.WillRepeatedly(Return(
existing_dir
? dircrypto::KeyState::ENCRYPTED
: dircrypto::KeyState::NO_KEY)); // EXPECT_CALL(platform_,
// CheckDircryptoKeyIoctlSupport()).WillOnce(Return(true));
EXPECT_CALL(
platform_,
SetDirCryptoKey(backing_dir, DirCryptoReferenceMatcher(reference)))
.WillOnce(Return(success));
}
void SetHomedir(const Username& username) {
const ObfuscatedUsername obfuscated_username =
brillo::cryptohome::home::SanitizeUserName(username);
ASSERT_TRUE(platform_.CreateDirectory(UserPath(obfuscated_username)));
}
void SetDmcryptPrereqs(const Username& username) {
const ObfuscatedUsername obfuscated_username =
brillo::cryptohome::home::SanitizeUserName(username);
SetHomedir(username);
ON_CALL(platform_, GetStatefulDevice())
.WillByDefault(Return(base::FilePath("/dev/somedev")));
ON_CALL(platform_, GetBlkSize(_, _))
.WillByDefault(DoAll(SetArgPointee<1>(4096), Return(true)));
ON_CALL(platform_, UdevAdmSettle(_, _)).WillByDefault(Return(true));
ON_CALL(platform_, FormatExt4(_, _, _)).WillByDefault(Return(true));
ON_CALL(platform_, Tune2Fs(_, _)).WillByDefault(Return(true));
ASSERT_TRUE(platform_.WriteStringToFile(
GetDmcryptDataVolume(obfuscated_username), std::string(2048, 0)));
ASSERT_TRUE(platform_.WriteStringToFile(
GetDmcryptCacheVolume(obfuscated_username), std::string(2048, 0)));
}
};
namespace {
TEST_F(PersistentSystemTest, NoEcryptfsMountWhenForcedDircrypto) {
// Verify force_dircrypto flag prohibits ecryptfs mounts.
const FileSystemKeyset keyset = FileSystemKeyset::CreateRandom();
CryptohomeVault::Options options = {
.force_type = libstorage::StorageContainerType::kEcryptfs,
};
// Essentially, EcryptFS type should be called for mount.
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::ECRYPTFS, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
}
TEST_F(PersistentSystemTest, MigrateAttemptOnUnmountedPath) {
EXPECT_CALL(*mock_mount_interface_, MountPerformed())
.WillRepeatedly(Return(false));
ASSERT_FALSE(mount_->MigrateEncryption(
base::BindRepeating(
[](const user_data_auth::DircryptoMigrationProgress& unused) {}),
MigrationType::FULL));
}
TEST_F(PersistentSystemTest, MigrateEcryptfsToFscrypt) {
// Verify ecryptfs->dircrypto migration.
const std::string kContent{"some_content"};
const base::FilePath kFile{"some_file"};
const FileSystemKeyset keyset = FileSystemKeyset::CreateRandom();
// Create ecryptfs
CryptohomeVault::Options options = {
.force_type = libstorage::StorageContainerType::kEcryptfs,
};
MockPreclearKeyring(/*success=*/true);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::ECRYPTFS, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
ASSERT_TRUE(platform_.WriteStringToFile(
base::FilePath(kHomeChronosUser).Append(kFile), kContent));
ASSERT_TRUE(mount_->UnmountCryptohome());
// The vault exists.
const ObfuscatedUsername obfuscated_username =
brillo::cryptohome::home::SanitizeUserName(kUser);
const base::FilePath ecryptfs_vault =
GetEcryptfsUserVaultPath(obfuscated_username);
// Vault exists before migration.
ASSERT_TRUE(platform_.DirectoryExists(ecryptfs_vault));
// Start migration
// Create a new mount object, because interface rises a flag prohibiting
// migration on unmount.
// TODO(dlunev): fix the behaviour.
std::unique_ptr<MockMounterHelperInterface> mock_mount_helper_interface =
std::make_unique<MockMounterHelperInterface>();
mock_mount_interface_ = mock_mount_helper_interface.get();
mount_ = new Mount(&platform_, homedirs_.get(),
std::move(mock_mount_helper_interface));
options = {
.migrate = true,
};
MockPreclearKeyring(/*success=*/true);
MockDircryptoKeyringSetup(kUser, keyset, /*existing_dir=*/false,
/*success=*/true);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::ECRYPTFS_TO_DIR_CRYPTO, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
EXPECT_CALL(*mock_mount_interface_, IsPathMounted(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_mount_interface_, MountPerformed())
.WillRepeatedly(Return(true));
ASSERT_TRUE(mount_->MigrateEncryption(
base::BindRepeating(
[](const user_data_auth::DircryptoMigrationProgress& unused) {}),
MigrationType::FULL));
// "vault" should be gone.
ASSERT_FALSE(platform_.DirectoryExists(ecryptfs_vault));
const base::FilePath dircrypto_mount_point =
GetUserMountDirectory(obfuscated_username);
ASSERT_TRUE(platform_.DirectoryExists(dircrypto_mount_point));
// Now we should be able to mount with dircrypto.
options = {
.force_type = libstorage::StorageContainerType::kFscrypt,
};
MockPreclearKeyring(/*success=*/true);
MockDircryptoKeyringSetup(kUser, keyset, /*existing_dir=*/true,
/*success=*/true);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::DIR_CRYPTO, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
std::string result;
ASSERT_TRUE(platform_.ReadFileToString(
base::FilePath(kHomeChronosUser).Append(kFile), &result));
ASSERT_THAT(result, kContent);
}
TEST_F(PersistentSystemTest, MigrateEcryptfsToDmcrypt) {
// Verify ecryptfs->dircrypto migration.
const std::string kContent{"some_content"};
const base::FilePath kFile{"some_file"};
const FileSystemKeyset keyset = FileSystemKeyset::CreateRandom();
homedirs_->set_lvm_migration_enabled(true);
// Create ecryptfs
CryptohomeVault::Options options = {
.force_type = libstorage::StorageContainerType::kEcryptfs,
};
MockPreclearKeyring(/*success=*/true);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::ECRYPTFS, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
ASSERT_TRUE(platform_.WriteStringToFile(
base::FilePath(kHomeChronosUser).Append(kFile), kContent));
ASSERT_TRUE(mount_->UnmountCryptohome());
// The vault exists.
const ObfuscatedUsername obfuscated_username =
brillo::cryptohome::home::SanitizeUserName(kUser);
const base::FilePath ecryptfs_vault =
GetEcryptfsUserVaultPath(obfuscated_username);
// Vault exists before migration.
ASSERT_TRUE(platform_.DirectoryExists(ecryptfs_vault));
// Start migration
// Create a new mount object, because interface rises a flag prohibiting
// migration on unmount.
// TODO(dlunev): fix the behaviour.
std::unique_ptr<MockMounterHelperInterface> mock_mount_helper_interface =
std::make_unique<MockMounterHelperInterface>();
mock_mount_interface_ = mock_mount_helper_interface.get();
mount_ = new Mount(&platform_, homedirs_.get(),
std::move(mock_mount_helper_interface));
options = {
.migrate = true,
};
MockPreclearKeyring(/*success=*/true);
SetDmcryptPrereqs(kUser);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::ECRYPTFS_TO_DMCRYPT, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
EXPECT_CALL(*mock_mount_interface_, IsPathMounted(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_mount_interface_, MountPerformed())
.WillRepeatedly(Return(true));
ASSERT_TRUE(mount_->MigrateEncryption(
base::BindRepeating(
[](const user_data_auth::DircryptoMigrationProgress& unused) {}),
MigrationType::FULL));
// "vault" should be gone.
ASSERT_FALSE(platform_.DirectoryExists(ecryptfs_vault));
// Now we should be able to mount with dircrypto.
options = {
.force_type = libstorage::StorageContainerType::kDmcrypt,
};
MockPreclearKeyring(/*success=*/true);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::DMCRYPT, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
std::string result;
ASSERT_TRUE(platform_.ReadFileToString(
base::FilePath(kHomeChronosUser).Append(kFile), &result));
ASSERT_THAT(result, kContent);
}
TEST_F(PersistentSystemTest, MigrateFscryptToDmcrypt) {
// Verify ecryptfs->dircrypto migration.
const std::string kContent{"some_content"};
const base::FilePath kFile{"some_file"};
const FileSystemKeyset keyset = FileSystemKeyset::CreateRandom();
homedirs_->set_lvm_migration_enabled(true);
// Create ecryptfs
CryptohomeVault::Options options = {
.force_type = libstorage::StorageContainerType::kFscrypt,
};
MockPreclearKeyring(/*success=*/true);
MockDircryptoKeyringSetup(kUser, keyset, /*existing_dir=*/false,
/*success=*/true);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::DIR_CRYPTO, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
ASSERT_TRUE(platform_.WriteStringToFile(
base::FilePath(kHomeChronosUser).Append(kFile), kContent));
ASSERT_TRUE(mount_->UnmountCryptohome());
// "vault" should be gone.
const ObfuscatedUsername obfuscated_username =
brillo::cryptohome::home::SanitizeUserName(kUser);
const base::FilePath dircrypto_mount_point =
GetUserMountDirectory(obfuscated_username);
ASSERT_TRUE(platform_.DirectoryExists(dircrypto_mount_point));
// Start migration
// Create a new mount object, because interface rises a flag prohibiting
// migration on unmount.
// TODO(dlunev): fix the behaviour.
std::unique_ptr<MockMounterHelperInterface> mock_mount_helper_interface =
std::make_unique<MockMounterHelperInterface>();
mock_mount_interface_ = mock_mount_helper_interface.get();
mount_ = new Mount(&platform_, homedirs_.get(),
std::move(mock_mount_helper_interface));
options = {
.migrate = true,
};
MockPreclearKeyring(/*success=*/true);
MockDircryptoKeyringSetup(kUser, keyset, /*existing_dir=*/true,
/*success=*/true);
SetDmcryptPrereqs(kUser);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::DIR_CRYPTO_TO_DMCRYPT, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
EXPECT_CALL(*mock_mount_interface_, IsPathMounted(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_mount_interface_, MountPerformed())
.WillRepeatedly(Return(true));
ASSERT_TRUE(mount_->MigrateEncryption(
base::BindRepeating(
[](const user_data_auth::DircryptoMigrationProgress& unused) {}),
MigrationType::FULL));
// "vault" should be gone.
ASSERT_FALSE(platform_.DirectoryExists(dircrypto_mount_point));
// Now we should be able to mount with dircrypto.
options = {
.force_type = libstorage::StorageContainerType::kDmcrypt,
};
MockPreclearKeyring(/*success=*/true);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::DMCRYPT, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
std::string result;
ASSERT_TRUE(platform_.ReadFileToString(
base::FilePath(kHomeChronosUser).Append(kFile), &result));
ASSERT_THAT(result, kContent);
}
TEST_F(PersistentSystemTest, MountRestoreSelinux) {
// Verify we don't restore android-data selinux label.
const FileSystemKeyset keyset = FileSystemKeyset::CreateRandom();
const ObfuscatedUsername obfuscated_username =
brillo::cryptohome::home::SanitizeUserName(kUser);
// Create ecryptfs
CryptohomeVault::Options options = {
.force_type = libstorage::StorageContainerType::kFscrypt,
};
MockPreclearKeyring(/*success=*/true);
MockDircryptoKeyringSetup(kUser, keyset, /*existing_dir=*/false,
/*success=*/true);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::DIR_CRYPTO, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
std::vector<base::FilePath> exclude_path;
base::FilePath path_ap = GetUserMountDirectory(obfuscated_username)
.Append("root")
.Append("android-data")
.Append("data");
EXPECT_CALL(platform_,
AddGlobalSELinuxRestoreconExclusion(ElementsAre(path_ap)));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
ASSERT_TRUE(mount_->UnmountCryptohome());
}
TEST_F(PersistentSystemTest, MountEvictKeyRestoreKey) {
const FileSystemKeyset keyset = FileSystemKeyset::CreateRandom();
CryptohomeVault::Options options = {
.force_type = libstorage::StorageContainerType::kDmcrypt,
};
MockPreclearKeyring(/*success=*/true);
SetDmcryptPrereqs(kUser);
EXPECT_CALL(*mock_mount_interface_,
PerformMount(MountType::DMCRYPT, kUser, _, _))
.WillOnce(Return(StorageStatus::Ok()));
EXPECT_CALL(*mock_mount_interface_, MountPerformed())
.WillRepeatedly(Return(true));
ASSERT_THAT(mount_->MountCryptohome(kUser, keyset, options), IsOk());
ASSERT_THAT(mount_->EvictCryptohomeKey(), IsOk());
ASSERT_THAT(mount_->RestoreCryptohomeKey(keyset), IsOk());
}
TEST_F(PersistentSystemTest, RestoreKeyFailedWithoutVault) {
const FileSystemKeyset keyset = FileSystemKeyset::CreateRandom();
// Intentionally skip MountCryptohome() call so there is no
// valid vault to carry out restore operation.
ASSERT_THAT(mount_->RestoreCryptohomeKey(keyset),
IsError(MOUNT_ERROR_KEY_RESTORE_FAILED));
}
} // namespace
class EphemeralSystemTest : public ::testing::Test {
public:
const Username kUser{"someuser"};
EphemeralSystemTest() : platform_(std::make_unique<FakePlatform>()) {}
void SetUp() {
ASSERT_NO_FATAL_FAILURE(PrepareDirectoryStructure(&platform_));
std::unique_ptr<libstorage::StorageContainerFactory> container_factory =
std::make_unique<libstorage::StorageContainerFactory>(
&platform_, /* metrics */ nullptr,
std::make_unique<libstorage::FakeKeyring>(),
std::make_unique<libstorage::BackingDeviceFactory>(&platform_));
vault_factory_ = std::make_unique<CryptohomeVaultFactory>(
&platform_, std::move(container_factory));
homedirs_ = std::make_unique<HomeDirs>(
&platform_, std::make_unique<policy::PolicyProvider>(),
base::BindRepeating([](const ObfuscatedUsername& unused) {}),
vault_factory_.get());
std::unique_ptr<MockMounterHelperInterface> mock_mount_helper_interface =
std::make_unique<MockMounterHelperInterface>();
mock_mount_interface_ = mock_mount_helper_interface.get();
mount_ = new Mount(&platform_, homedirs_.get(),
std::move(mock_mount_helper_interface));
SetupFSMock();
EXPECT_CALL(*mock_mount_interface_, CanPerformEphemeralMount)
.WillOnce(Return(true));
}
protected:
// Protected for trivial access.
NiceMock<libstorage::MockPlatform> platform_;
std::unique_ptr<CryptohomeVaultFactory> vault_factory_;
std::unique_ptr<HomeDirs> homedirs_;
scoped_refptr<Mount> mount_;
MockMounterHelperInterface* mock_mount_interface_;
struct statfs ephemeral_statfs_;
base::FilePath EphemeralBackingFile(const Username& username) {
const ObfuscatedUsername obfuscated_username =
brillo::cryptohome::home::SanitizeUserName(username);
return base::FilePath(kEphemeralCryptohomeDir)
.Append(kSparseFileDir)
.Append(*obfuscated_username);
}
base::FilePath EphemeralMountPoint(const Username& username) {
const ObfuscatedUsername obfuscated_username =
brillo::cryptohome::home::SanitizeUserName(username);
return base::FilePath(kEphemeralCryptohomeDir)
.Append(kEphemeralMountDir)
.Append(*obfuscated_username);
}
private:
void SetupFSMock() {
ephemeral_statfs_ = {0};
ephemeral_statfs_.f_type = TMPFS_MAGIC;
ephemeral_statfs_.f_frsize = kEphemeralFSFragmentSize;
ephemeral_statfs_.f_blocks = kEphemeralFSSize / kEphemeralFSFragmentSize;
ON_CALL(platform_, StatFS(base::FilePath(kEphemeralCryptohomeDir), _))
.WillByDefault(
DoAll(SetArgPointee<1>(ephemeral_statfs_), Return(true)));
}
};
namespace {
TEST_F(EphemeralSystemTest, EpmeneralMount_FSFailure) {
// Checks the case when ephemeral statfs call fails.
ON_CALL(platform_, StatFS(base::FilePath(kEphemeralCryptohomeDir), _))
.WillByDefault(Return(false));
ASSERT_THAT(mount_->MountEphemeralCryptohome(kUser),
IsError(MOUNT_ERROR_FATAL));
}
TEST_F(EphemeralSystemTest, EphemeralMount_CreateSparseDirFailure) {
// Checks the case when directory for ephemeral sparse files fails to be
// created.
EXPECT_CALL(platform_, CreateDirectory(EphemeralBackingFile(kUser).DirName()))
.WillOnce(Return(false));
ASSERT_THAT(mount_->MountEphemeralCryptohome(kUser),
IsError(MOUNT_ERROR_KEYRING_FAILED));
}
TEST_F(EphemeralSystemTest, EphemeralMount_CreateSparseFailure) {
// Checks the case when ephemeral sparse file fails to create.
EXPECT_CALL(platform_, CreateSparseFile(EphemeralBackingFile(kUser), _))
.WillOnce(Return(false));
ASSERT_THAT(mount_->MountEphemeralCryptohome(kUser),
IsError(MOUNT_ERROR_KEYRING_FAILED));
}
TEST_F(EphemeralSystemTest, EphemeralMount_FormatFailure) {
// Checks that when ephemeral loop device fails to be formatted, clean up
// happens appropriately.
EXPECT_CALL(platform_, FormatExt4(Property(&base::FilePath::value,
StartsWith(kDevLoopPrefix)),
_, _))
.WillOnce(Return(false));
ASSERT_THAT(mount_->MountEphemeralCryptohome(kUser),
IsError(MOUNT_ERROR_KEYRING_FAILED));
}
} // namespace
} // namespace cryptohome