| // Copyright 2018 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/clobber/clobber_state.h" |
| |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <sys/ioctl.h> |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| #include <sys/sysmacros.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <base/check.h> |
| |
| // Keep after <sys/mount.h> to avoid build errors. |
| #include <linux/fs.h> |
| |
| #include <algorithm> |
| #include <cstdlib> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/bits.h> |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/functional/callback_helpers.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/blkdev_utils/get_backing_block_device.h> |
| #include <brillo/blkdev_utils/lvm.h> |
| #include <brillo/blkdev_utils/storage_device.h> |
| #include <brillo/blkdev_utils/storage_utils.h> |
| #include <brillo/cryptohome.h> |
| #include <brillo/files/file_util.h> |
| #include <brillo/process/process.h> |
| #include <chromeos/constants/imageloader.h> |
| #include <chromeos/secure_erase_file/secure_erase_file.h> |
| #include <libcrossystem/crossystem.h> |
| #include <libdlcservice/utils.h> |
| #include <libstorage/platform/platform.h> |
| #include <rootdev/rootdev.h> |
| |
| #include "init/clobber/clobber_state_log.h" |
| #include "init/encrypted_reboot_vault/encrypted_reboot_vault.h" |
| #include "init/utils.h" |
| |
| namespace { |
| |
| constexpr char kStatefulPath[] = "/mnt/stateful_partition"; |
| constexpr char kPowerWashCountPath[] = "unencrypted/preserve/powerwash_count"; |
| constexpr char kLastPowerWashTimePath[] = |
| "unencrypted/preserve/last_powerwash_time"; |
| constexpr char kRmaStateFilePath[] = "unencrypted/rma-data/state"; |
| constexpr char kBioWashPath[] = "/usr/bin/bio_wash"; |
| constexpr char kPreservedFilesTarPath[] = "/tmp/preserve.tar"; |
| constexpr char kStatefulClobberLogPath[] = "unencrypted/clobber.log"; |
| constexpr char kMountEncryptedPath[] = "/usr/sbin/mount-encrypted"; |
| constexpr char kRollbackFileForPstorePath[] = |
| "/var/lib/oobe_config_save/data_for_pstore"; |
| constexpr char kPstoreInputPath[] = "/dev/pmsg0"; |
| // Keep file names in sync with update_engine prefs. |
| const char* kUpdateEnginePrefsFiles[] = {"last-active-ping-day", |
| "last-roll-call-ping-day"}; |
| constexpr char kUpdateEnginePrefsPath[] = "var/lib/update_engine/prefs/"; |
| constexpr char kUpdateEnginePreservePath[] = |
| "unencrypted/preserve/update_engine/prefs/"; |
| constexpr char kChromadMigrationSkipOobePreservePath[] = |
| "unencrypted/preserve/chromad_migration_skip_oobe"; |
| // CrOS Private Computing (go/chromeos-data-pc) will save the device last |
| // active dates in different use cases into a file. |
| constexpr char kPsmDeviceActiveLocalPrefPath[] = |
| "var/lib/private_computing/last_active_dates"; |
| constexpr char kPsmDeviceActivePreservePath[] = |
| "unencrypted/preserve/last_active_dates"; |
| constexpr char kFlexLocalPath[] = "var/lib/flex_id/"; |
| constexpr char kFlexPreservePath[] = "unencrypted/preserve/flex/"; |
| const char* kFlexFiles[] = {"flex_id", "flex_state_key"}; |
| constexpr base::TimeDelta kMinClobberDuration = base::Minutes(5); |
| |
| // The presence of this file indicates that crash report collection across |
| // clobber is disabled in developer mode. |
| constexpr char kDisableClobberCrashCollectionPath[] = |
| "/run/disable-clobber-crash-collection"; |
| // The presence of this file indicates that the kernel supports ext4 directory |
| // level encryption. |
| constexpr char kExt4DircryptoSupportedPath[] = |
| "/sys/fs/ext4/features/encryption"; |
| |
| // Attempt to save logs from the boot when the clobber happened into the |
| // stateful partition. |
| void CollectClobberCrashReports() { |
| brillo::ProcessImpl crash_reporter_early_collect; |
| crash_reporter_early_collect.AddArg("/sbin/crash_reporter"); |
| crash_reporter_early_collect.AddArg("--early"); |
| crash_reporter_early_collect.AddArg("--log_to_stderr"); |
| crash_reporter_early_collect.AddArg("--preserve_across_clobber"); |
| crash_reporter_early_collect.AddArg("--boot_collect"); |
| if (crash_reporter_early_collect.Run() != 0) |
| LOG(WARNING) << "Unable to collect logs and crashes from current run."; |
| |
| return; |
| } |
| |
| bool MountEncryptedStateful() { |
| brillo::ProcessImpl mount_encstateful; |
| mount_encstateful.AddArg(kMountEncryptedPath); |
| if (mount_encstateful.Run() != 0) { |
| PLOG(ERROR) << "Failed to mount encrypted stateful."; |
| return false; |
| } |
| return true; |
| } |
| |
| void UnmountEncryptedStateful() { |
| for (int attempts = 0; attempts < 10; ++attempts) { |
| brillo::ProcessImpl umount_encstateful; |
| umount_encstateful.AddArg(kMountEncryptedPath); |
| umount_encstateful.AddArg("umount"); |
| if (umount_encstateful.Run()) { |
| return; |
| } |
| } |
| PLOG(ERROR) << "Failed to unmount encrypted stateful."; |
| } |
| |
| void UnmountStateful(const base::FilePath& stateful) { |
| LOG(INFO) << "Unmounting stateful partition"; |
| for (int attempts = 0; attempts < 10; ++attempts) { |
| int ret = umount(stateful.value().c_str()); |
| if (ret) { |
| // Disambiguate failures from busy or already unmounted stateful partition |
| // from other generic failures. |
| if (errno == EBUSY) { |
| PLOG(ERROR) << "Failed to unmount busy stateful partition"; |
| base::PlatformThread::Sleep(base::Milliseconds(200)); |
| continue; |
| } else if (errno != EINVAL) { |
| PLOG(ERROR) << "Unable to unmount " << stateful; |
| } else { |
| PLOG(INFO) << "Stateful partition already unmounted"; |
| } |
| } |
| return; |
| } |
| } |
| |
| void MoveRollbackFileToPstore() { |
| const base::FilePath file_for_pstore(kRollbackFileForPstorePath); |
| |
| std::string data; |
| if (!base::ReadFileToString(file_for_pstore, &data)) { |
| if (errno != ENOENT) { |
| PLOG(ERROR) << "Failed to read rollback data for pstore."; |
| } |
| return; |
| } |
| |
| if (!base::AppendToFile(base::FilePath(kPstoreInputPath), data + "\n")) { |
| if (errno == ENOENT) { |
| PLOG(WARNING) |
| << "Could not write rollback data because /dev/pmsg0 does not exist."; |
| } else { |
| PLOG(ERROR) << "Failed to write rollback data to pstore."; |
| } |
| } |
| // The rollback file will be lost on tpm reset, so we do not need to |
| // delete it manually. |
| } |
| |
| } // namespace |
| |
| // static |
| ClobberState::Arguments ClobberState::ParseArgv(int argc, |
| char const* const argv[]) { |
| Arguments args; |
| if (argc <= 1) |
| return args; |
| |
| // Due to historical usage, the command line parsing is a bit weird. |
| // We split the first argument into multiple keywords. |
| std::vector<std::string> split_args = base::SplitString( |
| argv[1], " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| for (int i = 2; i < argc; ++i) |
| split_args.push_back(argv[i]); |
| |
| for (const std::string& arg : split_args) { |
| if (arg == "factory") { |
| args.factory_wipe = true; |
| // Factory mode implies fast wipe. |
| args.fast_wipe = true; |
| } else if (arg == "fast") { |
| args.fast_wipe = true; |
| } else if (arg == "keepimg") { |
| args.keepimg = true; |
| } else if (arg == "safe") { |
| args.safe_wipe = true; |
| } else if (arg == "rollback") { |
| args.rollback_wipe = true; |
| } else if (base::StartsWith( |
| arg, "reason=", base::CompareCase::INSENSITIVE_ASCII)) { |
| args.reason = arg; |
| } else if (arg == "rma") { |
| args.rma_wipe = true; |
| } else if (arg == "ad_migration") { |
| args.ad_migration_wipe = true; |
| } else if (arg == "preserve_lvs") { |
| args.preserve_lvs = USE_LVM_STATEFUL_PARTITION; |
| } else if (arg == "disable_lvm_install" || USE_DISABLE_LVM_INSTALL) { |
| args.disable_lvm_install = true; |
| } |
| } |
| |
| if (args.disable_lvm_install) |
| args.preserve_lvs = false; |
| |
| return args; |
| } |
| |
| // static |
| bool ClobberState::IncrementFileCounter(const base::FilePath& path) { |
| int value; |
| if (!utils::ReadFileToInt(path, &value) || value < 0 || value >= INT_MAX) { |
| return base::WriteFile(path, "1\n"); |
| } |
| |
| std::string new_value = std::to_string(value + 1); |
| new_value.append("\n"); |
| return base::WriteFile(path, new_value); |
| } |
| |
| // static |
| bool ClobberState::WriteLastPowerwashTime(const base::FilePath& path, |
| const base::Time& time) { |
| return base::WriteFile(path, base::StringPrintf("%ld\n", time.ToTimeT())); |
| } |
| |
| // static |
| int ClobberState::PreserveFiles( |
| const base::FilePath& preserved_files_root, |
| const std::vector<base::FilePath>& preserved_files, |
| const base::FilePath& tar_file_path) { |
| // Remove any stale tar files from previous clobber-state runs. |
| brillo::DeleteFile(tar_file_path); |
| |
| // We want to preserve permissions and recreate the directory structure |
| // for all of the files in |preserved_files|. In order to do so we run tar |
| // --no-recursion and specify the names of each of the parent directories. |
| // For example for home/.shadow/install_attributes.pb |
| // we pass to tar home, home/.shadow, home/.shadow/install_attributes.pb. |
| std::vector<std::string> paths_to_tar; |
| for (const base::FilePath& path : preserved_files) { |
| // All paths should be relative to |preserved_files_root|. |
| if (path.IsAbsolute()) { |
| LOG(WARNING) << "Non-relative path " << path.value() |
| << " passed to PreserveFiles, ignoring."; |
| continue; |
| } |
| if (!base::PathExists(preserved_files_root.Append(path))) |
| continue; |
| base::FilePath current = path; |
| while (current != base::FilePath(base::FilePath::kCurrentDirectory)) { |
| // List of paths is built in an order that is reversed from what we want |
| // (parent directories first), but will then be passed to tar in reverse |
| // order. |
| // |
| // e.g. for home/.shadow/install_attributes.pb, |paths_to_tar| will have |
| // home/.shadow/install_attributes.pb, then home/.shadow, then home. |
| paths_to_tar.push_back(current.value()); |
| current = current.DirName(); |
| } |
| } |
| |
| // We can't create an empty tar file. |
| if (paths_to_tar.size() == 0) { |
| LOG(INFO) |
| << "PreserveFiles found no files to preserve, no tar file created."; |
| return 0; |
| } |
| |
| brillo::ProcessImpl tar; |
| tar.AddArg("/bin/tar"); |
| tar.AddArg("-c"); |
| tar.AddStringOption("-f", tar_file_path.value()); |
| tar.AddStringOption("-C", preserved_files_root.value()); |
| tar.AddArg("--no-recursion"); |
| tar.AddArg("--"); |
| |
| // Add paths in reverse order because we built up the list of paths backwards. |
| for (auto it = paths_to_tar.rbegin(); it != paths_to_tar.rend(); ++it) { |
| tar.AddArg(*it); |
| } |
| return tar.Run(); |
| } |
| |
| // static |
| bool ClobberState::GetDevicesToWipe( |
| const base::FilePath& root_disk, |
| const base::FilePath& root_device, |
| const ClobberWipe::PartitionNumbers& partitions, |
| ClobberState::DeviceWipeInfo* wipe_info_out) { |
| if (!wipe_info_out) { |
| LOG(ERROR) << "wipe_info_out must be non-null"; |
| return false; |
| } |
| |
| if (partitions.root_a < 0 || partitions.root_b < 0 || |
| partitions.kernel_a < 0 || partitions.kernel_b < 0 || |
| partitions.stateful < 0) { |
| LOG(ERROR) << "Invalid partition numbers for GetDevicesToWipe"; |
| return false; |
| } |
| |
| if (root_disk.empty()) { |
| LOG(ERROR) << "Invalid root disk for GetDevicesToWipe"; |
| return false; |
| } |
| |
| if (root_device.empty()) { |
| LOG(ERROR) << "Invalid root device for GetDevicesToWipe"; |
| return false; |
| } |
| |
| std::string base_device; |
| int active_root_partition; |
| if (!utils::GetDevicePathComponents(root_device, &base_device, |
| &active_root_partition)) { |
| LOG(ERROR) << "Extracting partition number and base device from " |
| "root_device failed: " |
| << root_device.value(); |
| return false; |
| } |
| |
| ClobberState::DeviceWipeInfo wipe_info; |
| if (active_root_partition == partitions.root_a) { |
| wipe_info.inactive_root_device = |
| base::FilePath(base_device + std::to_string(partitions.root_b)); |
| wipe_info.inactive_kernel_device = |
| base::FilePath(base_device + std::to_string(partitions.kernel_b)); |
| wipe_info.active_kernel_partition = partitions.kernel_a; |
| } else if (active_root_partition == partitions.root_b) { |
| wipe_info.inactive_root_device = |
| base::FilePath(base_device + std::to_string(partitions.root_a)); |
| wipe_info.inactive_kernel_device = |
| base::FilePath(base_device + std::to_string(partitions.kernel_a)); |
| wipe_info.active_kernel_partition = partitions.kernel_b; |
| } else { |
| LOG(ERROR) << "Active root device partition number (" |
| << active_root_partition |
| << ") does not match either root partition number: " |
| << partitions.root_a << ", " << partitions.root_b; |
| return false; |
| } |
| |
| base::FilePath kernel_device; |
| wipe_info.stateful_partition_device = |
| base::FilePath(base_device + std::to_string(partitions.stateful)); |
| |
| if (active_root_partition == partitions.root_a) { |
| kernel_device = |
| base::FilePath(base_device + std::to_string(partitions.kernel_a)); |
| } else if (active_root_partition == partitions.root_b) { |
| kernel_device = |
| base::FilePath(base_device + std::to_string(partitions.kernel_b)); |
| } |
| |
| *wipe_info_out = wipe_info; |
| return true; |
| } |
| |
| // static |
| void ClobberState::RemoveVpdKeys() { |
| constexpr std::array<const char*, 2> keys_to_remove{ |
| // This key is used for caching the feature level. |
| // Need to remove it, as it must be recalculated when re-entering normal |
| // mode. |
| "feature_device_info", |
| // This key is used to store LVM migration status. |
| // Need to remove this to prevent incoherence. |
| "thinpool_migration_status", |
| }; |
| for (auto key : keys_to_remove) { |
| brillo::ProcessImpl vpd; |
| vpd.AddArg("/usr/sbin/vpd"); |
| vpd.AddStringOption("-i", "RW_VPD"); |
| vpd.AddStringOption("-d", key); |
| // Do not report failures as the key might not even exist in the VPD. |
| vpd.RedirectOutputToMemory(true); |
| vpd.Run(); |
| init::AppendToLog("vpd", vpd.GetOutputString(STDOUT_FILENO)); |
| } |
| } |
| |
| ClobberState::ClobberState(const Arguments& args, |
| std::unique_ptr<crossystem::Crossystem> cros_system, |
| std::unique_ptr<ClobberUi> ui, |
| std::unique_ptr<ClobberWipe> clobber_wipe, |
| std::unique_ptr<ClobberLvm> clobber_lvm) |
| : args_(args), |
| cros_system_(std::move(cros_system)), |
| ui_(std::move(ui)), |
| stateful_(kStatefulPath), |
| root_path_("/"), |
| clobber_lvm_(std::move(clobber_lvm)), |
| clobber_wipe_(std::move(clobber_wipe)), |
| weak_ptr_factory_(this) {} |
| |
| std::vector<base::FilePath> ClobberState::GetPreservedFilesList() { |
| std::vector<std::string> stateful_paths; |
| // Preserve these files in safe mode. (Please request a privacy review before |
| // adding files.) |
| // |
| // - unencrypted/preserve/update_engine/prefs/rollback-happened: Contains a |
| // boolean value indicating whether a rollback has happened since the last |
| // update check where device policy was available. Needed to avoid forced |
| // updates after rollbacks (device policy is not yet loaded at this time). |
| if (args_.safe_wipe) { |
| stateful_paths.push_back(kPowerWashCountPath); |
| stateful_paths.push_back( |
| "unencrypted/preserve/tpm_firmware_update_request"); |
| stateful_paths.push_back(std::string(kUpdateEnginePreservePath) + |
| "rollback-happened"); |
| stateful_paths.push_back(std::string(kUpdateEnginePreservePath) + |
| "rollback-version"); |
| |
| for (const auto* ue_prefs_filename : kUpdateEnginePrefsFiles) { |
| stateful_paths.push_back(std::string(kUpdateEnginePreservePath) + |
| std::string(ue_prefs_filename)); |
| } |
| // Preserve the device last active dates to Private Set Computing (psm). |
| stateful_paths.push_back(kPsmDeviceActivePreservePath); |
| |
| // For the Chromad to cloud migration, we store a flag file to indicate that |
| // some OOBE screens should be skipped after the device is powerwashed. |
| if (args_.ad_migration_wipe) { |
| stateful_paths.push_back(kChromadMigrationSkipOobePreservePath); |
| } |
| |
| // Preserve pre-installed demo mode resources for offline Demo Mode. |
| std::string demo_mode_resources_dir = |
| "unencrypted/cros-components/offline-demo-mode-resources/"; |
| stateful_paths.push_back(demo_mode_resources_dir + "image.squash"); |
| stateful_paths.push_back(demo_mode_resources_dir + "imageloader.json"); |
| stateful_paths.push_back(demo_mode_resources_dir + "imageloader.sig.1"); |
| stateful_paths.push_back(demo_mode_resources_dir + "imageloader.sig.2"); |
| stateful_paths.push_back(demo_mode_resources_dir + "manifest.fingerprint"); |
| stateful_paths.push_back(demo_mode_resources_dir + "manifest.json"); |
| stateful_paths.push_back(demo_mode_resources_dir + "table"); |
| |
| // For rollback wipes, we preserve the rollback metrics file and additional |
| // data as defined in oobe_config/rollback_data.proto. |
| if (args_.rollback_wipe) { |
| stateful_paths.push_back( |
| "unencrypted/preserve/enterprise-rollback-metrics-data"); |
| // Devices produced >= 2023 use the new rollback data |
| // ("rollback_data_tpm") encryption. |
| stateful_paths.push_back("unencrypted/preserve/rollback_data_tpm"); |
| // TODO(b/263065223) Preservation of the old format ("rollback_data") can |
| // be removed when all devices produced before 2023 are EOL. |
| stateful_paths.push_back("unencrypted/preserve/rollback_data"); |
| } |
| |
| // Preserve the latest GSC crash ID to prevent uploading previously seen GSC |
| // crashes on every boot. |
| stateful_paths.push_back("unencrypted/preserve/gsc_prev_crash_log_id"); |
| |
| // Preserve the files used to identify ChromeOS Flex devices. |
| for (const auto* flex_filename : kFlexFiles) { |
| stateful_paths.push_back(std::string(kFlexPreservePath) + |
| std::string(flex_filename)); |
| } |
| } |
| |
| // Preserve RMA state file in RMA mode. |
| if (args_.rma_wipe) { |
| stateful_paths.push_back(kRmaStateFilePath); |
| } |
| |
| // Test images in the lab enable certain extra behaviors if the |
| // .labmachine flag file is present. Those behaviors include some |
| // important recovery behaviors (cf. the recover_duts upstart job). |
| // We need those behaviors to survive across power wash, otherwise, |
| // the current boot could wind up as a black hole. |
| std::optional<int> debug_build = |
| cros_system_->VbGetSystemPropertyInt(crossystem::Crossystem::kDebugBuild); |
| if (debug_build == 1) { |
| stateful_paths.push_back(".labmachine"); |
| } |
| |
| std::vector<base::FilePath> preserved_files; |
| for (const std::string& path : stateful_paths) { |
| preserved_files.push_back(base::FilePath(path)); |
| } |
| |
| if (args_.factory_wipe) { |
| base::FileEnumerator crx_enumerator( |
| stateful_.Append("unencrypted/import_extensions/extensions"), false, |
| base::FileEnumerator::FileType::FILES, "*.crx"); |
| for (base::FilePath name = crx_enumerator.Next(); !name.empty(); |
| name = crx_enumerator.Next()) { |
| preserved_files.push_back( |
| base::FilePath("unencrypted/import_extensions/extensions") |
| .Append(name.BaseName())); |
| } |
| |
| base::FileEnumerator dlc_enumerator( |
| stateful_.Append("unencrypted/dlc-factory-images"), false, |
| base::FileEnumerator::DIRECTORIES); |
| for (base::FilePath dir = dlc_enumerator.Next(); !dir.empty(); |
| dir = dlc_enumerator.Next()) { |
| base::FilePath dlc_image_path = |
| base::FilePath("unencrypted/dlc-factory-images") |
| .Append(dir.BaseName()) |
| .Append("package") |
| .Append("dlc.img"); |
| if (base::PathExists(stateful_.Append(dlc_image_path))) { |
| preserved_files.push_back(dlc_image_path); |
| } |
| } |
| } |
| |
| return preserved_files; |
| } |
| |
| int ClobberState::CreateStatefulFileSystem( |
| const std::string& stateful_filesystem_device) { |
| brillo::ProcessImpl mkfs; |
| mkfs.AddArg("/sbin/mkfs.ext4"); |
| // Check if encryption is supported. If yes, enable the flag during mkfs. |
| if (base::PathExists(base::FilePath(kExt4DircryptoSupportedPath))) |
| mkfs.AddStringOption("-O", "encrypt"); |
| mkfs.AddArg(stateful_filesystem_device); |
| // TODO(wad) tune2fs. |
| mkfs.RedirectOutputToMemory(true); |
| LOG(INFO) << "Creating stateful file system"; |
| int ret = mkfs.Run(); |
| init::AppendToLog("mkfs.ext4", mkfs.GetOutputString(STDOUT_FILENO)); |
| return ret; |
| } |
| |
| void ClobberState::PreserveEncryptedFiles() { |
| // Preserve Update Engine prefs when the device is powerwashed. |
| base::FilePath ue_prefs_path(root_path_.Append(kUpdateEnginePrefsPath)); |
| base::FilePath ue_preserve_prefs_path( |
| stateful_.Append(kUpdateEnginePreservePath)); |
| if (base::CreateDirectory(ue_preserve_prefs_path)) { |
| for (const auto* ue_prefs_filename : kUpdateEnginePrefsFiles) { |
| base::FilePath ue_prefs_file(ue_prefs_path.Append(ue_prefs_filename)); |
| base::FilePath ue_preserved_prefs_file( |
| ue_preserve_prefs_path.Append(ue_prefs_filename)); |
| if (!base::CopyFile(ue_prefs_file, ue_preserved_prefs_file)) |
| LOG(ERROR) << "Error copying file. Source: " << ue_prefs_file |
| << " Target: " << ue_preserved_prefs_file; |
| } |
| } else { |
| LOG(ERROR) << "Error creating directory: " << ue_preserve_prefs_path; |
| } |
| |
| // Preserve the psm device active dates when the device is powerwashed. |
| base::FilePath psm_local_pref_file( |
| root_path_.Append(kPsmDeviceActiveLocalPrefPath)); |
| base::FilePath psm_preserved_pref_file( |
| stateful_.Append(kPsmDeviceActivePreservePath)); |
| if (!base::CopyFile(psm_local_pref_file, psm_preserved_pref_file)) |
| LOG(ERROR) << "Error copying file. Source: " << psm_local_pref_file |
| << " Target: " << psm_preserved_pref_file; |
| |
| // Preserve the files used to identify ChromeOS Flex devices. |
| base::FilePath flex_path(root_path_.Append(kFlexLocalPath)); |
| base::FilePath flex_preserve_path(stateful_.Append(kFlexPreservePath)); |
| if (base::CreateDirectory(flex_preserve_path)) { |
| for (const auto* flex_filename : kFlexFiles) { |
| base::FilePath flex_file(flex_path.Append(flex_filename)); |
| base::FilePath flex_preserved_file( |
| flex_preserve_path.Append(flex_filename)); |
| if (!base::CopyFile(flex_file, flex_preserved_file)) |
| LOG(ERROR) << "Error copying file. Source: " << flex_file |
| << " Target: " << flex_preserved_file; |
| } |
| } else { |
| LOG(ERROR) << "Error creating directory: " << flex_preserve_path; |
| } |
| } |
| |
| int ClobberState::Run() { |
| DCHECK(cros_system_); |
| |
| wipe_start_time_ = base::TimeTicks::Now(); |
| |
| // Defer callback to relocate log file back to stateful partition so that it |
| // will be preserved after a reboot. |
| base::ScopedClosureRunner relocate_clobber_state_log(base::BindRepeating( |
| [](base::FilePath stateful_path) { |
| base::Move(base::FilePath(init::kClobberLogPath), |
| stateful_path.Append("unencrypted/clobber-state.log")); |
| }, |
| stateful_)); |
| |
| // Check if this powerwash was triggered by a session manager request. |
| // StartDeviceWipe D-Bus call is restricted to "chronos" so it is probably |
| // safe to assume that such requests were initiated by the user. |
| bool user_triggered_powerwash = |
| (args_.reason.find("session_manager_dbus_request") != std::string::npos); |
| |
| // Allow crash preservation across clobber if the device is in developer mode. |
| // For testing purposes, use a tmpfs path to disable collection. |
| bool preserve_dev_mode_crash_reports = |
| IsInDeveloperMode() && |
| !base::PathExists(base::FilePath(kDisableClobberCrashCollectionPath)); |
| |
| // Check if sensitive files should be preserved. Sensitive files should be |
| // preserved if any of the following conditions are met: |
| // 1. The device is in developer mode and crash report collection is allowed. |
| // 2. The request doesn't originate from a user-triggered powerwash. |
| bool preserve_sensitive_files = |
| !user_triggered_powerwash || preserve_dev_mode_crash_reports; |
| |
| // True if we should ensure that this powerwash takes at least 5 minutes. |
| // Saved here because we may switch to using a fast wipe later, but we still |
| // want to enforce the delay in that case. |
| bool should_force_delay = !args_.fast_wipe && !args_.factory_wipe; |
| |
| LOG(INFO) << "Beginning clobber-state run"; |
| LOG(INFO) << "Factory wipe: " << args_.factory_wipe; |
| LOG(INFO) << "Fast wipe: " << args_.fast_wipe; |
| LOG(INFO) << "Keepimg: " << args_.keepimg; |
| LOG(INFO) << "Safe wipe: " << args_.safe_wipe; |
| LOG(INFO) << "Rollback wipe: " << args_.rollback_wipe; |
| LOG(INFO) << "Reason: " << args_.reason; |
| LOG(INFO) << "RMA wipe: " << args_.rma_wipe; |
| LOG(INFO) << "AD migration wipe: " << args_.ad_migration_wipe; |
| |
| // Most effective means of destroying user data is run at the start: Throwing |
| // away the key to encrypted stateful by requesting the TPM to be cleared at |
| // next boot. |
| if (!cros_system_->VbSetSystemPropertyInt( |
| crossystem::Crossystem::kClearTpmOwnerRequest, 1)) { |
| LOG(ERROR) << "Requesting TPM wipe via crossystem failed"; |
| } |
| |
| // In cases where biometric sensors are available, reset the internal entropy |
| // used by those sensors for encryption, to render related data/templates etc. |
| // undecipherable. |
| if (!ClearBiometricSensorEntropy()) { |
| LOG(ERROR) << "Clearing biometric sensor internal entropy failed"; |
| } |
| |
| // Try to mount encrypted stateful to save some files from there. |
| bool encrypted_stateful_mounted = false; |
| |
| // Update Engine and OOBE config utilities require preservation of files in |
| // /var across powerwash. Attempt to mount the encrypted stateful partition |
| // if: |
| // 1. The encrypted stateful partition is enabled on the device. |
| // 2. clobber-state is not running in factory mode: mount-encrypted is not |
| // accessible within the factory environment. |
| // Failure to mount the encrypted stateful partition prevents the preservation |
| // of these files across powerwash, but functionally does not affect clobber. |
| encrypted_stateful_mounted = |
| USE_ENCRYPTED_STATEFUL && !args_.factory_wipe && MountEncryptedStateful(); |
| |
| if (args_.safe_wipe) { |
| IncrementFileCounter(stateful_.Append(kPowerWashCountPath)); |
| if (encrypted_stateful_mounted) |
| PreserveEncryptedFiles(); |
| } |
| |
| // Clear clobber log if needed. |
| if (!preserve_sensitive_files) { |
| brillo::DeleteFile(stateful_.Append(kStatefulClobberLogPath)); |
| } |
| |
| std::vector<base::FilePath> preserved_files = GetPreservedFilesList(); |
| for (const base::FilePath& fp : preserved_files) { |
| LOG(INFO) << "Preserving file: " << fp.value(); |
| } |
| |
| base::FilePath preserved_tar_file(kPreservedFilesTarPath); |
| int ret = PreserveFiles(stateful_, preserved_files, preserved_tar_file); |
| if (ret) { |
| LOG(ERROR) << "Preserving files failed with code " << ret; |
| } |
| |
| if (encrypted_stateful_mounted) { |
| // Preserve a rollback data file separately as it's sensitive and must not |
| // be stored unencrypted on the hard drive. |
| if (args_.rollback_wipe) { |
| MoveRollbackFileToPstore(); |
| } |
| UnmountEncryptedStateful(); |
| } |
| |
| // As we move factory wiping from release image to factory test image, |
| // clobber-state will be invoked directly under a tmpfs. GetRootDevice cannot |
| // report correct output under such a situation. Therefore, the output is |
| // preserved then assigned to environment variables ROOT_DEV/ROOT_DISK for |
| // clobber-state. For other cases, the environment variables will be empty and |
| // it falls back to using GetRootDevice. |
| const char* root_disk_cstr = getenv("ROOT_DISK"); |
| if (root_disk_cstr != nullptr) { |
| root_disk_ = base::FilePath(root_disk_cstr); |
| } else { |
| root_disk_ = utils::GetRootDevice(/*strip_partition=*/true); |
| } |
| |
| base::FilePath root_device; |
| const char* root_device_cstr = getenv("ROOT_DEV"); |
| if (root_device_cstr != nullptr) { |
| root_device = base::FilePath(root_device_cstr); |
| } else { |
| root_device = utils::GetRootDevice(/*strip_partition=*/false); |
| } |
| |
| LOG(INFO) << "Root disk: " << root_disk_.value(); |
| LOG(INFO) << "Root device: " << root_device.value(); |
| |
| partitions_.stateful = utils::GetPartitionNumber(root_disk_, "STATE"); |
| partitions_.root_a = utils::GetPartitionNumber(root_disk_, "ROOT-A"); |
| partitions_.root_b = utils::GetPartitionNumber(root_disk_, "ROOT-B"); |
| partitions_.kernel_a = utils::GetPartitionNumber(root_disk_, "KERN-A"); |
| partitions_.kernel_b = utils::GetPartitionNumber(root_disk_, "KERN-B"); |
| |
| if (!GetDevicesToWipe(root_disk_, root_device, partitions_, &wipe_info_)) { |
| LOG(ERROR) << "Getting devices to wipe failed, aborting run"; |
| return 1; |
| } |
| |
| LOG(INFO) << "Stateful device: " |
| << wipe_info_.stateful_partition_device.value(); |
| LOG(INFO) << "Inactive root device: " |
| << wipe_info_.inactive_root_device.value(); |
| LOG(INFO) << "Inactive kernel device: " |
| << wipe_info_.inactive_kernel_device.value(); |
| |
| brillo::ProcessImpl log_preserve; |
| log_preserve.AddArg("/sbin/clobber-log"); |
| log_preserve.AddArg("--preserve"); |
| log_preserve.AddArg("clobber-state"); |
| |
| if (args_.factory_wipe) |
| log_preserve.AddArg("factory"); |
| if (args_.fast_wipe) |
| log_preserve.AddArg("fast"); |
| if (args_.keepimg) |
| log_preserve.AddArg("keepimg"); |
| if (args_.safe_wipe) |
| log_preserve.AddArg("safe"); |
| if (args_.rollback_wipe) |
| log_preserve.AddArg("rollback"); |
| if (!args_.reason.empty()) |
| log_preserve.AddArg(args_.reason); |
| if (args_.rma_wipe) |
| log_preserve.AddArg("rma"); |
| if (args_.ad_migration_wipe) |
| log_preserve.AddArg("ad_migration"); |
| if (args_.disable_lvm_install) |
| log_preserve.AddArg("disable_lvm_install"); |
| |
| log_preserve.RedirectOutputToMemory(true); |
| log_preserve.Run(); |
| init::AppendToLog("clobber-log", log_preserve.GetOutputString(STDOUT_FILENO)); |
| |
| AttemptSwitchToFastWipe( |
| clobber_wipe_->IsRotational(wipe_info_.stateful_partition_device)); |
| |
| // Make sure the stateful partition has been unmounted. |
| UnmountStateful(stateful_); |
| |
| // Ready for wiping. |
| clobber_wipe_->SetPartitionInfo(partitions_); |
| clobber_wipe_->SetFastWipe(args_.fast_wipe); |
| |
| base::ScopedClosureRunner reset_stateful(base::BindOnce( |
| &ClobberState::ResetStatefulPartition, weak_ptr_factory_.GetWeakPtr())); |
| |
| if (args_.preserve_lvs) { |
| dlcservice::PartitionSlot slot = |
| wipe_info_.active_kernel_partition == partitions_.kernel_a |
| ? dlcservice::PartitionSlot::A |
| : dlcservice::PartitionSlot::B; |
| if (!clobber_lvm_->PreserveLogicalVolumesWipe( |
| wipe_info_.stateful_partition_device, |
| clobber_lvm_->PreserveLogicalVolumesWipeArgs(slot))) { |
| args_.preserve_lvs = false; |
| LOG(WARNING) << "Preserve logical volumes wipe failed " |
| << "(falling back to default LVM stateful wipe)."; |
| } else { |
| LOG(INFO) << "Preserve logical volumes, skipping device level wipe."; |
| reset_stateful.ReplaceClosure(base::DoNothing()); |
| } |
| } |
| |
| reset_stateful.RunAndReset(); |
| |
| // `preserve_lvs` precedence check over creating a blank LVM setup. |
| std::optional<base::FilePath> new_stateful_filesystem_device; |
| if (args_.preserve_lvs) { |
| new_stateful_filesystem_device = |
| clobber_lvm_->CreateLogicalVolumeStackForPreserved( |
| wipe_info_.stateful_partition_device); |
| } else if (USE_LVM_STATEFUL_PARTITION && !args_.disable_lvm_install) { |
| new_stateful_filesystem_device = clobber_lvm_->CreateLogicalVolumeStack( |
| wipe_info_.stateful_partition_device); |
| } else { |
| // Set up the stateful filesystem on top of the stateful partition. |
| new_stateful_filesystem_device = wipe_info_.stateful_partition_device; |
| } |
| if (new_stateful_filesystem_device) { |
| wipe_info_.stateful_filesystem_device = *new_stateful_filesystem_device; |
| } else { |
| LOG(ERROR) << "Unable to create stateful device"; |
| // Give an empty value, we are going to fail all the following steps and |
| // reach reboot. |
| wipe_info_.stateful_filesystem_device = base::FilePath(""); |
| } |
| |
| ret = CreateStatefulFileSystem(wipe_info_.stateful_filesystem_device.value()); |
| if (ret) |
| LOG(ERROR) << "Unable to create stateful file system. Error code: " << ret; |
| |
| // Mount the fresh image for last minute additions. |
| if (mount(wipe_info_.stateful_filesystem_device.value().c_str(), |
| stateful_.value().c_str(), "ext4", 0, nullptr) != 0) { |
| PLOG(ERROR) << "Unable to mount stateful partition at " |
| << stateful_.value(); |
| } |
| |
| if (base::PathExists(preserved_tar_file)) { |
| brillo::ProcessImpl tar; |
| tar.AddArg("/bin/tar"); |
| tar.AddStringOption("-C", stateful_.value()); |
| tar.AddArg("-x"); |
| tar.AddStringOption("-f", preserved_tar_file.value()); |
| tar.RedirectOutputToMemory(true); |
| ret = tar.Run(); |
| init::AppendToLog("tar", tar.GetOutputString(STDOUT_FILENO)); |
| if (ret != 0) { |
| LOG(WARNING) << "Restoring preserved files failed with code " << ret; |
| } |
| base::WriteFile(stateful_.Append("unencrypted/.powerwash_completed"), ""); |
| // TODO(b/190143108) Add one unit test in the context of |
| // ClobberState::Run() to check the powerwash time file existence. |
| if (!WriteLastPowerwashTime(stateful_.Append(kLastPowerWashTimePath), |
| base::Time::Now())) { |
| PLOG(WARNING) << "Write the last_powerwash_time to file failed"; |
| } |
| } |
| |
| brillo::ProcessImpl log_restore; |
| log_restore.AddArg("/sbin/clobber-log"); |
| log_restore.AddArg("--restore"); |
| log_restore.AddArg("clobber-state"); |
| log_restore.RedirectOutputToMemory(true); |
| ret = log_restore.Run(); |
| init::AppendToLog("clobber-log", log_restore.GetOutputString(STDOUT_FILENO)); |
| if (ret != 0) { |
| LOG(WARNING) << "Restoring clobber.log failed with code " << ret; |
| } |
| |
| // Attempt to collect crashes into the reboot vault crash directory. Do not |
| // collect crashes if this is a user triggered or a factory powerwash. |
| if (preserve_sensitive_files && !args_.factory_wipe) { |
| libstorage::Platform platform; |
| encrypted_reboot_vault::EncryptedRebootVault vault(&platform); |
| if (vault.UnlockVault()) |
| CollectClobberCrashReports(); |
| } |
| |
| // Remove keys that may alter device state. |
| RemoveVpdKeys(); |
| |
| if (!args_.keepimg) { |
| utils::EnsureKernelIsBootable(root_disk_, |
| wipe_info_.active_kernel_partition); |
| clobber_wipe_->WipeDevice(wipe_info_.inactive_root_device); |
| clobber_wipe_->WipeDevice(wipe_info_.inactive_kernel_device); |
| } |
| |
| // Ensure that we've run for at least 5 minutes if this run requires it. |
| if (should_force_delay) { |
| ForceDelay(); |
| } |
| |
| // Check if we're in developer mode, and if so, create developer mode marker |
| // file so that we don't run clobber-state again after reboot. |
| if (!MarkDeveloperMode()) { |
| LOG(ERROR) << "Creating developer mode marker file failed."; |
| } |
| |
| // Schedule flush of filesystem caches to disk. |
| sync(); |
| |
| LOG(INFO) << "clobber-state has completed"; |
| relocate_clobber_state_log.RunAndReset(); |
| |
| // Factory wipe should stop here. |
| if (args_.factory_wipe) |
| return 0; |
| |
| // If everything worked, reboot. |
| Reboot(); |
| // This return won't actually be reached unless reboot fails. |
| return 0; |
| } |
| |
| bool ClobberState::IsInDeveloperMode() { |
| std::optional<int> dev_mode_flag = cros_system_->VbGetSystemPropertyInt( |
| crossystem::Crossystem::kDevSwitchBoot); |
| // No flag or not in dev mode: |
| if (!dev_mode_flag || *dev_mode_flag != 1) |
| return false; |
| std::optional<std::string> firmware_name = |
| cros_system_->VbGetSystemPropertyString( |
| crossystem::Crossystem::kMainFirmwareActive); |
| // We are running ChromeOS firmware and we are not in recovery: |
| return firmware_name && *firmware_name != "recovery"; |
| } |
| |
| bool ClobberState::MarkDeveloperMode() { |
| if (IsInDeveloperMode()) |
| return base::WriteFile(stateful_.Append(".developer_mode"), ""); |
| |
| return true; |
| } |
| |
| void ClobberState::AttemptSwitchToFastWipe(bool is_rotational) { |
| // On a non-fast wipe, rotational drives take too long. Override to run them |
| // through "fast" mode. Sensitive contents should already |
| // be encrypted. |
| if (!args_.fast_wipe && is_rotational) { |
| LOG(INFO) << "Stateful device is on rotational disk, shredding files"; |
| ShredRotationalStatefulFiles(); |
| args_.fast_wipe = true; |
| LOG(INFO) << "Switching to fast wipe"; |
| } |
| |
| // Do not use legacy salt as a fast_wipe allowence marker on devices which |
| // allow non-tpm fallback for encryption. |
| if (!USE_TPM_INSECURE_FALLBACK) { |
| if (!brillo::cryptohome::home::IsLegacySystemSalt(stateful_)) { |
| args_.fast_wipe = true; |
| LOG(INFO) << "No legacy salt file, switching to fast wipe"; |
| return; |
| } |
| } |
| |
| // For drives that support secure erasure, wipe the stateful key material, and |
| // then run the drives through "fast" mode. |
| // |
| // Note: currently only eMMC-based SSDs are supported. |
| if (!args_.fast_wipe) { |
| LOG(INFO) << "Attempting to wipe key material"; |
| if (WipeKeyMaterial()) { |
| LOG(INFO) << "Wiping key material succeeded"; |
| args_.fast_wipe = true; |
| LOG(INFO) << "Switching to fast wipe"; |
| } else { |
| LOG(INFO) << "Wiping key material failed"; |
| } |
| } |
| } |
| |
| void ClobberState::ShredRotationalStatefulFiles() { |
| // Directly remove things that are already encrypted (which are also the |
| // large things), or are static from images. |
| brillo::DeleteFile(stateful_.Append("encrypted.block")); |
| brillo::DeletePathRecursively(stateful_.Append("var_overlay")); |
| brillo::DeletePathRecursively(stateful_.Append("dev_image")); |
| |
| base::FileEnumerator shadow_files( |
| stateful_.Append("home/.shadow"), |
| /*recursive=*/true, base::FileEnumerator::FileType::DIRECTORIES); |
| for (base::FilePath path = shadow_files.Next(); !path.empty(); |
| path = shadow_files.Next()) { |
| if (path.BaseName() == base::FilePath("vault")) { |
| brillo::DeletePathRecursively(path); |
| } |
| } |
| |
| // Shred everything else. We care about contents not filenames, so do not |
| // use "-u" since metadata updates via fdatasync dominate the shred time. |
| // Note that if the count-down is interrupted, the reset file continues |
| // to exist, which correctly continues to indicate a needed wipe. |
| brillo::ProcessImpl shred; |
| shred.AddArg("/usr/bin/shred"); |
| shred.AddArg("--force"); |
| shred.AddArg("--zero"); |
| base::FileEnumerator stateful_files(stateful_, /*recursive=*/true, |
| base::FileEnumerator::FileType::FILES); |
| for (base::FilePath path = stateful_files.Next(); !path.empty(); |
| path = stateful_files.Next()) { |
| shred.AddArg(path.value()); |
| } |
| shred.RedirectOutputToMemory(true); |
| shred.Run(); |
| init::AppendToLog("shred", shred.GetOutputString(STDOUT_FILENO)); |
| |
| sync(); |
| } |
| |
| bool ClobberState::WipeKeyMaterial() { |
| // Delete all of the top-level key files. |
| std::vector<std::string> key_files{ |
| "encrypted.key", "encrypted.needs-finalization", |
| "home/.shadow/cryptohome.key", "home/.shadow/salt", |
| "home/.shadow/salt.sum"}; |
| bool found_file = false; |
| for (const std::string& str : key_files) { |
| base::FilePath path = stateful_.Append(str); |
| if (base::PathExists(path)) { |
| found_file = true; |
| if (!clobber_wipe_->SecureErase(path)) { |
| LOG(ERROR) << "Securely erasing file failed: " << path.value(); |
| return false; |
| } |
| } |
| } |
| |
| // Delete user-specific keyfiles in individual user shadow directories. |
| base::FileEnumerator directories(stateful_.Append("home/.shadow"), |
| /*recursive=*/false, |
| base::FileEnumerator::FileType::DIRECTORIES); |
| for (base::FilePath user_dir = directories.Next(); !user_dir.empty(); |
| user_dir = directories.Next()) { |
| std::vector<base::FilePath> files_to_erase; |
| // Find old-style vault keyset files. This support can be removed once |
| // cryptohomed no longer has support for reading from VaultKeyset files. |
| base::FileEnumerator vk_files(user_dir, /*recursive=*/false, |
| base::FileEnumerator::FileType::FILES); |
| for (base::FilePath file = vk_files.Next(); !file.empty(); |
| file = vk_files.Next()) { |
| if (file.RemoveFinalExtension().BaseName() == base::FilePath("master")) { |
| files_to_erase.push_back(std::move(file)); |
| } |
| } |
| // Find new-style auth factor files. |
| base::FileEnumerator af_files(user_dir.Append("auth_factors"), |
| /*recursive=*/false, |
| base::FileEnumerator::FileType::FILES); |
| for (base::FilePath file = af_files.Next(); !file.empty(); |
| file = af_files.Next()) { |
| files_to_erase.push_back(std::move(file)); |
| } |
| // Find user secret stashes. |
| base::FileEnumerator uss_files( |
| user_dir.Append("user_secret_stash"), |
| /*recursive=*/false, base::FileEnumerator::FileType::FILES, "uss.*"); |
| for (base::FilePath file = uss_files.Next(); !file.empty(); |
| file = uss_files.Next()) { |
| files_to_erase.push_back(std::move(file)); |
| } |
| // Try to erase all of the found files. |
| for (const base::FilePath& file : files_to_erase) { |
| found_file = true; |
| if (!clobber_wipe_->SecureErase(file)) { |
| LOG(ERROR) << "Securely erasing file failed: " << file.value(); |
| return false; |
| } |
| } |
| } |
| |
| // If no files were found, then we can't say whether or not secure erase |
| // works. Assume it doesn't. |
| if (!found_file) { |
| LOG(WARNING) << "No files existed to attempt secure erase"; |
| return false; |
| } |
| |
| return clobber_wipe_->DropCaches(); |
| } |
| |
| void ClobberState::ForceDelay() { |
| base::TimeDelta elapsed = base::TimeTicks::Now() - wipe_start_time_; |
| LOG(INFO) << "Clobber has already run for " << elapsed.InSeconds() |
| << " seconds"; |
| base::TimeDelta remaining = kMinClobberDuration - elapsed; |
| if (remaining <= base::Seconds(0)) { |
| LOG(INFO) << "Skipping forced delay"; |
| return; |
| } |
| LOG(INFO) << "Forcing a delay of " << remaining.InSeconds() << " seconds"; |
| if (!ui_->ShowCountdownTimer(remaining)) { |
| // If showing the timer failed, we still want to make sure that we don't |
| // run for less than |kMinClobberDuration|. |
| base::PlatformThread::Sleep(remaining); |
| } |
| } |
| |
| void ClobberState::SetArgsForTest(const ClobberState::Arguments& args) { |
| args_ = args; |
| } |
| |
| ClobberState::Arguments ClobberState::GetArgsForTest() { |
| return args_; |
| } |
| |
| void ClobberState::SetStatefulForTest(const base::FilePath& stateful_path) { |
| stateful_ = stateful_path; |
| } |
| |
| void ClobberState::SetRootPathForTest(const base::FilePath& root_path) { |
| root_path_ = root_path; |
| } |
| |
| bool ClobberState::ClearBiometricSensorEntropy() { |
| if (base::PathExists(base::FilePath(kBioWashPath))) { |
| brillo::ProcessImpl bio_wash; |
| bio_wash.AddArg(kBioWashPath); |
| return bio_wash.Run() == 0; |
| } |
| // Return true here so that we don't report spurious failures on platforms |
| // without the bio_wash executable. |
| return true; |
| } |
| |
| void ClobberState::Reboot() { |
| brillo::ProcessImpl proc; |
| proc.AddArg("/sbin/shutdown"); |
| proc.AddArg("-r"); |
| proc.AddArg("now"); |
| int ret = proc.Run(); |
| if (ret == 0) { |
| // Wait for reboot to finish (it's an async call). |
| sleep(60 * 60 * 24); |
| } |
| // If we've reached here, reboot (probably) failed. |
| LOG(ERROR) << "Requesting reboot failed with failure code " << ret; |
| } |
| |
| void ClobberState::ResetStatefulPartition() { |
| // Attempt to remove the logical volume stack unconditionally: this covers the |
| // situation where a device may rollback to a version that doesn't support |
| // the LVM stateful partition setup. |
| if (clobber_lvm_) |
| clobber_lvm_->RemoveLogicalVolumeStack( |
| wipe_info_.stateful_partition_device); |
| |
| // Destroy user data: wipe the stateful partition. |
| if (!clobber_wipe_->WipeDevice(wipe_info_.stateful_partition_device)) { |
| LOG(ERROR) << "Unable to wipe device " |
| << wipe_info_.stateful_partition_device.value(); |
| } |
| } |