| // 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 <string> |
| #include <utility> |
| |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/json/json_reader.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/process/process.h> |
| #include <brillo/key_value_store.h> |
| #include <brillo/secure_blob.h> |
| #include <libstorage/platform/platform.h> |
| #include <metrics/bootstat.h> |
| #include <rootdev/rootdev.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 kQuotaProjectOpt[] = "project"; |
| 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 kVar[] = "var"; |
| constexpr char kNew[] = "_new"; |
| constexpr char kOverlay[] = "_overlay"; |
| |
| constexpr char kVarLogAsan[] = "var/log/asan"; |
| constexpr char kStatefulDevImage[] = "dev_image"; |
| constexpr char kUsrLocal[] = "usr/local"; |
| constexpr char kDisableStatefulSecurityHardening[] = |
| "usr/share/cros/startup/disable_stateful_security_hardening"; |
| constexpr char kTmpPortage[] = "var/tmp/portage"; |
| constexpr char kProcMounts[] = "proc/mounts"; |
| constexpr char kMountOptionsLog[] = "var/log/mount_options.log"; |
| constexpr char kPartitionsVars[] = "usr/sbin/partition_vars.json"; |
| const std::vector<const char*> kMountDirs = {"db/pkg", "lib/portage", |
| "cache/dlc-images"}; |
| |
| // TODO(asavery): update the check for removable devices to be |
| // more advanced, b/209476959 |
| bool RemovableRootdev(const base::FilePath& path, int* ret) { |
| base::FilePath removable("/sys/block"); |
| removable = removable.Append(path.BaseName()); |
| removable = removable.Append("removable"); |
| return utils::ReadFileToInt(removable, ret); |
| } |
| |
| 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; |
| } |
| |
| bool IsFeatureEnabled(const std::string& fs_features, |
| const std::string& feature) { |
| // Check if feature is already enabled. |
| return fs_features.find(feature) != std::string::npos; |
| } |
| |
| bool IsReservedGidSet(const std::string& state_dumpe2fs) { |
| std::size_t rbg_pos = state_dumpe2fs.find("Reserved blocks gid"); |
| if (rbg_pos != std::string::npos) { |
| std::size_t nwl_pos = state_dumpe2fs.find("\n", rbg_pos); |
| std::string rbg = state_dumpe2fs.substr(rbg_pos, nwl_pos); |
| if (rbg.find(kReservedBlocksGID) == std::string::npos) { |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| void AppendOption(const std::string& fs_features, |
| std::vector<std::string>* sb_options, |
| const std::string& option_name) { |
| if (!IsFeatureEnabled(fs_features, option_name)) { |
| sb_options->push_back(option_name); |
| } |
| } |
| |
| // Get the partition number for the given key, |
| // e.g. "PARTITION_NUM_STATE". Fails with a `CHECK()` if any error occurs. |
| int GetPartitionNumFromImageVars(const base::Value::Dict& image_dict, |
| std::string_view key) { |
| const std::string* value = image_dict.FindString(key); |
| CHECK_NE(value, nullptr); |
| int num = 0; |
| CHECK(base::StringToInt(*value, &num)); |
| return num; |
| } |
| |
| } // namespace |
| |
| namespace startup { |
| |
| StatefulMount::StatefulMount(const Flags& flags, |
| const base::FilePath& root, |
| const base::FilePath& stateful, |
| libstorage::Platform* platform, |
| StartupDep* startup_dep, |
| MountHelper* mount_helper) |
| : flags_(flags), |
| root_(root), |
| stateful_(stateful), |
| platform_(platform), |
| startup_dep_(startup_dep), |
| mount_helper_(mount_helper) {} |
| |
| std::optional<base::Value> StatefulMount::GetImageVars(base::FilePath json_file, |
| std::string key) { |
| std::string json_string; |
| if (!platform_->ReadFileToString(json_file, &json_string)) { |
| PLOG(ERROR) << "Unable to read json file: " << json_file; |
| return std::nullopt; |
| } |
| std::optional<base::Value> part_vars = base::JSONReader::Read( |
| json_string, base::JSON_PARSE_RFC, 10 /* max_depth */); |
| if (!part_vars) { |
| PLOG(ERROR) << "Failed to parse image variables."; |
| return std::nullopt; |
| } |
| if (!part_vars->is_dict()) { |
| LOG(ERROR) << "Failed to read json file as a dictionary"; |
| return std::nullopt; |
| } |
| |
| base::Value::Dict* image_vars = part_vars->GetDict().FindDict(key); |
| if (image_vars == nullptr) { |
| LOG(ERROR) << "Failed to get image variables from " << json_file; |
| return std::nullopt; |
| } |
| return base::Value(std::move(*image_vars)); |
| } |
| |
| void StatefulMount::AppendQuotaFeaturesAndOptions( |
| const std::string& fs_features, |
| const std::string& state_dumpe2fs, |
| std::vector<std::string>* sb_options, |
| std::vector<std::string>* sb_features) { |
| // Enable/disable quota feature. |
| if (!IsReservedGidSet(state_dumpe2fs)) { |
| // 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 |
| if (!IsFeatureEnabled(fs_features, kQuotaOpt)) { |
| sb_options->push_back(kQuotaOpt); |
| sb_features->push_back("-Qusrquota,grpquota"); |
| } |
| if (flags_.prjquota) { |
| if (!IsFeatureEnabled(fs_features, kQuotaProjectOpt)) { |
| sb_features->push_back("-Qprjquota"); |
| } |
| } else { |
| if (IsFeatureEnabled(fs_features, kQuotaProjectOpt)) { |
| sb_features->push_back("-Q^prjquota"); |
| } |
| } |
| } |
| |
| void StatefulMount::EnableExt4Features() { |
| std::vector<std::string> sb_features = GenerateExt4FeaturesWrapper(); |
| |
| if (!sb_features.empty()) { |
| brillo::ProcessImpl tune2fs; |
| tune2fs.AddArg("/sbin/tune2fs"); |
| for (const std::string& arg : sb_features) { |
| tune2fs.AddArg(arg); |
| } |
| tune2fs.AddArg(state_dev_.value()); |
| tune2fs.RedirectOutputToMemory(true); |
| int status = tune2fs.Run(); |
| if (status == 0) { |
| return; |
| } else if (status < 0) { |
| PLOG(ERROR) << "Failed to run tune2fs"; |
| } else { |
| LOG(WARNING) << "tune2fs returned non zero exit code: " << status; |
| } |
| } |
| } |
| |
| std::vector<std::string> StatefulMount::GenerateExt4FeaturesWrapper() { |
| std::unique_ptr<brillo::Process> dump = platform_->CreateProcessInstance(); |
| dump->AddArg("/sbin/dumpe2fs"); |
| dump->AddArg("-h"); |
| dump->AddArg(state_dev_.value()); |
| |
| std::string state_dumpe2fs; |
| dump->RedirectUsingMemory(STDOUT_FILENO); |
| if (dump->Run() != 0) { |
| PLOG(ERROR) << "Failed dumpe2fs for stateful partition."; |
| state_dumpe2fs = ""; |
| } else { |
| state_dumpe2fs = dump->GetOutputString(STDOUT_FILENO); |
| } |
| return GenerateExt4Features(state_dumpe2fs); |
| } |
| |
| std::vector<std::string> StatefulMount::GenerateExt4Features( |
| const std::string state_dumpe2fs) { |
| std::vector<std::string> sb_features; |
| std::vector<std::string> sb_options; |
| std::string feat; |
| std::string fs_features; |
| std::size_t feature_pos = state_dumpe2fs.find("Filesystem features:"); |
| if (feature_pos != std::string::npos) { |
| std::size_t nl_pos = state_dumpe2fs.find("\n", feature_pos); |
| fs_features = state_dumpe2fs.substr(feature_pos, nl_pos); |
| } |
| |
| base::FilePath encryption = root_.Append(kExt4Features).Append("encryption"); |
| if (flags_.direncryption && platform_->FileExists(encryption)) { |
| AppendOption(fs_features, &sb_options, "encrypt"); |
| } |
| |
| base::FilePath verity_file = root_.Append(kExt4Features).Append("verity"); |
| if (flags_.fsverity && platform_->FileExists(verity_file)) { |
| AppendOption(fs_features, &sb_options, "verity"); |
| } |
| |
| AppendQuotaFeaturesAndOptions(fs_features, state_dumpe2fs, &sb_options, |
| &sb_features); |
| |
| if (!sb_features.empty() || !sb_options.empty()) { |
| // Ensure to replay the journal first so it doesn't overwrite the flag. |
| startup_dep_->ReplayExt4Journal(state_dev_); |
| |
| if (!sb_options.empty()) { |
| std::string opts = base::JoinString(sb_options, ","); |
| sb_features.push_back("-O"); |
| sb_features.push_back(opts); |
| } |
| } |
| |
| return sb_features; |
| } |
| |
| void StatefulMount::ClobberStateful( |
| const std::vector<std::string>& clobber_args, |
| const std::string& clobber_message) { |
| startup_dep_->BootAlert("self_repair"); |
| startup_dep_->ClobberLogRepair(state_dev_, clobber_message); |
| startup_dep_->AddClobberCrashReport( |
| {"--mount_failure", "--mount_device=stateful"}); |
| startup_dep_->Clobber(clobber_args); |
| } |
| |
| bool StatefulMount::AttemptStatefulMigration() { |
| std::unique_ptr<brillo::Process> thinpool_migrator = |
| platform_->CreateProcessInstance(); |
| thinpool_migrator->AddArg("/usr/sbin/thinpool_migrator"); |
| thinpool_migrator->AddArg( |
| base::StringPrintf("--device=%s", state_dev_.value().c_str())); |
| |
| if (thinpool_migrator->Run() != 0) { |
| LOG(ERROR) << "Failed to run thinpool migrator"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void StatefulMount::MountStateful() { |
| base::FilePath root_dev; |
| // Prepare to mount stateful partition. |
| root_device_ = utils::GetRootDevice(true); |
| int removable = 0; |
| if (root_device_.empty()) { |
| PLOG(INFO) << "rootdev could not find root device."; |
| } else if (!RemovableRootdev(root_device_, &removable)) { |
| PLOG(WARNING) |
| << "Unable to read if rootdev is removable; assuming it's not"; |
| } |
| std::string load_vars; |
| if (removable == 1) { |
| load_vars = "load_partition_vars"; |
| } else { |
| load_vars = "load_base_vars"; |
| } |
| |
| base::FilePath json_file = root_.Append(kPartitionsVars); |
| std::optional<base::Value> image_vars = GetImageVars(json_file, load_vars); |
| if (!image_vars) { |
| return; |
| } |
| if (!image_vars->is_dict()) { |
| PLOG(ERROR) << "Failed to parse dictionary from " << json_file; |
| return; |
| } |
| const base::Value::Dict& image_vars_dict = image_vars->GetDict(); |
| |
| bool status; |
| int32_t stateful_mount_flags; |
| std::string stateful_mount_opts; |
| |
| // Check if we are booted on physical media. rootdev will fail if we are in |
| // an initramfs or tmpfs rootfs (ex, factory installer images. Note recovery |
| // image also uses initramfs but it never reaches here). When using |
| // initrd+tftpboot (some old netboot factory installer), ROOTDEV_TYPE will be |
| // /dev/ram. |
| if (!root_device_.empty() && root_device_ != base::FilePath("/dev/ram")) { |
| // Find our stateful partition mount point. |
| stateful_mount_flags = kCommonMountFlags | MS_NOATIME; |
| const int part_num_state = |
| GetPartitionNumFromImageVars(image_vars_dict, "PARTITION_NUM_STATE"); |
| const std::string* fs_form_state = |
| image_vars_dict.FindString("FS_FORMAT_STATE"); |
| state_dev_ = brillo::AppendPartition(root_device_, part_num_state); |
| if (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; |
| 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(state_dev_); |
| |
| 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()) { |
| LOG(ERROR) << "Failed to migrate stateful partition to a thinpool"; |
| } else { |
| // Reset the physical volume on success from thinpool migration. |
| pv = lvm->GetPhysicalVolume(state_dev_); |
| } |
| } |
| |
| 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. |
| std::optional<brillo::Thinpool> thinpool = |
| lvm->GetThinpool(*volume_group_, "thinpool"); |
| |
| if (!thinpool) { |
| LOG(ERROR) << "Thinpool does not exist"; |
| ClobberStateful({"fast", "keepimg"}, "Invalid thinpool"); |
| } |
| |
| if (!thinpool->Activate()) { |
| LOG(WARNING) << "Failed to activate thinpool, attempting repair"; |
| if (!thinpool->Activate(/*check=*/true)) { |
| LOG(ERROR) << "Failed to repair and activate thinpool"; |
| ClobberStateful({"fast", "keepimg"}, "Corrupt thinpool"); |
| } |
| } |
| |
| // Attempt to now activate the unencrypted stateful logical volume. |
| std::optional<brillo::LogicalVolume> unencrypted_lv = |
| lvm->GetLogicalVolume(*volume_group_, "unencrypted"); |
| |
| if (!unencrypted_lv || !unencrypted_lv->Activate()) { |
| LOG(ERROR) << "Failed to activate unencrypted stateful logical " |
| << "volume."; |
| |
| ClobberStateful({"fast", "keepimg", "preserve_lvs"}, |
| "Invalid unencrypted logical volume"); |
| } |
| should_mount_lvm = true; |
| } |
| } |
| bootstat_.LogEvent("lvm-activation-complete"); |
| } |
| |
| if (should_mount_lvm) { |
| state_dev_ = root_.Append("dev") |
| .Append(volume_group_->GetName()) |
| .Append("unencrypted"); |
| } |
| |
| EnableExt4Features(); |
| |
| // 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({"keepimg", "preserve_lvs"}, |
| "Self-repair corrupted stateful partition"); |
| } |
| |
| // 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 = |
| 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_device_, 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_; |
| } |
| |
| // 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) { |
| 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. |
| std::string var(kVar); |
| std::string dev_image(kStatefulDevImage); |
| base::FilePath var_new = stateful_.Append(var + kNew); |
| base::FilePath developer_new = stateful_.Append(dev_image + kNew); |
| base::FilePath developer_target = stateful_.Append(dev_image); |
| base::FilePath var_target = stateful_.Append(var + kOverlay); |
| std::vector<base::FilePath> paths_to_rm; |
| |
| // Only replace the developer and var_overlay directories if new replacements |
| // are available. |
| if (platform_->DirectoryExists(developer_new) && |
| platform_->DirectoryExists(var_new)) { |
| std::string update = "Updating from " + developer_new.value() + " && " + |
| var_new.value() + "."; |
| startup_dep_->ClobberLog(update); |
| |
| for (const std::string& path : {var, dev_image}) { |
| base::FilePath path_new = stateful_.Append(path + kNew); |
| base::FilePath path_target; |
| if (path == "var") { |
| path_target = stateful_.Append(path + kOverlay); |
| } else { |
| path_target = stateful_.Append(path); |
| } |
| if (!platform_->DeletePathRecursively(path_target)) { |
| PLOG(WARNING) << "Failed to delete " << path_target.value(); |
| } |
| |
| if (!platform_->CreateDirectory(path_target)) { |
| PLOG(WARNING) << "Failed to create " << path_target.value(); |
| } |
| |
| if (!platform_->SetPermissions(path_target, 0755)) { |
| PLOG(WARNING) << "chmod failed for " << path_target.value(); |
| } |
| |
| std::unique_ptr<libstorage::FileEnumerator> enumerator( |
| platform_->GetFileEnumerator( |
| path_new, false /* recursive */, |
| base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES | |
| base::FileEnumerator::SHOW_SYM_LINKS)); |
| |
| for (base::FilePath fd = enumerator->Next(); !fd.empty(); |
| fd = enumerator->Next()) { |
| // Filesystem crossing if new var comes in a logical volume. |
| if (!platform_->Rename(fd, path_target.Append(fd.BaseName()), true)) { |
| PLOG(WARNING) << "Failed to copy " << fd.value() << " to " |
| << path_target.value(); |
| } |
| } |
| paths_to_rm.push_back(path_new); |
| } |
| startup_dep_->RemoveInBackground(paths_to_rm); |
| } 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("unencrypted/preserve"); |
| |
| // 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), developer_target, var_target, |
| preserve_dir}; |
| std::unique_ptr<libstorage::FileEnumerator> enumerator( |
| platform_->GetFileEnumerator(stateful_, true, |
| base::FileEnumerator::FILES | |
| base::FileEnumerator::DIRECTORIES | |
| base::FileEnumerator::SHOW_SYM_LINKS)); |
| 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) || |
| path.IsParent(preserved_path)) { |
| preserve = true; |
| break; |
| } |
| } |
| |
| if (!preserve) { |
| if (platform_->DirectoryExists(path)) { |
| platform_->DeletePathRecursively(path); |
| } else { |
| platform_->DeleteFile(path); |
| } |
| } |
| } |
| // Let's really be done before coming back. |
| sync(); |
| } |
| |
| std::vector<base::FilePath> rm_paths{stateful_update_file}; |
| startup_dep_->RemoveInBackground(rm_paths); |
| |
| 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("unencrypted/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::DevMountPackages() { |
| // 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); |
| if (!platform_->DirectoryExists(stateful_dev_image)) { |
| if (!platform_->CreateDirectory(stateful_dev_image)) { |
| PLOG(ERROR) << "Failed to create " << stateful_dev_image.value(); |
| } |
| if (!platform_->SetPermissions(stateful_dev_image, 0755)) { |
| PLOG(ERROR) << "Failed to set permissions for " |
| << stateful_dev_image.value(); |
| } |
| } |
| |
| // Checks and updates stateful partition. |
| DevUpdateStatefulPartition(""); |
| |
| // 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(); |
| } |
| |
| base::FilePath disable_state_sec_hard = |
| root_.Append(kDisableStatefulSecurityHardening); |
| if (!platform_->FileExists(disable_state_sec_hard)) { |
| // 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)) { |
| LOG(WARNING) << "Path does not exists, can not mount: " << dest.value(); |
| continue; |
| } |
| mount_helper_->BindMountOrFail(full, dest); |
| } |
| } |
| } |
| |
| } // namespace startup |