blob: 1f2db92e370ed323d662e39f488725fefc107334 [file] [log] [blame]
// Copyright (c) 2012 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 "mount.h"
#include <errno.h>
#include <sys/stat.h>
#include <base/bind.h>
#include <base/file_util.h>
#include <base/logging.h>
#include <base/threading/platform_thread.h>
#include <base/string_util.h>
#include <base/stringprintf.h>
#include <base/values.h>
#include <chromeos/cryptohome.h>
#include <chromeos/utility.h>
#include <set>
#include "crypto.h"
#include "cryptohome_common.h"
#include "cryptolib.h"
#include "homedirs.h"
#include "platform.h"
#include "username_passkey.h"
#include "vault_keyset.h"
#include "vault_keyset.pb.h"
using chromeos::SecureBlob;
using std::string;
namespace cryptohome {
const char kDefaultUserRoot[] = "/home/chronos";
const char kDefaultHomeDir[] = "/home/chronos/user";
const char kChapsTokenDir[] = "/home/chronos/user/.chaps";
const char kTokenSaltFile[] = "/home/chronos/user/.chaps/auth_data_salt";
const char kDefaultShadowRoot[] = "/home/.shadow";
const char kDefaultSharedUser[] = "chronos";
const char kChapsUserName[] = "chaps";
const char kDefaultSharedAccessGroup[] = "chronos-access";
const char kDefaultSkeletonSource[] = "/etc/skel";
const uid_t kMountOwnerUid = 0;
const gid_t kMountOwnerGid = 0;
// TODO(fes): Remove once UI for BWSI switches to MountGuest()
const char kIncognitoUser[] = "incognito";
// Encrypted files/directories in ecryptfs have file names that start with the
// following constant. When clearing tracked subdirectories, we ignore these
// and only delete the pass-through directories.
const char kEncryptedFilePrefix[] = "ECRYPTFS_FNEK_ENCRYPTED.";
// Tracked directories - special sub-directories of the cryptohome
// vault, that are visible even if not mounted. Contents is still encrypted.
const char kVaultDir[] = "vault";
const char kCacheDir[] = "Cache";
const char kDownloadsDir[] = "Downloads";
const char kGCacheDir[] = "GCache";
const char kGCacheVersionDir[] = "v1";
const char kGCacheTmpDir[] = "tmp";
const char kUserHomeSuffix[] = "user";
const char kRootHomeSuffix[] = "root";
const char kMountDir[] = "mount";
const char kKeyFile[] = "master.0";
const char kEphemeralDir[] = "ephemeralfs";
const char kEphemeralMountType[] = "tmpfs";
const char kEphemeralMountPerms[] = "mode=0700";
const base::TimeDelta kOldUserLastActivityTime = base::TimeDelta::FromDays(92);
const char kDefaultEcryptfsCryptoAlg[] = "aes";
const int kDefaultEcryptfsKeySize = CRYPTOHOME_AES_KEY_BYTES;
const gid_t kDaemonStoreGid = 400;
// A helper class for scoping umask changes.
class ScopedUmask {
public:
ScopedUmask(Platform* platform, int mask)
: platform_(platform),
old_mask_(platform_->SetMask(mask)) {}
~ScopedUmask() {platform_->SetMask(old_mask_);}
private:
Platform* platform_;
int old_mask_;
};
Mount::Mount()
: default_user_(-1),
chaps_user_(-1),
default_group_(-1),
default_access_group_(-1),
shadow_root_(kDefaultShadowRoot),
skel_source_(kDefaultSkeletonSource),
system_salt_(),
default_crypto_(new Crypto()),
crypto_(default_crypto_.get()),
default_platform_(new Platform()),
platform_(default_platform_.get()),
use_tpm_(true),
default_current_user_(new UserSession()),
current_user_(default_current_user_.get()),
default_user_timestamp_(new UserOldestActivityTimestampCache()),
user_timestamp_(default_user_timestamp_.get()),
enterprise_owned_(false),
old_user_last_activity_time_(kOldUserLastActivityTime),
pkcs11_state_(kUninitialized),
is_pkcs11_passkey_migration_required_(false) {
}
Mount::~Mount() {
if (IsCryptohomeMounted())
UnmountCryptohome();
}
bool Mount::Init() {
bool result = true;
homedirs_.set_platform(platform_);
homedirs_.set_shadow_root(shadow_root_);
homedirs_.set_timestamp_cache(user_timestamp_);
homedirs_.set_enterprise_owned(enterprise_owned_);
homedirs_.set_old_user_last_activity_time(old_user_last_activity_time_);
// Make sure both we and |homedirs_| have a proper device policy object.
EnsureDevicePolicyLoaded(false);
homedirs_.set_policy_provider(policy_provider_.get());
homedirs_.set_crypto(crypto_);
if (!homedirs_.Init())
result = false;
// 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_, &not_used)) {
result = false;
}
// Get the group id of the default shared access group.
if (!platform_->GetGroupId(kDefaultSharedAccessGroup,
&default_access_group_)) {
result = false;
}
crypto_->set_use_tpm(use_tpm_);
int original_mask = platform_->SetMask(kDefaultUmask);
// Create the shadow root if it doesn't exist
FilePath shadow_root(shadow_root_);
if (!file_util::DirectoryExists(shadow_root)) {
file_util::CreateDirectory(shadow_root);
}
if (!crypto_->Init(platform_)) {
result = false;
}
// One-time load of the global system salt (used in generating username
// hashes)
FilePath system_salt_file(StringPrintf("%s/salt", shadow_root_.c_str()));
if (!crypto_->GetOrCreateSalt(system_salt_file,
CRYPTOHOME_DEFAULT_SALT_LENGTH, false,
&system_salt_)) {
LOG(ERROR) << "Failed to load or create the system salt";
result = false;
}
platform_->SetMask(original_mask);
current_user_->Init(system_salt_);
return result;
}
bool Mount::EnsureCryptohome(const Credentials& credentials,
bool* created) const {
// If the user has an old-style cryptohome, delete it
FilePath old_image_path(StringPrintf("%s/image",
GetUserDirectory(credentials).c_str()));
if (file_util::PathExists(old_image_path)) {
file_util::Delete(FilePath(GetUserDirectory(credentials)), true);
}
if (!EnsureUserMountPoints(credentials)) {
return false;
}
// Now check for the presence of a vault directory
FilePath vault_path(GetUserVaultPath(
credentials.GetObfuscatedUsername(system_salt_)));
if (!file_util::DirectoryExists(vault_path)) {
// If the vault directory doesn't exist, then create the cryptohome from
// scratch
bool result = CreateCryptohome(credentials);
if (created) {
*created = result;
}
return result;
}
if (created) {
*created = false;
}
return true;
}
bool Mount::DoesCryptohomeExist(const Credentials& credentials) const {
// Check for the presence of a vault directory
FilePath vault_path(GetUserVaultPath(
credentials.GetObfuscatedUsername(system_salt_)));
return file_util::DirectoryExists(vault_path);
}
bool Mount::MountCryptohome(const Credentials& credentials,
const Mount::MountArgs& mount_args,
MountError* mount_error) {
MountError local_mount_error = MOUNT_ERROR_NONE;
bool result = MountCryptohomeInner(credentials,
mount_args,
true,
&local_mount_error);
// Retry once if there is a TPM communications failure
if (!result && local_mount_error == MOUNT_ERROR_TPM_COMM_ERROR) {
result = MountCryptohomeInner(credentials,
mount_args,
true,
&local_mount_error);
}
if (mount_error) {
*mount_error = local_mount_error;
}
return result;
}
bool Mount::MountCryptohomeInner(const Credentials& credentials,
const Mount::MountArgs& mount_args,
bool recreate_decrypt_fatal,
MountError* mount_error) {
current_user_->Reset();
string username = credentials.username();
if (username.compare(kIncognitoUser) == 0) {
// TODO(fes): Have guest set error conditions?
if (mount_error) {
*mount_error = MOUNT_ERROR_NONE;
}
return MountGuestCryptohome();
}
ReloadDevicePolicy();
bool ephemeral_users = AreEphemeralUsersEnabled();
const string obfuscated_owner = GetObfuscatedOwner();
if (ephemeral_users)
RemoveNonOwnerCryptohomes();
// Mount an ephemeral cryptohome if the ephemeral users policy is enabled and
// the user is not the device owner.
if (ephemeral_users &&
(enterprise_owned_ || !obfuscated_owner.empty()) &&
(credentials.GetObfuscatedUsername(system_salt_) != obfuscated_owner)) {
if (!mount_args.create_if_missing) {
LOG(ERROR) << "An ephemeral cryptohome can only be mounted when its "
<< "creation on-the-fly is allowed.";
*mount_error = MOUNT_ERROR_USER_DOES_NOT_EXIST;
return false;
}
if (!MountEphemeralCryptohome(credentials)) {
homedirs_.Remove(credentials.username());
*mount_error = MOUNT_ERROR_FATAL;
return false;
}
current_user_->SetUser(credentials);
*mount_error = MOUNT_ERROR_NONE;
return true;
}
if (!mount_args.create_if_missing && !DoesCryptohomeExist(credentials)) {
if (mount_error) {
LOG(ERROR) << "Asked to mount nonexistent user";
*mount_error = MOUNT_ERROR_USER_DOES_NOT_EXIST;
}
return false;
}
bool created = false;
if (!EnsureCryptohome(credentials, &created)) {
LOG(ERROR) << "Error creating cryptohome.";
if (mount_error) {
*mount_error = MOUNT_ERROR_FATAL;
}
return false;
}
// Attempt to decrypt the vault keyset with the specified credentials
VaultKeyset vault_keyset(platform_, crypto_);
SerializedVaultKeyset serialized;
MountError local_mount_error = MOUNT_ERROR_NONE;
if (!DecryptVaultKeyset(credentials, true, &vault_keyset, &serialized,
&local_mount_error)) {
if (mount_error) {
*mount_error = local_mount_error;
}
if (recreate_decrypt_fatal & (local_mount_error & MOUNT_ERROR_FATAL)) {
LOG(ERROR) << "Error, cryptohome must be re-created because of fatal "
<< "error.";
if (!homedirs_.Remove(credentials.username())) {
LOG(ERROR) << "Fatal decryption error, but unable to remove "
<< "cryptohome.";
return false;
}
// Allow one recursion into MountCryptohomeInner by blocking re-create on
// fatal.
bool local_result = MountCryptohomeInner(credentials,
mount_args,
false,
mount_error);
// If the mount was successful, set the status to indicate that the
// cryptohome was recreated.
if (local_result && mount_error) {
*mount_error = MOUNT_ERROR_RECREATED;
}
return local_result;
}
return false;
}
crypto_->ClearKeyset();
// Add the decrypted key to the keyring so that ecryptfs can use it
string key_signature, fnek_signature;
if (!crypto_->AddKeyset(vault_keyset, &key_signature, &fnek_signature)) {
LOG(INFO) << "Cryptohome mount failed because of keyring failure.";
if (mount_error) {
*mount_error = MOUNT_ERROR_FATAL;
}
return false;
}
// Specify the ecryptfs options for mounting the user's cryptohome
string ecryptfs_options = StringPrintf("ecryptfs_cipher=aes"
",ecryptfs_key_bytes=%d"
",ecryptfs_fnek_sig=%s"
",ecryptfs_sig=%s"
",ecryptfs_unlink_sigs",
kDefaultEcryptfsKeySize,
fnek_signature.c_str(),
key_signature.c_str());
// 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
string vault_path = GetUserVaultPath(
credentials.GetObfuscatedUsername(system_salt_));
// Create vault_path/user as a passthrough directory, move all the (encrypted)
// contents of vault_path into vault_path/user, create vault_path/root.
MigrateToUserHome(vault_path);
mount_point_ = GetUserMountDirectory(credentials);
if (!platform_->CreateDirectory(mount_point_)) {
PLOG(ERROR) << "Directory creation failed for " << mount_point_;
if (mount_error) {
*mount_error = MOUNT_ERROR_FATAL;
}
return false;
}
if (!MountForUser(current_user_, vault_path, mount_point_, "ecryptfs",
ecryptfs_options)) {
PLOG(ERROR) << "Cryptohome mount failed for vault " << vault_path;
if (mount_error) {
*mount_error = MOUNT_ERROR_FATAL;
}
return false;
}
// Move the tracked subdirectories from mount_point_/user to vault_path
// as passthrough directories.
CreateTrackedSubdirectories(credentials, created);
string user_home = GetMountedUserHomePath(credentials);
if (!BindForUser(current_user_, user_home, kDefaultHomeDir)) {
PLOG(ERROR) << "Bind mount failed: " << user_home << " -> "
<< kDefaultHomeDir;
UnmountAllForUser(current_user_);
if (mount_error) {
*mount_error = MOUNT_ERROR_FATAL;
}
return false;
}
string user_multi_home =
chromeos::cryptohome::home::GetUserPath(username).value();
if (!BindForUser(current_user_, user_home, user_multi_home)) {
PLOG(ERROR) << "Bind mount failed: " << user_home << " -> "
<< user_multi_home;
UnmountAllForUser(current_user_);
if (mount_error) {
*mount_error = MOUNT_ERROR_FATAL;
}
return false;
}
string root_home = GetMountedRootHomePath(credentials);
string root_multi_home =
chromeos::cryptohome::home::GetRootPath(username).value();
if (!BindForUser(current_user_, root_home, root_multi_home)) {
PLOG(ERROR) << "Bind mount failed: " << root_home << " -> "
<< root_multi_home;
UnmountAllForUser(current_user_);
if (mount_error) {
*mount_error = MOUNT_ERROR_FATAL;
}
return false;
}
// TODO(ellyjones): Expose the path to the root directory over dbus for use by
// daemons. We may also want to bind-mount it somewhere stable.
if (created) {
CopySkeletonForUser(credentials);
}
if (!SetupGroupAccess()) {
UnmountAllForUser(current_user_);
if (mount_error) {
*mount_error = MOUNT_ERROR_FATAL;
}
return false;
}
if (mount_error) {
*mount_error = MOUNT_ERROR_NONE;
}
current_user_->SetUser(credentials);
credentials.GetPasskey(&pkcs11_passkey_);
return true;
}
bool Mount::MountEphemeralCryptohome(const Credentials& credentials) {
const string username = credentials.username();
const string path = GetUserEphemeralPath(
credentials.GetObfuscatedUsername(system_salt_));
const string user_multi_home =
chromeos::cryptohome::home::GetUserPath(username).value();
const string root_multi_home =
chromeos::cryptohome::home::GetRootPath(username).value();
if (!EnsureUserMountPoints(credentials))
return false;
if (!MountForUser(current_user_,
path,
user_multi_home,
kEphemeralMountType,
kEphemeralMountPerms)) {
LOG(ERROR) << "Mount of ephemeral user home at " << user_multi_home
<< "failed: " << errno;
return false;
}
if (!MountForUser(current_user_,
path,
root_multi_home,
kEphemeralMountType,
kEphemeralMountPerms)) {
LOG(ERROR) << "Mount of ephemeral root home at " << root_multi_home
<< "failed: " << errno;
UnmountAllForUser(current_user_);
return false;
}
if (!BindForUser(current_user_, user_multi_home, kDefaultHomeDir)) {
LOG(ERROR) << "Bind mount of ephemeral user home from " << user_multi_home
<< " to " << kDefaultHomeDir << " failed: " << errno;
UnmountAllForUser(current_user_);
return false;
}
return SetUpEphemeralCryptohome(user_multi_home);
}
bool Mount::SetUpEphemeralCryptohome(const std::string& home_dir) {
if (!platform_->SetOwnership(home_dir, default_user_, default_group_)) {
LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":"
<< default_group_ << ") of path: " << home_dir;
UnmountAllForUser(current_user_);
return false;
}
CopySkeleton();
// Create the Downloads directory if it does not exist so that it can later be
// made group accessible when SetupGroupAccess() is called.
FilePath downloads_path = FilePath(home_dir).Append(kDownloadsDir);
if (!file_util::DirectoryExists(downloads_path)) {
if (!file_util::CreateDirectory(downloads_path) ||
!platform_->SetOwnership(downloads_path.value(),
default_user_, default_group_)) {
LOG(ERROR) << "Couldn't create user Downloads directory: "
<< downloads_path.value();
UnmountAllForUser(current_user_);
return false;
}
}
if (!SetupGroupAccess()) {
UnmountAllForUser(current_user_);
return false;
}
return true;
}
bool Mount::MountForUser(UserSession *user, const std::string& src,
const std::string& dest, const std::string& type,
const std::string& options) {
if (platform_->Mount(src, dest, type, options)) {
mounts_.Push(dest);
return true;
}
return false;
}
bool Mount::BindForUser(UserSession *user, const std::string& src,
const std::string& dest) {
if (platform_->Bind(src, dest)) {
mounts_.Push(dest);
return true;
}
return false;
}
bool Mount::UnmountForUser(UserSession *user) {
std::string mount_point;
if (!mounts_.Pop(&mount_point)) {
return false;
}
ForceUnmount(mount_point);
return true;
}
void Mount::UnmountAllForUser(UserSession *user) {
while (UnmountForUser(user)) { }
}
void Mount::ForceUnmount(const std::string& mount_point) {
// Try an immediate unmount
bool was_busy;
if (!platform_->Unmount(mount_point, false, &was_busy)) {
LOG(ERROR) << "Couldn't unmount vault immediately, was_busy = " << was_busy;
if (was_busy) {
std::vector<ProcessInformation> processes;
platform_->GetProcessesWithOpenFiles(mount_point, &processes);
for (std::vector<ProcessInformation>::iterator proc_itr =
processes.begin();
proc_itr != processes.end();
proc_itr++) {
LOG(ERROR) << "Process " << proc_itr->get_process_id()
<< " had open files. Command line: "
<< proc_itr->GetCommandLine();
if (proc_itr->get_cwd().length()) {
LOG(ERROR) << " (" << proc_itr->get_process_id() << ") CWD: "
<< proc_itr->get_cwd();
}
for (std::set<std::string>::iterator file_itr =
proc_itr->get_open_files().begin();
file_itr != proc_itr->get_open_files().end();
file_itr++) {
LOG(ERROR) << " (" << proc_itr->get_process_id() << ") Open File: "
<< (*file_itr);
}
}
sync();
}
// Failed to unmount immediately, do a lazy unmount
platform_->Unmount(mount_point, true, NULL);
sync();
}
}
void Mount::RemoveNonOwnerCryptohomesCallback(const FilePath& vault) {
if (vault != FilePath(GetUserVaultPath(GetObfuscatedOwner())))
file_util::Delete(vault.DirName(), true);
}
void Mount::RemoveNonOwnerDirectories(const FilePath& prefix) {
file_util::FileEnumerator dir_enumerator(prefix, false,
file_util::FileEnumerator::DIRECTORIES);
for (FilePath next_path = dir_enumerator.Next(); !next_path.empty();
next_path = dir_enumerator.Next()) {
const std::string str_dir_name = next_path.BaseName().value();
if (!base::strcasecmp(str_dir_name.c_str(), GetObfuscatedOwner().c_str()))
continue; // Skip the owner's directory.
if (!chromeos::cryptohome::home::IsSanitizedUserName(str_dir_name))
continue; // Skip any directory whose name is not an obfuscated user
// name.
if (platform_->IsDirectoryMounted(next_path.value()))
continue; // Skip any directory that is currently mounted.
platform_->DeleteFile(next_path.value(), true);
}
}
void Mount::RemoveNonOwnerCryptohomes() {
if (!enterprise_owned_ && GetObfuscatedOwner().empty())
return;
DoForEveryUnmountedCryptohome(base::Bind(
&Mount::RemoveNonOwnerCryptohomesCallback,
base::Unretained(this)));
RemoveNonOwnerDirectories(chromeos::cryptohome::home::GetUserPathPrefix());
RemoveNonOwnerDirectories(chromeos::cryptohome::home::GetRootPathPrefix());
}
bool Mount::UnmountCryptohome() {
UnmountAllForUser(current_user_);
ReloadDevicePolicy();
if (AreEphemeralUsersEnabled())
RemoveNonOwnerCryptohomes();
else
UpdateCurrentUserActivityTimestamp(0);
current_user_->Reset();
// Clear the user keyring if the unmount was successful
crypto_->ClearKeyset();
return true;
}
bool Mount::IsCryptohomeMounted() const {
return platform_->IsDirectoryMounted(kDefaultHomeDir);
}
bool Mount::IsCryptohomeMountedForUser(const Credentials& credentials) const {
const std::string obfuscated_owner =
credentials.GetObfuscatedUsername(system_salt_);
return (platform_->IsDirectoryMountedWith(
kDefaultHomeDir,
GetUserVaultPath(obfuscated_owner)) ||
platform_->IsDirectoryMountedWith(
kDefaultHomeDir,
GetUserEphemeralPath(obfuscated_owner)));
}
bool Mount::IsVaultMountedForUser(const Credentials& credentials) const {
return platform_->IsDirectoryMountedWith(
kDefaultHomeDir,
GetUserVaultPath(credentials.GetObfuscatedUsername(system_salt_)));
}
bool Mount::CreateCryptohome(const Credentials& credentials) const {
int original_mask = platform_->SetMask(kDefaultUmask);
// Create the user's entry in the shadow root
FilePath user_dir(GetUserDirectory(credentials));
file_util::CreateDirectory(user_dir);
// Generate a new master key
VaultKeyset vault_keyset(platform_, crypto_);
vault_keyset.CreateRandom();
SerializedVaultKeyset serialized;
if (!AddVaultKeyset(credentials, vault_keyset, &serialized)) {
platform_->SetMask(original_mask);
return false;
}
if (!StoreVaultKeyset(credentials, serialized)) {
platform_->SetMask(original_mask);
return false;
}
// Create the user's vault.
std::string vault_path = GetUserVaultPath(
credentials.GetObfuscatedUsername(system_salt_));
if (!platform_->CreateDirectory(vault_path)) {
LOG(ERROR) << "Couldn't create vault path: " << vault_path.c_str();
platform_->SetMask(original_mask);
return false;
}
// Restore the umask
platform_->SetMask(original_mask);
return true;
}
bool Mount::CreateTrackedSubdirectories(const Credentials& credentials,
bool is_new) const {
const int original_mask = platform_->SetMask(kDefaultUmask);
// Add the subdirectories if they do not exist.
const FilePath shadow_home(VaultPathToUserPath(GetUserVaultPath(
credentials.GetObfuscatedUsername(system_salt_))));
if (!file_util::DirectoryExists(shadow_home)) {
LOG(ERROR) << "Can't create tracked subdirectories for a missing user.";
platform_->SetMask(original_mask);
return false;
}
const FilePath user_home(GetMountedUserHomePath(credentials));
static const FilePath kTrackedDirs[] = {
FilePath(kCacheDir),
FilePath(kDownloadsDir),
FilePath(kGCacheDir),
FilePath(kGCacheDir).Append(kGCacheVersionDir),
FilePath(kGCacheDir).Append(kGCacheVersionDir).Append(kGCacheTmpDir),
};
// The call is allowed to partially fail if directory creation fails, but we
// want to have as many of the specified tracked directories created as
// possible.
bool result = true;
for (size_t index = 0; index < arraysize(kTrackedDirs); ++index) {
const FilePath shadowside_dir = shadow_home.Append(kTrackedDirs[index]);
const FilePath userside_dir = user_home.Append(kTrackedDirs[index]);
// If non-pass-through dir with the same name existed - delete it
// to prevent duplication.
if (!is_new && file_util::DirectoryExists(userside_dir) &&
!file_util::DirectoryExists(shadowside_dir)) {
file_util::Delete(userside_dir, true);
}
// Create pass-through directory.
if (!file_util::DirectoryExists(shadowside_dir)) {
LOG(INFO) << "Creating pass-through directories "
<< shadowside_dir.value();
file_util::CreateDirectory(shadowside_dir);
if (!platform_->SetOwnership(shadowside_dir.value(),
default_user_, default_group_)) {
LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":"
<< default_group_ << ") of tracked directory path: "
<< shadowside_dir.value();
file_util::Delete(shadowside_dir, true);
result = false;
continue;
}
}
}
// Restore the umask
platform_->SetMask(original_mask);
return result;
}
bool Mount::UpdateCurrentUserActivityTimestamp(int time_shift_sec) {
string obfuscated_username;
current_user_->GetObfuscatedUsername(&obfuscated_username);
if (!obfuscated_username.empty()) {
SerializedVaultKeyset serialized;
LoadVaultKeysetForUser(obfuscated_username, &serialized);
base::Time timestamp = platform_->GetCurrentTime();
if (time_shift_sec > 0)
timestamp -= base::TimeDelta::FromSeconds(time_shift_sec);
serialized.set_last_activity_timestamp(timestamp.ToInternalValue());
StoreVaultKeysetForUser(obfuscated_username, serialized);
if (user_timestamp_->initialized()) {
user_timestamp_->UpdateExistingUser(
FilePath(GetUserDirectoryForUser(obfuscated_username)), timestamp);
}
return true;
}
return false;
}
void Mount::EnsureDevicePolicyLoaded(bool force_reload) {
if (!policy_provider_.get()) {
policy_provider_.reset(new policy::PolicyProvider());
homedirs_.set_policy_provider(policy_provider_.get());
} else if (force_reload) {
policy_provider_->Reload();
}
}
void Mount::DoForEveryUnmountedCryptohome(
const CryptohomeCallback& cryptohome_cb) {
FilePath shadow_root(shadow_root_);
file_util::FileEnumerator dir_enumerator(shadow_root, false,
file_util::FileEnumerator::DIRECTORIES);
for (FilePath next_path = dir_enumerator.Next(); !next_path.empty();
next_path = dir_enumerator.Next()) {
const std::string str_dir_name = next_path.BaseName().value();
if (!chromeos::cryptohome::home::IsSanitizedUserName(str_dir_name))
continue;
const FilePath vault_path = next_path.Append(kVaultDir);
if (!file_util::DirectoryExists(vault_path)) {
continue;
}
if (platform_->IsDirectoryMountedWith(kDefaultHomeDir,
vault_path.value())) {
continue;
}
cryptohome_cb.Run(vault_path);
}
}
bool Mount::DoAutomaticFreeDiskSpaceControl() {
return homedirs_.FreeDiskSpace();
}
bool Mount::SetupGroupAccess() const {
// Make the following directories group accessible by other system daemons:
// /home/chronos/user
// /home/chronos/user/Downloads
// /home/chronos/user/GCache (only if it exists)
// /home/chronos/user/GCache/v1 (only if it exists)
const struct {
FilePath path;
bool optional;
} kGroupAccessiblePaths[] = {
{ FilePath(kDefaultHomeDir), false },
{ FilePath(kDefaultHomeDir).Append(kDownloadsDir), false },
{ FilePath(kDefaultHomeDir).Append(kGCacheDir), true },
{ FilePath(kDefaultHomeDir).Append(kGCacheDir).Append(kGCacheVersionDir),
true },
};
mode_t mode = S_IXGRP;
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kGroupAccessiblePaths); ++i) {
if (!file_util::PathExists(kGroupAccessiblePaths[i].path) &&
kGroupAccessiblePaths[i].optional)
continue;
if (!platform_->SetGroupAccessible(kGroupAccessiblePaths[i].path.value(),
default_access_group_, mode))
return false;
}
return true;
}
bool Mount::AreSameUser(const Credentials& credentials) {
return current_user_->CheckUser(credentials);
}
bool Mount::AreValid(const Credentials& credentials) {
// If the current logged in user matches, use the UserSession to verify the
// credentials. This is less costly than a trip to the TPM, and only verifies
// a user during their logged in session.
if (current_user_->CheckUser(credentials)) {
return current_user_->Verify(credentials);
}
return false;
}
bool Mount::LoadVaultKeyset(const Credentials& credentials,
SerializedVaultKeyset* serialized) const {
return LoadVaultKeysetForUser(credentials.GetObfuscatedUsername(system_salt_),
serialized);
}
bool Mount::LoadVaultKeysetForUser(const string& obfuscated_username,
SerializedVaultKeyset* serialized) const {
// Load the encrypted keyset
std::string user_key_file = GetUserKeyFileForUser(obfuscated_username);
if (!platform_->FileExists(user_key_file)) {
return false;
}
SecureBlob cipher_text;
if (!platform_->ReadFile(user_key_file, &cipher_text)) {
return false;
}
if (!serialized->ParseFromArray(
static_cast<const unsigned char*>(cipher_text.data()),
cipher_text.size())) {
return false;
}
return true;
}
bool Mount::StoreVaultKeyset(const Credentials& credentials,
const SerializedVaultKeyset& serialized) const {
return StoreVaultKeysetForUser(
credentials.GetObfuscatedUsername(system_salt_),
serialized);
}
bool Mount::StoreVaultKeysetForUser(
const string& obfuscated_username,
const SerializedVaultKeyset& serialized) const {
SecureBlob final_blob(serialized.ByteSize());
serialized.SerializeWithCachedSizesToArray(
static_cast<google::protobuf::uint8*>(final_blob.data()));
unsigned int data_written = file_util::WriteFile(
FilePath(GetUserKeyFileForUser(obfuscated_username)),
static_cast<const char*>(final_blob.const_data()),
final_blob.size());
if (data_written != final_blob.size()) {
return false;
}
return true;
}
bool Mount::DecryptVaultKeyset(const Credentials& credentials,
bool migrate_if_needed,
VaultKeyset* vault_keyset,
SerializedVaultKeyset* serialized,
MountError* error) const {
SecureBlob passkey;
credentials.GetPasskey(&passkey);
// Load the encrypted keyset
if (!LoadVaultKeyset(credentials, serialized)) {
if (error) {
*error = MOUNT_ERROR_FATAL;
}
return false;
}
// Attempt decrypt the master key with the passkey
unsigned int crypt_flags = 0;
Crypto::CryptoError crypto_error = Crypto::CE_NONE;
if (!crypto_->DecryptVaultKeyset(*serialized, passkey, &crypt_flags,
&crypto_error, vault_keyset)) {
if (error) {
switch (crypto_error) {
case Crypto::CE_TPM_FATAL:
case Crypto::CE_OTHER_FATAL:
*error = MOUNT_ERROR_FATAL;
break;
case Crypto::CE_TPM_COMM_ERROR:
*error = MOUNT_ERROR_TPM_COMM_ERROR;
break;
case Crypto::CE_TPM_DEFEND_LOCK:
*error = MOUNT_ERROR_TPM_DEFEND_LOCK;
break;
default:
*error = MOUNT_ERROR_KEY_FAILURE;
break;
}
}
return false;
}
if (!migrate_if_needed)
return true;
// Calling EnsureTpm here handles the case where a user logged in while
// cryptohome was taking TPM ownership. In that case, their vault keyset
// would be scrypt-wrapped and the TPM would not be connected. If we're
// configured to use the TPM, calling EnsureTpm will try to connect, and
// if successful, the call to has_tpm() below will succeed, allowing
// re-wrapping (migration) using the TPM.
if (use_tpm_) {
crypto_->EnsureTpm(false);
}
// If the vault keyset's TPM state is not the same as that configured for
// the device, re-save the keyset (this will save in the device's default
// method).
// 1 2 3 4 5 6 7 8 9 10 11 12
// use_tpm - - - X X X X X X - - -
//
// fallback_to_scrypt - - - - - - X X X X X X
//
// tpm_wrapped - X - - X - - X - - X -
//
// scrypt_wrapped - - X - - X - - X - - X
//
// migrate N Y Y Y N Y Y N Y Y Y N
bool tpm_wrapped =
(crypt_flags & SerializedVaultKeyset::TPM_WRAPPED) != 0;
bool scrypt_wrapped =
(crypt_flags & SerializedVaultKeyset::SCRYPT_WRAPPED) != 0;
bool should_tpm = (crypto_->has_tpm() && use_tpm_ &&
crypto_->is_tpm_connected());
bool should_scrypt = true;
do {
// If the keyset was TPM-wrapped, but there was no public key hash,
// always re-save. Otherwise, check the table.
if (crypto_error != Crypto::CE_NO_PUBLIC_KEY_HASH) {
if (tpm_wrapped && should_tpm)
break; // 5, 8
if (scrypt_wrapped && should_scrypt && !should_tpm)
break; // 12
if (!tpm_wrapped && !scrypt_wrapped && !should_tpm && !should_scrypt)
break; // 1
}
// This is not considered a fatal error. Re-saving with the desired
// protection is ideal, but not required.
SerializedVaultKeyset new_serialized;
new_serialized.CopyFrom(*serialized);
if (ReEncryptVaultKeyset(credentials, *vault_keyset, &new_serialized)) {
serialized->CopyFrom(new_serialized);
}
} while (false);
return true;
}
bool Mount::AddVaultKeyset(const Credentials& credentials,
const VaultKeyset& vault_keyset,
SerializedVaultKeyset* serialized) const {
// We don't do passkey to wrapper conversion because it is salted during save
SecureBlob passkey;
credentials.GetPasskey(&passkey);
// Encrypt the vault keyset
SecureBlob salt(CRYPTOHOME_DEFAULT_KEY_SALT_SIZE);
CryptoLib::GetSecureRandom(static_cast<unsigned char*>(salt.data()),
salt.size());
if (!crypto_->EncryptVaultKeyset(vault_keyset, passkey, salt, serialized)) {
LOG(ERROR) << "Encrypting vault keyset failed";
return false;
}
return true;
}
bool Mount::ReEncryptVaultKeyset(const Credentials& credentials,
const VaultKeyset& vault_keyset,
SerializedVaultKeyset* serialized) const {
std::vector<std::string> files(2);
files[0] = GetUserSaltFile(credentials);
files[1] = GetUserKeyFile(credentials);
if (!CacheOldFiles(files)) {
LOG(ERROR) << "Couldn't cache old key material.";
return false;
}
if (!AddVaultKeyset(credentials, vault_keyset, serialized)) {
LOG(ERROR) << "Couldn't add keyset.";
RevertCacheFiles(files);
return false;
}
if (!StoreVaultKeyset(credentials, *serialized)) {
LOG(ERROR) << "Write to master key failed";
RevertCacheFiles(files);
return false;
}
DeleteCacheFiles(files);
return true;
}
bool Mount::MountGuestCryptohome() {
current_user_->Reset();
// Attempt to mount guestfs
if (!MountForUser(current_user_, "guestfs", kDefaultHomeDir,
kEphemeralMountType, kEphemeralMountPerms)) {
LOG(ERROR) << "Cryptohome mount failed: " << errno << " for guestfs";
return false;
}
return SetUpEphemeralCryptohome(kDefaultHomeDir);
}
string Mount::GetUserDirectory(const Credentials& credentials) const {
return GetUserDirectoryForUser(
credentials.GetObfuscatedUsername(system_salt_));
}
string Mount::GetUserDirectoryForUser(const string& obfuscated_username) const {
return StringPrintf("%s/%s",
shadow_root_.c_str(),
obfuscated_username.c_str());
}
string Mount::GetUserSaltFile(const Credentials& credentials) const {
return StringPrintf("%s/%s/master.0.salt",
shadow_root_.c_str(),
credentials.GetObfuscatedUsername(system_salt_).c_str());
}
string Mount::GetUserKeyFile(const Credentials& credentials) const {
return GetUserKeyFileForUser(credentials.GetObfuscatedUsername(system_salt_));
}
string Mount::GetUserKeyFileForUser(const string& obfuscated_username) const {
return StringPrintf("%s/%s/%s",
shadow_root_.c_str(),
obfuscated_username.c_str(),
kKeyFile);
}
string Mount::GetUserEphemeralPath(const string& obfuscated_username) const {
return StringPrintf("%s/%s", kEphemeralDir, obfuscated_username.c_str());
}
string Mount::GetUserVaultPath(const std::string& obfuscated_username) const {
return StringPrintf("%s/%s/%s",
shadow_root_.c_str(),
obfuscated_username.c_str(),
kVaultDir);
}
string Mount::GetUserMountDirectory(const Credentials& credentials) const {
return StringPrintf("%s/%s/%s",
shadow_root_.c_str(),
credentials.GetObfuscatedUsername(system_salt_).c_str(),
kMountDir);
}
string Mount::VaultPathToUserPath(const std::string& vault) const {
return StringPrintf("%s/%s", vault.c_str(), kUserHomeSuffix);
}
string Mount::VaultPathToRootPath(const std::string& vault) const {
return StringPrintf("%s/%s", vault.c_str(), kRootHomeSuffix);
}
string Mount::GetMountedUserHomePath(const Credentials& credentials) const {
return StringPrintf("%s/%s", GetUserMountDirectory(credentials).c_str(),
kUserHomeSuffix);
}
string Mount::GetMountedRootHomePath(const Credentials& credentials) const {
return StringPrintf("%s/%s", GetUserMountDirectory(credentials).c_str(),
kRootHomeSuffix);
}
string Mount::GetObfuscatedOwner() {
EnsureDevicePolicyLoaded(false);
string owner;
if (policy_provider_->device_policy_is_loaded())
policy_provider_->GetDevicePolicy().GetOwner(&owner);
if (!owner.empty()) {
return UsernamePasskey(owner.c_str(), chromeos::Blob())
.GetObfuscatedUsername(system_salt_);
}
return "";
}
bool Mount::AreEphemeralUsersEnabled() {
EnsureDevicePolicyLoaded(false);
// 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;
}
void Mount::ReloadDevicePolicy() {
EnsureDevicePolicyLoaded(true);
}
bool Mount::CheckChapsDirectory(bool* permissions_status) {
// If the Chaps database directory does not exist, create it.
DCHECK(permissions_status);
*permissions_status = true;
// 0750: u + rwx, g + rx
mode_t chaps_perms = S_IRWXU | S_IRGRP | S_IXGRP;
if (!platform_->DirectoryExists(kChapsTokenDir)) {
if (!platform_->CreateDirectory(kChapsTokenDir)) {
LOG(ERROR) << "Failed to create " << kChapsTokenDir;
*permissions_status = false;
return false;
}
if (!platform_->SetOwnership(kChapsTokenDir, chaps_user_,
default_access_group_)) {
LOG(ERROR) << "Couldn't set file ownership for " << kChapsTokenDir;
*permissions_status = false;
return false;
}
if (!platform_->SetPermissions(kChapsTokenDir, chaps_perms)) {
LOG(ERROR) << "Couldn't set permissions for " << kChapsTokenDir;
*permissions_status = false;
return false;
}
return true;
}
// Directory already exists so check permissions and log a warning
// if not as expected.
mode_t current_perms;
if (!platform_->GetPermissions(kChapsTokenDir, &current_perms)) {
LOG(ERROR) << "Couldn't get permissions for " << kChapsTokenDir;
*permissions_status = false;
return false;
}
if (current_perms != chaps_perms) {
LOG(WARNING) << "Chaps directory had incorrect permissions: "
<< StringPrintf("Expected: %o Found: %o",
chaps_perms, current_perms);
*permissions_status = false;
}
uid_t current_user;
gid_t current_group;
if (!platform_->GetOwnership(kChapsTokenDir, &current_user,
&current_group)) {
LOG(ERROR) << "Couldn't get ownership for " << kChapsTokenDir;
*permissions_status = false;
return false;
}
if (current_user != chaps_user_) {
LOG(WARNING) << "Chaps directory had incorrect owner: "
<< StringPrintf("Expected: %u Found: %u",
chaps_user_, current_user);
*permissions_status = false;
}
if (current_group != default_access_group_) {
LOG(WARNING) << "Chaps directory had incorrect group: "
<< StringPrintf("Expected: %u Found: %u",
default_access_group_, current_group);
*permissions_status = false;
}
return true;
}
bool Mount::InsertPkcs11Token() {
bool permissions_status = false;
if (!CheckChapsDirectory(&permissions_status))
return false;
// We may create a salt file and, if so, we want to restrict access to it.
ScopedUmask scoped_umask(platform_, kDefaultUmask);
// Derive authorization data for the token from the passkey.
FilePath salt_file(kTokenSaltFile);
SecureBlob auth_data;
if (!crypto_->PasskeyToTokenAuthData(pkcs11_passkey_, salt_file, &auth_data))
return false;
// If migration is required, send it before the login event.
if (is_pkcs11_passkey_migration_required_) {
LOG(INFO) << "Migrating authorization data.";
SecureBlob old_auth_data;
if (!crypto_->PasskeyToTokenAuthData(pkcs11_old_passkey_,
salt_file,
&old_auth_data))
return false;
chaps_event_client_.FireChangeAuthDataEvent(
kChapsTokenDir,
static_cast<const uint8_t*>(old_auth_data.const_data()),
old_auth_data.size(),
static_cast<const uint8_t*>(auth_data.const_data()),
auth_data.size());
is_pkcs11_passkey_migration_required_ = false;
pkcs11_old_passkey_.clear_contents();
}
chaps_event_client_.FireLoginEvent(
kChapsTokenDir,
static_cast<const uint8_t*>(auth_data.const_data()),
auth_data.size());
pkcs11_passkey_.clear_contents();
return permissions_status;
}
void Mount::RemovePkcs11Token() {
chaps_event_client_.FireLogoutEvent(kChapsTokenDir);
}
void Mount::RecursiveCopy(const FilePath& destination,
const FilePath& source) const {
file_util::FileEnumerator file_enumerator(source, false,
file_util::FileEnumerator::FILES);
FilePath next_path;
while (!(next_path = file_enumerator.Next()).empty()) {
FilePath file_name = next_path.BaseName();
FilePath destination_file = destination.Append(file_name);
// TODO(ellyjones): use Platform
file_util::CopyFile(next_path, destination_file);
if (chown(destination_file.value().c_str(), default_user_,
default_group_)) {
LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":"
<< default_group_ << ") of skeleton path: "
<< destination_file.value().c_str();
}
}
file_util::FileEnumerator dir_enumerator(source, false,
file_util::FileEnumerator::DIRECTORIES);
while (!(next_path = dir_enumerator.Next()).empty()) {
FilePath dir_name = next_path.BaseName();
FilePath destination_dir = destination.Append(dir_name);
LOG(INFO) << "RecursiveCopy: " << destination_dir.value();
file_util::CreateDirectory(destination_dir);
if (chown(destination_dir.value().c_str(), default_user_,
default_group_)) {
LOG(ERROR) << "Couldn't change owner (" << default_user_ << ":"
<< default_group_ << ") of skeleton path: "
<< destination_dir.value().c_str();
}
RecursiveCopy(destination_dir, next_path);
}
}
void Mount::MigrateToUserHome(const std::string& vault_path) const {
std::vector<string> ent_list;
std::vector<string>::iterator ent_iter;
FilePath user_path(VaultPathToUserPath(vault_path));
FilePath root_path(VaultPathToRootPath(vault_path));
struct stat st;
// This check makes the migration idempotent; if we completed a migration,
// root_path will exist and we're done, and if we didn't complete it, we can
// finish it.
if (platform_->Stat(root_path.value(), &st) &&
S_ISDIR(st.st_mode) &&
st.st_mode & S_ISVTX &&
st.st_uid == kMountOwnerUid &&
st.st_gid == kDaemonStoreGid) {
return;
}
// There are three ways to get here:
// 1) the Stat() call above succeeded, but what we saw was not a root-owned
// directory.
// 2) the Stat() call above failed with -ENOENT
// 3) the Stat() call above failed for some other reason
// In any of these cases, it is safe for us to rm root_path, since the only
// way it could have gotten there is if someone undertook some funny business
// as root.
platform_->DeleteFile(root_path.value(), true);
// Get the list of entries before we create user_path, since user_path will be
// inside dir.
platform_->EnumerateDirectoryEntries(vault_path, false, &ent_list);
if (!platform_->CreateDirectory(user_path.value())) {
PLOG(ERROR) << "CreateDirectory() failed: " << user_path.value();
return;
}
if (!platform_->SetOwnership(user_path.value(), default_user_,
default_group_)) {
PLOG(ERROR) << "SetOwnership() failed: " << user_path.value();
return;
}
for (ent_iter = ent_list.begin(); ent_iter != ent_list.end(); ent_iter++) {
FilePath basename(*ent_iter);
FilePath next_path = basename;
basename = basename.BaseName();
// Don't move the user/ directory itself. We're currently operating on an
// _unmounted_ ecryptfs, which means all the filenames are encrypted except
// the user and root passthrough directories.
if (basename.value() == kUserHomeSuffix) {
LOG(WARNING) << "Interrupted migration detected.";
continue;
}
FilePath dest_path(user_path);
dest_path = dest_path.Append(basename);
if (!platform_->Rename(next_path.value(), dest_path.value())) {
// TODO(ellyjones): UMA event log for this
PLOG(WARNING) << "Migration fault: can't move " << next_path.value()
<< " to " << dest_path.value();
}
}
// Create root_path at the end as a sentinel for migration.
if (!platform_->CreateDirectory(root_path.value())) {
PLOG(ERROR) << "CreateDirectory() failed: " << root_path.value();
return;
}
if (!platform_->SetOwnership(root_path.value(), kMountOwnerUid,
kDaemonStoreGid)) {
PLOG(ERROR) << "SetOwnership() failed: " << root_path.value();
return;
}
if (!platform_->SetPermissions(root_path.value(),
S_IRWXU | S_IRWXG | S_ISVTX)) {
PLOG(ERROR) << "SetPermissions() failed: " << root_path.value();
return;
}
LOG(INFO) << "Migrated user directory: " << vault_path.c_str();
}
void Mount::CopySkeletonForUser(const Credentials& credentials) const {
if (IsCryptohomeMountedForUser(credentials)) {
CopySkeleton();
}
}
void Mount::CopySkeleton() const {
RecursiveCopy(FilePath(kDefaultHomeDir), FilePath(skel_source_));
}
bool Mount::CacheOldFiles(const std::vector<std::string>& files) const {
for (unsigned int index = 0; index < files.size(); ++index) {
FilePath file(files[index]);
FilePath file_bak(StringPrintf("%s.bak", files[index].c_str()));
if (file_util::PathExists(file_bak)) {
if (!file_util::Delete(file_bak, false)) {
return false;
}
}
if (file_util::PathExists(file)) {
if (!file_util::Move(file, file_bak)) {
return false;
}
}
}
return true;
}
bool Mount::RevertCacheFiles(const std::vector<std::string>& files) const {
for (unsigned int index = 0; index < files.size(); ++index) {
FilePath file(files[index]);
FilePath file_bak(StringPrintf("%s.bak", files[index].c_str()));
if (file_util::PathExists(file_bak)) {
if (!file_util::Move(file_bak, file)) {
return false;
}
}
}
return true;
}
bool Mount::DeleteCacheFiles(const std::vector<std::string>& files) const {
for (unsigned int index = 0; index < files.size(); ++index) {
FilePath file(files[index]);
FilePath file_bak(StringPrintf("%s.bak", files[index].c_str()));
if (file_util::PathExists(file_bak)) {
if (!file_util::Delete(file_bak, false)) {
return false;
}
}
}
return true;
}
void Mount::GetUserSalt(const Credentials& credentials, bool force,
SecureBlob* salt) const {
FilePath path(GetUserSaltFile(credentials));
crypto_->GetOrCreateSalt(path, CRYPTOHOME_DEFAULT_SALT_LENGTH, force, salt);
}
bool Mount::EnsureDirHasOwner(const FilePath& fp, uid_t final_uid,
gid_t final_gid) const {
std::vector<std::string> path_parts;
fp.GetComponents(&path_parts);
// The path given should be absolute to that its first part is /. This is not
// actually checked so that relative paths can be used during testing.
FilePath check_path(path_parts[0]);
for (size_t i = 1; i < path_parts.size(); i++) {
check_path = check_path.Append(path_parts[i]);
bool last = (i == (path_parts.size() - 1));
uid_t uid = last ? final_uid : kMountOwnerUid;
gid_t gid = last ? final_gid : kMountOwnerGid;
struct stat st;
if (!platform_->Stat(check_path.value(), &st)) {
// Dirent not there, so create and set ownership.
if (!platform_->CreateDirectory(check_path.value())) {
PLOG(ERROR) << "Can't create: " << check_path.value();
return false;
}
if (!platform_->SetOwnership(check_path.value(), uid, gid)) {
PLOG(ERROR) << "Can't chown/chgrp: " << check_path.value()
<< " uid " << uid << " gid " << gid;
return false;
}
} else {
// Dirent there; make sure it's acceptable.
if (!S_ISDIR(st.st_mode)) {
LOG(ERROR) << "Non-directory path: " << check_path.value();
return false;
}
if (st.st_uid != uid) {
LOG(ERROR) << "Owner mismatch: " << check_path.value()
<< " " << st.st_uid << " != " << uid;
return false;
}
if (st.st_gid != gid) {
LOG(ERROR) << "Group mismatch: " << check_path.value()
<< " " << st.st_gid << " != " << gid;
return false;
}
if (st.st_mode & S_IWOTH) {
LOG(ERROR) << "Permissions too lenient: " << check_path.value()
<< " has " << std::oct << st.st_mode;
return false;
}
}
}
return true;
}
bool Mount::EnsureUserMountPoints(const Credentials& credentials) const {
const std::string username = credentials.username();
FilePath root_path = chromeos::cryptohome::home::GetRootPath(username);
FilePath user_path = chromeos::cryptohome::home::GetUserPath(username);
if (!EnsureDirHasOwner(root_path, kMountOwnerUid, kMountOwnerGid)) {
LOG(ERROR) << "Couldn't ensure root path: " << root_path.value();
return false;
}
if (!EnsureDirHasOwner(user_path, default_user_, default_access_group_)) {
LOG(ERROR) << "Couldn't ensure user path: " << user_path.value();
return false;
}
return true;
}
Value* Mount::GetStatus() {
std::string user;
SerializedVaultKeyset keyset;
DictionaryValue* dv = new DictionaryValue();
current_user_->GetObfuscatedUsername(&user);
ListValue* keysets = new ListValue();
if (user.length()) {
DictionaryValue* keyset0 = new DictionaryValue();
if (LoadVaultKeysetForUser(user, &keyset)) {
bool tpm = keyset.flags() & SerializedVaultKeyset::TPM_WRAPPED;
bool scrypt = keyset.flags() & SerializedVaultKeyset::SCRYPT_WRAPPED;
keyset0->SetBoolean("tpm", tpm);
keyset0->SetBoolean("scrypt", scrypt);
keyset0->SetBoolean("ok", true);
} else {
keyset0->SetBoolean("ok", false);
}
keysets->Append(keyset0);
}
dv->Set("keysets", keysets);
dv->SetBoolean("mounted", IsCryptohomeMounted());
dv->SetString("owner", GetObfuscatedOwner());
dv->SetBoolean("enterprise", enterprise_owned_);
return dv;
}
} // namespace cryptohome