| // Copyright 2021 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <flasher/device.hpp> |
| #include <flasher/file.hpp> |
| #include <flasher/file/memory.hpp> |
| #include <flasher/mod.hpp> |
| #include <flasher/ops.hpp> |
| #include <flashupdate/args.hpp> |
| #include <flashupdate/flash.hpp> |
| #include <flashupdate/info.hpp> |
| #include <flashupdate/logging.hpp> |
| #include <flashupdate/ops.hpp> |
| #include <flashupdate/validator.hpp> |
| #include <flashupdate/version.hpp> |
| |
| #include <filesystem> |
| #include <format> |
| #include <string> |
| |
| namespace flashupdate |
| { |
| namespace ops |
| { |
| |
| using flasher::ModArgs; |
| using stdplus::fd::OpenAccess; |
| using stdplus::fd::OpenFlag; |
| using stdplus::fd::OpenFlags; |
| |
| inline void writeWithoutValidation( |
| flash::Flash* flashHelper, flasher::Reader& inputImage, |
| std::span<std::pair<size_t, size_t>> partitions) |
| { |
| // Secondary partition only. |
| auto flash = flashHelper->getFlash(false, inputImage.getSize()); |
| if (!flash) |
| { |
| throw std::runtime_error( |
| std::format("failed to find secondary Flash partition")); |
| } |
| |
| // Validate the image currently in the flash. |
| // It can be the primary or secondary flash. |
| std::string flashDev(flash->first); |
| auto devMod = ModArgs(flashDev); |
| flashDev = devMod.arr.back(); |
| flasher::NestedMutate mutate{}; |
| auto dev = flasher::openDevice(devMod); |
| LOG(LogLevel::Info, "Flash image to {}\n", flashDev); |
| for (auto& [offset, size] : partitions) |
| { |
| flasher::ops::automatic(/*dst=*/*dev, /*dst_offset=*/offset, |
| /*src=*/inputImage, /*src_offset=*/offset, |
| mutate, /*max_size=*/size, std::nullopt, |
| /*noread=*/false); |
| } |
| } |
| |
| void writeHelper(const Args& args, std::string_view image, |
| flasher::Reader& inputImage) |
| { |
| std::vector<std::pair<size_t, size_t>> partitions; |
| size_t size = inputImage.getSize(); |
| |
| // Write without validation only if we are skipping validation and not |
| // updating the staging index. The staging index is the index that points to |
| // the partition that is allowed to write to the primary partition and |
| // validation is required to update it. |
| if (args.skipValidation && !args.updateStagingIndex) |
| { |
| if (args.primary) |
| { |
| throw std::runtime_error( |
| std::format("Cannot skip validation on primary partition")); |
| } |
| |
| args.flashHelper->setup(args.config, args.keepMux); |
| partitions.emplace_back(0, size); |
| writeWithoutValidation(args.flashHelper, inputImage, partitions); |
| return; |
| } |
| |
| auto& helper = args.validatorHelper; |
| if (helper == nullptr) |
| { |
| throw std::runtime_error("invalid Validator Helper"); |
| } |
| |
| if (!helper->validateImage(inputImage, args.config.flash.validationKey)) |
| { |
| throw std::runtime_error(std::format( |
| "failed to validate the CR51 descriptor for the next image: {}", |
| image)); |
| } |
| |
| if (args.config.supportedVersion != std::nullopt) |
| { |
| std::string versionStr = helper->imageVersion(); |
| auto version = version::Version(versionStr); |
| |
| bool validVersion = true; |
| for (const auto& supportedVersion : *args.config.supportedVersion) |
| { |
| if ( |
| // Less than min version |
| (supportedVersion.min != std::nullopt && |
| version < *supportedVersion.min) || |
| // greater than or equal to max version |
| (supportedVersion.max != std::nullopt && |
| version >= *supportedVersion.max)) |
| { |
| validVersion = false; |
| break; |
| } |
| } |
| |
| if (!validVersion) |
| { |
| throw std::runtime_error(std::format( |
| "Next image's version is not supported for {}", versionStr)); |
| } |
| } |
| |
| auto& flashHelper = args.flashHelper; |
| flashHelper->setup(args.config, args.keepMux); |
| |
| auto flash = flashHelper->getFlash(args.primary, size); |
| if (!flash) |
| { |
| throw std::runtime_error("failed to find Flash partition"); |
| } |
| |
| // Save the information from the next image |
| auto nextDescriptorHashSpan = helper->descriptorHash().hash; |
| std::vector<uint8_t> nextDescriptorHash(nextDescriptorHashSpan.begin(), |
| nextDescriptorHashSpan.end()); |
| auto nextIsProd = helper->prodImage(); |
| |
| // Validate the image currently in the flash. |
| // It can be the primary or secondary flash. |
| std::string flashDev(flash->first); |
| auto devMod = ModArgs(flashDev); |
| flashDev = devMod.arr.back(); |
| |
| // Fetch BIOS Staging Information |
| auto info = fetchInfo(args); |
| |
| // Check Image Descriptor Hash |
| // Prevent flashing the image to primary partition if the hash of image |
| // descriptor does not match the cached. |
| // If the primary partition is invalid, skip the check and allow firmware |
| // update. |
| if (args.primary) |
| { |
| auto metadataDescriptor = info.stageDescriptor; |
| std::string flashDev(flash->first); |
| |
| bool currentIsProd = args.validatorHelper->isProdImage( |
| devMod, metadataDescriptor.offset, metadataDescriptor.size); |
| |
| // Flash Support only for |
| // Prod <-> Prod |
| // Dev -> Prod |
| // Dev <-> Dev |
| // The Prod to dev is controlled through the image validation config. |
| if (!args.config.cr51->prodToDev && currentIsProd && !nextIsProd) |
| { |
| throw std::logic_error("Prod to dev update is not enabled."); |
| } |
| |
| if (args.stagingIndex != info.stagingIndex) |
| { |
| throw std::logic_error( |
| std::format("The Staged Partition is not in the expected " |
| "partition: want {}, got {}", |
| info.stagingIndex, args.stagingIndex)); |
| } |
| |
| // Get the data to match the hash size in the installing image. |
| std::span<const uint8_t> expectedHash( |
| &(info.stageDescriptor.hash[0]), |
| &(info.stageDescriptor.hash[0]) + |
| std::min(static_cast<size_t>(SHA256_DIGEST_LENGTH), |
| nextDescriptorHash.size())); |
| |
| LOG(LogLevel::Info, "Checking HASH for CR51 descriptor between " |
| "staged partition and cache"); |
| |
| if (!std::equal(nextDescriptorHash.begin(), nextDescriptorHash.end(), |
| expectedHash.begin(), expectedHash.end())) |
| { |
| throw std::logic_error( |
| "SHA256 of the staged image in the cache " |
| "does not match the image in the staged partition"); |
| } |
| |
| // Insert the non-persistent regions |
| auto regions = helper->persistentRegions(); |
| size_t start = 0; |
| for (const auto& region : regions) |
| { |
| LOG(LogLevel::Notice, "Region: {}, Inject offset: {}, Length: {}\n", |
| region.name, region.offset, region.size); |
| if (start == region.offset) |
| { |
| start += region.size; |
| continue; |
| } |
| partitions.emplace_back(start, region.offset - start); |
| start = region.offset + region.size; |
| } |
| partitions.emplace_back(start, size - start); |
| } |
| // Not writing to active/primary partition. Write the whole image |
| else |
| { |
| partitions.emplace_back(0, size); |
| } |
| |
| LOG(LogLevel::Info, "finished setting up {} and {}", image, flashDev); |
| { |
| // All checks passed, continue to flash the image |
| flasher::NestedMutate mutate{}; |
| auto dev = flasher::openDevice(devMod); |
| LOG(LogLevel::Info, "Flash image to {}", flashDev); |
| for (auto& [offset, size] : partitions) |
| { |
| LOG(LogLevel::Debug, "Writing partition: offset {}, size {}", |
| offset, size); |
| flasher::ops::automatic(/*dst=*/*dev, /*dst_offset=*/offset, |
| /*src=*/inputImage, /*src_offset=*/offset, |
| mutate, /*max_size=*/size, std::nullopt, |
| /*noread=*/false); |
| } |
| |
| LOG(LogLevel::Info, "finished flashing {} to {}", image, flashDev); |
| // Validate the flash after writing the image |
| auto fileMod = ModArgs(flashDev); |
| auto nextImage = |
| flasher::openFile(fileMod, OpenFlags(OpenAccess::ReadOnly)); |
| if (!helper->validateImage(*nextImage, args.config.flash.validationKey)) |
| { |
| throw std::runtime_error(std::format( |
| "failed to validate the CR51 descriptor for the image " |
| "in the flash after overwriting it: {}", |
| image)); |
| } |
| } |
| info::UpdateInfo::State expectedState; |
| |
| // Update the Active Version the and State to UPDATED |
| if (args.primary) |
| { |
| info.active = version::Version(helper->imageVersion()); |
| expectedState = info::UpdateInfo::State::UPDATED; |
| info.state = static_cast<uint8_t>(expectedState); |
| } |
| else |
| // Update the Staged version the the State to STAGED. |
| // |
| // Flashing to secondary flash will save the has of CR51 image descriptor |
| // to the EEPROM for the final check when write to primary flash. |
| { |
| info.stage = version::Version(helper->imageVersion()); |
| expectedState = info::UpdateInfo::State::STAGED; |
| info.state = static_cast<uint8_t>(expectedState); |
| |
| // Update the Staging Index to make sure the it uses the same staged |
| // partition between the stage and active. |
| if (args.updateStagingIndex) |
| { |
| info.stagingIndex = args.stagingIndex; |
| } |
| |
| // Save the CR51 descriptor hash for the staged image |
| // The hash size could be 16, 32, or 64 bytes depending on the Hash |
| // type. Save up to 32 bytes (SHA256_DIGEST_LENGTH) of data. |
| auto descriptor = helper->descriptorHash(); |
| auto descriptorHashSize = std::min( |
| static_cast<size_t>(SHA256_DIGEST_LENGTH), descriptor.hash.size()); |
| memcpy(info.stageDescriptor.hash, descriptor.hash.data(), |
| descriptorHashSize); |
| info.stageDescriptor.offset = descriptor.offset; |
| info.stageDescriptor.size = descriptor.size; |
| } |
| |
| info::printUpdateInfo(args, info); |
| |
| auto findState = info::stateToString.find(info.state); |
| if (findState != info::stateToString.end()) |
| { |
| LOG(LogLevel::Notice, "Updated the staged BIOS to {}", |
| findState->second); |
| } |
| writeInfo(args, info); |
| |
| // Confirm the Update State is what we expected |
| info = fetchInfo(args); |
| if (info.state != static_cast<uint8_t>(expectedState)) |
| { |
| throw std::logic_error(std::format( |
| "the update state is not what we expected: want {}, got {}", |
| static_cast<uint8_t>(expectedState), info.state)); |
| } |
| } |
| |
| void write(const Args& args) |
| { |
| // Validate the next image before attempting to flash it |
| std::string image = args.file->arr.back(); |
| LOG(LogLevel::Notice, "Validate BIOS image: {}", image); |
| |
| auto fileMod = ModArgs(image); |
| auto inputImage = |
| flasher::openFile(fileMod, OpenFlags(OpenAccess::ReadOnly)); |
| |
| writeHelper(args, image, *inputImage); |
| } |
| |
| } // namespace ops |
| } // namespace flashupdate |