blob: 559415ffab2a3a69c884dcee893ea190cfcd4a31 [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.
#include "cryptohome/storage/homedirs.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <set>
#include <utility>
#include <vector>
#include <base/files/file_path.h>
#include <base/functional/bind.h>
#include <base/functional/callback.h>
#include <base/functional/callback_helpers.h>
#include <base/location.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <brillo/blkdev_utils/lvm.h>
#include <brillo/cryptohome.h>
#include <brillo/scoped_umask.h>
#include <brillo/secure_blob.h>
#include <chromeos/constants/cryptohome.h>
#include <cryptohome/proto_bindings/key.pb.h>
#include <libhwsec-foundation/status/status_chain_macros.h>
#include <libstorage/platform/dircrypto_util.h>
#include <libstorage/platform/platform.h>
#include <libstorage/storage_container/storage_container.h>
#include "cryptohome/filesystem_layout.h"
#include "cryptohome/storage/cryptohome_vault.h"
#include "cryptohome/storage/cryptohome_vault_factory.h"
#include "cryptohome/storage/ephemeral_policy_util.h"
#include "cryptohome/storage/error.h"
#include "cryptohome/storage/mount_constants.h"
#include "cryptohome/username.h"
using base::FilePath;
using brillo::SecureBlob;
using brillo::cryptohome::home::SanitizeUserName;
namespace cryptohome {
const char kForceKeylockerForTestingFlag[] =
"/run/cryptohome/.force_keylocker_for_testing";
HomeDirs::HomeDirs(libstorage::Platform* platform,
std::unique_ptr<policy::PolicyProvider> policy_provider,
const RemoveCallback& remove_callback,
CryptohomeVaultFactory* vault_factory)
: platform_(platform),
policy_provider_(std::move(policy_provider)),
enterprise_owned_(false),
lvm_migration_enabled_(false),
vault_factory_(vault_factory),
remove_callback_(remove_callback) {}
void HomeDirs::LoadDevicePolicy() {
policy_provider_->Reload();
}
bool HomeDirs::GetEphemeralSettings(
policy::DevicePolicy::EphemeralSettings* settings) {
LoadDevicePolicy();
if (!policy_provider_->device_policy_is_loaded()) {
return false;
}
std::optional<policy::DevicePolicy::EphemeralSettings> ephemeral_settings =
policy_provider_->GetDevicePolicy().GetEphemeralSettings();
if (!ephemeral_settings)
return false;
*settings = *ephemeral_settings;
return true;
}
bool HomeDirs::KeylockerForStorageEncryptionEnabled() {
// Search through /proc/crypto for 'aeskl' as an indicator that AES Keylocker
// is supported.
if (!IsAesKeylockerSupported())
return false;
// Check if keylocker is force enabled for testing.
// TODO(sarthakkukreti@, b/209516710): Remove in M102.
if (platform_->FileExists(base::FilePath(kForceKeylockerForTestingFlag))) {
LOG(INFO) << "Forced keylocker enabled for testing";
return true;
}
LoadDevicePolicy();
// If the policy cannot be loaded, default to AESNI.
bool keylocker_for_storage_encryption_enabled = false;
if (policy_provider_->device_policy_is_loaded())
policy_provider_->GetDevicePolicy()
.GetDeviceKeylockerForStorageEncryptionEnabled(
&keylocker_for_storage_encryption_enabled);
return keylocker_for_storage_encryption_enabled;
}
bool HomeDirs::MustRunAutomaticCleanupOnLogin() {
// If the policy cannot be loaded, default to not run cleanup.
if (!policy_provider_->device_policy_is_loaded())
return false;
// If the device is not enterprise owned, do not run cleanup.
if (!enterprise_owned()) {
return false;
}
// Get the value of the policy and default to true if unset.
return policy_provider_->GetDevicePolicy()
.GetRunAutomaticCleanupOnLogin()
.value_or(true);
}
bool HomeDirs::SetLockedToSingleUser() const {
return platform_->TouchFileDurable(base::FilePath(kLockedToSingleUserFile));
}
bool HomeDirs::Exists(const ObfuscatedUsername& username) const {
FilePath user_dir = UserPath(username);
return platform_->DirectoryExists(user_dir);
}
StorageStatusOr<bool> HomeDirs::CryptohomeExists(
const ObfuscatedUsername& username) const {
ASSIGN_OR_RETURN(bool dircrypto_exists, DircryptoCryptohomeExists(username));
return EcryptfsCryptohomeExists(username) || dircrypto_exists ||
DmcryptCryptohomeExists(username);
}
bool HomeDirs::EcryptfsCryptohomeExists(
const ObfuscatedUsername& username) const {
// Check for the presence of a vault directory for ecryptfs.
return platform_->DirectoryExists(GetEcryptfsUserVaultPath(username));
}
StorageStatusOr<bool> HomeDirs::DircryptoCryptohomeExists(
const ObfuscatedUsername& username) const {
// Check for the presence of an encrypted mount directory for dircrypto.
FilePath mount_path = GetUserMountDirectory(username);
if (!platform_->DirectoryExists(mount_path)) {
return false;
}
switch (platform_->GetDirCryptoKeyState(mount_path)) {
case dircrypto::KeyState::NO_KEY:
case dircrypto::KeyState::NOT_SUPPORTED:
return false;
case dircrypto::KeyState::ENCRYPTED:
return true;
case dircrypto::KeyState::UNKNOWN:
return StorageStatus::Make(
FROM_HERE,
std::string("Directory has inconsistent Fscrypt state: ") +
mount_path.value(),
MOUNT_ERROR_FATAL);
}
return false;
}
bool HomeDirs::DmcryptContainerExists(
const ObfuscatedUsername& username,
const std::string& container_suffix) const {
// Check for the presence of the logical volume for the user's data container.
std::string logical_volume_container =
LogicalVolumePrefix(username).append(container_suffix);
return vault_factory_->ContainerExists(logical_volume_container);
}
bool HomeDirs::DmcryptCryptohomeExists(
const ObfuscatedUsername& username) const {
return DmcryptContainerExists(username, kDmcryptDataContainerSuffix);
}
bool HomeDirs::DmcryptCacheContainerExists(
const ObfuscatedUsername& username) const {
return DmcryptContainerExists(username, kDmcryptCacheContainerSuffix);
}
HomeDirs::CryptohomesRemovedStatus HomeDirs::RemoveCryptohomesBasedOnPolicy() {
// If the device is not enterprise owned it should have an owner user.
auto state = HomeDirs::CryptohomesRemovedStatus::kError;
ObfuscatedUsername owner;
bool has_owner = GetOwner(&owner);
if (!enterprise_owned() && !has_owner) {
return state;
}
auto homedirs = GetHomeDirs();
FilterMountedHomedirs(&homedirs);
policy::DevicePolicy::EphemeralSettings settings;
if (!GetEphemeralSettings(&settings)) {
return state;
}
size_t cryptohomes_removed = 0;
EphemeralPolicyUtil ephemeral_util(settings);
for (const auto& dir : homedirs) {
if (has_owner && !enterprise_owned() && dir.obfuscated == owner) {
continue; // Owner vault shouldn't be remove.
}
if (!ephemeral_util.ShouldRemoveBasedOnPolicy(dir.obfuscated)) {
continue;
}
if (HomeDirs::Remove(dir.obfuscated)) {
cryptohomes_removed++;
} else {
LOG(WARNING)
<< "Failed to remove ephemeral cryptohome with obfuscated username: "
<< dir.obfuscated;
}
}
if (cryptohomes_removed == 0) {
state = HomeDirs::CryptohomesRemovedStatus::kNone;
} else if (cryptohomes_removed == homedirs.size()) {
state = HomeDirs::CryptohomesRemovedStatus::kAll;
} else {
state = HomeDirs::CryptohomesRemovedStatus::kSome;
}
return state;
}
std::vector<HomeDirs::HomeDir> HomeDirs::GetHomeDirs() {
std::vector<HomeDirs::HomeDir> ret;
std::vector<FilePath> entries;
if (!platform_->EnumerateDirectoryEntries(ShadowRoot(), false, &entries)) {
return ret;
}
for (const auto& entry : entries) {
HomeDirs::HomeDir dir;
FilePath basename = entry.BaseName();
if (!brillo::cryptohome::home::IsSanitizedUserName(basename.value())) {
continue;
}
dir.obfuscated = ObfuscatedUsername(basename.value());
ret.push_back(dir);
}
std::vector<FilePath> user_paths;
std::transform(
ret.begin(), ret.end(), std::back_inserter(user_paths),
[](const HomeDirs::HomeDir& homedir) {
return brillo::cryptohome::home::GetUserPath(homedir.obfuscated);
});
auto is_mounted = platform_->AreDirectoriesMounted(user_paths);
if (!is_mounted)
return ret; // assume all are unmounted
int i = 0;
for (const bool& m : is_mounted.value()) {
ret[i++].is_mounted = m;
}
return ret;
}
void HomeDirs::FilterMountedHomedirs(std::vector<HomeDirs::HomeDir>* homedirs) {
homedirs->erase(std::remove_if(homedirs->begin(), homedirs->end(),
[](const HomeDirs::HomeDir& dir) {
return dir.is_mounted;
}),
homedirs->end());
}
bool HomeDirs::GetTrackedDirectory(const FilePath& user_dir,
const FilePath& tracked_dir_name,
FilePath* out) {
FilePath vault_path = user_dir.Append(kEcryptfsVaultDir);
if (platform_->DirectoryExists(vault_path)) {
// On Ecryptfs, tracked directories' names are not encrypted.
*out = user_dir.Append(kEcryptfsVaultDir).Append(tracked_dir_name);
return true;
}
// This is dircrypto. Use the xattr to locate the directory.
return GetTrackedDirectoryForDirCrypto(user_dir.Append(kMountDir),
tracked_dir_name, out);
}
bool HomeDirs::GetTrackedDirectoryForDirCrypto(const FilePath& mount_dir,
const FilePath& tracked_dir_name,
FilePath* out) {
FilePath current_name;
FilePath current_path = mount_dir;
// Iterate over name components. This way, we don't have to inspect every
// directory under |mount_dir|.
std::vector<std::string> name_components = tracked_dir_name.GetComponents();
for (const auto& name_component : name_components) {
FilePath next_path;
std::unique_ptr<libstorage::FileEnumerator> enumerator(
platform_->GetFileEnumerator(current_path, false /* recursive */,
base::FileEnumerator::DIRECTORIES));
for (FilePath dir = enumerator->Next(); !dir.empty();
dir = enumerator->Next()) {
if (platform_->HasExtendedFileAttribute(dir,
kTrackedDirectoryNameAttribute)) {
std::string name;
if (!platform_->GetExtendedFileAttributeAsString(
dir, kTrackedDirectoryNameAttribute, &name))
return false;
if (name == name_component) {
// This is the directory we're looking for.
next_path = dir;
break;
}
}
}
if (next_path.empty()) {
LOG(ERROR) << "Tracked dir not found " << tracked_dir_name.value();
return false;
}
current_path = next_path;
}
*out = current_path;
return true;
}
libstorage::StorageContainerType HomeDirs::ChooseVaultType() {
// Validate stateful partition logical volume support.
if (platform_->IsStatefulLogicalVolumeSupported())
return libstorage::StorageContainerType::kDmcrypt;
dircrypto::KeyState state = platform_->GetDirCryptoKeyState(ShadowRoot());
switch (state) {
case dircrypto::KeyState::NOT_SUPPORTED:
return libstorage::StorageContainerType::kEcryptfs;
case dircrypto::KeyState::NO_KEY:
return libstorage::StorageContainerType::kFscrypt;
case dircrypto::KeyState::UNKNOWN:
case dircrypto::KeyState::ENCRYPTED:
LOG(ERROR) << "Unexpected state " << static_cast<int>(state);
return libstorage::StorageContainerType::kUnknown;
}
}
StorageStatusOr<libstorage::StorageContainerType> HomeDirs::GetVaultType(
const ObfuscatedUsername& username) {
ASSIGN_OR_RETURN(bool dircrypto_exists, DircryptoCryptohomeExists(username),
(_.LogError() << "Can't get vault type"));
if (EcryptfsCryptohomeExists(username)) {
if (dircrypto_exists) {
return libstorage::StorageContainerType::kEcryptfsToFscrypt;
}
if (DmcryptCryptohomeExists(username)) {
return libstorage::StorageContainerType::kEcryptfsToDmcrypt;
}
return libstorage::StorageContainerType::kEcryptfs;
} else if (dircrypto_exists) {
if (DmcryptCryptohomeExists(username)) {
return libstorage::StorageContainerType::kFscryptToDmcrypt;
}
return libstorage::StorageContainerType::kFscrypt;
} else if (DmcryptCryptohomeExists(username)) {
return libstorage::StorageContainerType::kDmcrypt;
}
return libstorage::StorageContainerType::kUnknown;
}
bool HomeDirs::enterprise_owned() const {
CHECK(device_management_client_)
<< __func__ << " proxy class instance is not initialized";
return device_management_client_->IsEnterpriseOwned();
}
StorageStatusOr<libstorage::StorageContainerType> HomeDirs::PickVaultType(
const ObfuscatedUsername& username,
const CryptohomeVault::Options& options) {
// See if the vault exists.
ASSIGN_OR_RETURN(libstorage::StorageContainerType vault_type,
GetVaultType(username));
// If existing vault is ecryptfs and migrate == true - make migrating vault.
if (vault_type == libstorage::StorageContainerType::kEcryptfs &&
options.migrate) {
if (lvm_migration_enabled_) {
vault_type = libstorage::StorageContainerType::kEcryptfsToDmcrypt;
} else {
vault_type = libstorage::StorageContainerType::kEcryptfsToFscrypt;
}
}
if (vault_type == libstorage::StorageContainerType::kFscrypt &&
options.migrate) {
vault_type = libstorage::StorageContainerType::kFscryptToDmcrypt;
}
if (vault_type == libstorage::StorageContainerType::kEcryptfs &&
options.block_ecryptfs) {
return StorageStatus::Make(FROM_HERE,
"Mount attempt with block_ecryptfs on eCryptfs.",
MOUNT_ERROR_OLD_ENCRYPTION);
}
if (libstorage::StorageContainer::IsMigratingType(vault_type) &&
!options.migrate) {
return StorageStatus::Make(
FROM_HERE,
"Mount failed because both eCryptfs and dircrypto home"
" directories were found. Need to resume and finish"
" migration first.",
MOUNT_ERROR_PREVIOUS_MIGRATION_INCOMPLETE);
}
if (!libstorage::StorageContainer::IsMigratingType(vault_type) &&
options.migrate) {
return StorageStatus::Make(
FROM_HERE, "Mount attempt with migration on non-eCryptfs mount",
MOUNT_ERROR_UNEXPECTED_MOUNT_TYPE);
}
// Vault exists, so we return its type.
if (vault_type != libstorage::StorageContainerType::kUnknown) {
return vault_type;
}
if (options.migrate) {
return StorageStatus::Make(
FROM_HERE, "Can not set up migration for a non-existing vault.",
MOUNT_ERROR_UNEXPECTED_MOUNT_TYPE);
}
if (options.block_ecryptfs) {
LOG(WARNING) << "Ecryptfs mount block flag has no effect for new vaults.";
}
// If there is no existing vault, see if we are asked for a specific type.
// Otherwise choose the best type based on configuration.
return options.force_type != libstorage::StorageContainerType::kUnknown
? options.force_type
: ChooseVaultType();
}
void HomeDirs::CreateAndSetDeviceManagementClientProxy(
scoped_refptr<dbus::Bus> bus) {
default_device_management_client_ =
std::make_unique<DeviceManagementClientProxy>(bus);
device_management_client_ = default_device_management_client_.get();
}
bool HomeDirs::GetOwner(ObfuscatedUsername* owner) {
LoadDevicePolicy();
if (!policy_provider_->device_policy_is_loaded()) {
return false;
}
std::string owner_str;
policy_provider_->GetDevicePolicy().GetOwner(&owner_str);
Username plain_owner(owner_str);
if (plain_owner->empty()) {
return false;
}
*owner = SanitizeUserName(plain_owner);
return true;
}
bool HomeDirs::IsOrWillBeOwner(const ObfuscatedUsername& username) {
ObfuscatedUsername owner;
GetOwner(&owner);
return !enterprise_owned() && (owner->empty() || username == owner);
}
bool HomeDirs::Create(const ObfuscatedUsername& username) {
brillo::ScopedUmask scoped_umask(libstorage::kDefaultUmask);
// Create the user's entry in the shadow root
FilePath user_dir = UserPath(username);
if (!platform_->CreateDirectory(user_dir)) {
return false;
}
return true;
}
bool HomeDirs::Remove(const ObfuscatedUsername& username) {
remove_callback_.Run(username);
FilePath user_dir = UserPath(username);
FilePath user_path =
brillo::cryptohome::home::GetUserPathPrefix().Append(*username);
FilePath root_path =
brillo::cryptohome::home::GetRootPathPrefix().Append(*username);
if (platform_->IsDirectoryMounted(user_path) ||
platform_->IsDirectoryMounted(root_path)) {
LOG(ERROR) << "Can't remove mounted vault";
return false;
}
bool ret = true;
if (DmcryptCryptohomeExists(username)) {
auto vault =
vault_factory_->Generate(username, libstorage::FileSystemKeyReference(),
libstorage::StorageContainerType::kDmcrypt);
ret = vault->Purge();
}
return ret && platform_->DeletePathRecursively(user_dir) &&
platform_->DeletePathRecursively(user_path) &&
platform_->DeletePathRecursively(root_path);
}
bool HomeDirs::RemoveDmcryptCacheContainer(const ObfuscatedUsername& username) {
if (!DmcryptCacheContainerExists(username))
return false;
auto vault =
vault_factory_->Generate(username, libstorage::FileSystemKeyReference(),
libstorage::StorageContainerType::kDmcrypt);
if (!vault)
return false;
if (vault->GetCacheContainerType() !=
libstorage::StorageContainerType::kDmcrypt)
return false;
return vault->PurgeCacheContainer();
}
int64_t HomeDirs::ComputeDiskUsage(const ObfuscatedUsername& username) {
// Note that for ephemeral mounts, there could be a vault that's not
// ephemeral, but the current mount is ephemeral. In this case,
// ComputeDiskUsage() return the non ephemeral on disk vault's size.
FilePath user_dir = UserPath(username);
int64_t size = 0;
if (!platform_->DirectoryExists(user_dir)) {
// It's either ephemeral or the user doesn't exist. In either case, we check
// /home/user/$hash.
FilePath user_home_dir = brillo::cryptohome::home::GetUserPath(username);
size = platform_->ComputeDirectoryDiskUsage(user_home_dir);
} else {
// Note that we'll need to handle both ecryptfs and dircrypto.
// dircrypto:
// /home/.shadow/$hash/mount: Always equal to the size occupied.
// ecryptfs:
// /home/.shadow/$hash/vault: Always equal to the size occupied.
// /home/.shadow/$hash/mount: Equal to the size occupied only when mounted.
// Therefore, we check to see if vault exists, if it exists, we compute
// vault's size, otherwise, we check mount's size.
FilePath mount_dir = user_dir.Append(kMountDir);
FilePath vault_dir = user_dir.Append(kEcryptfsVaultDir);
if (platform_->DirectoryExists(vault_dir)) {
// ecryptfs
size = platform_->ComputeDirectoryDiskUsage(vault_dir);
} else {
// dircrypto
size = platform_->ComputeDirectoryDiskUsage(mount_dir);
}
}
if (size > 0) {
return size;
}
return 0;
}
namespace {
const char* kChapsDaemonName = "chaps";
} // namespace
FilePath HomeDirs::GetChapsTokenDir(const ObfuscatedUsername& username) const {
return brillo::cryptohome::home::GetDaemonStorePath(username,
kChapsDaemonName);
}
bool HomeDirs::NeedsDircryptoMigration(
const ObfuscatedUsername& username) const {
// Bail if dircrypto is not supported.
const dircrypto::KeyState state =
platform_->GetDirCryptoKeyState(ShadowRoot());
if (state == dircrypto::KeyState::UNKNOWN ||
state == dircrypto::KeyState::NOT_SUPPORTED) {
return false;
}
// Use the existence of eCryptfs vault as a single of whether the user needs
// dircrypto migration. eCryptfs test is adapted from
// Mount::DoesEcryptfsCryptohomeExist.
const FilePath user_ecryptfs_vault_dir =
UserPath(username).Append(kEcryptfsVaultDir);
return platform_->DirectoryExists(user_ecryptfs_vault_dir);
}
int32_t HomeDirs::GetUnmountedAndroidDataCount() {
const auto homedirs = GetHomeDirs();
return std::count_if(
homedirs.begin(), homedirs.end(), [this](const HomeDirs::HomeDir& dir) {
if (dir.is_mounted)
return false;
if (EcryptfsCryptohomeExists(dir.obfuscated))
return false;
FilePath shadow_dir = UserPath(dir.obfuscated);
FilePath root_home_dir;
return GetTrackedDirectory(shadow_dir, FilePath(kRootHomeSuffix),
&root_home_dir) &&
MayContainAndroidData(root_home_dir);
});
}
bool HomeDirs::MayContainAndroidData(
const base::FilePath& root_home_dir) const {
// The root home directory is considered to contain Android data if its
// grandchild (supposedly android-data/data) is owned by android's system UID.
std::unique_ptr<libstorage::FileEnumerator> dir_enum(
platform_->GetFileEnumerator(root_home_dir, false,
base::FileEnumerator::DIRECTORIES));
for (base::FilePath subdirectory = dir_enum->Next(); !subdirectory.empty();
subdirectory = dir_enum->Next()) {
if (LooksLikeAndroidData(subdirectory)) {
return true;
}
}
return false;
}
bool HomeDirs::LooksLikeAndroidData(const base::FilePath& directory) const {
std::unique_ptr<libstorage::FileEnumerator> dir_enum(
platform_->GetFileEnumerator(directory, false,
base::FileEnumerator::DIRECTORIES));
for (base::FilePath subdirectory = dir_enum->Next(); !subdirectory.empty();
subdirectory = dir_enum->Next()) {
if (IsOwnedByAndroidSystem(subdirectory)) {
return true;
}
}
return false;
}
bool HomeDirs::IsOwnedByAndroidSystem(const base::FilePath& directory) const {
uid_t uid = 0;
gid_t gid = 0;
if (!platform_->GetOwnership(directory, &uid, &gid, false)) {
return false;
}
return uid == kAndroidSystemUid + kArcContainerShiftUid;
}
bool HomeDirs::IsAesKeylockerSupported() {
// Perform the check only if there's no cached result yet.
if (!is_aes_keylocker_supported_.has_value()) {
std::string proc_crypto_contents;
is_aes_keylocker_supported_ =
platform_->ReadFileToString(base::FilePath("/proc/crypto"),
&proc_crypto_contents) &&
proc_crypto_contents.find("aeskl") != std::string::npos;
}
return is_aes_keylocker_supported_.value();
}
} // namespace cryptohome