blob: d5f5aeb29ee21950bafca353301ca74128df78da [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 Platform
#include "cryptohome/platform.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <linux/loop.h>
#include <mntent.h>
#include <pwd.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/quota.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <memory>
#include <base/check_op.h>
#ifndef MS_NOSYMFOLLOW
// Added locally in kernels 4.x+.
#define MS_NOSYMFOLLOW 256
#endif
#include <ios>
#include <limits>
#include <sstream>
#include <utility>
#if USE_SELINUX
#include <selinux/restorecon.h>
#include <selinux/selinux.h>
#endif
#include <base/bind.h>
#include <base/callback.h>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/location.h>
#include <base/logging.h>
#include <base/numerics/safe_conversions.h>
#include <base/posix/eintr_wrapper.h>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/system/sys_info.h>
#include <base/threading/thread.h>
#include <base/time/time.h>
#include <brillo/file_utils.h>
#include <brillo/files/safe_fd.h>
#include <brillo/process/process.h>
#include <brillo/scoped_umask.h>
#include <brillo/secure_blob.h>
#include <openssl/rand.h>
#include <rootdev/rootdev.h>
#include <secure_erase_file/secure_erase_file.h>
extern "C" {
#include <keyutils.h>
#include <linux/fs.h>
// Uses libvboot_host for accessing crossystem variables.
#include <vboot/crossystem.h>
}
#include "cryptohome/crc32.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/dircrypto_util.h"
using base::FilePath;
using base::SplitString;
using base::StringPrintf;
namespace {
// Log sync(), fsync(), etc. calls that take this many seconds or longer.
const int kLongSyncSec = 10;
class ScopedPath {
public:
ScopedPath(cryptohome::Platform* platform, const FilePath& dir)
: platform_(platform), dir_(dir) {}
~ScopedPath() {
if (!dir_.empty() && !platform_->DeletePathRecursively(dir_)) {
PLOG(WARNING) << "Failed to clean up " << dir_.value();
}
}
void release() { dir_.clear(); }
private:
cryptohome::Platform* platform_;
FilePath dir_;
};
bool IsDirectory(const base::stat_wrapper_t& file_info) {
return !!S_ISDIR(file_info.st_mode);
}
/*
* Split a /proc/<id>/mountinfo line in arguments and
* populate information into |mount_info|.
* Return true if the line seems valid.
*/
bool DecodeProcInfoLine(const std::string& line,
cryptohome::DecodedProcMountInfo* mount_info) {
auto args =
SplitString(line, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
size_t fs_idx = 6;
if (args.size() <= fs_idx) {
LOG(ERROR) << "Invalid procinfo: too few items: " << line;
return false;
}
while (fs_idx < args.size() && args[fs_idx++] != "-") {
}
if (fs_idx + 1 >= args.size()) {
LOG(ERROR) << "Invalid procinfo: separator or mount_source not found: "
<< line;
return false;
} else {
mount_info->root = args[3];
mount_info->mount_point = args[4];
mount_info->filesystem_type = args[fs_idx];
mount_info->mount_source = args[fs_idx + 1];
return true;
}
}
} // namespace
namespace cryptohome {
const uint32_t kDefaultMountFlags = MS_NOEXEC | MS_NOSUID | MS_NODEV;
const int kDefaultPwnameLength = 1024;
const int kDefaultUmask =
S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH;
const char kProcDir[] = "/proc";
const char kMountInfoFile[] = "mountinfo";
const char kPathTune2fs[] = "/sbin/tune2fs";
const char kEcryptFS[] = "ecryptfs";
const char kLoopControl[] = "/dev/loop-control";
const char kLoopPrefix[] = "/dev/loop";
const char kSysBlockPath[] = "/sys/block";
const char kDevPath[] = "/dev";
const char kLoopBackingFile[] = "loop/backing_file";
const std::vector<std::string> kDefaultExt4FormatOpts(
{// Always use 'default' configuration.
"-T", "default",
// reserved-blocks-percentage = 0%
"-m", "0",
// ^huge_file: Do not allow files larger than 2TB.
// ^flex_bg: Do not allow per-block group metadata to be placed anywhere.
// ^has_journal: Do not create journal.
"-O", "^huge_file,^flex_bg,^has_journal",
// Attempt to discard blocks at mkfs time.
"-E", "discard"});
Platform::Platform() {
pid_t pid = getpid();
mount_info_path_ =
FilePath(kProcDir).Append(std::to_string(pid)).Append(kMountInfoFile);
}
Platform::~Platform() {}
std::vector<DecodedProcMountInfo> Platform::ReadMountInfoFile() {
std::string contents;
if (!base::ReadFileToString(mount_info_path_, &contents))
return std::vector<DecodedProcMountInfo>();
std::vector<std::string> lines = SplitString(
contents, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
std::vector<DecodedProcMountInfo> mount_info_content;
for (const auto& line : lines) {
DecodedProcMountInfo mount_info;
if (!DecodeProcInfoLine(line, &mount_info))
return std::vector<DecodedProcMountInfo>();
mount_info_content.push_back(mount_info);
}
return mount_info_content;
}
bool Platform::GetLoopDeviceMounts(
std::multimap<const FilePath, const FilePath>* mounts) {
std::vector<DecodedProcMountInfo> proc_mounts = ReadMountInfoFile();
// Populate all mounts from loop devices "/dev/loop*".
for (const auto& mount : proc_mounts) {
if (!base::StartsWith(mount.mount_source, kLoopPrefix,
base::CompareCase::SENSITIVE))
continue;
mounts->insert(std::pair<const FilePath, const FilePath>(
FilePath(mount.mount_source), FilePath(mount.mount_point)));
}
return mounts && mounts->size() > 0;
}
bool Platform::GetMountsBySourcePrefix(
const FilePath& from_prefix,
std::multimap<const FilePath, const FilePath>* mounts) {
std::vector<DecodedProcMountInfo> proc_mounts = ReadMountInfoFile();
// When using ecryptfs, we compare the mount device, otherwise,
// we use the root directory .
for (const auto& mount : proc_mounts) {
FilePath root_dir;
if (mount.filesystem_type == kEcryptFS)
root_dir = FilePath(mount.mount_source);
else
root_dir = FilePath(mount.root);
if (!from_prefix.IsParent(root_dir))
continue;
// If there is no mounts pointer, we can return true right away.
if (!mounts)
return true;
mounts->insert(std::pair<const FilePath, const FilePath>(
root_dir, FilePath(mount.mount_point)));
}
return mounts && mounts->size();
}
bool Platform::IsDirectoryMounted(const FilePath& directory) {
// Trivial string match from /etc/mtab to see if the cryptohome mount point is
// listed. This works because Chrome OS is a controlled environment and the
// only way /home/chronos/user should be mounted is if cryptohome mounted it.
auto ret = AreDirectoriesMounted({directory});
if (!ret)
return false;
return ret.value()[0];
}
base::Optional<std::vector<bool>> Platform::AreDirectoriesMounted(
const std::vector<base::FilePath>& directories) {
std::string contents;
if (!base::ReadFileToString(mount_info_path_, &contents)) {
return base::nullopt;
}
std::vector<bool> ret;
ret.reserve(directories.size());
for (auto& directory : directories) {
bool is_mounted =
contents.find(StringPrintf(" %s ", directory.value().c_str())) !=
std::string::npos;
ret.push_back(is_mounted);
}
return ret;
}
bool Platform::Mount(const FilePath& from,
const FilePath& to,
const std::string& type,
uint32_t mount_flags,
const std::string& mount_options) {
if (mount(from.value().c_str(), to.value().c_str(), type.c_str(), mount_flags,
mount_options.c_str())) {
return false;
}
return true;
}
bool Platform::Bind(const FilePath& from,
const FilePath& to,
RemountOption remount,
bool nosymfollow) {
// To apply options specific to a bind mount, we have to call mount(2) twice.
if (mount(from.value().c_str(), to.value().c_str(), nullptr, MS_BIND,
nullptr))
return false;
uint32_t mount_flags = MS_REMOUNT | MS_BIND | kDefaultMountFlags;
std::string options;
if (nosymfollow) {
// Works only in 4.x+ kernels so far.
mount_flags |= MS_NOSYMFOLLOW;
options = "nosymfollow";
}
if (mount(nullptr, to.value().c_str(), nullptr, mount_flags, options.c_str()))
return false;
if (remount != RemountOption::kNoRemount) {
uint32_t remount_mode;
switch (remount) {
case RemountOption::kPrivate:
remount_mode = MS_PRIVATE;
break;
case RemountOption::kShared:
remount_mode = MS_SHARED;
break;
case RemountOption::kMountsFlowIn:
remount_mode = MS_SLAVE;
break;
case RemountOption::kUnbindable:
remount_mode = MS_UNBINDABLE;
break;
default:
return false;
}
if (mount(nullptr, to.value().c_str(), nullptr, remount_mode, nullptr))
return false;
}
return true;
}
ExpireMountResult Platform::ExpireMount(const FilePath& path) {
if (umount2(path.value().c_str(), MNT_EXPIRE)) {
if (errno == EAGAIN) {
return ExpireMountResult::kMarked;
} else {
PLOG(ERROR) << "ExpireMount(" << path.value() << ") failed";
if (errno == EBUSY) {
return ExpireMountResult::kBusy;
} else {
return ExpireMountResult::kError;
}
}
}
return ExpireMountResult::kUnmounted;
}
bool Platform::Unmount(const FilePath& path, bool lazy, bool* was_busy) {
if (lazy) {
if (umount2(path.value().c_str(), MNT_DETACH)) {
if (was_busy) {
*was_busy = (errno == EBUSY);
}
return false;
}
} else {
if (umount(path.value().c_str())) {
if (was_busy) {
*was_busy = (errno == EBUSY);
}
return false;
}
}
if (was_busy) {
*was_busy = false;
}
return true;
}
void Platform::LazyUnmount(const FilePath& path) {
if (umount2(path.value().c_str(), MNT_DETACH | UMOUNT_NOFOLLOW)) {
if (errno != EBUSY) {
PLOG(ERROR) << "Lazy unmount failed";
}
}
}
std::unique_ptr<brillo::Process> Platform::CreateProcessInstance() {
return std::make_unique<brillo::ProcessImpl>();
}
bool Platform::IsPathChild(const FilePath& parent_path,
const FilePath& child_path) {
std::string parent = parent_path.value();
std::string child = child_path.value();
if (parent.length() == 0 || child.length() == 0) {
return false;
}
if (child.length() >= parent.length()) {
if (child.compare(0, parent.length(), parent, 0, parent.length()) == 0) {
return true;
}
} else if ((parent[parent.length() - 1] == '/') &&
(child.length() == (parent.length() - 1))) {
if (child.compare(0, child.length(), parent, 0, parent.length() - 1) == 0) {
return true;
}
}
return false;
}
bool Platform::GetOwnership(const FilePath& path,
uid_t* user_id,
gid_t* group_id,
bool follow_links) const {
struct stat path_status;
int ret;
if (follow_links)
ret = stat(path.value().c_str(), &path_status);
else
ret = lstat(path.value().c_str(), &path_status);
if (ret != 0) {
PLOG(ERROR) << (follow_links ? "" : "l") << "stat() of " << path.value()
<< " failed.";
return false;
}
if (user_id)
*user_id = path_status.st_uid;
if (group_id)
*group_id = path_status.st_gid;
return true;
}
bool Platform::SetOwnership(const FilePath& path,
uid_t user_id,
gid_t group_id,
bool follow_links) const {
int ret;
if (follow_links)
ret = chown(path.value().c_str(), user_id, group_id);
else
ret = lchown(path.value().c_str(), user_id, group_id);
if (ret) {
PLOG(ERROR) << (follow_links ? "" : "l") << "chown() of " << path.value()
<< " to (" << user_id << "," << group_id << ") failed.";
return false;
}
return true;
}
bool Platform::GetPermissions(const FilePath& path, mode_t* mode) const {
struct stat path_status;
if (stat(path.value().c_str(), &path_status) != 0) {
PLOG(ERROR) << "stat() of " << path.value() << " failed.";
return false;
}
*mode = path_status.st_mode;
return true;
}
bool Platform::SetPermissions(const FilePath& path, mode_t mode) const {
if (chmod(path.value().c_str(), mode)) {
PLOG(ERROR) << "chmod() of " << path.value() << " to (" << std::oct << mode
<< ") failed.";
return false;
}
return true;
}
bool Platform::SetGroupAccessible(const FilePath& path,
gid_t group_id,
mode_t group_mode) const {
uid_t user_id;
mode_t mode;
if (!GetOwnership(path, &user_id, NULL, true) ||
!GetPermissions(path, &mode) ||
!SetOwnership(path, user_id, group_id, true /* follow_links */) ||
!SetPermissions(path, (mode & ~S_IRWXG) | (group_mode & S_IRWXG))) {
LOG(ERROR) << "Couldn't set up group access on directory: " << path.value();
return false;
}
return true;
}
bool Platform::GetUserId(const std::string& user,
uid_t* user_id,
gid_t* group_id) const {
// Load the passwd entry
long user_name_length = sysconf(_SC_GETPW_R_SIZE_MAX); // NOLINT long
if (user_name_length == -1) {
user_name_length = kDefaultPwnameLength;
}
struct passwd user_info, *user_infop;
std::vector<char> user_name_buf(user_name_length);
if (getpwnam_r(user.c_str(), &user_info, user_name_buf.data(),
user_name_length, &user_infop)) {
return false;
}
*user_id = user_info.pw_uid;
*group_id = user_info.pw_gid;
return true;
}
bool Platform::GetGroupId(const std::string& group, gid_t* group_id) const {
// Load the group entry
long group_name_length = sysconf(_SC_GETGR_R_SIZE_MAX); // NOLINT long
if (group_name_length == -1) {
group_name_length = kDefaultPwnameLength;
}
struct group group_info, *group_infop = nullptr;
std::vector<char> group_name_buf(group_name_length);
// In getgrgid_r(), err can be 0 even when a group is not found.
// Only *group_infop matters: if NULL, the group was not found.
getgrnam_r(group.c_str(), &group_info, group_name_buf.data(),
group_name_length, &group_infop);
if (!group_infop)
return false;
*group_id = group_infop->gr_gid;
return true;
}
int64_t Platform::AmountOfFreeDiskSpace(const FilePath& path) const {
return base::SysInfo::AmountOfFreeDiskSpace(path);
}
int64_t Platform::GetQuotaCurrentSpaceForUid(const base::FilePath& device,
uid_t user_id) const {
struct dqblk dq = {};
if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device.value().c_str(), user_id,
reinterpret_cast<char*>(&dq)) != 0) {
return -1;
}
return dq.dqb_curspace;
}
int64_t Platform::GetQuotaCurrentSpaceForGid(const base::FilePath& device,
gid_t group_id) const {
struct dqblk dq = {};
if (quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), device.value().c_str(), group_id,
reinterpret_cast<char*>(&dq)) != 0) {
return -1;
}
return dq.dqb_curspace;
}
int64_t Platform::GetQuotaCurrentSpaceForProjectId(const base::FilePath& device,
int project_id) const {
struct dqblk dq = {};
if (quotactl(QCMD(Q_GETQUOTA, PRJQUOTA), device.value().c_str(), project_id,
reinterpret_cast<char*>(&dq)) != 0) {
return -1;
}
return dq.dqb_curspace;
}
bool Platform::SetQuotaProjectId(int project_id,
const base::FilePath& path) const {
base::stat_wrapper_t stat;
if (base::File::Lstat(path.value().c_str(), &stat) != 0) {
PLOG(ERROR) << "Failed to stat " << path.value();
return false;
}
brillo::SafeFD fd;
brillo::SafeFD::Error err;
if (S_ISDIR(stat.st_mode)) {
std::tie(fd, err) = brillo::SafeFD::Root().first.OpenExistingDir(path);
} else {
std::tie(fd, err) = brillo::SafeFD::Root().first.OpenExistingFile(path);
}
if (brillo::SafeFD::IsError(err)) {
PLOG(ERROR) << "Failed to open " << path.value() << " with error "
<< static_cast<int>(err);
return false;
}
if (!fd.is_valid()) {
PLOG(ERROR) << "Failed to open " << path.value();
return false;
}
struct fsxattr fsx = {};
if (ioctl(fd.get(), FS_IOC_FSGETXATTR, &fsx) < 0) {
PLOG(ERROR) << "ioctl FSGETXATTR: " << path.value();
return false;
}
fsx.fsx_projid = project_id;
if (ioctl(fd.get(), FS_IOC_FSSETXATTR, &fsx) < 0) {
PLOG(ERROR) << "ioctl FSSETXATTR: " << path.value();
return false;
}
return true;
}
bool Platform::FileExists(const FilePath& path) {
return base::PathExists(path);
}
int Platform::Access(const FilePath& path, uint32_t flag) {
return HANDLE_EINTR(access(path.value().c_str(), flag));
}
bool Platform::DirectoryExists(const FilePath& path) {
base::stat_wrapper_t buf = {};
return Stat(path, &buf) && S_ISDIR(buf.st_mode);
}
bool Platform::GetFileSize(const FilePath& path, int64_t* size) {
return base::GetFileSize(path, size);
}
int64_t Platform::ComputeDirectoryDiskUsage(const FilePath& path) {
return brillo::ComputeDirectoryDiskUsage(path);
}
FILE* Platform::CreateAndOpenTemporaryFile(FilePath* path) {
return base::CreateAndOpenTemporaryStream(path).release();
}
FILE* Platform::OpenFile(const FilePath& path, const char* mode) {
return base::OpenFile(path, mode);
}
bool Platform::CloseFile(FILE* fp) {
return base::CloseFile(fp);
}
void Platform::InitializeFile(base::File* file,
const base::FilePath& path,
uint32_t flags) {
return file->Initialize(path, flags);
}
bool Platform::LockFile(int fd) {
return HANDLE_EINTR(flock(fd, LOCK_EX)) == 0;
}
bool Platform::WriteOpenFile(FILE* fp, const brillo::Blob& blob) {
return (fwrite(static_cast<const void*>(&blob.at(0)), 1, blob.size(), fp) !=
blob.size());
}
bool Platform::WriteFile(const FilePath& path, const brillo::Blob& blob) {
return brillo::WriteBlobToFile<brillo::Blob>(path, blob);
}
bool Platform::WriteSecureBlobToFile(const FilePath& path,
const brillo::SecureBlob& blob) {
return brillo::WriteBlobToFile<brillo::SecureBlob>(path, blob);
}
bool Platform::WriteStringToFile(const FilePath& path,
const std::string& data) {
return brillo::WriteStringToFile(path, data);
}
bool Platform::WriteArrayToFile(const FilePath& path,
const char* data,
size_t size) {
return brillo::WriteToFile(path, data, size);
}
std::string Platform::GetRandomSuffix() {
const int kBufferSize = 6;
unsigned char buffer[kBufferSize];
if (RAND_bytes(buffer, kBufferSize) < 0) {
return std::string();
}
std::string suffix;
for (int i = 0; i < kBufferSize; ++i) {
int random_value = buffer[i] % (2 * 26 + 10);
if (random_value < 26) {
suffix.push_back('a' + random_value);
} else if (random_value < 2 * 26) {
suffix.push_back('A' + random_value - 26);
} else {
suffix.push_back('0' + random_value - 2 * 26);
}
}
return suffix;
}
bool Platform::WriteFileAtomic(const FilePath& path,
const brillo::Blob& blob,
mode_t mode) {
return brillo::WriteBlobToFileAtomic<brillo::Blob>(path, blob, mode);
}
bool Platform::WriteSecureBlobToFileAtomic(const FilePath& path,
const brillo::SecureBlob& blob,
mode_t mode) {
return brillo::WriteBlobToFileAtomic<brillo::SecureBlob>(path, blob, mode);
}
bool Platform::WriteStringToFileAtomic(const FilePath& path,
const std::string& data,
mode_t mode) {
return brillo::WriteToFileAtomic(path, data.data(), data.size(), mode);
}
bool Platform::WriteFileAtomicDurable(const FilePath& path,
const brillo::Blob& blob,
mode_t mode) {
const std::string data(reinterpret_cast<const char*>(blob.data()),
blob.size());
return WriteStringToFileAtomicDurable(path, data, mode);
}
bool Platform::WriteSecureBlobToFileAtomicDurable(
const FilePath& path, const brillo::SecureBlob& blob, mode_t mode) {
if (!WriteSecureBlobToFileAtomic(path, blob, mode))
return false;
WriteChecksum(path, blob.data(), blob.size(), mode);
return SyncDirectory(FilePath(path).DirName());
}
bool Platform::WriteStringToFileAtomicDurable(const FilePath& path,
const std::string& data,
mode_t mode) {
if (!WriteStringToFileAtomic(path, data, mode))
return false;
WriteChecksum(path, data.data(), data.size(), mode);
return SyncDirectory(FilePath(path).DirName());
}
bool Platform::TouchFileDurable(const FilePath& path) {
brillo::Blob empty_blob(0);
if (!WriteFile(path, empty_blob))
return false;
return SyncDirectory(FilePath(path).DirName());
}
bool Platform::ReadFile(const FilePath& path, brillo::Blob* blob) {
return ReadFileToBlob<brillo::Blob>(path, blob);
}
bool Platform::ReadFileToString(const FilePath& path, std::string* string) {
if (!base::ReadFileToString(path, string)) {
return false;
}
VerifyChecksum(path, string->data(), string->size());
return true;
}
bool Platform::ReadFileToSecureBlob(const FilePath& path,
brillo::SecureBlob* sblob) {
return ReadFileToBlob<brillo::SecureBlob>(path, sblob);
}
bool Platform::CreateDirectory(const FilePath& path) {
return base::CreateDirectory(path);
}
bool Platform::SafeDirChmod(const base::FilePath& path, mode_t mode) {
// Reset mask since we are setting the mode explicitly.
brillo::ScopedUmask scoped_umask(0);
auto root_fd_result = brillo::SafeFD::Root();
if (root_fd_result.second != brillo::SafeFD::Error::kNoError) {
return false;
}
auto path_result = root_fd_result.first.OpenExistingDir(path);
if (path_result.second != brillo::SafeFD::Error::kNoError) {
return false;
}
if (HANDLE_EINTR(fchmod(path_result.first.get(), mode)) != 0) {
PLOG(ERROR) << "Failed to set permissions in SafeDirChmod() for \""
<< path.value() << '"';
return false;
}
return true;
}
bool Platform::SafeCreateDirAndSetOwnershipAndPermissions(
const base::FilePath& path, mode_t mode, uid_t user_id, gid_t group_id) {
// Reset mask since we are setting the mode explicitly.
brillo::ScopedUmask scoped_umask(0);
auto root_fd_result = brillo::SafeFD::Root();
if (root_fd_result.second != brillo::SafeFD::Error::kNoError) {
return false;
}
auto path_result =
root_fd_result.first.MakeDir(path, mode, user_id, group_id);
if (path_result.second != brillo::SafeFD::Error::kNoError) {
return false;
}
// mkdirat, which is used within MakeDir, only sets permissions under 01777
// mask. There should be a separate chmod to allow SetGid and SetUid modes.
// It is done here in a safe manner by doing fchmod on the returned
// descriptor.
constexpr mode_t mkdirat_mask = 01777;
if ((mode & ~mkdirat_mask) == 0) {
return true;
}
if (HANDLE_EINTR(fchmod(path_result.first.get(), mode)) != 0) {
PLOG(ERROR) << "Failed to set permissions in MakeDir() for \""
<< path.value() << '"';
return false;
}
return true;
}
bool Platform::SafeCreateDirAndSetOwnership(const base::FilePath& path,
uid_t user_id,
gid_t group_id) {
return SafeCreateDirAndSetOwnershipAndPermissions(
path, brillo::SafeFD::kDefaultDirPermissions, user_id, group_id);
}
bool Platform::UdevAdmSettle(const base::FilePath& device_path,
bool wait_for_device) {
brillo::ProcessImpl udevadm_process;
udevadm_process.AddArg("/bin/udevadm");
udevadm_process.AddArg("settle");
if (wait_for_device) {
udevadm_process.AddArg("-t");
udevadm_process.AddArg("10");
udevadm_process.AddArg("-E");
udevadm_process.AddArg(device_path.value());
}
// Close unused file descriptors in child process.
udevadm_process.SetCloseUnusedFileDescriptors(true);
// Start the process and return.
int rc = udevadm_process.Run();
if (rc != 0)
return false;
return true;
}
base::FilePath Platform::GetStatefulDevice() {
char root_device[PATH_MAX];
int ret = rootdev(root_device, sizeof(root_device),
true, // Do full resolution.
true); // Remove partition number.
if (ret != 0) {
LOG(WARNING) << "rootdev failed with error code " << ret;
return base::FilePath();
}
// For some storage devices (eg. eMMC), the path ends in a digit
// (eg. /dev/mmcblk0). Use 'p' as the partition separator while generating
// the partition's block device path. For other types of paths (/dev/sda), we
// directly append the partition number.
std::string root_dev(root_device);
if (base::IsAsciiDigit(root_dev[root_dev.size() - 1]))
root_dev += 'p';
root_dev += '1';
return base::FilePath(root_dev);
}
bool Platform::DeleteFile(const FilePath& path) {
return base::DeleteFile(path);
}
bool Platform::DeletePathRecursively(const FilePath& path) {
return base::DeletePathRecursively(path);
}
bool Platform::DeleteFileDurable(const FilePath& path) {
if (!base::DeletePathRecursively(path))
return false;
return SyncDirectory(path.DirName());
}
bool Platform::DeleteFileSecurely(const FilePath& path) {
return secure_erase_file::SecureErase(path) &&
secure_erase_file::DropCaches();
}
bool Platform::Move(const FilePath& from, const FilePath& to) {
return base::Move(from, to);
}
bool Platform::EnumerateDirectoryEntries(const FilePath& path,
bool recursive,
std::vector<FilePath>* ent_list) {
auto ft = static_cast<base::FileEnumerator::FileType>(
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
base::FileEnumerator::SHOW_SYM_LINKS);
base::FileEnumerator ent_enum(path, recursive, ft);
for (FilePath path = ent_enum.Next(); !path.empty(); path = ent_enum.Next())
ent_list->push_back(path);
return true;
}
base::Time Platform::GetCurrentTime() const {
return base::Time::Now();
}
bool Platform::Stat(const FilePath& path, base::stat_wrapper_t* buf) {
return base::File::Lstat(path.value().c_str(), buf) == 0;
}
bool Platform::HasExtendedFileAttribute(const FilePath& path,
const std::string& name) {
ssize_t sz = lgetxattr(path.value().c_str(), name.c_str(), nullptr, 0);
if (sz < 0) {
if (errno != ENODATA) {
PLOG(ERROR) << "lgetxattr: " << path.value();
}
return false;
}
return true;
}
bool Platform::ListExtendedFileAttributes(const FilePath& path,
std::vector<std::string>* attr_list) {
ssize_t sz = llistxattr(path.value().c_str(), nullptr, 0);
if (sz < 0) {
PLOG(ERROR) << "llistxattr: " << path.value();
return false;
}
std::vector<char> names(sz);
if (llistxattr(path.value().c_str(), names.data(), sz) < 0) {
PLOG(ERROR) << "llistxattr: " << path.value();
return false;
}
int pos = 0;
while (pos < sz) {
attr_list->emplace_back(names.data() + pos);
pos += attr_list->back().length() + 1;
}
return true;
}
bool Platform::GetExtendedFileAttributeAsString(const base::FilePath& path,
const std::string& name,
std::string* value) {
ssize_t sz = lgetxattr(path.value().c_str(), name.c_str(), nullptr, 0);
if (sz < 0) {
PLOG(ERROR) << "lgetxattr: " << path.value();
return false;
}
std::vector<char> value_vector(sz);
if (!GetExtendedFileAttribute(path, name, value_vector.data(), sz)) {
return false;
}
value->assign(value_vector.data(), sz);
return true;
}
bool Platform::GetExtendedFileAttribute(const base::FilePath& path,
const std::string& name,
char* value,
ssize_t size) {
if (lgetxattr(path.value().c_str(), name.c_str(), value, size) != size) {
PLOG(ERROR) << "lgetxattr: " << path.value();
return false;
}
return true;
}
bool Platform::SetExtendedFileAttribute(const base::FilePath& path,
const std::string& name,
const char* value,
size_t size) {
if (lsetxattr(path.value().c_str(), name.c_str(), value, size, 0) != 0) {
PLOG(ERROR) << "lsetxattr: " << path.value();
return false;
}
return true;
}
bool Platform::RemoveExtendedFileAttribute(const base::FilePath& path,
const std::string& name) {
if (lremovexattr(path.value().c_str(), name.c_str()) != 0) {
PLOG(ERROR) << "lremovexattr: " << path.value();
return false;
}
return true;
}
bool Platform::GetExtFileAttributes(const FilePath& path, int* flags) {
int fd = HANDLE_EINTR(open(path.value().c_str(), O_RDONLY));
if (fd < 0) {
PLOG(ERROR) << "open: " << path.value();
return false;
}
// FS_IOC_GETFLAGS actually takes int*
// though the signature suggests long*.
// https://lwn.net/Articles/575846/
if (ioctl(fd, FS_IOC_GETFLAGS, flags) < 0) {
PLOG(ERROR) << "ioctl: " << path.value();
IGNORE_EINTR(close(fd));
return false;
}
IGNORE_EINTR(close(fd));
return true;
}
bool Platform::SetExtFileAttributes(const FilePath& path, int flags) {
int fd = HANDLE_EINTR(open(path.value().c_str(), O_RDONLY));
if (fd < 0) {
PLOG(ERROR) << "open: " << path.value();
return false;
}
// FS_IOC_SETFLAGS actually takes int*
// though the signature suggests long*.
// https://lwn.net/Articles/575846/
int current_flag;
if (ioctl(fd, FS_IOC_GETFLAGS, &current_flag) < 0) {
PLOG(ERROR) << "ioctl GETFLAGS: " << path.value();
IGNORE_EINTR(close(fd));
return false;
}
flags |= current_flag;
if (ioctl(fd, FS_IOC_SETFLAGS, &flags) < 0) {
PLOG(ERROR) << "ioctl SETFLAGS: " << path.value();
IGNORE_EINTR(close(fd));
return false;
}
IGNORE_EINTR(close(fd));
return true;
}
bool Platform::HasNoDumpFileAttribute(const FilePath& path) {
int flags;
return GetExtFileAttributes(path, &flags) &&
(flags & FS_NODUMP_FL) == FS_NODUMP_FL;
}
bool Platform::Rename(const FilePath& from, const FilePath& to) {
return base::ReplaceFile(from, to, /*error=*/nullptr);
}
bool Platform::Copy(const FilePath& from, const FilePath& to) {
return base::CopyDirectory(from, to, true);
}
bool Platform::CopyPermissionsCallback(const FilePath& old_base,
const FilePath& new_base,
const FilePath& file_path,
const base::stat_wrapper_t& file_info
) {
// Find the new path that corresponds with the old path given by file_info.
FilePath old_path = file_path;
FilePath new_path = new_base;
if (old_path != old_base) {
if (old_path.IsAbsolute()) {
if (!old_base.AppendRelativePath(old_path, &new_path)) {
LOG(ERROR) << "AppendRelativePath failed: parent=" << old_base.value()
<< ", child=" << old_path.value();
return false;
}
} else {
new_path = new_base.Append(old_path);
}
}
if (!SetOwnership(new_path, file_info.st_uid, file_info.st_gid,
true /* follow_links */)) {
PLOG(ERROR) << "Failed to set ownership for " << new_path.value();
return false;
}
const mode_t permissions_mask = 07777;
if (!SetPermissions(new_path, file_info.st_mode & permissions_mask)) {
PLOG(ERROR) << "Failed to set permissions for " << new_path.value();
return false;
}
return true;
}
bool Platform::CopyWithPermissions(const FilePath& from_path,
const FilePath& to_path) {
if (!Copy(from_path, to_path)) {
PLOG(ERROR) << "Failed to copy " << from_path.value();
return false;
}
// If something goes wrong we want to blow away the half-baked path.
ScopedPath scoped_new_path(this, to_path);
// Unfortunately, ownership and permissions are not always retained.
// Apply the old ownership / permissions on a per-file basis.
FileEnumeratorCallback callback =
base::Bind(&Platform::CopyPermissionsCallback, base::Unretained(this),
from_path, to_path);
if (!WalkPath(from_path, callback))
return false;
// The copy is done, keep the new path.
scoped_new_path.release();
return true;
}
bool Platform::StatVFS(const FilePath& path, struct statvfs* vfs) {
return statvfs(path.value().c_str(), vfs) == 0;
}
bool Platform::SameVFS(const base::FilePath& mnt_a,
const base::FilePath& mnt_b) {
struct stat stat_a, stat_b;
if (lstat(mnt_a.value().c_str(), &stat_a)) {
PLOG(ERROR) << "lstat: " << mnt_a.value().c_str();
return false;
}
if (lstat(mnt_b.value().c_str(), &stat_b)) {
PLOG(ERROR) << "lstat: " << mnt_b.value().c_str();
return false;
}
return (stat_a.st_dev == stat_b.st_dev);
}
bool Platform::FindFilesystemDevice(const FilePath& filesystem_in,
std::string* device) {
/* Clear device to indicate failure case. */
device->clear();
/* Removing trailing slashes. */
FilePath filesystem = filesystem_in.StripTrailingSeparators();
std::vector<DecodedProcMountInfo> proc_mounts = ReadMountInfoFile();
for (const auto& mount : proc_mounts) {
if (mount.mount_point != filesystem.value())
continue;
*device = mount.mount_source;
}
return (device->length() > 0);
}
bool Platform::ReportFilesystemDetails(const FilePath& filesystem,
const FilePath& logfile) {
brillo::ProcessImpl process;
int rc;
std::string device;
if (!FindFilesystemDevice(filesystem, &device)) {
LOG(ERROR) << "Failed to find device for " << filesystem.value();
return false;
}
process.RedirectOutput(logfile.value());
process.AddArg(kPathTune2fs);
process.AddArg("-l");
process.AddArg(device);
rc = process.Run();
if (rc == 0)
return true;
LOG(ERROR) << "Failed to run tune2fs on " << device << " ("
<< filesystem.value() << ", exit " << rc << ")";
return false;
}
bool Platform::FirmwareWriteProtected() {
return VbGetSystemPropertyInt("wpsw_cur") != 0;
}
bool Platform::SyncFileOrDirectory(const FilePath& path,
bool is_directory,
bool data_sync) {
return brillo::SyncFileOrDirectory(path, is_directory, data_sync);
}
bool Platform::DataSyncFile(const FilePath& path) {
return SyncFileOrDirectory(path, false /* directory */, true /* data_sync */);
}
bool Platform::SyncFile(const FilePath& path) {
return SyncFileOrDirectory(path, false /* directory */,
false /* data_sync */);
}
bool Platform::SyncDirectory(const FilePath& path) {
return SyncFileOrDirectory(path, true /* directory */, false /* data_sync */);
}
void Platform::Sync() {
const base::TimeTicks start = base::TimeTicks::Now();
sync();
const base::TimeDelta delta = base::TimeTicks::Now() - start;
if (delta > base::TimeDelta::FromSeconds(kLongSyncSec)) {
LOG(WARNING) << "Long sync(): " << delta.InSeconds() << " seconds";
}
}
std::string Platform::GetHardwareID() {
char buffer[VB_MAX_STRING_PROPERTY];
const char* rc =
VbGetSystemPropertyString("hwid", buffer, base::size(buffer));
if (rc != nullptr) {
return std::string(rc);
}
LOG(WARNING) << "Could not read hwid property";
return std::string();
}
bool Platform::CreateSymbolicLink(const base::FilePath& path,
const base::FilePath& target) {
if (!base::CreateSymbolicLink(target, path)) {
PLOG(ERROR) << "Failed to create link " << path.value();
return false;
}
return true;
}
bool Platform::ReadLink(const base::FilePath& path, base::FilePath* target) {
if (!base::ReadSymbolicLink(path, target)) {
PLOG(ERROR) << "Failed to read link " << path.value();
return false;
}
return true;
}
bool Platform::SetFileTimes(const base::FilePath& path,
const struct timespec& atime,
const struct timespec& mtime,
bool follow_links) {
const struct timespec times[2] = {atime, mtime};
if (utimensat(AT_FDCWD, path.value().c_str(), times,
follow_links ? 0 : AT_SYMLINK_NOFOLLOW)) {
PLOG(ERROR) << "Failed to update times for file " << path.value();
return false;
}
return true;
}
bool Platform::SendFile(int fd_to, int fd_from, off_t offset, size_t count) {
while (count > 0) {
ssize_t written = sendfile(fd_to, fd_from, &offset, count);
if (written < 0) {
PLOG(ERROR) << "sendfile failed to copy data";
return false;
}
if (written == 0) {
LOG(ERROR) << "Attempting to read past the end of the file";
return false;
}
count -= written;
}
return true;
}
bool Platform::CreateSparseFile(const base::FilePath& path, int64_t size) {
base::File file;
InitializeFile(&file, path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!file.IsValid()) {
PLOG(ERROR) << "open sparse file " << path.value();
return false;
}
return file.SetLength(size);
}
bool Platform::GetBlkSize(const base::FilePath& device, uint64_t* size) {
base::ScopedFD fd(
HANDLE_EINTR(open(device.value().c_str(), O_RDONLY | O_CLOEXEC)));
if (!fd.is_valid()) {
PLOG(ERROR) << "open " << device.value();
return false;
}
if (ioctl(fd.get(), BLKGETSIZE64, size)) {
PLOG(ERROR) << "ioctl(BLKGETSIZE): " << device.value();
return false;
}
return true;
}
base::FilePath Platform::AttachLoop(const base::FilePath& path) {
base::ScopedFD control_fd(
HANDLE_EINTR(open(kLoopControl, O_RDONLY | O_CLOEXEC)));
if (!control_fd.is_valid()) {
PLOG(ERROR) << "open loop control";
return base::FilePath();
}
std::string loopback;
while (true) {
int num = ioctl(control_fd.get(), LOOP_CTL_GET_FREE);
if (num < 0) {
PLOG(ERROR) << "ioctl(LOOP_CTL_GET_FREE)";
return base::FilePath();
}
loopback = kLoopPrefix + base::NumberToString(num);
base::ScopedFD loop_fd(
HANDLE_EINTR(open(loopback.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC)));
if (!loop_fd.is_valid()) {
PLOG(ERROR) << "open " + loopback;
return base::FilePath();
}
base::ScopedFD fd(
HANDLE_EINTR(open(path.value().c_str(), O_RDWR | O_CLOEXEC)));
if (!fd.is_valid()) {
PLOG(ERROR) << "open " + path.value();
return base::FilePath();
}
if (ioctl(loop_fd.get(), LOOP_SET_FD, fd.get()) == 0)
break;
// Retry on LOOP_SET_FD coming back with EBUSY.
if (errno != EBUSY) {
PLOG(ERROR) << "LOOP_SET_FD";
return base::FilePath();
}
}
return base::FilePath(loopback);
}
bool Platform::DetachLoop(const base::FilePath& device) {
base::ScopedFD loop_fd(HANDLE_EINTR(
open(device.value().c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
if (!loop_fd.is_valid()) {
PLOG(ERROR) << "open " + device.value();
return false;
}
if (ioctl(loop_fd.get(), LOOP_CLR_FD, 0)) {
PLOG(ERROR) << "LOOP_CLR_FD";
return false;
}
return true;
}
std::vector<Platform::LoopDevice> Platform::GetAttachedLoopDevices() {
// Read /sys/block to discover all loop devices.
std::vector<FilePath> sysfs_block_devices;
EnumerateDirectoryEntries(FilePath(kSysBlockPath), false /* is_recursive */,
&sysfs_block_devices);
std::vector<LoopDevice> devices;
for (const auto& sysfs_block_device : sysfs_block_devices) {
FilePath device = FilePath(kDevPath).Append(sysfs_block_device.BaseName());
// Backing file contains path to associated source for loop devices.
FilePath sysfs_backing_file = sysfs_block_device.Append(kLoopBackingFile);
std::string backing_file_content;
// If the backing file doesn't exist, it's not an attached loop device.
if (!ReadFileToString(sysfs_backing_file, &backing_file_content))
continue;
FilePath backing_file(
base::TrimWhitespaceASCII(backing_file_content, base::TRIM_ALL));
devices.push_back({backing_file, device});
}
return devices;
}
bool Platform::FormatExt4(const base::FilePath& file,
const std::vector<std::string>& opts,
uint64_t blocks) {
brillo::ProcessImpl format_process;
format_process.AddArg("/sbin/mkfs.ext4");
for (const auto& arg : opts)
format_process.AddArg(arg);
format_process.AddArg(file.value());
if (blocks != 0)
format_process.AddArg(std::to_string(blocks));
// No need to emit output.
format_process.AddArg("-q");
// Close unused file descriptors in child process.
format_process.SetCloseUnusedFileDescriptors(true);
// Avoid polluting the parent process' stdout.
format_process.RedirectOutput("/dev/null");
int rc = format_process.Run();
if (rc != 0) {
LOG(ERROR) << "Can't format '" << file.value()
<< "' as ext4, exit status: " << rc;
return false;
}
// Tune the formatted filesystem:
// -c 0: Disable max mount count checking.
// -i 0: Disable filesystem checking.
return Tune2Fs(file, {"-c", "0", "-i", "0"});
}
bool Platform::Tune2Fs(const base::FilePath& file,
const std::vector<std::string>& opts) {
brillo::ProcessImpl tune_process;
tune_process.AddArg("/sbin/tune2fs");
for (const auto& arg : opts)
tune_process.AddArg(arg);
tune_process.AddArg(file.value());
// Close unused file descriptors in child process.
tune_process.SetCloseUnusedFileDescriptors(true);
// Avoid polluting the parent process' stdout.
tune_process.RedirectOutput("/dev/null");
int rc = tune_process.Run();
if (rc != 0) {
LOG(ERROR) << "Can't tune ext4: " << file.value() << ", error: " << rc;
return false;
}
return true;
}
bool Platform::ResizeFilesystem(const base::FilePath& file, uint64_t blocks) {
brillo::ProcessImpl resize_process;
resize_process.AddArg("/sbin/resize2fs");
resize_process.AddArg("-f");
resize_process.AddArg(file.value().c_str());
resize_process.AddArg(std::to_string(blocks));
// Close unused file descriptors in child process.
resize_process.SetCloseUnusedFileDescriptors(true);
// Start the process and return.
LOG(INFO) << "Resizing filesystem on " << file.value() << " to " << blocks;
int rc = resize_process.Run();
if (rc != 0)
return false;
LOG(INFO) << "Resizing process started.";
return true;
}
bool Platform::RestoreSELinuxContexts(const base::FilePath& path,
bool recursive) {
#if USE_SELINUX
LOG(INFO) << "Restoring SELinux contexts for: " << path.value()
<< ", recursive=" << std::boolalpha << recursive;
int restorecon_flag = 0;
if (recursive)
restorecon_flag |= SELINUX_RESTORECON_RECURSE;
if (selinux_restorecon(path.value().c_str(), restorecon_flag) != 0) {
PLOG(ERROR) << "restorecon(" << path.value() << ") failed";
return false;
}
#endif
return true;
}
bool Platform::SetSELinuxContext(const base::FilePath& path,
const std::string& context) {
#if USE_SELINUX
int result = setfilecon(path.value().c_str(), context.c_str());
if (result != 0) {
LOG(ERROR) << "Failed to set SELinux context for " << path
<< ", errno = " << errno;
return false;
}
#else
LOG(WARNING)
<< "Try to set SELinux context when SELinux is disabled at compile time.";
#endif
return true;
}
bool Platform::SetupProcessKeyring() {
// We have patched upstart to set up a session keyring in init.
// This results in the user keyring not present under the session keyring and
// it breaks eCryptfs. Set up a process keyring and link the user keyring to
// it to fix this.
if (keyctl_link(KEY_SPEC_USER_KEYRING, KEY_SPEC_PROCESS_KEYRING)) {
PLOG(ERROR) << "Failed to link the user keyring to the process keyring.";
return false;
}
// When we have a process keyring, it hides the session keyring and it breaks
// ext4 encryption.
// Link the session keyring to the process keyring so that request_key() can
// find keys under the session keyring too.
if (keyctl_link(KEY_SPEC_SESSION_KEYRING, KEY_SPEC_PROCESS_KEYRING)) {
PLOG(ERROR) << "Failed to link the session keyring to the process keyring.";
return false;
}
return true;
}
// Encapsulate these helpers to avoid include conflicts.
namespace ecryptfs {
extern "C" {
#include <ecryptfs.h> // NOLINT(build/include_alpha)
}
long AddEcryptfsAuthToken( // NOLINT(runtime/int)
const brillo::SecureBlob& key,
const std::string& key_sig,
const brillo::SecureBlob& salt) {
DCHECK_EQ(static_cast<size_t>(ECRYPTFS_MAX_KEY_BYTES), key.size());
DCHECK_EQ(static_cast<size_t>(ECRYPTFS_SIG_SIZE) * 2, key_sig.length());
DCHECK_EQ(static_cast<size_t>(ECRYPTFS_SALT_SIZE), salt.size());
struct ecryptfs_auth_tok auth_token;
generate_payload(&auth_token, const_cast<char*>(key_sig.c_str()),
const_cast<char*>(salt.char_data()),
const_cast<char*>(key.char_data()));
return ecryptfs_add_auth_tok_to_keyring(&auth_token,
const_cast<char*>(key_sig.c_str()));
}
} // namespace ecryptfs
dircrypto::KeyState Platform::GetDirCryptoKeyState(const FilePath& dir) {
return dircrypto::GetDirectoryKeyState(dir);
}
bool Platform::SetDirCryptoKey(const FilePath& dir,
const dircrypto::KeyReference& key_reference) {
return dircrypto::SetDirectoryKey(dir, key_reference);
}
bool Platform::AddDirCryptoKeyToKeyring(
const brillo::SecureBlob& key, dircrypto::KeyReference* key_reference) {
return dircrypto::AddDirectoryKey(key, key_reference);
}
bool Platform::InvalidateDirCryptoKey(
const dircrypto::KeyReference& key_reference, const FilePath& shadow_root) {
return dircrypto::RemoveDirectoryKey(key_reference, shadow_root);
}
bool Platform::ClearUserKeyring() {
/* Flush cache to prevent corruption */
return (keyctl(KEYCTL_CLEAR, KEY_SPEC_USER_KEYRING) == 0);
}
bool Platform::AddEcryptfsAuthToken(const brillo::SecureBlob& key,
const std::string& key_sig,
const brillo::SecureBlob& salt) {
return (ecryptfs::AddEcryptfsAuthToken(key, key_sig, salt) >= 0);
}
FileEnumerator* Platform::GetFileEnumerator(const FilePath& root_path,
bool recursive,
int file_type) {
return new FileEnumerator(root_path, recursive, file_type);
}
bool Platform::WalkPath(const FilePath& path,
const FileEnumeratorCallback& callback) {
base::stat_wrapper_t base_entry_info;
if (!Stat(path, &base_entry_info)) {
PLOG(ERROR) << "Failed to stat " << path.value();
return false;
}
if (!callback.Run(path, base_entry_info))
return false;
if (IsDirectory(base_entry_info)) {
int file_types =
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES;
std::unique_ptr<FileEnumerator> file_enumerator(
GetFileEnumerator(path, true, file_types));
FilePath entry_path;
while (!(entry_path = file_enumerator->Next()).empty()) {
if (!callback.Run(entry_path, file_enumerator->GetInfo().stat()))
return false;
}
}
return true;
}
template <class T>
bool Platform::ReadFileToBlob(const FilePath& path, T* blob) {
int64_t file_size;
if (!base::PathExists(path)) {
return false;
}
if (!base::GetFileSize(path, &file_size)) {
LOG(ERROR) << "Could not get size of " << path.value();
return false;
}
// Compare to the max of a signed integer.
if (file_size > static_cast<int64_t>(std::numeric_limits<int>::max())) {
LOG(ERROR) << "File " << path.value() << " is too large: " << file_size
<< " bytes.";
return false;
}
blob->resize(file_size);
int data_read =
base::ReadFile(path, reinterpret_cast<char*>(blob->data()), blob->size());
// Cast is okay because of comparison to INT_MAX above.
if (data_read != static_cast<int>(file_size)) {
LOG(ERROR) << "Only read " << data_read << " of " << file_size << " bytes"
<< " from " << path.value() << ".";
return false;
}
VerifyChecksum(path, blob->data(), blob->size());
return true;
}
std::string Platform::GetChecksum(const void* input, size_t input_size) {
uint32_t sum = Crc32(input, input_size);
return base::HexEncode(&sum, 4);
}
void Platform::WriteChecksum(const FilePath& path,
const void* content,
const size_t content_size,
mode_t mode) {
FilePath name = path.AddExtension("sum");
WriteStringToFileAtomic(name, GetChecksum(content, content_size), mode);
}
void Platform::VerifyChecksum(const FilePath& path,
const void* content,
const size_t content_size) {
// Exclude some system paths.
std::string path_value = path.value();
if (base::StartsWith(path_value, "/etc", base::CompareCase::SENSITIVE) ||
base::StartsWith(path_value, "/dev", base::CompareCase::SENSITIVE) ||
base::StartsWith(path_value, "/sys", base::CompareCase::SENSITIVE) ||
base::StartsWith(path_value, "/proc", base::CompareCase::SENSITIVE)) {
return;
}
FilePath name = path.AddExtension("sum");
if (!FileExists(name)) {
ReportChecksum(kChecksumDoesNotExist);
return;
}
std::string saved_sum;
if (!base::ReadFileToString(name, &saved_sum)) {
LOG(ERROR) << "CHECKSUM: Failed to read checksum for " << path.value();
ReportChecksum(kChecksumReadError);
return;
}
if (saved_sum != GetChecksum(content, content_size)) {
// Check if the last modified time is out-of-sync for the two files. If they
// weren't written together they can't be expected to match.
base::File::Info content_file_info;
base::File::Info checksum_file_info;
if (!base::GetFileInfo(path, &content_file_info) ||
!base::GetFileInfo(name, &checksum_file_info)) {
LOG(ERROR) << "CHECKSUM: Failed to read file info for " << path.value();
ReportChecksum(kChecksumReadError);
return;
}
base::TimeDelta checksum_timestamp_diff =
checksum_file_info.last_modified - content_file_info.last_modified;
if (checksum_timestamp_diff.magnitude().InSeconds() > 1) {
LOG(ERROR) << "CHECKSUM: Checksum out-of-sync for " << path.value();
ReportChecksum(kChecksumOutOfSync);
} else {
LOG(ERROR) << "CHECKSUM: Failed to verify checksum for " << path.value();
ReportChecksum(kChecksumMismatch);
}
// Attempt to update the checksum to match the current content.
mode_t current_mode;
if (GetPermissions(name, &current_mode)) {
WriteChecksum(path, content, content_size, current_mode);
}
return;
}
ReportChecksum(kChecksumOK);
}
FileEnumerator::FileInfo::FileInfo(
const base::FileEnumerator::FileInfo& file_info) {
Assign(file_info);
}
FileEnumerator::FileInfo::FileInfo(const FilePath& name,
const base::stat_wrapper_t& stat)
: name_(name), stat_(stat) {}
FileEnumerator::FileInfo::FileInfo(const FileEnumerator::FileInfo& other) {
if (other.info_.get()) {
Assign(*other.info_);
} else {
info_.reset();
name_ = other.name_;
stat_ = other.stat_;
}
}
FileEnumerator::FileInfo::FileInfo() {
Assign(base::FileEnumerator::FileInfo());
}
FileEnumerator::FileInfo::~FileInfo() {}
FileEnumerator::FileInfo& FileEnumerator::FileInfo::operator=(
const FileEnumerator::FileInfo& other) {
if (other.info_.get()) {
Assign(*other.info_);
} else {
info_.reset();
name_ = other.name_;
stat_ = other.stat_;
}
return *this;
}
bool FileEnumerator::FileInfo::IsDirectory() const {
if (info_.get())
return info_->IsDirectory();
return ::IsDirectory(stat_);
}
FilePath FileEnumerator::FileInfo::GetName() const {
if (info_.get())
return info_->GetName();
return name_;
}
int64_t FileEnumerator::FileInfo::GetSize() const {
if (info_.get())
return info_->GetSize();
return stat_.st_size;
}
base::Time FileEnumerator::FileInfo::GetLastModifiedTime() const {
if (info_.get())
return info_->GetLastModifiedTime();
return base::Time::FromTimeT(stat_.st_mtime);
}
const base::stat_wrapper_t& FileEnumerator::FileInfo::stat() const {
if (info_.get())
return info_->stat();
return stat_;
}
void FileEnumerator::FileInfo::Assign(
const base::FileEnumerator::FileInfo& file_info) {
info_.reset(new base::FileEnumerator::FileInfo(file_info));
memset(&stat_, 0, sizeof(stat_));
}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type) {
enumerator_.reset(new base::FileEnumerator(root_path, recursive, file_type));
}
FileEnumerator::FileEnumerator(const FilePath& root_path,
bool recursive,
int file_type,
const std::string& pattern) {
enumerator_.reset(
new base::FileEnumerator(root_path, recursive, file_type, pattern));
}
FileEnumerator::FileEnumerator() {}
FileEnumerator::~FileEnumerator() {}
FilePath FileEnumerator::Next() {
if (!enumerator_.get())
return FilePath();
return enumerator_->Next();
}
FileEnumerator::FileInfo FileEnumerator::GetInfo() {
return FileInfo(enumerator_->GetInfo());
}
} // namespace cryptohome