| // 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 "update_engine/cros/install_action.h" | |
| #include <inttypes.h> | |
| #include <string> | |
| #include <utility> | |
| #include <base/files/file_path.h> | |
| #include <base/files/file_util.h> | |
| #include <base/logging.h> | |
| #include <base/strings/string_number_conversions.h> | |
| #include <chromeos/constants/imageloader.h> | |
| #include <crypto/secure_hash.h> | |
| #include <crypto/sha2.h> | |
| #include <libimageloader/manifest.h> | |
| #include "update_engine/common/boot_control.h" | |
| #include "update_engine/common/dlcservice_interface.h" | |
| #include "update_engine/common/system_state.h" | |
| #include "update_engine/common/utils.h" | |
| #include "update_engine/cros/image_properties.h" | |
| namespace chromeos_update_engine { | |
| namespace { | |
| constexpr char kBandaidUrl[] = "https://edgedl.me.gvt1.com/edgedl/dlc"; | |
| constexpr char kLorryUrl[] = "https://dl.google.com/dlc"; | |
| constexpr char kBandaidArtifactsMetaUrl[] = "https://edgedl.me.gvt1.com/edgedl"; | |
| constexpr char kLorryArtifactsMetaUrl[] = "https://dl.google.com"; | |
| constexpr char kUrlNameBandaid[] = "<BANDAID>"; | |
| constexpr char kUrlNameLorry[] = "<LORRY>"; | |
| constexpr char kUrlNameBandaidArtifactsMeta[] = "<BANDAID_ARTIFACTS_META>"; | |
| constexpr char kUrlNameLorryArtifactsMeta[] = "<LORRY_ARTIFACTS_META>"; | |
| constexpr char kDefaultArtifact[] = "dlc.img"; | |
| constexpr char kDefaultPackage[] = "package"; | |
| constexpr char kRedactedDlcPartition[] = "<REDACTED_PARTITION>"; | |
| } // namespace | |
| const char kDefaultSlotting[] = "dlc-scaled"; | |
| const char kForceOTASlotting[] = "dlc"; | |
| InstallAction::InstallAction(std::unique_ptr<HttpFetcher> http_fetcher, | |
| const std::string& id, | |
| const std::string& slotting, | |
| const std::string& manifest_dir) | |
| : http_fetcher_(std::move(http_fetcher)), id_(id) { | |
| slotting_ = slotting.empty() ? kDefaultSlotting : slotting; | |
| manifest_dir_ = | |
| manifest_dir.empty() ? imageloader::kDlcManifestRootpath : manifest_dir; | |
| } | |
| InstallAction::~InstallAction() {} | |
| void InstallAction::PerformAction() { | |
| LOG(INFO) << "InstallAction performing action."; | |
| manifest_ = SystemState::Get()->dlc_utils()->GetDlcManifest( | |
| id_, base::FilePath(manifest_dir_)); | |
| if (!manifest_) { | |
| LOG(ERROR) << "Could not retrieve manifest for " << id_; | |
| processor_->ActionComplete(this, ErrorCode::kScaledInstallationError); | |
| return; | |
| } | |
| image_props_ = LoadImageProperties(); | |
| http_fetcher_->set_delegate(this); | |
| bool user_tied = manifest_->user_tied(); | |
| if (user_tied) | |
| http_fetcher_->set_payload_info_visible(false); | |
| // Get the DLC device partition. | |
| auto partition_name = | |
| base::FilePath("dlc").Append(id_).Append(kDefaultPackage).value(); | |
| auto* boot_control = SystemState::Get()->boot_control(); | |
| std::string partition; | |
| const auto& sanitized_id = manifest_->sanitized_id(); | |
| if (!boot_control->GetPartitionDevice( | |
| partition_name, boot_control->GetCurrentSlot(), &partition)) { | |
| LOG(ERROR) << "Could not retrieve device partition for " << sanitized_id; | |
| processor_->ActionComplete(this, ErrorCode::kScaledInstallationError); | |
| return; | |
| } | |
| const auto& sanitized_partition = | |
| user_tied ? kRedactedDlcPartition : partition; | |
| f_.Initialize(base::FilePath(partition), base::File::Flags::FLAG_OPEN | | |
| base::File::Flags::FLAG_READ | | |
| base::File::Flags::FLAG_WRITE); | |
| if (!f_.IsValid()) { | |
| LOG(ERROR) << "Could not open device partition for " << sanitized_id | |
| << " at " << sanitized_partition; | |
| processor_->ActionComplete(this, ErrorCode::kScaledInstallationError); | |
| return; | |
| } | |
| LOG(INFO) << "Installing to " << sanitized_partition; | |
| std::string url_to_fetch; | |
| std::string sanitized_url; | |
| const auto& artifacts_meta = manifest_->artifacts_meta(); | |
| if (artifacts_meta.valid) { | |
| auto UrlToFetch = [artifacts_meta](const std::string& url) -> std::string { | |
| return base::FilePath(url) | |
| .Append(artifacts_meta.uri) | |
| .Append(kDefaultArtifact) | |
| .value(); | |
| }; | |
| url_to_fetch = UrlToFetch(kBandaidArtifactsMetaUrl); | |
| sanitized_url = user_tied ? kUrlNameBandaidArtifactsMeta : url_to_fetch; | |
| backup_urls_ = {UrlToFetch(kLorryArtifactsMetaUrl)}; | |
| backup_urls_sanitized_ = | |
| user_tied ? std::vector<std::string>({kUrlNameLorryArtifactsMeta}) | |
| : backup_urls_; | |
| } else { | |
| auto UrlToFetch = [this](const std::string& url) -> std::string { | |
| return base::FilePath(url) | |
| .Append(image_props_.builder_path) | |
| .Append(slotting_) | |
| .Append(id_) | |
| .Append(kDefaultPackage) | |
| .Append(kDefaultArtifact) | |
| .value(); | |
| }; | |
| url_to_fetch = UrlToFetch(kBandaidUrl); | |
| sanitized_url = user_tied ? kUrlNameBandaid : url_to_fetch; | |
| backup_urls_ = {UrlToFetch(kLorryUrl)}; | |
| backup_urls_sanitized_ = | |
| user_tied ? std::vector<std::string>({kUrlNameLorry}) : backup_urls_; | |
| } | |
| StartInstallation(url_to_fetch, sanitized_url); | |
| } | |
| void InstallAction::TerminateProcessing() { | |
| http_fetcher_->TerminateTransfer(); | |
| } | |
| bool InstallAction::ReceivedBytes(HttpFetcher* fetcher, | |
| const void* bytes, | |
| size_t length) { | |
| uint64_t new_offset = offset_ + length; | |
| // Overflow upper bound check against manifest. | |
| if (new_offset > manifest_->size()) { | |
| LOG(ERROR) << "Overflow of bytes, terminating."; | |
| http_fetcher_->TerminateTransfer(); | |
| return false; | |
| } | |
| if (delegate()) { | |
| delegate_->BytesReceived(new_offset, manifest_->size()); | |
| } | |
| hash_->Update(bytes, length); | |
| int64_t total_written_bytes = 0; | |
| do { | |
| int written_bytes = | |
| f_.Write(offset_ + total_written_bytes, | |
| static_cast<const char*>(bytes) + total_written_bytes, | |
| length - total_written_bytes); | |
| if (written_bytes == -1) { | |
| PLOG(ERROR) << "Failed to write bytes."; | |
| http_fetcher_->TerminateTransfer(); | |
| return false; | |
| } | |
| total_written_bytes += written_bytes; | |
| } while (total_written_bytes != length); | |
| offset_ = new_offset; | |
| return true; | |
| } | |
| void InstallAction::TransferComplete(HttpFetcher* fetcher, bool successful) { | |
| if (!successful) { | |
| // Continue to use backup URLs. | |
| if (backup_url_index_ < backup_urls_.size()) { | |
| LOG(INFO) << "Using backup url at index=" << backup_url_index_; | |
| const auto& url = backup_urls_[backup_url_index_]; | |
| const auto& url_sanitized = backup_urls_sanitized_[backup_url_index_]; | |
| ++backup_url_index_; | |
| StartInstallation(url, url_sanitized); | |
| return; | |
| } | |
| LOG(ERROR) << "Transfer failed."; | |
| TerminateInstallation(); | |
| return; | |
| } | |
| auto expected_offset = manifest_->size(); | |
| if (offset_ != expected_offset) { | |
| LOG(ERROR) << "Transferred bytes offset (" << offset_ | |
| << ") don't match the expected offset (" | |
| << manifest_->sanitized_size() << ")."; | |
| TerminateInstallation(); | |
| return; | |
| } | |
| LOG(INFO) << "Transferred bytes offset (" << manifest_->sanitized_size() | |
| << ") is valid."; | |
| std::vector<uint8_t> sha256(crypto::kSHA256Length); | |
| hash_->Finish(sha256.data(), sha256.size()); | |
| auto expected_sha256 = manifest_->image_sha256(); | |
| auto expected_sha256_str = | |
| base::HexEncode(expected_sha256.data(), expected_sha256.size()); | |
| if (sha256 != expected_sha256) { | |
| LOG(ERROR) << "Transferred bytes hash (" | |
| << base::HexEncode(sha256.data(), sha256.size()) | |
| << ") don't match the expected hash (" | |
| << manifest_->sanitized_image_sha256() << ")."; | |
| TerminateInstallation(); | |
| return; | |
| } | |
| LOG(INFO) << "Transferred bytes hash (" << manifest_->sanitized_image_sha256() | |
| << ") is valid."; | |
| processor_->ActionComplete(this, ErrorCode::kSuccess); | |
| } | |
| void InstallAction::TransferTerminated(HttpFetcher* fetcher) { | |
| LOG(ERROR) << "Failed to complete transfer."; | |
| TerminateInstallation(); | |
| } | |
| void InstallAction::StartInstallation(const std::string& url_to_fetch, | |
| const std::string& sanitized_url) { | |
| LOG(INFO) << "Starting installation using URL=" << sanitized_url; | |
| offset_ = 0; | |
| hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256)); | |
| http_fetcher_->SetOffset(0); | |
| http_fetcher_->UnsetLength(); | |
| http_fetcher_->BeginTransfer(url_to_fetch); | |
| } | |
| void InstallAction::TerminateInstallation() { | |
| processor_->ActionComplete(this, ErrorCode::kScaledInstallationError); | |
| } | |
| } // namespace chromeos_update_engine |