blob: 1f127de996cc97b39c43c33cb44a6e8c0327408d [file] [log] [blame]
// 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