blob: da45605955f6db4612b69c6f2a7a4915bbcf1b6b [file] [edit]
// 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 <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/logging.h>
#include <base/strings/string_split.h>
#include <brillo/blkdev_utils/lvm.h>
#include <brillo/files/file_util.h>
#include <brillo/process/process.h>
#include <brillo/strings/string_utils.h>
#include <brillo/userdb_utils.h>
#include "init/crossystem.h"
#include "init/crossystem_impl.h"
#include "init/file_attrs_cleaner.h"
#include "init/startup/chromeos_startup.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/platform_impl.h"
#include "init/startup/security_manager.h"
#include "init/startup/standard_mount_helper.h"
#include "init/startup/stateful_mount.h"
#include "init/startup/test_mode_mount_helper.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";
// The "/." ensures we trigger the automount, instead of just examining the
// mount point.
// TODO(b/244186883): remove this.
constexpr char kKernelDebugTracingDir[] = "kernel/debug/tracing/.";
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";
// The name of the filesystem for accessing UEFI variables.
constexpr char kEfivarfs[] = "efivarfs";
// Where the filesystem for accessing UEFI variables is mounted.
constexpr char kSysFirmwareEfiEfivars[] = "sys/firmware/efi/efivars";
constexpr char kTpmSimulator[] = "etc/init/tpm2-simulator.conf";
constexpr char kSELinuxEnforce[] = "fs/selinux/enforce";
// 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 initialized and the device is owned.
constexpr char kInstallAttributesFile[] = "home/.shadow/install_attributes.pb";
// 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";
// kEncryptedStatefulMnt stores the path to the initial mount point for
// the encrypted stateful partition
constexpr char kEncryptedStatefulMnt[] = "encrypted";
// This file is written to when /var is too full and the logs are deleted.
constexpr char kReclaimFullVar[] = ".reclaim_full_var";
// This value is threshold for determining that /var is full.
const int kVarFullThreshold = 10485760;
constexpr char kDaemonStore[] = "daemon-store";
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";
} // namespace
namespace startup {
// Process the arguments from included USE flags.
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_stateful = USE_LVM_STATEFUL_PARTITION;
}
// 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).
// 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 < kBaseSecs) {
struct timespec stime;
stime.tv_sec = kBaseSecs;
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.
brillo::ProcessImpl proc;
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 if the TPM is owned or couldn't determine.
bool ChromeosStartup::IsTPMOwned() {
int output = 0;
base::FilePath owned = root_.Append(kTPMOwnedPath);
// Check file contents
if (!utils::ReadFileToInt(owned, &output)) {
PLOG(WARNING) << "Could not determine TPM owned, failed to read "
<< owned.value();
return true;
}
if (output == 0) {
return false;
}
return true;
}
// 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 install_attrs = stateful_.Append(kInstallAttributesFile);
struct stat statbuf;
if (!IsTPMOwned() &&
(!platform_->Stat(preservation_request, &statbuf) ||
statbuf.st_uid != getuid()) &&
(platform_->Stat(install_attrs, &statbuf) &&
statbuf.st_uid == getuid())) {
return true;
}
return false;
}
// Returns 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) {
int boot;
std::string dstr;
return (cros_system_->GetInt("devsw_boot", &boot) && boot == devsw_boot) &&
(cros_system_->GetString("mainfw_type", &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"};
platform_->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<CrosSystem> cros_system,
const Flags& flags,
const base::FilePath& root,
const base::FilePath& stateful,
const base::FilePath& lsb_file,
const base::FilePath& proc_file,
std::unique_ptr<Platform> platform,
std::unique_ptr<MountHelper> mount_helper)
: cros_system_(std::move(cros_system)),
flags_(flags),
lsb_file_(lsb_file),
proc_(proc_file),
root_(root),
stateful_(stateful),
platform_(std::move(platform)),
mount_helper_(std::move(mount_helper)) {}
void ChromeosStartup::EarlySetup() {
const base::FilePath sysfs = root_.Append(kSysfs);
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("debugfs", debug, "debugfs", kCommonMountFlags,
data)) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "Unable to mount " << debug.value();
}
}
// HACK(b/244186883): ensure we trigger the /sys/kernel/debug/tracing
// automount now (before we set 0755 below), because otherwise the kernel may
// change its permissions whenever it eventually does get automounted.
// TODO(b/244186883): remove this.
struct stat st;
const base::FilePath debug_tracing = sysfs.Append(kKernelDebugTracingDir);
// Ignore errors.
platform_->Stat(debug_tracing, &st);
// 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("tracefs", 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 (base::DirectoryExists(configfs)) {
if (!platform_->Mount("configfs", configfs, "configfs", kCommonMountFlags,
"")) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "Unable to mount " << configfs.value();
}
}
// Mount securityfs as it is used to configure inode security policies below.
const base::FilePath securityfs = sysfs.Append(kKernelSecurity);
if (!platform_->Mount("securityfs", securityfs, "securityfs",
kCommonMountFlags, "")) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "Unable to mount " << securityfs.value();
}
if (!SetupLoadPinVerityDigests(root_, platform_.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_ = !base::PathExists(disable_sec_hard);
if (!enable_stateful_security_hardening_ &&
!ConfigureProcessMgmtSecurity(root_)) {
PLOG(WARNING) << "Failed to configure process management security.";
}
}
// Apply /mnt/stateful_partition specific tmpfiles.d configurations
void ChromeosStartup::TmpfilesConfiguration(
const std::vector<std::string>& dirs) {
brillo::ProcessImpl tmpfiles;
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;
struct stat stbuf;
std::string boot_alert_msg;
std::string clobber_log_msg;
base::FilePath reset_file = stateful_.Append(kResetFile);
if ((lstat(reset_file.value().c_str(), &stbuf) == 0 &&
S_ISLNK(stbuf.st_mode)) ||
base::PathExists(reset_file)) {
boot_alert_msg = "power_wash";
// If it's not a plain file owned by us, force a powerwash.
if (stbuf.st_uid != getuid() || !S_ISREG(stbuf.st_mode)) {
clobber_args.push_back("keepimg");
} else {
std::string str;
if (!base::ReadFileToString(reset_file, &str)) {
PLOG(WARNING) << "Failed to read reset file";
} else {
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)) {
bool res = platform_->Stat(dev_mode_allowed_file_, &stbuf);
if ((res && stbuf.st_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 && stbuf.st_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.
DevUpdateStatefulPartition("clobber");
}
}
} else if (IsDevToVerifiedModeTransition(1)) {
if (!platform_->Stat(dev_mode_allowed_file_, &stbuf) ||
stbuf.st_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.
DevUpdateStatefulPartition("clobber");
if (!PathExists(dev_mode_allowed_file_)) {
if (!base::WriteFile(dev_mode_allowed_file_, "")) {
PLOG(WARNING) << "Failed to create file: "
<< dev_mode_allowed_file_.value();
}
}
}
}
}
if (!clobber_args.empty()) {
platform_->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_->MountOrFail(home, home_root, "", MS_BIND, "");
// 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 (base::PathExists(tpm_simulator)) {
brillo::ProcessImpl ictl;
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. This must happen before mounting
// stateful, which will initialize the TPM again.
void ChromeosStartup::CleanupTpm() {
base::FilePath tpm_update_req =
stateful_.Append(kTpmFirmwareUpdateRequestFlagFile);
if (base::PathExists(tpm_update_req)) {
base::FilePath tpm_cleanup = root_.Append(kTpmFirmwareUpdateCleanup);
if (base::PathExists(tpm_cleanup)) {
platform_->RunProcess(tpm_cleanup);
}
}
}
// 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 (base::DirectoryExists(whitelist)) {
if (base::IsDirectoryEmpty(whitelist)) {
// If it is empty, delete it.
if (!brillo::DeleteFile(whitelist)) {
PLOG(WARNING) << "Failed to delete path " << whitelist.value();
}
} else if (brillo::DeleteFile(devicesettings)) {
// If devicesettings didn't exist, or was empty, DeleteFile passed.
// Rename the old path.
if (!base::Move(whitelist, devicesettings)) {
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";
}
}
}
// Mount /sys/firmware/efi/efivarfs, if supported.
void ChromeosStartup::MaybeMountEfivarfs() {
// If we're sure this is a supported filesystem (failure to check is
// interpreted as "unsupported").
if (IsSupportedFilesystem(kEfivarfs, root_)) {
const base::FilePath efivars = root_.Append(kSysFirmwareEfiEfivars);
if (!platform_->Mount(kEfivarfs, efivars, kEfivarfs, kCommonMountFlags,
"")) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "Unable to mount " << efivars.value();
}
}
}
// Create daemon store folders.
// See
// https://chromium.googlesource.com/chromiumos/docs/+/HEAD/sandboxing.md#securely-mounting-daemon-store-folders.
void ChromeosStartup::CreateDaemonStore() {
base::FilePath run_ds = root_.Append(kRun).Append(kDaemonStore);
base::FilePath etc_ds = root_.Append(kEtc).Append(kDaemonStore);
base::FileEnumerator iter(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 (!base::CreateDirectory(rds)) {
PLOG(WARNING) << "mkdir failed for " << rds.value();
continue;
}
if (!base::SetPosixFilePermissions(rds, 0755)) {
PLOG(WARNING) << "chmod failed for " << rds.value();
continue;
}
platform_->Mount(rds, rds, "", MS_BIND, "");
platform_->Mount(base::FilePath("none"), 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);
base::ScopedFD dfd(open(var_empty.value().c_str(), O_DIRECTORY | O_CLOEXEC));
if (!dfd.is_valid()) {
if (errno != ENOENT) {
PLOG(WARNING) << "Unable to open directory " << var_empty.value();
}
return;
}
file_attrs_cleaner::AttributeCheckStatus status =
file_attrs_cleaner::CheckFileAttributes(var_empty, true, dfd.get());
if (status != file_attrs_cleaner::AttributeCheckStatus::CLEARED) {
PLOG(WARNING) << "Unexpected CheckFileAttributes status for "
<< var_empty.value();
}
if (!base::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);
base::FileEnumerator var_iter(
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 (base::IsLink(path)) {
base::FilePath realpath;
if (!base::NormalizeFilePath(path, &realpath) ||
!varLog.IsParent(realpath)) {
if (!brillo::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)(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 (!base::PathExists(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(var, exc_empty, true, true);
// Restoring file contexts for sysfs. tracefs is excluded from this
// invocation and delayed in a separate job to improve boot time.
std::vector<base::FilePath> exclude = {sysfs.Append(kKernelDebug),
sysfs.Append(kKernelTracing)};
restorecon_func(sysfs, exclude, 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};
base::FileEnumerator shadow_files(shadow, false,
base::FileEnumerator::FileType::FILES, "*");
for (base::FilePath path = shadow_files.Next(); !path.empty();
path = shadow_files.Next()) {
shadow_paths.push_back(path);
}
base::FileEnumerator shadow_dot(shadow, false,
base::FileEnumerator::FileType::FILES, ".*");
for (base::FilePath path = shadow_dot.Next(); !path.empty();
path = shadow_dot.Next()) {
shadow_paths.push_back(path);
}
base::FileEnumerator shadow_subdir(
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(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(h_path, exc_empty, true, true);
}
}
// Main function to run chromeos_startup.
int ChromeosStartup::Run() {
dev_mode_ = platform_->InDevMode(cros_system_.get());
// 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_ = std::make_unique<StatefulMount>(
flags_, root_, stateful_, platform_.get(),
std::make_unique<brillo::LogicalVolumeManager>());
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(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();
base::FilePath encrypted_failed = stateful_.Append(kMountEncryptedFailedFile);
struct stat stbuf;
if (!mount_helper_->DoMountVarAndHomeChronos()) {
if (!platform_->Stat(encrypted_failed, &stbuf) ||
stbuf.st_uid != getuid()) {
base::WriteFile(encrypted_failed, "");
} else {
cros_system_->SetInt("recovery_request", 1);
}
utils::Reboot();
return 0;
}
brillo::DeleteFile(encrypted_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) {
if (!utils::UnlockEncryptedRebootVault()) {
utils::CreateEncryptedRebootVault();
}
}
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));
base::FilePath reclaim_full_var = stateful_.Append(kReclaimFullVar);
base::WriteFile(reclaim_full_var, "Startup.ReclaimFullVar");
}
// 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.
brillo::ProcessImpl crash_reporter;
crash_reporter.AddArg("/sbin/crash_reporter");
crash_reporter.AddArg("--ephemeral_collect");
if (crash_reporter.Run() != 0) {
PLOG(WARNING) << "Unable to collect early logs and crashes.";
}
if (enable_stateful_security_hardening_) {
ConfigureFilesystemExceptions(root_);
}
std::vector<std::string> tmpfile_args = {root_.Append(kHome).value(),
root_.Append(kVar).value()};
TmpfilesConfiguration(tmpfile_args);
MoveToLibDeviceSettings();
MaybeMountEfivarfs();
// /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);
platform_->Mount(root_run, var.Append(kRun), "", MS_BIND, "");
mount_helper_->RememberMount(root_run);
// Bind mount /run/lock to /var/lock.
const base::FilePath root_run_lock = root_run.Append(kLock);
platform_->Mount(root_run_lock, var.Append(kLock), "", MS_BIND, "");
mount_helper_->RememberMount(root_run_lock);
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);
int ret = RunChromeosStartupScript();
if (ret) {
// TODO(b/232901639): Improve failure reporting.
PLOG(WARNING) << "chromeos_startup.sh returned with code " << ret;
mount_helper_->CleanupMounts("cleanup triggered in bash");
}
// Unmount securityfs so that further modifications to inode security
// policies are not possible
const base::FilePath kernel_sec =
root_.Append(kSysfs).Append(kKernelSecurity);
if (!platform_->Umount(kernel_sec)) {
PLOG(WARNING) << "Failed to umount: " << kernel_sec;
}
bootstat_.LogEvent("post-startup");
return ret;
}
// Temporary function during the migration of the code. Run the bash
// version of chromeos_startup, which has been copied to chromeos_startup.sh
// to allow editing without effecting existing script. As more functionality
// moves to c++, it will be removed from chromeos_startup.sh.
int ChromeosStartup::RunChromeosStartupScript() {
brillo::ProcessImpl proc;
proc.AddArg("/sbin/chromeos_startup.sh");
return proc.Run();
}
// 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;
}
int devsw;
int debug;
int rec_reason;
if (!cros_system_->GetInt("devsw_boot", &devsw) ||
!cros_system_->GetInt("debug_build", &debug) ||
!cros_system_->GetInt("recovery_reason", &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;
}
// The file indicates the system has booted in developer mode and must
// initiate a wiping process in the next (normal mode) boot.
base::FilePath vpd_block_dir = root_.Append("sys/firmware/vpd/rw");
base::FilePath vpd_block_file = vpd_block_dir.Append("block_devmode");
bool block_devmode = false;
// Checks ordered by run time.
// 1. Try reading VPD through /sys.
// 2. Try crossystem.
// 3. Re-read VPD directly from SPI flash (slow!) but only for systems
// that don't have VPD in sysplatform and only when NVRAM indicates that it
// has been cleared.
int crossys_block;
int nvram;
int val;
if (utils::ReadFileToInt(vpd_block_file, &val) && val == 1) {
block_devmode = true;
} else if (cros_system_->GetInt("block_devmode", &crossys_block) &&
crossys_block == 1) {
block_devmode = true;
} else if (!base::DirectoryExists(vpd_block_dir) &&
cros_system_->GetInt("nvram_cleared", &nvram) && nvram == 1) {
std::string output;
std::vector<std::string> args = {"-i", "RW_VPD", "-g", "block_devmode"};
if (platform_->VpdSlow(args, &output) && output == "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 (!PathExists(dev_mode_file)) {
base::WriteFile(dev_mode_file, "");
}
platform_->BootAlert("block_devmode");
}
}
// Set dev_mode_ for tests.
void ChromeosStartup::SetDevMode(bool dev_mode) {
dev_mode_ = dev_mode;
}
bool ChromeosStartup::DevIsDebugBuild() const {
if (!dev_mode_) {
return false;
}
return platform_->IsDebugBuild(cros_system_.get());
}
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_);
}
}
} // namespace startup