blob: 7de344244060ed0c346d6bc364d58a93844b9587 [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "init/startup/chromeos_startup.h"
#include <fcntl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <filesystem>
#include <memory>
#include <utility>
#include <vector>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/functional/callback_helpers.h>
#include <base/logging.h>
#include <base/strings/strcat.h>
#include <base/strings/string_split.h>
#include <brillo/files/file_util.h>
#include <brillo/flag_helper.h>
#include <brillo/process/process.h>
#include <brillo/secure_blob.h>
#include <brillo/strings/string_utils.h>
#include <brillo/userdb_utils.h>
#include <libcrossystem/crossystem.h>
#include <libhwsec-foundation/tlcl_wrapper/tlcl_wrapper.h>
#include <libstorage/platform/platform.h>
#include <openssl/sha.h>
#include <vpd/vpd.h>
#include "init/encrypted_reboot_vault/encrypted_reboot_vault.h"
#include "init/file_attrs_cleaner.h"
#include "init/metrics/metrics.h"
#include "init/startup/constants.h"
#include "init/startup/factory_mode_mount_helper.h"
#include "init/startup/flags.h"
#include "init/startup/mount_helper.h"
#include "init/startup/mount_helper_factory.h"
#include "init/startup/security_manager.h"
#include "init/startup/standard_mount_helper.h"
#include "init/startup/startup_dep_impl.h"
#include "init/startup/stateful_mount.h"
#include "init/startup/test_mode_mount_helper.h"
#include "init/startup/uefi_startup.h"
#include "init/tpm_encryption/tpm.h"
#include "init/tpm_encryption/tpm_setup.h"
#include "init/utils.h"
namespace {
constexpr char kHome[] = "home";
constexpr char kUnencrypted[] = "unencrypted";
constexpr char kVar[] = "var";
constexpr char kVarLog[] = "var/log";
constexpr char kChronos[] = "chronos";
constexpr char kUser[] = "user";
constexpr char kRoot[] = "root";
constexpr char kProcCmdline[] = "proc/cmdline";
constexpr int kVersionAttestationPcr = 13;
constexpr char kRunNamespaces[] = "run/namespaces";
constexpr char kRun[] = "run";
constexpr char kLock[] = "lock";
constexpr char kEmpty[] = "empty";
constexpr char kMedia[] = "media";
constexpr char kSysfs[] = "sys";
constexpr char kKernelConfig[] = "kernel/config";
constexpr char kKernelDebug[] = "kernel/debug";
constexpr char kKernelSecurity[] = "kernel/security";
constexpr char kKernelTracing[] = "kernel/tracing";
constexpr char kSysfsCpu[] = "devices/system/cpu";
constexpr char kTpmSimulator[] = "etc/init/tpm2-simulator.conf";
constexpr char kSELinuxEnforce[] = "fs/selinux/enforce";
constexpr char kBpf[] = "fs/bpf";
constexpr char kBpfAccessGrp[] = "bpf-access";
// This file is created by clobber-state after the transition to dev mode.
constexpr char kDevModeFile[] = ".developer_mode";
// Flag file indicating that encrypted stateful should be preserved across
// TPM clear. If the file is present, it's expected that TPM is not owned.
constexpr char kPreservationRequestFile[] = "preservation_request";
// This file is created after the TPM is owned/ready and before the
// enterprise enrollment.
constexpr char kCryptohomeKeyFile[] = "home/.shadow/cryptohome.key";
// This file should not exist on the newer system after the TPM is cleared.
constexpr char kEncStatefulNeedFinalizationFile[] =
"encrypted.needs-finalization";
// File used to trigger a stateful reset. Contains arguments for the
// clobber-state" call. This file may exist at boot time, as some use cases
// operate by creating this file with the necessary arguments and then
// rebooting.
constexpr char kResetFile[] = "factory_install_reset";
// Flag file indicating that mount encrypted stateful failed last time.
// If the file is present and mount_encrypted failed again, machine would
// enter self-repair mode.
constexpr char kMountEncryptedFailedFile[] = "mount_encrypted_failed";
// Flag file indicating that PCR Extend operation failed.
// Currently this is for UMA/diagnostics, but in the future failure will
// result in reboot/self-repair.
constexpr char kVersionPCRExtendFailedFile[] = "version_pcr_extend_failed";
// kEncryptedStatefulMnt stores the path to the initial mount point for
// the encrypted stateful partition
constexpr char kEncryptedStatefulMnt[] = "encrypted";
// This value is threshold for determining that /var is full.
const int kVarFullThreshold = 10485760;
constexpr char kDaemonStore[] = "daemon-store";
constexpr char kDaemonStoreCache[] = "daemon-store-cache";
constexpr char kEtc[] = "etc";
constexpr char kDisableStatefulSecurityHard[] =
"usr/share/cros/startup/disable_stateful_security_hardening";
constexpr char kDebugfsAccessGrp[] = "debugfs-access";
constexpr char kTpmFirmwareUpdateCleanup[] =
"usr/sbin/tpm-firmware-update-cleanup";
constexpr char kTpmFirmwareUpdateRequestFlagFile[] =
"unencrypted/preserve/tpm_firmware_update_request";
constexpr char kLibWhitelist[] = "lib/whitelist";
constexpr char kLibDevicesettings[] = "lib/devicesettings";
constexpr char kPreserve[] = "preserve";
const std::array<const char*, 4> kPreserveDirs = {
"var/lib/servod",
"usr/local/servod",
"var/lib/device_health_profile",
"usr/local/etc/wifi_creds",
};
} // namespace
namespace startup {
// Process the arguments from included USE flags only.
void ChromeosStartup::ParseFlags(Flags* flags) {
flags->direncryption = USE_DIRENCRYPTION;
flags->fsverity = USE_FSVERITY;
flags->prjquota = USE_PRJQUOTA;
flags->encstateful = USE_ENCRYPTED_STATEFUL;
if (flags->encstateful) {
flags->sys_key_util = USE_TPM2;
}
// Note: encrypted_reboot_vault is disabled only for Gale
// to be able to use openssl 1.1.1.
flags->encrypted_reboot_vault = USE_ENCRYPTED_REBOOT_VAULT;
flags->lvm_migration = USE_LVM_MIGRATION;
flags->lvm_stateful = USE_LVM_STATEFUL_PARTITION;
flags->verbosity = 0;
}
// Process the arguments from included USE flags and command line arguments.
bool ChromeosStartup::ParseFlags(Flags* flags, int argc, const char* argv[]) {
ParseFlags(flags);
DEFINE_uint32(verbosity, 0,
"logging verbosity: 0: warning, 1: info, 2: verbose");
DEFINE_bool(encrypted_stateful, true,
"--no-encrypted-stateful disable encrypted stateful");
bool result = brillo::FlagHelper::Init(
argc, argv, "Tool run early during ChromeOS boot.");
// Exit early if parsing failed.
if (!result)
return result;
if (FLAGS_verbosity > 2) {
return false;
}
flags->verbosity = FLAGS_verbosity;
flags->encstateful &= FLAGS_encrypted_stateful;
return true;
}
// We manage this base timestamp by hand. It isolates us from bad clocks on
// the system where this image was built/modified, and on the runtime image
// (in case a dev modified random paths while the clock was out of sync) or
// if the RTC is buggy or battery is dead.
// TODO(b/234157809): Our namespaces module doesn't support time namespaces
// currently. Add unittests for CheckClock once we add support.
void ChromeosStartup::CheckClock() {
time_t cur_time;
time(&cur_time);
if (cur_time < kMinSecs || cur_time > kMaxSecs) {
struct timespec stime;
stime.tv_sec = kMinSecs;
stime.tv_nsec = 0;
if (clock_settime(CLOCK_REALTIME, &stime) != 0) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "Unable to set time.";
}
}
}
void ChromeosStartup::Sysctl() {
// Initialize kernel sysctl settings early so that they take effect for boot
// processes.
std::unique_ptr<brillo::Process> proc = platform_->CreateProcessInstance();
proc->AddArg("/usr/sbin/sysctl");
proc->AddArg("-q");
proc->AddArg("--system");
int status = proc->Run();
if (status != 0) {
LOG(WARNING) << "Failed to initialize kernel sysctl settings.";
}
}
// Returns true if the TPM is owned or couldn't determine.
bool ChromeosStartup::IsTPMOwned() {
encryption::Tpm tpm(tlcl_.get());
bool owned;
if (!tpm.IsOwned(&owned))
return true;
return owned;
}
// Returns if device needs to clobber even though there's no devmode file
// present and boot is in verified mode.
bool ChromeosStartup::NeedsClobberWithoutDevModeFile() {
base::FilePath preservation_request =
stateful_.Append(kPreservationRequestFile);
base::FilePath cryptohome_key = stateful_.Append(kCryptohomeKeyFile);
base::FilePath need_finalization =
stateful_.Append(kEncStatefulNeedFinalizationFile);
if (IsTPMOwned()) {
return false;
}
if (platform_->FileExists(need_finalization)) {
return true;
}
// This should only be supported on the non-TPM2 device.
if (!USE_TPM2) {
uid_t uid;
if (platform_->GetOwnership(preservation_request, &uid, nullptr,
false /* follow_links */) &&
(uid == getuid()))
return false;
}
if (platform_->FileExists(cryptohome_key)) {
return true;
}
return false;
}
// Returns true if the device is in transitioning between verified boot and dev
// mode. devsw_boot is the expected value of devsw_boot.
bool ChromeosStartup::IsDevToVerifiedModeTransition(int devsw_boot) {
crossystem::Crossystem* crossystem = platform_->GetCrosssystem();
std::optional<int> boot = crossystem->VbGetSystemPropertyInt(
crossystem::Crossystem::kDevSwitchBoot);
if (!boot || *boot != devsw_boot)
return false;
std::optional<std::string> dstr = crossystem->VbGetSystemPropertyString(
crossystem::Crossystem::kMainFirmwareType);
return dstr && dstr != "recovery";
}
// Walk the specified path and reset any file attributes (like immutable bit).
void ChromeosStartup::ForceCleanFileAttrs(const base::FilePath& path) {
// No physical stateful partition available, usually due to initramfs
// (recovery image, factory install shim or netboot. Do not check.
if (state_dev_.empty()) {
return;
}
std::vector<std::string> skip;
bool status = file_attrs_cleaner::ScanDir(path.value(), skip);
if (!status) {
std::vector<std::string> args = {"keepimg", "preserve_lvs"};
startup_dep_->Clobber(
"self-repair", args,
std::string("Bad file attrs under ").append(path.value()));
}
}
// Checks if /var is close to being full.
// Returns true if there is less than 10MB of free space left in /var or if
// there are less than 100 inodes available on the underlying filesystem.
bool ChromeosStartup::IsVarFull() {
struct statvfs st;
base::FilePath var = root_.Append(kVar);
if (!platform_->StatVFS(var, &st)) {
PLOG(WARNING) << "Failed statvfs " << var.value();
return false;
}
return (st.f_bavail < kVarFullThreshold / st.f_bsize || st.f_favail < 100);
}
ChromeosStartup::ChromeosStartup(
std::unique_ptr<vpd::Vpd> vpd,
const Flags& flags,
const base::FilePath& root,
const base::FilePath& stateful,
const base::FilePath& lsb_file,
libstorage::Platform* platform,
StartupDep* startup_dep,
std::unique_ptr<MountHelper> mount_helper,
std::unique_ptr<hwsec_foundation::TlclWrapper> tlcl,
init_metrics::InitMetrics* metrics)
: platform_(platform),
vpd_(std::move(vpd)),
flags_(flags),
lsb_file_(lsb_file),
root_(root),
stateful_(stateful),
startup_dep_(startup_dep),
mount_helper_(std::move(mount_helper)),
tlcl_(std::move(tlcl)),
metrics_(metrics) {
stateful_mount_ = std::make_unique<StatefulMount>(
flags_, root_, stateful_, platform_, startup_dep_, mount_helper_.get());
}
void ChromeosStartup::EarlySetup() {
const base::FilePath sysfs = root_.Append(kSysfs);
const base::FilePath empty;
gid_t debugfs_grp;
if (!brillo::userdb::GetGroupInfo(kDebugfsAccessGrp, &debugfs_grp)) {
PLOG(WARNING) << "Can't get gid for " << kDebugfsAccessGrp;
} else {
char data[25];
snprintf(data, sizeof(data), "mode=0750,uid=0,gid=%d", debugfs_grp);
const base::FilePath debug = sysfs.Append(kKernelDebug);
if (!platform_->Mount(empty, debug, "debugfs", kCommonMountFlags, data)) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "Unable to mount " << debug.value();
}
}
// Mount tracefs at /sys/kernel/tracing. On older kernels, tracing was part
// of debugfs and was present at /sys/kernel/debug/tracing. Newer kernels
// continue to automount it there when accessed via
// /sys/kernel/debug/tracing/, but we avoid that where possible, to limit our
// dependence on debugfs.
const base::FilePath tracefs = sysfs.Append(kKernelTracing);
// All users may need to access the tracing directory.
if (!platform_->Mount(empty, tracefs, "tracefs", kCommonMountFlags,
"mode=0755")) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "Unable to mount " << tracefs.value();
}
// Mount configfs, if present.
const base::FilePath configfs = sysfs.Append(kKernelConfig);
if (platform_->DirectoryExists(configfs)) {
if (!platform_->Mount(empty, configfs, "configfs", kCommonMountFlags, "")) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "Unable to mount " << configfs.value();
}
}
// Mount bpffs for loading and pinning ebpf objects.
gid_t bpffs_grp;
if (!brillo::userdb::GetGroupInfo(kBpfAccessGrp, &bpffs_grp)) {
PLOG(WARNING) << "Can't get gid for " << kBpfAccessGrp;
} else {
const std::string data =
base::StrCat({"mode=0770,gid=", std::to_string(bpffs_grp)});
const base::FilePath bpffs = sysfs.Append(kBpf);
if (!platform_->Mount(empty, bpffs, "bpf", kCommonMountFlags,
data.c_str())) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "Unable to mount " << bpffs.value();
}
}
// Mount securityfs as it is used to configure inode security policies below.
const base::FilePath securityfs = sysfs.Append(kKernelSecurity);
if (!platform_->Mount(empty, securityfs, "securityfs", kCommonMountFlags,
"")) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "Unable to mount " << securityfs.value();
}
if (!SetupLoadPinVerityDigests(platform_, root_, startup_dep_.get())) {
LOG(WARNING) << "Failed to setup LoadPin verity digests.";
}
// Initialize kernel sysctl settings early so that they take effect for boot
// processes.
Sysctl();
// Protect a bind mount to the Chrome mount namespace.
const base::FilePath namespaces = root_.Append(kRunNamespaces);
if (!platform_->Mount(namespaces, namespaces, "", MS_BIND, "") ||
!platform_->Mount(base::FilePath(), namespaces, "", MS_PRIVATE, "")) {
PLOG(WARNING) << "Unable to mount " << namespaces.value();
}
const base::FilePath disable_sec_hard =
root_.Append(kDisableStatefulSecurityHard);
enable_stateful_security_hardening_ =
!platform_->FileExists(disable_sec_hard);
if (enable_stateful_security_hardening_) {
if (!ConfigureProcessMgmtSecurity(platform_, root_)) {
PLOG(ERROR) << "Failed to configure process management security.";
}
} else {
LOG(WARNING) << "Process management security disabled by flag file.";
}
}
// Apply /mnt/stateful_partition specific tmpfiles.d configurations
void ChromeosStartup::TmpfilesConfiguration(
const std::vector<std::string>& dirs) {
std::unique_ptr<brillo::Process> tmpfiles =
platform_->CreateProcessInstance();
tmpfiles->AddArg("/usr/bin/systemd-tmpfiles");
tmpfiles->AddArg("--create");
tmpfiles->AddArg("--remove");
tmpfiles->AddArg("--boot");
for (std::string path : dirs) {
tmpfiles->AddArg("--prefix");
tmpfiles->AddArg(path);
}
if (tmpfiles->Run() != 0) {
std::string msg =
"tmpfiles.d failed for " + brillo::string_utils::Join(",", dirs);
mount_helper_->CleanupMounts(msg);
}
}
// Check for whether we need a stateful wipe, and alert the user as
// necessary.
void ChromeosStartup::CheckForStatefulWipe() {
// We can wipe for several different reasons:
// + User requested "power wash" which will create kResetFile.
// + Switch from verified mode to dev mode. We do this if we're in
// dev mode, and kDevModeFile doesn't exist. clobber-state
// in this case will create the file, to prevent re-wipe.
// + Switch from dev mode to verified mode. We do this if we're in
// verified mode, and kDevModeFile still exists. (This check
// isn't necessarily reliable.)
//
// Stateful wipe for dev mode switching is skipped if the build is a debug
// build or if we've booted a non-recovery image in recovery mode (for
// example, doing Esc-F3-Power on a Chromebook with DEV-signed firmware);
// this protects various development use cases, most especially prototype
// units or booting Chromium OS on non-Chrome hardware. And because crossystem
// is slow on some platforms, we want to do the additional checks only
// after verified kDevModeFile existence.
std::vector<std::string> clobber_args;
std::string boot_alert_msg;
std::string clobber_log_msg;
base::FilePath reset_file = stateful_.Append(kResetFile);
if (platform_->IsLink(reset_file) || platform_->FileExists(reset_file)) {
boot_alert_msg = "power_wash";
uid_t uid;
platform_->GetOwnership(reset_file, &uid, nullptr,
false /* follow_links */);
// If it's not a plain file owned by us, force a powerwash.
if (uid != getuid() || platform_->IsLink(reset_file)) {
clobber_log_msg =
"Powerwash initiated by Reset file presence, but invalid";
} else {
std::string str;
if (!platform_->ReadFileToString(reset_file, &str)) {
PLOG(WARNING) << "Failed to read reset file";
clobber_log_msg =
"Powerwash initiated by Reset file presence, but unreadable";
} else {
clobber_log_msg = "Powerwash initiated by Reset file presence";
std::vector<std::string> split_args = base::SplitString(
str, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const std::string& arg : split_args) {
clobber_args.push_back(arg);
}
}
}
if (clobber_args.empty()) {
clobber_args.push_back("keepimg");
}
} else if (state_dev_.empty()) {
// No physical stateful partition available, usually due to initramfs
// (recovery image, factory install shim or netboot). Do not wipe.
} else if (IsDevToVerifiedModeTransition(0)) {
uid_t uid;
bool res = platform_->FileExists(dev_mode_allowed_file_) &&
platform_->GetOwnership(dev_mode_allowed_file_, &uid, nullptr,
false /* follow_links */);
if ((res && uid == getuid()) || NeedsClobberWithoutDevModeFile()) {
if (!DevIsDebugBuild()) {
// We're transitioning from dev mode to verified boot.
// When coming back from developer mode, we don't need to
// clobber as aggressively. Fast will do the trick.
boot_alert_msg = "leave_dev";
clobber_args.push_back("fast");
clobber_args.push_back("keepimg");
std::string msg;
if (res && uid == getuid()) {
msg = "Leave developer mode, dev_mode file present";
} else {
msg = "Leave developer mode, no dev_mode file";
}
clobber_log_msg = msg;
} else {
// Only fast "clobber" the non-protected paths in debug build to
// preserve the testing tools. We are not invoking clobber, cleaning up
// stateful manually.
clobber_log_msg = "Leave developer mode on a debug build";
DevUpdateStatefulPartition("clobber");
}
}
} else if (IsDevToVerifiedModeTransition(1)) {
uid_t uid;
bool res = platform_->FileExists(dev_mode_allowed_file_) &&
platform_->GetOwnership(dev_mode_allowed_file_, &uid, nullptr,
false /* follow_links */);
if (!res || uid != getuid()) {
if (!DevIsDebugBuild()) {
// We're transitioning from verified boot to dev mode.
boot_alert_msg = "enter_dev";
clobber_args.push_back("keepimg");
clobber_log_msg = "Enter developer mode";
} else {
// Only fast clobber the non-protected paths in debug build to preserve
// the testing tools.
clobber_log_msg = "Enter developer mode on a debug build";
DevUpdateStatefulPartition("clobber");
if (!platform_->FileExists(dev_mode_allowed_file_)) {
if (!platform_->TouchFileDurable(dev_mode_allowed_file_)) {
PLOG(WARNING) << "Failed to create file: "
<< dev_mode_allowed_file_.value();
}
}
}
}
}
if (clobber_args.empty()) {
if (!clobber_log_msg.empty()) {
startup_dep_->ClobberLog(clobber_log_msg);
}
} else {
startup_dep_->Clobber(boot_alert_msg, clobber_args, clobber_log_msg);
}
}
// Mount /home.
void ChromeosStartup::MountHome() {
const base::FilePath home = stateful_.Append(kHome);
const base::FilePath home_root = root_.Append(kHome);
mount_helper_->BindMountOrFail(home, home_root);
// Remount /home with nosymfollow: bind mounts do not accept the option
// within the same command.
if (!platform_->Mount(base::FilePath(), home_root, "",
MS_REMOUNT | kCommonMountFlags | MS_NOSYMFOLLOW, "")) {
PLOG(WARNING) << "Unable to remount " << home_root.value();
}
}
// Start tpm2-simulator if it exists.
// TODO(b:261148112): Replace initctl call with logic to directly communicate
// with upstart.
void ChromeosStartup::StartTpm2Simulator() {
base::FilePath tpm_simulator = root_.Append(kTpmSimulator);
if (platform_->FileExists(tpm_simulator)) {
std::unique_ptr<brillo::Process> ictl = platform_->CreateProcessInstance();
ictl->AddArg("/sbin/initctl");
ictl->AddArg("start");
ictl->AddArg("tpm2-simulator");
// Failure is fine, we just continue.
ictl->Run();
}
}
// Clean up after a TPM firmware update.
// Called before initiatlizing the TPM, which is done just before
// mount encrypted stateful.
void ChromeosStartup::CleanupTpm() {
base::FilePath tpm_update_req =
stateful_.Append(kTpmFirmwareUpdateRequestFlagFile);
if (platform_->FileExists(tpm_update_req)) {
base::FilePath tpm_cleanup = root_.Append(kTpmFirmwareUpdateCleanup);
if (platform_->FileExists(tpm_cleanup)) {
std::unique_ptr<brillo::Process> tpm_cleanup_process =
platform_->CreateProcessInstance();
tpm_cleanup_process->AddArg(tpm_cleanup.value());
if (tpm_cleanup_process->Run() != 0) {
PLOG(ERROR) << tpm_cleanup.value() << " failed.";
}
}
}
}
bool ChromeosStartup::ExtendPCRForVersionAttestation() {
if (USE_TPM_INSECURE_FALLBACK) {
// Not needed on devices whereby the secure element is not mandatory.
return true;
}
if (!USE_TPM2) {
// Only TPM2.0 supported.
return true;
}
base::FilePath cmdline_path = root_.Append(kProcCmdline);
brillo::Blob cmdline;
if (!platform_->ReadFile(cmdline_path, &cmdline)) {
PLOG(WARNING) << "Failure to read /proc/cmdline for PCR Extension.";
return false;
}
brillo::Blob digest(SHA256_DIGEST_LENGTH);
SHA256(cmdline.data(), cmdline.size(), digest.data());
if (tlcl_->Init() != 0) {
PLOG(WARNING) << "Failure to init TlclWrapper.";
return false;
}
base::ScopedClosureRunner close(base::BindOnce(
[](hwsec_foundation::TlclWrapper* tlcl) {
if (tlcl->Close() != 0) {
PLOG(WARNING) << "Failed to shutdown TlclWrapper.";
}
},
tlcl_.get()));
if (tlcl_->Extend(kVersionAttestationPcr, digest, nullptr) != 0) {
PLOG(WARNING) << "Failure to extend PCR with TlclWrapper.";
return false;
}
return true;
}
// Move from /var/lib/whitelist to /var/lib/devicesettings if it is empty or
// non-existing. If /var/lib/devicesettings already exists, just remove
// /var/lib/whitelist.
// TODO(b/219506748): Remove the following lines by 2030 the latest. If there
// was a stepping stone to R99+ for all boards in between, or the number of
// devices using a version that did not have this code is less than the number
// of devices suffering from disk corruption, code can be removed earlier.
void ChromeosStartup::MoveToLibDeviceSettings() {
base::FilePath whitelist = root_.Append(kVar).Append(kLibWhitelist);
base::FilePath devicesettings = root_.Append(kVar).Append(kLibDevicesettings);
// If the old whitelist dir still exists, try to migrate it.
if (platform_->DirectoryExists(whitelist)) {
if (platform_->IsDirectoryEmpty(whitelist)) {
// If it is empty, delete it.
if (!platform_->DeleteFile(whitelist)) {
PLOG(WARNING) << "Failed to delete path " << whitelist.value();
}
} else if (platform_->DeleteFile(devicesettings)) {
// If devicesettings didn't exist, or was empty, DeleteFile passed.
// Rename the old path.
if (!platform_->Rename(whitelist, devicesettings, false /* cros_fs */)) {
PLOG(WARNING) << "Failed to move " << whitelist.value() << " to "
<< devicesettings.value();
}
} else {
// Both directories exist and are not empty. Do nothing.
LOG(WARNING) << "Unable to move " << whitelist.value() << " to "
<< devicesettings.value()
<< ", both directories are not empty";
}
}
}
// Create daemon store folders.
// See
// https://chromium.googlesource.com/chromiumos/docs/+/HEAD/sandboxing.md#securely-mounting-daemon-store-folders.
void ChromeosStartup::CreateDaemonStore() {
// Create /run/daemon-store and /run/daemon-store-cache based on
// /etc/daemon-store.
CreateDaemonStore(root_.Append(kRun).Append(kDaemonStore),
root_.Append(kEtc).Append(kDaemonStore));
CreateDaemonStore(root_.Append(kRun).Append(kDaemonStoreCache),
root_.Append(kEtc).Append(kDaemonStore));
}
void ChromeosStartup::CreateDaemonStore(base::FilePath run_ds,
base::FilePath etc_ds) {
std::unique_ptr<libstorage::FileEnumerator> iter(platform_->GetFileEnumerator(
etc_ds, false, base::FileEnumerator::FileType::DIRECTORIES));
for (base::FilePath store = iter->Next(); !store.empty();
store = iter->Next()) {
base::FilePath rds = run_ds.Append(store.BaseName());
if (!platform_->CreateDirectory(rds)) {
PLOG(WARNING) << "mkdir failed for " << rds.value();
continue;
}
if (!platform_->SetPermissions(rds, 0755)) {
PLOG(WARNING) << "chmod failed for " << rds.value();
continue;
}
platform_->Mount(rds, rds, "", MS_BIND, "");
platform_->Mount(base::FilePath(), rds, "", MS_SHARED, "");
}
}
// Remove /var/empty if it exists. Use /mnt/empty instead.
void ChromeosStartup::RemoveVarEmpty() {
base::FilePath var_empty = root_.Append(kVar).Append(kEmpty);
if (!platform_->DirectoryExists(var_empty))
return;
platform_->SetExtFileAttributes(var_empty, 0, FS_IMMUTABLE_FL);
if (!platform_->DeletePathRecursively(var_empty)) {
PLOG(WARNING) << "Failed to delete path " << var_empty.value();
}
}
// Make sure that what gets written to /var/log stays in /var/log.
void ChromeosStartup::CheckVarLog() {
base::FilePath varLog = root_.Append(kVarLog);
std::unique_ptr<libstorage::FileEnumerator> var_iter(
platform_->GetFileEnumerator(
root_.Append(kVarLog), true,
base::FileEnumerator::FileType::FILES |
base::FileEnumerator::FileType::DIRECTORIES |
base::FileEnumerator::FileType::SHOW_SYM_LINKS));
for (base::FilePath path = var_iter->Next(); !path.empty();
path = var_iter->Next()) {
if (platform_->IsLink(path)) {
base::FilePath realpath;
if (!platform_->ReadLink(path, &realpath, true /* Normalize */) ||
!varLog.IsParent(realpath)) {
if (!platform_->DeleteFile(path)) {
// Bail out and wipe on failure to remove a symlink.
mount_helper_->CleanupMounts(
"Failed to remove symlinks under /var/log");
}
}
}
}
}
// Restore file contexts for /var.
void ChromeosStartup::RestoreContextsForVar(
void (*restorecon_func)(libstorage::Platform* platform_,
const base::FilePath& path,
const std::vector<base::FilePath>& exclude,
bool is_recursive,
bool set_digests)) {
// Restore file contexts for /var.
base::FilePath sysfs = root_.Append(kSysfs);
base::FilePath selinux = sysfs.Append(kSELinuxEnforce);
if (!platform_->FileExists(selinux)) {
LOG(INFO) << selinux.value()
<< " does not exist, can not restore file contexts";
return;
}
base::FilePath var = root_.Append(kVar);
std::vector<base::FilePath> exc_empty;
restorecon_func(platform_, var, exc_empty, true, true);
// Restoring file contexts for sysfs. We only need to restore a sub directory
// which requires regexp, because the kernel handles prefix match rules via
// genfscon policy rules. Handling prefix match rules here in user space would
// slow down boot significantly.
base::FilePath sysfs_cpu = sysfs.Append(kSysfsCpu);
restorecon_func(platform_, sysfs_cpu, exc_empty, true, false);
// We cannot do recursive for .shadow since userdata is encrypted (including
// file names) before user logs-in. Restoring context for it may mislabel
// files if encrypted filename happens to match something.
base::FilePath home = root_.Append(kHome);
base::FilePath shadow = home.Append(".shadow");
std::vector<base::FilePath> shadow_paths = {home, shadow};
std::unique_ptr<libstorage::FileEnumerator> shadow_files(
platform_->GetFileEnumerator(shadow, false,
base::FileEnumerator::FileType::FILES, "*"));
for (base::FilePath path = shadow_files->Next(); !path.empty();
path = shadow_files->Next()) {
shadow_paths.push_back(path);
}
std::unique_ptr<libstorage::FileEnumerator> shadow_dot(
platform_->GetFileEnumerator(
shadow, false, base::FileEnumerator::FileType::FILES, ".*"));
for (base::FilePath path = shadow_dot->Next(); !path.empty();
path = shadow_dot->Next()) {
shadow_paths.push_back(path);
}
std::unique_ptr<libstorage::FileEnumerator> shadow_subdir(
platform_->GetFileEnumerator(
shadow, false, base::FileEnumerator::FileType::FILES, "*/*"));
for (base::FilePath path = shadow_subdir->Next(); !path.empty();
path = shadow_subdir->Next()) {
shadow_paths.push_back(path);
}
for (auto path : shadow_paths) {
restorecon_func(platform_, path, exc_empty, false, false);
}
// It's safe to recursively restorecon /home/{user,root,chronos} since
// userdir is not bind-mounted here before logging in.
std::array<base::FilePath, 3> h_paths = {
home.Append(kUser), home.Append(kRoot), home.Append(kChronos)};
for (auto h_path : h_paths) {
restorecon_func(platform_, h_path, exc_empty, true, true);
}
}
// Main function to run chromeos_startup.
int ChromeosStartup::Run() {
crossystem::Crossystem* crossystem = platform_->GetCrosssystem();
dev_mode_ = InDevMode(crossystem);
// Make sure our clock is somewhat up-to-date. We don't need any resources
// mounted below, so do this early on.
CheckClock();
// bootstat writes timings to tmpfs.
bootstat_.LogEvent("pre-startup");
EarlySetup();
stateful_mount_->MountStateful();
state_dev_ = stateful_mount_->GetStateDev();
if (enable_stateful_security_hardening_) {
// Block symlink traversal and opening of FIFOs on stateful. Note that we
// set up exceptions for developer mode later on.
BlockSymlinkAndFifo(platform_, root_, stateful_.value());
}
// Checks if developer mode is blocked.
dev_mode_allowed_file_ = stateful_.Append(kDevModeFile);
DevCheckBlockDevMode(dev_mode_allowed_file_);
CheckForStatefulWipe();
// Cleanup the file attributes in the unencrypted stateful directory.
base::FilePath unencrypted = stateful_.Append(kUnencrypted);
ForceCleanFileAttrs(unencrypted);
std::vector<std::string> tmpfiles = {stateful_.value()};
TmpfilesConfiguration(tmpfiles);
MountHome();
StartTpm2Simulator();
CleanupTpm();
std::optional<encryption::EncryptionKey> key;
if (flags_.encstateful) {
base::FilePath encrypted_failed =
stateful_.Append(kMountEncryptedFailedFile);
// Setup the TPM
// In case of error, reboot once.
// If it fails again, trigger a recovery.
auto tpm_system_key = encryption::TpmSystemKey(platform_, tlcl_.get(),
metrics_, root_, stateful_);
key =
tpm_system_key.Load(true /* safe */, mount_helper_->GetKeyBackupFile());
if (!key) {
uid_t uid;
if (!platform_->GetOwnership(encrypted_failed, &uid, nullptr,
false /* follow_links */) ||
(uid != getuid())) {
platform_->TouchFileDurable(encrypted_failed);
} else {
crossystem->VbSetSystemPropertyInt("recovery_request", 1);
}
utils::Reboot();
return 0;
}
if (platform_->FileExists(encrypted_failed))
platform_->DeleteFile(encrypted_failed);
if (!tpm_system_key.Export()) {
// Unable to write to /tmp. Clobber will not help, the root or
// kernel has a problem, request a recovery.
crossystem->VbSetSystemPropertyInt("recovery_request", 1);
utils::Reboot();
return 0;
}
}
// Mount encstateful
// If it fails clobber. In testing mode, a backup may have been saved and
// a fresh encstateful created.
if (!mount_helper_->DoMountVarAndHomeChronos(key)) {
mount_helper_->CleanupMounts("Unable to mount encrypted");
// No return unless in unit tests.
return 0;
}
base::FilePath pcr_extend_failed =
stateful_.Append(kVersionPCRExtendFailedFile);
if (!ExtendPCRForVersionAttestation()) {
// At the moment we'll only log it but not force reboot or recovery.
// TODO(b/278071784): Monitor if the failure occurs frequently and later
// change this to reboot/send to recovery when it failed.
platform_->TouchFileDurable(pcr_extend_failed);
} else if (platform_->FileExists(pcr_extend_failed)) {
platform_->DeleteFile(pcr_extend_failed);
}
base::FilePath encrypted_state_mnt = stateful_.Append(kEncryptedStatefulMnt);
mount_helper_->RememberMount(encrypted_state_mnt);
// Setup the encrypted reboot vault once the encrypted stateful partition
// is available. If unlocking the encrypted reboot vault failed (due to
// power loss/reboot/invalid vault), attempt to recreate the encrypted reboot
// vault.
if (flags_.encrypted_reboot_vault) {
encrypted_reboot_vault::EncryptedRebootVault vault(platform_);
if (!vault.UnlockVault())
vault.CreateVault();
}
ForceCleanFileAttrs(root_.Append(kVar));
ForceCleanFileAttrs(root_.Append(kHome).Append(kChronos));
// If /var is too full, delete the logs so the device can boot successfully.
// It is possible that the fullness of /var was not due to logs, but that
// is very unlikely. If such a thing happens, we have a serious problem
// which should not be covered up here.
if (IsVarFull()) {
brillo::DeletePathRecursively(root_.Append(kVarLog));
}
// Gather logs if needed. This might clear /var, so all init has to be after
// this.
DevGatherLogs();
// Collect crash reports from early boot/mount failures.
std::unique_ptr<brillo::Process> crash_reporter =
platform_->CreateProcessInstance();
crash_reporter->AddArg("/sbin/crash_reporter");
crash_reporter->AddArg("--ephemeral_collect");
if (!crash_reporter->Start()) {
LOG(WARNING) << "Unable to collect early logs and crashes.";
}
if (enable_stateful_security_hardening_) {
ConfigureFilesystemExceptions(platform_, root_);
}
std::vector<std::string> tmpfile_args = {root_.Append(kHome).value(),
root_.Append(kVar).value()};
TmpfilesConfiguration(tmpfile_args);
MoveToLibDeviceSettings();
MaybeRunUefiStartup(*UefiDelegate::Create(platform_, root_));
// /run is tmpfs used for runtime data. Make sure /var/run and /var/lock
// are bind-mounted to /run and /run/lock respectively for backwards
// compatibility.
// Bind mount /run to /var/run.
const base::FilePath var = root_.Append(kVar);
const base::FilePath root_run = root_.Append(kRun);
mount_helper_->BindMountOrFail(root_run, var.Append(kRun));
// Bind mount /run/lock to /var/lock.
const base::FilePath root_run_lock = root_run.Append(kLock);
mount_helper_->BindMountOrFail(root_run_lock, var.Append(kLock));
CreateDaemonStore();
RemoveVarEmpty();
CheckVarLog();
// MS_SHARED to give other namespaces access to mount points under /media.
platform_->Mount(base::FilePath(kMedia), root_.Append(kMedia), "tmpfs",
MS_NOSUID | MS_NODEV | MS_NOEXEC, "");
platform_->Mount(base::FilePath(), root_.Append(kMedia), "", MS_SHARED, "");
std::vector<std::string> t_args = {root_.Append(kMedia).value()};
TmpfilesConfiguration(t_args);
RestoreContextsForVar(&utils::Restorecon);
// Mount dev packages.
DevMountPackages();
RestorePreservedPaths();
// Remount securityfs as readonly so that further modifications to inode
// security policies are not possible but reading the kernel lockdown file is
// still possible.
const base::FilePath kernel_sec =
root_.Append(kSysfs).Append(kKernelSecurity);
if (!platform_->Mount(base::FilePath(), kernel_sec, "securityfs",
MS_REMOUNT | MS_RDONLY | kCommonMountFlags, "")) {
PLOG(WARNING) << "Failed to remount " << kernel_sec << " as readonly.";
}
bootstat_.LogEvent("post-startup");
return 0;
}
// Check whether the device is allowed to boot in dev mode.
// 1. If a debug build is already installed on the system, ignore block_devmode.
// It is pointless in this case, as the device is already in a state where
// the local user has full control.
// 2. According to recovery mode only boot with signed images, the block_devmode
// could be ignored here -- otherwise factory shim will be blocked especially
// that RMA center can't reset this device.
void ChromeosStartup::DevCheckBlockDevMode(
const base::FilePath& dev_mode_file) const {
if (!dev_mode_) {
return;
}
crossystem::Crossystem* crossystem = platform_->GetCrosssystem();
std::optional<int> devsw = crossystem->VbGetSystemPropertyInt(
crossystem::Crossystem::kDevSwitchBoot);
std::optional<int> debug =
crossystem->VbGetSystemPropertyInt(crossystem::Crossystem::kDebugBuild);
std::optional<int> rec_reason = crossystem->VbGetSystemPropertyInt(
crossystem::Crossystem::kRecoveryReason);
if (!devsw || !debug || !rec_reason) {
LOG(WARNING) << "Failed to get boot information from crossystem";
return;
}
if (!(devsw == 1 && debug == 0 && rec_reason == 0)) {
DLOG(INFO) << "Debug build is already installed, ignore block_devmode";
return;
}
bool block_devmode = false;
// Checks ordered by run time.
// 1. Try reading VPD through vpd library.
// 2. Try crossystem.
std::optional<std::string> val =
vpd_->GetValue(vpd::VpdRw, crossystem::Crossystem::kBlockDevmode);
if (val == "1") {
block_devmode = true;
} else {
std::optional<int> crossys_block = crossystem->VbGetSystemPropertyInt(
crossystem::Crossystem::kBlockDevmode);
if (crossys_block == 1) {
block_devmode = true;
}
}
if (block_devmode) {
// Put a flag file into place that will trigger a stateful partition wipe
// after reboot in verified mode.
if (!platform_->FileExists(dev_mode_file)) {
platform_->TouchFileDurable(dev_mode_file);
}
startup_dep_->BootAlert("block_devmode");
}
}
// Set dev_mode_ for tests.
void ChromeosStartup::SetDevMode(bool dev_mode) {
dev_mode_ = dev_mode;
}
// Set dev_mode_allowed_file_ for tests.
void ChromeosStartup::SetDevModeAllowedFile(
const base::FilePath& allowed_file) {
dev_mode_allowed_file_ = allowed_file;
}
// Set state_dev_ for tests.
void ChromeosStartup::SetStateDev(const base::FilePath& state_dev) {
state_dev_ = state_dev;
}
bool ChromeosStartup::DevIsDebugBuild() const {
if (!dev_mode_) {
return false;
}
return IsDebugBuild(platform_->GetCrosssystem());
}
bool ChromeosStartup::DevUpdateStatefulPartition(const std::string& args) {
if (!dev_mode_) {
return true;
}
return stateful_mount_->DevUpdateStatefulPartition(args);
}
void ChromeosStartup::DevGatherLogs() {
if (dev_mode_) {
stateful_mount_->DevGatherLogs(root_);
}
}
void ChromeosStartup::DevMountPackages() {
if (!dev_mode_) {
return;
}
stateful_mount_->DevMountPackages();
}
void ChromeosStartup::RestorePreservedPaths() {
if (!dev_mode_) {
return;
}
base::FilePath preserve_dir =
stateful_.Append(kUnencrypted).Append(kPreserve);
for (const auto& path : kPreserveDirs) {
base::FilePath src = preserve_dir.Append(path);
if (platform_->DirectoryExists(src)) {
const base::FilePath dst = root_.Append(path);
platform_->CreateDirectory(dst);
// |preserve_dir| is the unencrypted volume, |dst| is in the encrypted
// volume, we need to cross filesystem boundaries.
if (!platform_->Rename(src, dst, true)) {
PLOG(WARNING) << "Failed to move " << src.value();
}
}
}
}
} // namespace startup