| // 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 "rmad/executor/executor.h" |
| |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <cctype> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/check.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/functional/bind.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/time/time.h> |
| #include <brillo/asynchronous_signal_handler.h> |
| #include <brillo/file_utils.h> |
| |
| #include "rmad/constants.h" |
| #include "rmad/executor/mojom/executor.mojom.h" |
| #include "rmad/executor/mount.h" |
| #include "rmad/utils/cmd_utils_impl.h" |
| #include "rmad/utils/crossystem_utils.h" |
| #include "rmad/utils/crossystem_utils_impl.h" |
| #include "rmad/utils/ec_utils_impl.h" |
| #include "rmad/utils/futility_utils_impl.h" |
| |
| namespace { |
| |
| // The executor process runs in a light sandbox with /tmp mounted as tmpfs. |
| constexpr char kTmpPath[] = "/tmp"; |
| |
| constexpr char kDevicePathFormat[] = "/dev/sd%c%d"; |
| constexpr char kSourceFirmwareUpdaterRelPath[] = |
| "usr/sbin/chromeos-firmwareupdate"; |
| constexpr char kTargetFirmwareUpdaterAbsPath[] = |
| "/var/lib/rmad/chromeos-firmwareupdate"; |
| constexpr char kSourceDiagnosticsAppSwbnRelPath[] = "diagnostics_app.swbn"; |
| constexpr char kSourceDiagnosticsAppCrxRelPath[] = "diagnostics_app.crx"; |
| constexpr char kTargetDiagnosticsAppSwbnAbsPath[] = |
| "/var/lib/rmad/diagnostics_app.swbn"; |
| constexpr char kTargetDiagnosticsAppCrxAbsPath[] = |
| "/var/lib/rmad/diagnostics_app.crx"; |
| |
| // chronos uid and gid. |
| constexpr uid_t kChronosUid = 1000; |
| constexpr gid_t kChronosGid = 1000; |
| |
| // Partition for stateful partition. |
| constexpr int kStatefulPartitionIndex = 1; |
| // Partition for rootfs A in a ChromeOS image. We don't check rootfs B. |
| constexpr int kRootfsPartitionIndex = 3; |
| |
| // Log file format. |
| constexpr char kDirectoryNameFormat[] = "rma-logs-%s"; |
| constexpr char kTextLogFilename[] = "text-log.txt"; |
| constexpr char kJsonLogFilename[] = "json-log.json"; |
| constexpr char kSystemLogFilename[] = "system-log.txt"; |
| // Supported file systems for stateful partition. |
| const std::vector<std::string> kStatefulFileSystems = {"vfat", "ext4", "ext3", |
| "ext2"}; |
| // Powerwash related constants. |
| constexpr char kPowerwashRequestFilePath[] = |
| "/mnt/stateful_partition/factory_install_reset"; |
| constexpr char kRmaPowerwashArgs[] = "fast safe keepimg rma factory"; |
| |
| // Reset FpMCU entropy. |
| constexpr char kDefaultBioWashPath[] = "/usr/bin/bio_wash"; |
| |
| std::string FormatTime(const base::Time& time) { |
| base::Time::Exploded e; |
| time.UTCExplode(&e); |
| // ISO 8601 format. |
| return base::StringPrintf("%04d%02d%02dT%02d%02d%02dZ", e.year, e.month, |
| e.day_of_month, e.hour, e.minute, e.second); |
| } |
| |
| rmad::Mount TryMount(const base::FilePath& device_file, |
| const base::FilePath& mount_point, |
| const std::vector<std::string>& fs_types, |
| bool read_only) { |
| for (const std::string& fs_type : fs_types) { |
| rmad::Mount mount(device_file, mount_point, fs_type, read_only); |
| if (mount.IsValid()) { |
| return mount; |
| } |
| } |
| return rmad::Mount(); |
| } |
| |
| bool CopyAndChown(const base::FilePath& from_path, |
| const base::FilePath& to_path, |
| uid_t uid, |
| gid_t gid) { |
| if (!base::CopyFile(from_path, to_path)) { |
| LOG(ERROR) << "Failed to copy " << from_path.value() << " to " |
| << to_path.value(); |
| return false; |
| } |
| if (chown(to_path.value().c_str(), uid, gid) != 0) { |
| PLOG(ERROR) << "Failed to chown file " << to_path.value() |
| << " with uid = " << uid << " and gid = " << gid; |
| return false; |
| } |
| return true; |
| } |
| |
| bool WriteStringToFileAtomic(const base::FilePath& path, |
| const std::string& data) { |
| if (!brillo::WriteStringToFile(path, data)) { |
| LOG(ERROR) << "Failed to write " << path.value(); |
| return false; |
| } |
| if (!brillo::SyncFileOrDirectory(path, /*is_directory=*/false, |
| /*data_sync=*/false)) { |
| LOG(ERROR) << "Failed to sync " << path.value(); |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace rmad { |
| |
| Executor::Executor( |
| mojo::PendingReceiver<chromeos::rmad::mojom::Executor> receiver) |
| : receiver_{this, std::move(receiver)} { |
| // Quit the executor when the communication disconnects. |
| receiver_.set_disconnect_handler( |
| base::BindOnce([]() { std::exit(EXIT_SUCCESS); })); |
| ec_utils_ = std::make_unique<EcUtilsImpl>(); |
| cmd_utils_ = std::make_unique<CmdUtilsImpl>(); |
| crossystem_utils_ = std::make_unique<CrosSystemUtilsImpl>(); |
| futility_utils_ = std::make_unique<FutilityUtilsImpl>(); |
| } |
| |
| void Executor::MountAndWriteLog(uint8_t device_id, |
| const std::string& text_log, |
| const std::string& json_log, |
| const std::string& system_log, |
| MountAndWriteLogCallback callback) { |
| // Input argument check. |
| if (!islower(device_id)) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| // Create temporary mount point. |
| base::ScopedTempDir temp_dir; |
| if (!temp_dir.CreateUniqueTempDirUnderPath(base::FilePath(kTmpPath))) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| |
| const base::FilePath device_path(base::StringPrintf( |
| kDevicePathFormat, device_id, kStatefulPartitionIndex)); |
| const base::FilePath mount_point = temp_dir.GetPath(); |
| const Mount mount = |
| TryMount(device_path, mount_point, kStatefulFileSystems, false); |
| if (mount.IsValid()) { |
| const std::string directory_name = base::StringPrintf( |
| kDirectoryNameFormat, FormatTime(base::Time::Now()).c_str()); |
| const base::FilePath directory_filepath = |
| mount_point.Append(directory_name); |
| if (!base::CreateDirectory(directory_filepath)) { |
| return; |
| } |
| |
| const base::FilePath system_log_path = |
| directory_filepath.Append(kSystemLogFilename); |
| if (!WriteStringToFileAtomic(system_log_path, system_log)) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| |
| const base::FilePath json_log_path = |
| directory_filepath.Append(kJsonLogFilename); |
| if (!WriteStringToFileAtomic(json_log_path, json_log)) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| |
| const base::FilePath text_log_path = |
| directory_filepath.Append(kTextLogFilename); |
| if (!WriteStringToFileAtomic(text_log_path, text_log.c_str())) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| |
| // The full log path is not useful because the mount point is a temporary |
| // directory. Returning the directory containing the logs is enough. |
| std::move(callback).Run(directory_name); |
| return; |
| } |
| std::move(callback).Run(std::nullopt); |
| } |
| |
| void Executor::CopyRootfsFirmwareUpdater( |
| CopyRootfsFirmwareUpdaterCallback callback) { |
| const base::FilePath source_updater_path = |
| base::FilePath{"/"}.Append(kSourceFirmwareUpdaterRelPath); |
| const base::FilePath target_updater_path(kTargetFirmwareUpdaterAbsPath); |
| if (base::PathExists(source_updater_path) && |
| base::CopyFile(source_updater_path, target_updater_path)) { |
| brillo::SyncFileOrDirectory(base::FilePath(target_updater_path), |
| /*is_directory=*/false, /*data_sync=*/false); |
| std::move(callback).Run(true); |
| return; |
| } |
| std::move(callback).Run(false); |
| } |
| |
| void Executor::MountAndCopyFirmwareUpdater( |
| uint8_t device_id, MountAndCopyFirmwareUpdaterCallback callback) { |
| // Input argument check. |
| if (!islower(device_id)) { |
| std::move(callback).Run(false); |
| return; |
| } |
| // Create temporary mount point. |
| base::ScopedTempDir temp_dir; |
| if (!temp_dir.CreateUniqueTempDirUnderPath(base::FilePath(kTmpPath))) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| const base::FilePath device_path( |
| base::StringPrintf(kDevicePathFormat, device_id, kRootfsPartitionIndex)); |
| const base::FilePath mount_point = temp_dir.GetPath(); |
| const Mount mount(device_path, mount_point, "ext2", true); |
| if (mount.IsValid()) { |
| const base::FilePath source_updater_path = |
| mount_point.Append(kSourceFirmwareUpdaterRelPath); |
| const base::FilePath target_updater_path(kTargetFirmwareUpdaterAbsPath); |
| if (base::PathExists(source_updater_path) && |
| base::CopyFile(source_updater_path, target_updater_path)) { |
| brillo::SyncFileOrDirectory(base::FilePath(target_updater_path), |
| /*is_directory=*/false, /*data_sync=*/false); |
| std::move(callback).Run(true); |
| return; |
| } |
| } |
| std::move(callback).Run(false); |
| } |
| |
| void Executor::MountAndCopyDiagnosticsApp( |
| uint8_t device_id, MountAndCopyDiagnosticsAppCallback callback) { |
| // Input argument check. |
| if (!islower(device_id)) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| // Create temporary mount point. |
| base::ScopedTempDir temp_dir; |
| if (!temp_dir.CreateUniqueTempDirUnderPath(base::FilePath(kTmpPath))) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| const base::FilePath device_path(base::StringPrintf( |
| kDevicePathFormat, device_id, kStatefulPartitionIndex)); |
| const base::FilePath mount_point = temp_dir.GetPath(); |
| const Mount mount = TryMount(device_path, mount_point, kStatefulFileSystems, |
| /*read_only=*/true); |
| if (mount.IsValid()) { |
| const base::FilePath from_swbn_path = |
| mount_point.Append(kSourceDiagnosticsAppSwbnRelPath); |
| const base::FilePath from_crx_path = |
| mount_point.Append(kSourceDiagnosticsAppCrxRelPath); |
| const base::FilePath to_swbn_path(kTargetDiagnosticsAppSwbnAbsPath); |
| const base::FilePath to_crx_path(kTargetDiagnosticsAppCrxAbsPath); |
| if (base::PathExists(from_swbn_path) && base::PathExists(from_crx_path) && |
| CopyAndChown(from_swbn_path, to_swbn_path, kChronosUid, kChronosGid) && |
| CopyAndChown(from_crx_path, to_crx_path, kChronosUid, kChronosGid)) { |
| // Send out the reply first. |
| auto info = chromeos::rmad::mojom::DiagnosticsAppInfo::New( |
| to_swbn_path.value(), to_crx_path.value()); |
| std::move(callback).Run(std::move(info)); |
| // Then sync the files. |
| if (!brillo::SyncFileOrDirectory(to_swbn_path, /*is_directory=*/false, |
| /*data_sync=*/false)) { |
| LOG(ERROR) << "Failed to sync " << to_swbn_path.value(); |
| } |
| if (!brillo::SyncFileOrDirectory(to_crx_path, /*is_directory=*/false, |
| /*data_sync=*/false)) { |
| LOG(ERROR) << "Failed to sync " << to_crx_path.value(); |
| } |
| return; |
| } |
| } |
| std::move(callback).Run(nullptr); |
| } |
| |
| void Executor::RebootEc(RebootEcCallback callback) { |
| std::move(callback).Run(ec_utils_->Reboot()); |
| } |
| |
| void Executor::RequestRmaPowerwash(RequestRmaPowerwashCallback callback) { |
| const base::FilePath powerwash_file_path(kPowerwashRequestFilePath); |
| if (!WriteStringToFileAtomic(powerwash_file_path, kRmaPowerwashArgs)) { |
| std::move(callback).Run(false); |
| return; |
| } |
| std::move(callback).Run(true); |
| } |
| |
| void Executor::RequestBatteryCutoff(RequestBatteryCutoffCallback callback) { |
| if (!crossystem_utils_->SetInt(CrosSystemUtils::kBatteryCutoffRequestProperty, |
| 1)) { |
| std::move(callback).Run(false); |
| return; |
| } |
| std::move(callback).Run(true); |
| } |
| |
| void Executor::ResetFpmcuEntropy(ResetFpmcuEntropyCallback callback) { |
| if (std::string output; |
| !cmd_utils_->GetOutputAndError({kDefaultBioWashPath}, &output)) { |
| LOG(ERROR) << "Failed to reset FpMCU entropy with: " << kDefaultBioWashPath; |
| LOG(ERROR) << output; |
| std::move(callback).Run(false); |
| return; |
| } |
| std::move(callback).Run(true); |
| } |
| |
| void Executor::GetFlashInfo(GetFlashInfoCallback callback) { |
| auto info = futility_utils_->GetFlashInfo(); |
| if (!info.has_value()) { |
| std::move(callback).Run(nullptr); |
| } |
| |
| auto result = chromeos::rmad::mojom::FlashInfo::New(info.value().flash_name, |
| info.value().wpsr_start, |
| info.value().wpsr_length); |
| std::move(callback).Run(std::move(result)); |
| } |
| |
| void Executor::PreseedRmaState(PreseedRmaStateCallback callback) { |
| sync(); |
| |
| // Preseed rmad state file so it can be preserved across TPM reset. |
| if (std::string output; |
| !cmd_utils_->GetOutputAndError({kPreserveRmaStateHelperPath}, &output)) { |
| LOG(ERROR) |
| << "Failed to call preserve_rma_state for preserving rma state file: " |
| << output; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| std::move(callback).Run(true); |
| } |
| |
| } // namespace rmad |