blob: 8b2622bae177099cc6ffd8a67850dd70f727b899 [file] [log] [blame]
// 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.
#include "cryptohome/storage/homedirs.h"
#include <algorithm>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/callback.h>
#include <base/callback_helpers.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <brillo/cryptohome.h>
// TODO(b/177929620): Cleanup once lvm utils are built unconditionally.
#if USE_LVM_STATEFUL_PARTITION
#include <brillo/blkdev_utils/lvm.h>
#endif // USE_LVM_STATEFUL_PARTITION
#include <brillo/scoped_umask.h>
#include <brillo/secure_blob.h>
#include <chromeos/constants/cryptohome.h>
#include <cryptohome/proto_bindings/key.pb.h>
#include "cryptohome/credentials.h"
#include "cryptohome/crypto.h"
#include "cryptohome/crypto_error.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/dircrypto_util.h"
#include "cryptohome/filesystem_layout.h"
#include "cryptohome/platform.h"
#include "cryptohome/storage/cryptohome_vault.h"
#include "cryptohome/storage/cryptohome_vault_factory.h"
#include "cryptohome/storage/encrypted_container/encrypted_container.h"
#include "cryptohome/storage/mount_helper.h"
using base::FilePath;
using brillo::SecureBlob;
using brillo::cryptohome::home::SanitizeUserName;
namespace cryptohome {
const char* kEmptyOwner = "";
// Each xattr is set to Android app internal data directory, contains
// 8-byte inode number of cache subdirectory. See
// frameworks/base/core/java/android/app/ContextImpl.java
const char kAndroidCacheInodeAttribute[] = "user.inode_cache";
const char kAndroidCodeCacheInodeAttribute[] = "user.inode_code_cache";
const char kTrackedDirectoryNameAttribute[] = "user.TrackedDirectoryName";
const char kRemovableFileAttribute[] = "user.GCacheRemovable";
bool IsAesKeylockerSupported() {
std::string proc_crypto_contents;
return base::ReadFileToString(base::FilePath("/proc/crypto"),
&proc_crypto_contents) &&
proc_crypto_contents.find("aeskl") != std::string::npos;
}
HomeDirs::HomeDirs(Platform* platform,
std::unique_ptr<policy::PolicyProvider> policy_provider,
const RemoveCallback& remove_callback)
: HomeDirs(platform,
std::move(policy_provider),
remove_callback,
std::make_unique<CryptohomeVaultFactory>(platform)) {}
HomeDirs::HomeDirs(Platform* platform,
std::unique_ptr<policy::PolicyProvider> policy_provider,
const RemoveCallback& remove_callback,
std::unique_ptr<CryptohomeVaultFactory> vault_factory)
: platform_(platform),
policy_provider_(std::move(policy_provider)),
enterprise_owned_(false),
vault_factory_(std::move(vault_factory)),
remove_callback_(remove_callback) {
// TODO(b/177929620): Cleanup once lvm utils are built unconditionally.
#if USE_LVM_STATEFUL_PARTITION
lvm_ = std::make_unique<brillo::LogicalVolumeManager>();
#endif // USE_LVM_STATEFUL_PARTITION
}
HomeDirs::~HomeDirs() {}
void HomeDirs::LoadDevicePolicy() {
policy_provider_->Reload();
}
bool HomeDirs::AreEphemeralUsersEnabled() {
LoadDevicePolicy();
// If the policy cannot be loaded, default to non-ephemeral users.
bool ephemeral_users_enabled = false;
if (policy_provider_->device_policy_is_loaded())
policy_provider_->GetDevicePolicy().GetEphemeralUsersEnabled(
&ephemeral_users_enabled);
return ephemeral_users_enabled;
}
bool HomeDirs::KeylockerForStorageEncryptionEnabled() {
// Search through /proc/crypto for 'aeskl' as an indicator that AES Keylocker
// is supported. Cache the results so that we don't add the latency of reading
// /proc/crypto for every cryptohome::Mount call.
static bool keylocker_supported = IsAesKeylockerSupported();
if (!keylocker_supported)
return false;
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::SetLockedToSingleUser() const {
return platform_->TouchFileDurable(base::FilePath(kLockedToSingleUserFile));
}
bool HomeDirs::Exists(const std::string& obfuscated_username) const {
FilePath user_dir = ShadowRoot().Append(obfuscated_username);
return platform_->DirectoryExists(user_dir);
}
bool HomeDirs::CryptohomeExists(const std::string& obfuscated_username,
MountError* error) const {
*error = MOUNT_ERROR_NONE;
return EcryptfsCryptohomeExists(obfuscated_username) ||
DircryptoCryptohomeExists(obfuscated_username, error) ||
DmcryptCryptohomeExists(obfuscated_username);
}
bool HomeDirs::EcryptfsCryptohomeExists(
const std::string& obfuscated_username) const {
// Check for the presence of a vault directory for ecryptfs.
return platform_->DirectoryExists(
GetEcryptfsUserVaultPath(obfuscated_username));
}
bool HomeDirs::DircryptoCryptohomeExists(const std::string& obfuscated_username,
MountError* error) const {
// Check for the presence of an encrypted mount directory for dircrypto.
FilePath mount_path = GetUserMountDirectory(obfuscated_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:
*error = MOUNT_ERROR_FATAL;
PLOG(ERROR) << "Directory has inconsistent Fscrypt state:"
<< mount_path.value();
return false;
}
return false;
}
bool HomeDirs::DmcryptContainerExists(
const std::string& obfuscated_username,
const std::string& container_suffix) const {
// TODO(b/177929620): Cleanup once lvm utils are built unconditionally.
#if USE_LVM_STATEFUL_PARTITION
// Check for the presence of the logical volume for the user's data container.
std::string logical_volume_container =
LogicalVolumePrefix(obfuscated_username).append(container_suffix);
// Attempt to check if the stateful partition is setup with a valid physical
// volume.
base::FilePath physical_volume = platform_->GetStatefulDevice();
if (physical_volume.empty())
return false;
auto pv = lvm_->GetPhysicalVolume(physical_volume);
if (!pv || !pv->IsValid())
return false;
auto vg = lvm_->GetVolumeGroup(*pv);
if (!vg || !vg->IsValid())
return false;
return lvm_->GetLogicalVolume(*vg, logical_volume_container) != base::nullopt;
#else
return false;
#endif // USE_LVM_STATEFUL_PARTITION
}
bool HomeDirs::DmcryptCryptohomeExists(
const std::string& obfuscated_username) const {
return DmcryptContainerExists(obfuscated_username,
kDmcryptDataContainerSuffix);
}
bool HomeDirs::DmcryptCacheContainerExists(
const std::string& obfuscated_username) const {
return DmcryptContainerExists(obfuscated_username,
kDmcryptCacheContainerSuffix);
}
void HomeDirs::RemoveNonOwnerCryptohomes() {
// If the device is not enterprise owned it should have an owner user.
std::string owner;
if (!enterprise_owned_ && !GetOwner(&owner)) {
return;
}
auto homedirs = GetHomeDirs();
FilterMountedHomedirs(&homedirs);
for (const auto& dir : homedirs) {
if (GetOwner(&owner)) {
if (dir.obfuscated == owner && !enterprise_owned_) {
continue; // Remove them all if enterprise owned.
}
}
if (platform_->IsDirectoryMounted(
brillo::cryptohome::home::GetUserPathPrefix().Append(
dir.obfuscated)) ||
platform_->IsDirectoryMounted(
brillo::cryptohome::home::GetRootPathPrefix().Append(
dir.obfuscated))) {
continue; // Don't use LE credentials if user cryptohome is mounted.
} else if (!HomeDirs::Remove(dir.obfuscated)) {
LOG(WARNING) << "Failed to remove all non-owner home directories.";
}
}
}
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;
dir.obfuscated = entry.BaseName().value();
if (!brillo::cryptohome::home::IsSanitizedUserName(dir.obfuscated))
continue;
if (!platform_->DirectoryExists(
brillo::cryptohome::home::GetHashedUserPath(dir.obfuscated)))
continue;
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::GetHashedUserPath(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(&name_components);
for (const auto& name_component : name_components) {
FilePath next_path;
std::unique_ptr<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;
}
EncryptedContainerType HomeDirs::ChooseVaultType() {
// TODO(b/177929620): Cleanup once lvm utils are built unconditionally.
#if USE_LVM_STATEFUL_PARTITION
// Validate stateful partition thinpool.
base::Optional<brillo::PhysicalVolume> pv =
lvm_->GetPhysicalVolume(platform_->GetStatefulDevice());
if (pv && pv->IsValid()) {
base::Optional<brillo::VolumeGroup> vg = lvm_->GetVolumeGroup(*pv);
if (vg && vg->IsValid()) {
base::Optional<brillo::Thinpool> thinpool =
lvm_->GetThinpool(*vg, "thinpool");
if (thinpool && thinpool->IsValid())
return EncryptedContainerType::kDmcrypt;
}
}
#endif // USE_LVM_STATEFUL_PARTITION
dircrypto::KeyState state = platform_->GetDirCryptoKeyState(ShadowRoot());
switch (state) {
case dircrypto::KeyState::NOT_SUPPORTED:
return EncryptedContainerType::kEcryptfs;
case dircrypto::KeyState::NO_KEY:
return EncryptedContainerType::kFscrypt;
case dircrypto::KeyState::UNKNOWN:
case dircrypto::KeyState::ENCRYPTED:
LOG(ERROR) << "Unexpected state " << static_cast<int>(state);
return EncryptedContainerType::kUnknown;
}
}
EncryptedContainerType HomeDirs::GetVaultType(
const std::string& obfuscated_username, MountError* error) {
if (EcryptfsCryptohomeExists(obfuscated_username)) {
if (DircryptoCryptohomeExists(obfuscated_username, error)) {
return EncryptedContainerType::kEcryptfsToFscrypt;
}
return EncryptedContainerType::kEcryptfs;
} else if (DircryptoCryptohomeExists(obfuscated_username, error)) {
return EncryptedContainerType::kFscrypt;
} else if (DmcryptCryptohomeExists(obfuscated_username)) {
return EncryptedContainerType::kDmcrypt;
}
return EncryptedContainerType::kUnknown;
}
EncryptedContainerType HomeDirs::PickVaultType(
const std::string& obfuscated_username,
const CryptohomeVault::Options& options,
MountError* error) {
// See if the vault exists.
EncryptedContainerType vault_type = GetVaultType(obfuscated_username, error);
// If existing vault is ecryptfs and migrate == true - make migrating vault.
if (vault_type == EncryptedContainerType::kEcryptfs && options.migrate) {
vault_type = EncryptedContainerType::kEcryptfsToFscrypt;
}
// Validate exiting vault options.
if (vault_type != EncryptedContainerType::kUnknown) {
*error = VerifyVaultType(vault_type, options);
if (*error != MOUNT_ERROR_NONE) {
return EncryptedContainerType::kUnknown;
}
return vault_type;
}
if (options.migrate) {
LOG(ERROR) << "Can not set up migration for a non-existing vault.";
*error = MOUNT_ERROR_UNEXPECTED_MOUNT_TYPE;
return EncryptedContainerType::kUnknown;
}
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 != EncryptedContainerType::kUnknown
? options.force_type
: ChooseVaultType();
}
// TODO(dlunev): this should merge with PickVaultType when we have absl.
MountError HomeDirs::VerifyVaultType(EncryptedContainerType vault_type,
const CryptohomeVault::Options& options) {
if (vault_type == EncryptedContainerType::kEcryptfs &&
options.block_ecryptfs) {
LOG(ERROR) << "Mount attempt with block_ecryptfs on eCryptfs.";
return MOUNT_ERROR_OLD_ENCRYPTION;
}
if (vault_type == EncryptedContainerType::kEcryptfsToFscrypt &&
!options.migrate) {
LOG(ERROR) << "Mount failed because both eCryptfs and dircrypto home"
<< " directories were found. Need to resume and finish"
<< " migration first.";
return MOUNT_ERROR_PREVIOUS_MIGRATION_INCOMPLETE;
}
if (vault_type != EncryptedContainerType::kEcryptfsToFscrypt &&
options.migrate) {
LOG(ERROR) << "Mount attempt with migration on non-eCryptfs mount";
return MOUNT_ERROR_UNEXPECTED_MOUNT_TYPE;
}
return MOUNT_ERROR_NONE;
}
bool HomeDirs::GetPlainOwner(std::string* owner) {
LoadDevicePolicy();
if (!policy_provider_->device_policy_is_loaded())
return false;
policy_provider_->GetDevicePolicy().GetOwner(owner);
return true;
}
bool HomeDirs::GetOwner(std::string* owner) {
std::string plain_owner;
if (!GetPlainOwner(&plain_owner) || plain_owner.empty())
return false;
*owner = SanitizeUserName(plain_owner);
return true;
}
bool HomeDirs::IsOrWillBeOwner(const std::string& account_id) {
std::string owner;
GetPlainOwner(&owner);
return !enterprise_owned_ && (owner.empty() || account_id == owner);
}
bool HomeDirs::Create(const std::string& username) {
brillo::ScopedUmask scoped_umask(kDefaultUmask);
std::string obfuscated_username = SanitizeUserName(username);
// Create the user's entry in the shadow root
FilePath user_dir = ShadowRoot().Append(obfuscated_username);
if (!platform_->CreateDirectory(user_dir)) {
return false;
}
return true;
}
bool HomeDirs::Remove(const std::string& obfuscated) {
remove_callback_.Run(obfuscated);
FilePath user_dir = ShadowRoot().Append(obfuscated);
FilePath user_path =
brillo::cryptohome::home::GetUserPathPrefix().Append(obfuscated);
FilePath root_path =
brillo::cryptohome::home::GetRootPathPrefix().Append(obfuscated);
bool ret = true;
if (DmcryptCryptohomeExists(obfuscated)) {
auto vault = vault_factory_->Generate(obfuscated, FileSystemKeyReference(),
EncryptedContainerType::kDmcrypt);
ret = vault->Purge();
}
return ret && platform_->DeletePathRecursively(user_dir) &&
platform_->DeletePathRecursively(user_path) &&
platform_->DeletePathRecursively(root_path);
}
bool HomeDirs::RemoveDmcryptCacheContainer(const std::string& obfuscated) {
if (!DmcryptCacheContainerExists(obfuscated))
return false;
auto vault = vault_factory_->Generate(obfuscated, FileSystemKeyReference(),
EncryptedContainerType::kDmcrypt);
if (!vault)
return false;
if (vault->GetCacheContainerType() != EncryptedContainerType::kDmcrypt)
return false;
return vault->PurgeCacheContainer();
}
int64_t HomeDirs::ComputeDiskUsage(const std::string& account_id) {
// SanitizeUserNameWithSalt below doesn't accept empty username.
if (account_id.empty()) {
// Empty account is always non-existent, return 0 as specified.
return 0;
}
// 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.
std::string obfuscated = SanitizeUserName(account_id);
FilePath user_dir = ShadowRoot().Append(obfuscated);
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(account_id);
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 std::string& user) const {
return brillo::cryptohome::home::GetDaemonStorePath(user, kChapsDaemonName);
}
bool HomeDirs::NeedsDircryptoMigration(
const std::string& obfuscated_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 =
ShadowRoot().Append(obfuscated_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(), [&](const HomeDirs::HomeDir& dir) {
if (dir.is_mounted)
return false;
if (EcryptfsCryptohomeExists(dir.obfuscated))
return false;
FilePath shadow_dir = ShadowRoot().Append(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<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<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;
}
} // namespace cryptohome