| // 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/stateful_mount.h" |
| |
| #include <fcntl.h> |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <time.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <optional> |
| #include <set> |
| #include <string> |
| #include <utility> |
| |
| #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_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/values.h> |
| #include <brillo/blkdev_utils/lvm.h> |
| #include <brillo/blkdev_utils/storage_utils.h> |
| #include <brillo/files/file_util.h> |
| #include <brillo/key_value_store.h> |
| #include <brillo/process/process.h> |
| #include <brillo/secure_blob.h> |
| #include <libhwsec-foundation/crypto/hkdf.h> |
| #include <libstorage/platform/platform.h> |
| #include <libstorage/storage_container/filesystem_key.h> |
| #include <libstorage/storage_container/storage_container.h> |
| #include <metrics/bootstat.h> |
| #include <rootdev/rootdev.h> |
| |
| #include "base/strings/stringprintf.h" |
| #include "init/libpreservation/ext2fs.h" |
| #include "init/libpreservation/file_preseeder.h" |
| #include "init/libpreservation/filesystem_manager.h" |
| #include "init/libpreservation/preservation.h" |
| #include "init/startup/constants.h" |
| #include "init/startup/flags.h" |
| #include "init/startup/mount_helper.h" |
| #include "init/startup/security_manager.h" |
| #include "init/startup/startup_dep_impl.h" |
| #include "init/utils.h" |
| |
| namespace { |
| |
| constexpr char kExt4Features[] = "sys/fs/ext4/features"; |
| constexpr char kReservedBlocksGID[] = "20119"; |
| constexpr char kQuotaOpt[] = "quota"; |
| constexpr char kDumpe2fsStatefulLog[] = |
| "run/chromeos_startup/dumpe2fs_stateful.log"; |
| constexpr char kDirtyExpireCentisecs[] = "proc/sys/vm/dirty_expire_centisecs"; |
| |
| constexpr char kUpdateAvailable[] = ".update_available"; |
| constexpr char kLabMachine[] = ".labmachine"; |
| constexpr char kDevModeFile[] = ".developer_mode"; |
| constexpr char kRmaData[] = "rma-data"; |
| |
| constexpr char kVar[] = "var"; |
| constexpr char kVarNew[] = "var_new"; |
| constexpr char kVarOverlay[] = "var_overlay"; |
| constexpr char kChronos[] = "chronos"; |
| constexpr char kUnencrypted[] = "unencrypted"; |
| |
| constexpr char kClobberLogFile[] = "clobber.log"; |
| constexpr char kClobberStateLogFile[] = "clobber-state.log"; |
| constexpr char kDevImageBlockFile[] = "dev_image.block"; |
| constexpr char kNewDevImageBlockFile[] = "dev_image_new.block"; |
| constexpr char kDeveloperToolsMount[] = "developer_tools"; |
| constexpr char kEncrypted[] = "defaultkey_encrypted"; |
| constexpr char kPreseedingStatus[] = |
| "mnt/chromeos_metadata_partition/preseeder.proto"; |
| |
| constexpr char kVarLogAsan[] = "var/log/asan"; |
| constexpr char kStatefulDevImage[] = "dev_image"; |
| constexpr char kStatefulDevImageNew[] = "dev_image_new"; |
| constexpr char kUsrLocal[] = "usr/local"; |
| constexpr char kTmpPortage[] = "var/tmp/portage"; |
| constexpr char kProcMounts[] = "proc/mounts"; |
| constexpr char kMountOptionsLog[] = "var/log/mount_options.log"; |
| constexpr char kPreserve[] = "preserve"; |
| const std::vector<const char*> kMountDirs = {"db/pkg", "lib/portage", |
| "cache/dlc-images"}; |
| |
| constexpr float kSizePercent = 0.9; |
| |
| uint64_t GetDirtyExpireCentisecs(libstorage::Platform* platform, |
| const base::FilePath& root) { |
| std::string dirty_expire; |
| uint64_t dirty_expire_centisecs = 0; |
| base::FilePath centisecs_path = root.Append(kDirtyExpireCentisecs); |
| if (!platform->ReadFileToString(centisecs_path, &dirty_expire)) { |
| PLOG(WARNING) << "Failed to read " << centisecs_path.value(); |
| return 0; |
| } |
| |
| base::TrimWhitespaceASCII(dirty_expire, base::TRIM_ALL, &dirty_expire); |
| if (!base::StringToUint64(dirty_expire, &dirty_expire_centisecs)) { |
| PLOG(WARNING) << "Failed to parse contents of " << centisecs_path.value(); |
| } |
| return dirty_expire_centisecs; |
| } |
| } // namespace |
| |
| namespace startup { |
| |
| using ::hwsec_foundation::Hkdf; |
| using ::hwsec_foundation::HkdfHash; |
| |
| StatefulMount::StatefulMount(const base::FilePath& root, |
| const base::FilePath& stateful, |
| libstorage::Platform* platform, |
| StartupDep* startup_dep) |
| : root_(root), |
| stateful_(stateful), |
| platform_(platform), |
| startup_dep_(startup_dep) {} |
| |
| void StatefulMount::AppendQuotaFeaturesAndOptions( |
| const Flags* flags, |
| std::vector<std::string>* sb_options, |
| std::vector<std::string>* sb_features) { |
| // Enable/disable quota feature. |
| // Add Android's AID_RESERVED_DISK to resgid. |
| sb_features->push_back("-g"); |
| sb_features->push_back(kReservedBlocksGID); |
| |
| // Quota is enabled in the kernel, make sure that quota is enabled in |
| // the filesystem |
| sb_options->push_back(kQuotaOpt); |
| sb_features->push_back("-Qusrquota,grpquota"); |
| if (flags->prjquota) { |
| sb_features->push_back("-Qprjquota"); |
| } else { |
| sb_features->push_back("-Q^prjquota"); |
| } |
| } |
| |
| std::vector<std::string> StatefulMount::GenerateExt4Features( |
| const Flags* flags) { |
| std::vector<std::string> sb_features; |
| std::vector<std::string> sb_options; |
| |
| base::FilePath encryption = root_.Append(kExt4Features).Append("encryption"); |
| if (flags->direncryption && platform_->FileExists(encryption)) { |
| sb_options.push_back("encrypt"); |
| } |
| |
| base::FilePath verity_file = root_.Append(kExt4Features).Append("verity"); |
| if (flags->fsverity && platform_->FileExists(verity_file)) { |
| sb_options.push_back("verity"); |
| } |
| |
| AppendQuotaFeaturesAndOptions(flags, &sb_options, &sb_features); |
| |
| if (!sb_features.empty() || !sb_options.empty()) { |
| if (!sb_options.empty()) { |
| std::string opts = base::JoinString(sb_options, ","); |
| sb_features.push_back("-O"); |
| sb_features.push_back(opts); |
| } |
| } |
| |
| return sb_features; |
| } |
| |
| // Only called in MountStateful(), trigger a clobber. |
| void StatefulMount::ClobberStateful( |
| const base::FilePath& stateful_device, |
| const std::vector<std::string>& clobber_args, |
| const std::string& clobber_message, |
| MountHelper* mount_helper) { |
| std::vector<base::FilePath> mounts; |
| mount_helper->CleanupMountsStack(&mounts); |
| |
| startup_dep_->BootAlert("self_repair"); |
| startup_dep_->ClobberLogRepair(stateful_device, clobber_message); |
| startup_dep_->AddClobberCrashReport( |
| {"--mount_failure", "--mount_device=stateful"}); |
| startup_dep_->Clobber(clobber_args); |
| } |
| |
| bool StatefulMount::AttemptStatefulMigration( |
| const base::FilePath& stateful_device) { |
| std::unique_ptr<brillo::Process> thinpool_migrator = |
| platform_->CreateProcessInstance(); |
| thinpool_migrator->AddArg("/usr/sbin/thinpool_migrator"); |
| thinpool_migrator->AddArg( |
| base::StringPrintf("--device=%s", stateful_device.value().c_str())); |
| |
| if (thinpool_migrator->Run() != 0) { |
| LOG(ERROR) << "Failed to run thinpool migrator"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void StatefulMount::MountStateful( |
| const base::FilePath& root_dev, |
| const Flags* flags, |
| MountHelper* mount_helper, |
| const base::Value& image_vars, |
| std::optional<encryption::EncryptionKey> key) { |
| const auto& image_vars_dict = image_vars.GetDict(); |
| bool status; |
| int32_t stateful_mount_flags; |
| std::string stateful_mount_opts; |
| libstorage::StorageContainerConfig config; |
| |
| // Find our stateful partition mount point. |
| stateful_mount_flags = kCommonMountFlags | MS_NOATIME; |
| const int part_num_state = utils::GetPartitionNumFromImageVars( |
| image_vars_dict, "PARTITION_NUM_STATE"); |
| const std::string* fs_form_state = |
| image_vars_dict.FindString("FS_FORMAT_STATE"); |
| base::FilePath backing_device = |
| brillo::AppendPartition(root_dev, part_num_state); |
| if (fs_form_state != nullptr && fs_form_state->compare("ext4") == 0) { |
| int dirty_expire_centisecs = GetDirtyExpireCentisecs(platform_, root_); |
| int commit_interval = dirty_expire_centisecs / 100; |
| if (commit_interval != 0) { |
| stateful_mount_opts = "commit=" + std::to_string(commit_interval); |
| stateful_mount_opts.append(",discard"); |
| } else { |
| LOG(INFO) << "Using default value for commit interval"; |
| stateful_mount_opts = "discard"; |
| } |
| } |
| |
| bool should_mount_lvm = false; |
| std::optional<brillo::Thinpool> thinpool; |
| if (USE_LVM_STATEFUL_PARTITION && flags->lvm_stateful) { |
| brillo::LogicalVolumeManager* lvm = platform_->GetLogicalVolumeManager(); |
| |
| // Attempt to get a valid volume group name. |
| bootstat_.LogEvent("pre-lvm-activation"); |
| std::optional<brillo::PhysicalVolume> pv = |
| lvm->GetPhysicalVolume(backing_device); |
| |
| if (!pv && flags->lvm_migration) { |
| // Attempt to migrate to thinpool if migration is enabled: if the |
| // migration fails, then we expect the stateful partition to be back to |
| // its original state. |
| |
| if (!AttemptStatefulMigration(backing_device)) { |
| LOG(ERROR) << "Failed to migrate stateful partition to a thinpool"; |
| } else { |
| // Reset the physical volume on success from thinpool migration. |
| pv = lvm->GetPhysicalVolume(backing_device); |
| } |
| } |
| |
| if (pv && pv->IsValid()) { |
| volume_group_ = lvm->GetVolumeGroup(*pv); |
| if (volume_group_ && volume_group_->IsValid()) { |
| // First attempt to activate the thinpool. If the activation of the |
| // thinpool fails, run thin_check to check all mappings. |
| thinpool = lvm->GetThinpool(*volume_group_, "thinpool"); |
| |
| if (!thinpool) { |
| LOG(ERROR) << "Thinpool does not exist"; |
| ClobberStateful(backing_device, {"fast", "keepimg"}, |
| "Invalid thinpool", mount_helper); |
| // Not reached, except during unit tests. |
| return; |
| } |
| |
| if (!thinpool->Activate()) { |
| LOG(WARNING) << "Failed to activate thinpool, attempting repair"; |
| if (!thinpool->Activate(/*check=*/true)) { |
| LOG(ERROR) << "Failed to repair and activate thinpool"; |
| std::vector<base::FilePath> mounts; |
| mount_helper->CleanupMountsStack(&mounts); |
| ClobberStateful(backing_device, {"fast", "keepimg"}, |
| "Corrupt thinpool", mount_helper); |
| // Not reached, except during unit tests. |
| return; |
| } |
| } |
| should_mount_lvm = true; |
| } |
| } |
| bootstat_.LogEvent("lvm-activation-complete"); |
| } |
| |
| libstorage::StorageContainerType backend_type; |
| libstorage::FileSystemKeyReference key_reference; |
| libstorage::FileSystemKey encryption_key; |
| if (key) { |
| config.dmsetup_config = { |
| .backing_device_config = {.type = |
| libstorage::BackingDeviceType::kPartition, |
| .name = backing_device.value()}, |
| .dmsetup_device_name = kEncrypted, |
| .dmsetup_cipher = std::string("aes-xts-plain64")}; |
| backend_type = libstorage::StorageContainerType::kDmDefaultKey; |
| // Not really needed, default_key does not use the keyring. |
| key_reference.fek_sig = brillo::SecureBlob(kEncrypted); |
| Hkdf(HkdfHash::kSha512, key->encryption_key(), |
| /*info=*/brillo::BlobFromString(kEncrypted), |
| /*salt=*/brillo::Blob(), |
| /*result_len=*/0, &encryption_key.fek); |
| stateful_mount_opts.append(",inlinecrypt"); |
| } else { |
| backend_type = libstorage::StorageContainerType::kUnencrypted; |
| key_reference = libstorage::FileSystemKeyReference(); |
| encryption_key = libstorage::FileSystemKey(); |
| if (should_mount_lvm) { |
| config.unencrypted_config = { |
| .backing_device_config = { |
| .type = |
| libstorage::BackingDeviceType::kLogicalVolumeBackingDevice, |
| .name = kUnencrypted, |
| .logical_volume = { |
| .vg = std::make_shared<brillo::VolumeGroup>(*volume_group_), |
| .thinpool = std::make_shared<brillo::Thinpool>(*thinpool)}}}; |
| } else { |
| config.unencrypted_config = { |
| .backing_device_config = { |
| .type = libstorage::BackingDeviceType::kPartition, |
| .name = backing_device.value()}}; |
| } |
| } |
| config.filesystem_config = {.tune2fs_opts = GenerateExt4Features(flags), |
| .backend_type = backend_type, |
| .recovery = libstorage::RecoveryType::kDoNothing, |
| .metrics_prefix = "Platform.FileSystem.Stateful"}; |
| |
| if (key && key->is_fresh()) { |
| // Need to reformat the container first. But since the partition already |
| // exists, the Ext4 storage container will try to use the current |
| // filesystem, since the dmsetup storage container also base its existence |
| // logic to the presence of the backup device. Force a purge on fsck failure |
| // which will happen. |
| config.filesystem_config.recovery = libstorage::RecoveryType::kPurge; |
| // Do not discard to preserve the pass-through files. |
| config.filesystem_config.mkfs_opts = { |
| "-E", |
| "nodiscard", |
| "-O", |
| "stable_inodes,encrypt", |
| }; |
| } |
| |
| std::unique_ptr<libstorage::StorageContainer> container = |
| mount_helper->GetStorageContainerFactory()->Generate( |
| config, libstorage::StorageContainerType::kExt4, key_reference); |
| |
| if (!container) { |
| LOG(ERROR) << "Failed to create stateful container"; |
| ClobberStateful(backing_device, {"fast", "keepimg", "preserve_lvs"}, |
| "Self-repair corrupted stateful partition", mount_helper); |
| // Not reached, except during unit tests. |
| return; |
| } |
| |
| base::FilePath metadata_path = root_.Append(kPreseedingStatus); |
| libpreservation::FilePreseeder preseeder({base::FilePath("unencrypted")}, |
| root_, stateful_, metadata_path); |
| |
| if (key && key->is_fresh()) { |
| // Mount the partition to get extent data. |
| if (platform_->Mount(backing_device, stateful_, *fs_form_state, |
| stateful_mount_flags | MS_RDONLY, |
| stateful_mount_opts)) { |
| // Generate file preservation list. |
| std::set<base::FilePath> file_allowlist; |
| for (auto path : libpreservation::GetPreservationFileList()) { |
| file_allowlist.insert(base::FilePath(path)); |
| } |
| for (auto path : |
| libpreservation::GetFactoryPreservationPathList(stateful_)) { |
| file_allowlist.insert(base::FilePath(path)); |
| } |
| for (auto path : libpreservation::GetStartupPreseedingPaths()) { |
| file_allowlist.insert(base::FilePath(path)); |
| } |
| |
| if (!preseeder.SaveFileState(file_allowlist)) { |
| LOG(ERROR) << "Failed to save file state"; |
| } |
| |
| platform_->Unmount(stateful_, false, nullptr); |
| } |
| } |
| |
| if (!container->Setup(encryption_key)) { |
| LOG(ERROR) << "Failed to setup stateful"; |
| ClobberStateful(backing_device, {"fast", "keepimg", "preserve_lvs"}, |
| "Self-repair corrupted stateful partition", mount_helper); |
| // Not reached, except during unit tests. |
| return; |
| } |
| |
| state_dev_ = container->GetPath(); |
| |
| if (base::PathExists(metadata_path)) { |
| if (preseeder.LoadMetadata()) { |
| auto ext2fs = libpreservation::Ext2fsImpl::Generate(state_dev_); |
| if (ext2fs) { |
| libpreservation::FilesystemManager fs_manager(std::move(ext2fs)); |
| |
| if (!preseeder.RestoreExtentFiles(&fs_manager)) { |
| LOG(ERROR) << "Failed to restore extent files"; |
| } |
| } |
| } else { |
| LOG(ERROR) << "Failed to load preseeding metadata"; |
| } |
| } |
| |
| // Mount stateful partition from state_dev. |
| if (!platform_->Mount(state_dev_, stateful_, *fs_form_state, |
| stateful_mount_flags, stateful_mount_opts)) { |
| // Try to rebuild the stateful partition by clobber-state. (Not using fast |
| // mode out of security consideration: the device might have gotten into |
| // this state through power loss during dev mode transition). |
| platform_->ReportFilesystemDetails(state_dev_, |
| root_.Append(kDumpe2fsStatefulLog)); |
| ClobberStateful(state_dev_, {"keepimg", "preserve_lvs"}, |
| "Self-repair corrupted stateful partition", mount_helper); |
| // Not reached, except during unit tests. |
| return; |
| } |
| |
| // For shutdown and clobber-state flows, let the device mapper target get |
| // cleaned up automatically. |
| if (container->IsLazyTeardownSupported() && |
| !container->SetLazyTeardownWhenUnused()) { |
| LOG(ERROR) << "Failed to set lazy teardown"; |
| } |
| |
| // Restore inline files. |
| if (base::PathExists(metadata_path)) { |
| if (preseeder.LoadMetadata() && !preseeder.RestoreInlineFiles()) { |
| LOG(ERROR) << "Failed to restore inline files"; |
| } |
| |
| std::set<base::FilePath> root_flag_allowlist; |
| for (auto& file : libpreservation::GetRootFlagFileAllowlist()) { |
| root_flag_allowlist.insert(base::FilePath(file)); |
| } |
| |
| if (!preseeder.RestoreRootFlagFiles(root_flag_allowlist)) { |
| LOG(ERROR) << "Failed to restore root flag files"; |
| } |
| |
| platform_->DeleteFile(metadata_path); |
| } |
| |
| // Mount the OEM partition. |
| // mount_or_fail isn't used since this partition only has a filesystem |
| // on some boards. |
| int32_t oem_flags = MS_RDONLY | kCommonMountFlags; |
| const int part_num_oem = |
| utils::GetPartitionNumFromImageVars(image_vars_dict, "PARTITION_NUM_OEM"); |
| const std::string* fs_form_oem = image_vars_dict.FindString("FS_FORMAT_OEM"); |
| const base::FilePath oem_dev = |
| brillo::AppendPartition(root_dev, part_num_oem); |
| status = platform_->Mount(oem_dev, base::FilePath("/usr/share/oem"), |
| *fs_form_oem, oem_flags, ""); |
| if (!status) { |
| PLOG(WARNING) << "mount of /usr/share/oem failed with code " << status; |
| } |
| } |
| |
| base::FilePath StatefulMount::GetStateDev() { |
| return state_dev_; |
| } |
| |
| // Remove empty directories that should not be preserved. |
| void StatefulMount::RemoveEmptyDirectory( |
| std::vector<base::FilePath> preserved_paths, base::FilePath directory) { |
| std::unique_ptr<libstorage::FileEnumerator> enumerator( |
| platform_->GetFileEnumerator(directory, false /* recursive */, |
| base::FileEnumerator::DIRECTORIES)); |
| for (auto path = enumerator->Next(); !path.empty(); |
| path = enumerator->Next()) { |
| if (platform_->IsLink(path)) { |
| continue; |
| } |
| bool preserve = false; |
| for (auto& preserved_path : preserved_paths) { |
| if (path == preserved_path || preserved_path.IsParent(path)) { |
| preserve = true; |
| break; |
| } |
| } |
| if (!preserve) { |
| RemoveEmptyDirectory(preserved_paths, path); |
| // Do not remove mounts for /var and /home/chronos, |
| // they have been already created during the mount of stateful. |
| std::vector<const char*> mounts = {kVar, kChronos}; |
| if (platform_->IsDirectoryEmpty(path) && |
| (std::find(mounts.begin(), mounts.end(), path.BaseName().value()) == |
| mounts.end())) { |
| platform_->DeleteFile(path); |
| } |
| } |
| } |
| } |
| |
| void StatefulMount::DevPerformStatefulUpdate() { |
| // Perform update. |
| std::vector<std::pair<base::FilePath, base::FilePath>> update_targets = { |
| {stateful_.Append(kVarNew), stateful_.Append(kVarOverlay)}, |
| {stateful_.Append(kStatefulDevImageNew), |
| stateful_.Append(kStatefulDevImage)}, |
| {stateful_.Append(kUnencrypted).Append(kNewDevImageBlockFile), |
| stateful_.Append(kUnencrypted).Append(kDevImageBlockFile)}}; |
| |
| for (auto& [src, dst] : update_targets) { |
| // Cleanup old target directories. |
| if (!platform_->DeletePathRecursively(dst)) { |
| PLOG(WARNING) << "Failed to delete " << dst; |
| } |
| |
| if (!platform_->Rename(src, dst, true)) { |
| LOG(WARNING) << "Failed to rename " << src; |
| continue; |
| } |
| |
| if (!platform_->SetPermissions(dst, 0755)) { |
| PLOG(WARNING) << "chmod failed for " << dst.value(); |
| } |
| } |
| } |
| |
| // Updates stateful partition if pending |
| // update is available. |
| // Returns true if there is no need to update or successful update. |
| bool StatefulMount::DevUpdateStatefulPartition( |
| const std::string& args, bool enable_stateful_security_hardening) { |
| base::FilePath stateful_update_file = stateful_.Append(kUpdateAvailable); |
| std::string stateful_update_args = args; |
| if (stateful_update_args.empty()) { |
| if (!platform_->ReadFileToString(stateful_update_file, |
| &stateful_update_args)) { |
| PLOG(WARNING) << "Failed to read from " << stateful_update_file.value(); |
| return true; |
| } |
| // The file often ends with a new line. |
| base::TrimString(stateful_update_args, "\n", &stateful_update_args); |
| } |
| |
| // To remain compatible with the prior update_stateful tarballs, expect |
| // the "var_new" unpack location, but move it into the new "var_overlay" |
| // target location. |
| base::FilePath var_new = stateful_.Append(kVarNew); |
| base::FilePath developer_new = stateful_.Append(kStatefulDevImageNew); |
| base::FilePath stateful_dev_image = stateful_.Append(kStatefulDevImage); |
| base::FilePath var_target = stateful_.Append(kVarOverlay); |
| base::FilePath dev_image_block_new = |
| stateful_.Append(kUnencrypted).Append(kNewDevImageBlockFile); |
| |
| // Only replace the developer and var_overlay directories if new replacements |
| // are available. |
| if ((platform_->DirectoryExists(developer_new) && |
| platform_->DirectoryExists(var_new)) || |
| platform_->FileExists(dev_image_block_new)) { |
| std::string update = "Updating from " + developer_new.value() + " && " + |
| var_new.value() + "."; |
| startup_dep_->ClobberLog(update); |
| DevPerformStatefulUpdate(); |
| } else { |
| std::string update = "Stateful update did not find " + |
| developer_new.value() + " & " + var_new.value() + |
| ".'\n'Keeping old development tools."; |
| startup_dep_->ClobberLog(update); |
| } |
| |
| // Check for clobber. |
| if (stateful_update_args.compare("clobber") == 0) { |
| // Because we want to preserve the testing tools under the /usr/local for |
| // test image, we only delete the cryptohome related LVs here. |
| // volume_group_ may be null if it was not found in MountStateful. |
| if (USE_LVM_STATEFUL_PARTITION && volume_group_ && |
| volume_group_->IsValid()) { |
| volume_group_->Activate(); |
| brillo::LogicalVolumeManager* lvm = platform_->GetLogicalVolumeManager(); |
| std::vector<brillo::LogicalVolume> lvs = |
| lvm->ListLogicalVolumes(volume_group_.value(), "cryptohome*"); |
| for (auto& lv : lvs) { |
| if (!lv.Remove()) { |
| LOG(WARNING) << "Failed to remove logical volume: " << lv.GetName(); |
| } |
| } |
| } |
| |
| base::FilePath preserve_dir = |
| stateful_.Append(kUnencrypted).Append(kPreserve); |
| base::FilePath dlc_factory_dir = |
| stateful_.Append("unencrypted/dlc-factory-images"); |
| |
| // Find everything in stateful and delete it, except for protected paths, |
| // and non-empty directories. The non-empty directories contain protected |
| // content or they would already be empty from depth first traversal. |
| std::vector<base::FilePath> preserved_paths = { |
| stateful_.Append(kLabMachine), |
| stateful_.Append(kDevModeFile), |
| stateful_.Append("encrypted.block"), |
| stateful_.Append("encrypted.key"), |
| stateful_.Append(kUnencrypted).Append(kClobberLogFile), |
| stateful_.Append(kUnencrypted).Append(kClobberStateLogFile), |
| stateful_.Append(kUnencrypted).Append(kDevImageBlockFile), |
| stateful_.Append(kUnencrypted).Append(kRmaData), |
| stateful_.Append(kDeveloperToolsMount), |
| stateful_dev_image, |
| var_target, |
| preserve_dir, |
| dlc_factory_dir}; |
| if (enable_stateful_security_hardening) { |
| // Allow traversal of preserve_dir, it contains link for /var/log |
| // Allow traversal of /var and dev_image: they may have been just created, |
| // and are usually allowed later. |
| for (auto& preserved_path : preserved_paths) { |
| if (platform_->DirectoryExists(preserved_path)) { |
| AllowSymlink(platform_, root_, preserved_path.value()); |
| } |
| } |
| } |
| |
| std::unique_ptr<libstorage::FileEnumerator> enumerator( |
| platform_->GetFileEnumerator(stateful_, true, |
| base::FileEnumerator::FILES)); |
| for (auto path = enumerator->Next(); !path.empty(); |
| path = enumerator->Next()) { |
| bool preserve = false; |
| for (auto& preserved_path : preserved_paths) { |
| if (path == preserved_path || preserved_path.IsParent(path)) { |
| preserve = true; |
| break; |
| } |
| } |
| if (!preserve) { |
| platform_->DeleteFile(path); |
| } |
| } |
| |
| // Remove the empty directories |
| RemoveEmptyDirectory(preserved_paths, stateful_); |
| |
| // Let's really be done before coming back. |
| sync(); |
| |
| if (enable_stateful_security_hardening) { |
| // Reapply base symlink exemption if needed. |
| SymlinkExceptions(platform_, root_); |
| } |
| } |
| |
| platform_->DeleteFile(stateful_update_file); |
| return true; |
| } |
| |
| // Gather logs. |
| void StatefulMount::DevGatherLogs(const base::FilePath& base_dir) { |
| // For dev/test images, if .gatherme presents, copy files listed in .gatherme |
| // to /mnt/stateful_partition/unencrypted/prior_logs. |
| base::FilePath lab_preserve_logs = stateful_.Append(".gatherme"); |
| base::FilePath prior_log_dir = |
| stateful_.Append(kUnencrypted).Append("prior_logs"); |
| std::string log_path; |
| |
| if (!platform_->FileExists(lab_preserve_logs)) { |
| return; |
| } |
| |
| std::string files; |
| platform_->ReadFileToString(lab_preserve_logs, &files); |
| std::vector<std::string> split_files = base::SplitString( |
| files, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| for (std::string log_path : split_files) { |
| if (log_path.find("#") != std::string::npos) { |
| continue; |
| } |
| base::FilePath log(log_path); |
| if (platform_->DirectoryExists(log)) { |
| if (!platform_->Copy(log, prior_log_dir)) { |
| PLOG(WARNING) << "Failed to copy directory " << log_path; |
| } |
| } else { |
| if (!platform_->Copy(log, prior_log_dir.Append(log.BaseName()))) { |
| PLOG(WARNING) << "Failed to copy file " << log_path; |
| } |
| } |
| } |
| |
| if (!platform_->DeleteFile(lab_preserve_logs)) { |
| PLOG(WARNING) << "Failed to delete file: " << lab_preserve_logs; |
| } |
| } |
| |
| void StatefulMount::SetUpDirectory(const base::FilePath& path) { |
| if (!platform_->DirectoryExists(path)) { |
| if (!platform_->CreateDirectory(path)) { |
| PLOG(ERROR) << "Failed to create " << path.value(); |
| return; |
| } |
| if (!platform_->SetPermissions(path, 0755)) { |
| PLOG(ERROR) << "Failed to set permissions for " << path.value(); |
| } |
| } |
| } |
| |
| void StatefulMount::DevMountDevImage(MountHelper* mount_helper) { |
| base::FilePath dev_image_block( |
| stateful_.Append(kUnencrypted).Append(kDevImageBlockFile)); |
| |
| if (!base::PathExists(dev_image_block)) { |
| return; |
| } |
| |
| auto file_size = base::GetFileSize(dev_image_block); |
| if (!file_size) { |
| LOG(ERROR) << "Failed to get dev_image.block size"; |
| return; |
| } |
| |
| // Check if we need to expand the dev_image.block file. |
| struct statvfs stateful_statbuf; |
| if (!platform_->StatVFS(stateful_, &stateful_statbuf)) { |
| PLOG(ERROR) << "stat() failed on: " << stateful_; |
| return; |
| } |
| |
| int64_t expected_file_size = static_cast<int64_t>(stateful_statbuf.f_blocks); |
| expected_file_size *= kSizePercent; |
| expected_file_size *= stateful_statbuf.f_frsize; |
| |
| if (expected_file_size > file_size) { |
| base::File file; |
| platform_->InitializeFile(&file, dev_image_block, |
| base::File::FLAG_OPEN | base::File::FLAG_WRITE); |
| |
| if (!file.IsValid()) { |
| LOG(ERROR) << "Unable to open backing device"; |
| return; |
| } |
| |
| LOG(INFO) << "Expanding underlying sparse file to " << expected_file_size; |
| file.SetLength(expected_file_size); |
| } |
| |
| libstorage::StorageContainerConfig container_config = { |
| .filesystem_config = |
| {.tune2fs_opts = {}, |
| .backend_type = libstorage::StorageContainerType::kUnencrypted, |
| .recovery = libstorage::RecoveryType::kEnforceCleaning, |
| .metrics_prefix = "Platform.FileSystem.DeveloperTools"}, |
| |
| .unencrypted_config = |
| {.backing_device_config = |
| {.type = libstorage::BackingDeviceType::kLoopbackDevice, |
| .name = "developer_tools", |
| .size = *file_size, |
| .loopback = {.backing_file_path = dev_image_block}}}, |
| }; |
| |
| std::unique_ptr<libstorage::StorageContainer> container = |
| mount_helper->GetStorageContainerFactory()->Generate( |
| container_config, libstorage::StorageContainerType::kExt4, |
| libstorage::FileSystemKeyReference()); |
| if (!container) { |
| LOG(ERROR) << "Failed to create ext4 container for developer tools"; |
| return; |
| } |
| |
| if (!container->Setup(libstorage::FileSystemKey())) { |
| LOG(ERROR) << "Failed to set up developer tools container."; |
| return; |
| } |
| |
| if (expected_file_size > file_size && !container->Resize(0)) { |
| LOG(ERROR) << "Failed to resize the developer tools container"; |
| } |
| |
| base::FilePath developer_tools_mount = stateful_.Append(kDeveloperToolsMount); |
| // Create developer_tools directory in base images in developer mode. |
| SetUpDirectory(developer_tools_mount); |
| if (!platform_->Mount(container->GetPath(), developer_tools_mount, "ext4", |
| kCommonMountFlags, "commit=600,discard")) { |
| LOG(WARNING) << "Failed to mount developer tools filesystem"; |
| return; |
| } |
| |
| // Set up lazy teardown for the dev-image loopback device. |
| // It will be automatically cleaned up during shutdown. |
| if (container->IsLazyTeardownSupported() && |
| !container->SetLazyTeardownWhenUnused()) { |
| LOG(ERROR) << "Failed to set lazy teardown for dev-image filesystem"; |
| } |
| |
| SetUpDirectory(developer_tools_mount.Append(kStatefulDevImage)); |
| SetUpDirectory(developer_tools_mount.Append(kVarOverlay)); |
| |
| mount_helper->BindMountOrFail(developer_tools_mount.Append(kStatefulDevImage), |
| stateful_.Append(kStatefulDevImage)); |
| mount_helper->BindMountOrFail(developer_tools_mount.Append(kVarOverlay), |
| stateful_.Append(kVarOverlay)); |
| } |
| |
| void StatefulMount::DevMountPackages(MountHelper* mount_helper, |
| bool enable_stateful_security_hardening) { |
| // Set up the logging dir that ASAN compiled programs will write to. We want |
| // any privileged account to be able to write here so unittests need not worry |
| // about setting things up ahead of time. See crbug.com/453579 for details. |
| base::FilePath asan_dir = root_.Append(kVarLogAsan); |
| if (!platform_->CreateDirectory(asan_dir)) { |
| PLOG(WARNING) << "Unable to create /var/log/asan directory, error code."; |
| } |
| if (!platform_->SetPermissions(asan_dir, 01777)) { |
| PLOG(WARNING) << "Set permissions failed for /var/log/asan"; |
| } |
| |
| // Capture a snapshot of "normal" mount state here, for auditability, |
| // before we start applying devmode-specific changes. |
| std::string mount_contents; |
| base::FilePath proc_mounts = root_.Append(kProcMounts); |
| if (!platform_->ReadFileToString(proc_mounts, &mount_contents)) { |
| PLOG(ERROR) << "Reading from " << proc_mounts.value() << " failed."; |
| } |
| |
| base::FilePath mount_options = root_.Append(kMountOptionsLog); |
| if (!platform_->WriteStringToFile(mount_options, mount_contents)) { |
| PLOG(ERROR) << "Writing " << proc_mounts.value() |
| << "to mount_options.log failed."; |
| } |
| |
| // Create dev_image directory in base images in developer mode. |
| base::FilePath stateful_dev_image = stateful_.Append(kStatefulDevImage); |
| SetUpDirectory(stateful_dev_image); |
| |
| // Checks and updates stateful partition. |
| DevUpdateStatefulPartition("", enable_stateful_security_hardening); |
| |
| // Checks for dev_image.block and mounts it in place. |
| DevMountDevImage(mount_helper); |
| |
| // Mount and then remount to enable exec/suid. |
| base::FilePath usrlocal = root_.Append(kUsrLocal); |
| mount_helper->BindMountOrFail(stateful_dev_image, usrlocal); |
| if (!platform_->Mount(base::FilePath(), usrlocal, "", MS_REMOUNT, "")) { |
| PLOG(WARNING) << "Failed to remount " << usrlocal.value(); |
| } |
| |
| if (enable_stateful_security_hardening) { |
| // Add exceptions to allow symlink traversal and opening of FIFOs in the |
| // dev_image subtree. |
| for (const auto& path : {root_.Append(kTmpPortage), stateful_dev_image}) { |
| if (!platform_->DirectoryExists(path)) { |
| if (!platform_->CreateDirectory(path)) { |
| PLOG(ERROR) << "Failed to create " << path.value(); |
| } |
| if (!platform_->SetPermissions(path, 0755)) { |
| PLOG(ERROR) << "Failed to set permissions for " << path.value(); |
| } |
| } |
| AllowSymlink(platform_, root_, path.value()); |
| AllowFifo(platform_, root_, path.value()); |
| } |
| } |
| |
| // Set up /var elements needed for deploying packages. |
| base::FilePath base = stateful_.Append("var_overlay"); |
| if (platform_->DirectoryExists(base)) { |
| for (const auto dir : kMountDirs) { |
| base::FilePath full = base.Append(dir); |
| if (!platform_->DirectoryExists(full)) { |
| continue; |
| } |
| base::FilePath dest = root_.Append(kVar).Append(dir); |
| if (!platform_->DirectoryExists(dest)) { |
| if (!platform_->CreateDirectory(dest)) { |
| LOG(WARNING) << "Path does not exists, can not create: " |
| << dest.value(); |
| continue; |
| } |
| if (!platform_->SetPermissions(dest, 0755)) { |
| LOG(WARNING) << "Path does not exists, can not set permissions: " |
| << dest.value(); |
| continue; |
| } |
| } |
| mount_helper->BindMountOrFail(full, dest); |
| } |
| } |
| } |
| |
| } // namespace startup |