blob: eb292614e003422cc89ba4eb81e012de10274186 [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 "config.h"
#include <fcntl.h>
#include <libcr51sign/cr51_image_descriptor.h>
#include <libcr51sign/libcr51sign.h>
#include <libcr51sign/libcr51sign_support.h>
#include <sys/types.h>
#include <unistd.h>
#include <flasher/device.hpp>
#include <flasher/file.hpp>
#include <flasher/mod.hpp>
#include <flasher/mutate.hpp>
#include <flashupdate/info.hpp>
#include <flashupdate/logging.hpp>
#include <flashupdate/validator/cr51.hpp>
#include <stdplus/raw.hpp>
#include <format>
#include <memory>
#include <optional>
#include <span>
#include <stdexcept>
#include <string_view>
#include <utility>
#include <vector>
struct FdState
{
stdplus::ManagedFd fd;
std::optional<size_t> offset;
};
flasher::Reader* cr51Reader;
flasher::File* mauvManager;
int ReadFromFd(const void*, uint32_t offset, uint32_t count,
uint8_t* buf) noexcept
{
if (cr51Reader == nullptr)
{
return LIBCR51SIGN_ERROR_RUNTIME_FAILURE;
}
try
{
cr51Reader->readAtExact(
std::span<std::byte>{reinterpret_cast<std::byte*>(buf), count},
offset);
return LIBCR51SIGN_SUCCESS;
}
catch (const std::exception& e)
{
LOG(flashupdate::LogLevel::Error, "Reading {}", e.what());
return LIBCR51SIGN_ERROR_RUNTIME_FAILURE;
}
}
int ReadImageMauv(const void*, uint8_t* const mauv, uint32_t* const size,
const uint32_t maxSize) noexcept
{
if (maxSize > IMAGE_MAUV_DATA_MAX_SIZE)
{
return LIBCR51SIGN_ERROR_MAX;
}
if (mauvManager == nullptr)
{
return LIBCR51SIGN_NO_STORED_MAUV_FOUND;
}
try
{
mauvManager->readAtExact(
std::span<std::byte>{reinterpret_cast<std::byte*>(mauv),
sizeof(struct image_mauv)},
CR51_MAUV_OFFSET);
}
catch (const std::exception& e)
{
LOG(flashupdate::LogLevel::Error, "Reading MAUV {}\n", e.what());
return LIBCR51SIGN_ERROR_RETRIEVING_STORED_IMAGE_MAUV_DATA;
}
uint32_t mauvStructVersion;
memcpy(&mauvStructVersion, mauv, sizeof(mauvStructVersion));
if (mauvStructVersion != IMAGE_MAUV_STRUCT_VERSION)
{
return LIBCR51SIGN_NO_STORED_MAUV_FOUND;
}
struct image_mauv* mauvTmp = reinterpret_cast<struct image_mauv*>(mauv);
size_t versionDenylistOffset =
offsetof(struct image_mauv, version_denylist);
size_t denyListSize =
std::min(mauvTmp->version_denylist_num_entries * sizeof(uint64_t),
IMAGE_MAUV_DATA_MAX_SIZE - sizeof(struct image_mauv));
try
{
mauvManager->readAtExact(
std::span<std::byte>{
reinterpret_cast<std::byte*>(mauv + versionDenylistOffset),
// Deny List size.
std::min(mauvTmp->version_denylist_num_entries *
sizeof(uint64_t),
IMAGE_MAUV_DATA_MAX_SIZE - sizeof(struct image_mauv))},
CR51_MAUV_OFFSET + versionDenylistOffset);
}
catch (const std::exception& e)
{
LOG(flashupdate::LogLevel::Error, "Reading MAUV {}\n", e.what());
return LIBCR51SIGN_ERROR_RETRIEVING_STORED_IMAGE_MAUV_DATA;
}
*size = std::min(maxSize, static_cast<uint32_t>(sizeof(struct image_mauv) +
denyListSize));
return LIBCR51SIGN_SUCCESS;
}
int WriteImageMauv(const void*, const uint8_t* const mauv,
const uint32_t size) noexcept
{
if (mauvManager == nullptr)
{
return LIBCR51SIGN_NO_STORED_MAUV_FOUND;
}
try
{
auto data = std::vector<std::byte>(size);
memcpy(data.data(), mauv, size);
mauvManager->writeAtExact(data, CR51_MAUV_OFFSET);
}
catch (const std::exception& e)
{
LOG(flashupdate::LogLevel::Error, "Writing MAUV {}\n", e.what());
return LIBCR51SIGN_ERROR_STORING_NEW_IMAGE_MAUV_DATA;
}
return LIBCR51SIGN_SUCCESS;
}
// TODO: Remove after https://gerrit.openbmc.org/c/47332 is submitted.
namespace google
{
namespace cr51
{
std::span<const uint8_t>
Cr51SignValidatorIpml::hashDescriptor(struct libcr51sign_ctx* ctx,
std::span<std::byte> imageDescriptor)
{
// Create the HASH of the CR51 descriptor on the firmware
size_t size = 0;
hash_type hashType = static_cast<hash_type>(ctx->descriptor.hash_type);
switch (hashType)
{
case HASH_SHA2_224:
case HASH_SHA3_224:
size = SHA224_DIGEST_LENGTH;
break;
case HASH_SHA2_256:
case HASH_SHA3_256:
size = SHA256_DIGEST_LENGTH;
break;
case HASH_SHA2_384:
case HASH_SHA3_384:
size = SHA384_DIGEST_LENGTH;
break;
case HASH_SHA2_512:
case HASH_SHA3_512:
size = SHA512_DIGEST_LENGTH;
break;
case HASH_NONE:
default:
stdplus::println(stderr, "CR51 Hash type is not supported: type {}",
static_cast<int>(hashType));
return {};
break;
}
hash.resize(size);
int ec = hash_init(ctx, hashType);
if (ec)
{
stdplus::println(stderr, "CR51 Hash init error: ec{}", ec);
return {};
}
ec = hash_update(ctx,
reinterpret_cast<const uint8_t*>(imageDescriptor.data()),
ctx->descriptor.descriptor_area_size);
if (ec)
{
stdplus::println(stderr, "CR51 Hash update error: ec{}", ec);
return {};
}
ec = hash_final(ctx, hash.data());
if (ec)
{
stdplus::println(stderr, "CR51 Hash final error: ec{}", ec);
return {};
}
return hash;
}
std::optional<struct libcr51sign_validated_regions>
Cr51SignValidatorIpml::validateDescriptor(struct libcr51sign_ctx* ctx)
{
// Disabled stderr for all info messages
fpos_t pos;
// Save stderr location
fgetpos(stderr, &pos);
int fd = dup(fileno(stderr));
// Redirect them to /dev/null
if (!freopen("/dev/null", "w", stderr))
{
throw std::runtime_error("failed to redirect stderr to /dev/null");
}
struct libcr51sign_intf intf = {};
struct libcr51sign_validated_regions imageRegions;
enum libcr51sign_validation_failure_reason ec;
/* Common functions are from the libcr51sign support header. */
intf.hash_init = &hash_init;
intf.hash_update = &hash_update;
intf.hash_final = &hash_final;
intf.verify_signature = &verify_signature;
intf.read = &ReadFromFd;
intf.retrieve_stored_image_mauv_data = &ReadImageMauv;
intf.store_new_image_mauv_data = &WriteImageMauv;
auto returnTrue = []() { return true; };
auto returnFalse = []() { return false; };
intf.prod_to_dev_downgrade_allowed = prodToDev ? returnTrue : returnFalse;
intf.is_production_mode = productionMode ? returnTrue : returnFalse;
// TODO: Validate the non-static regions after all of the regions are signed
// This only applies to clean image that is not read from the flash
// directly.
ec = libcr51sign_validate(ctx, &intf, &imageRegions);
fflush(stderr);
dup2(fd, fileno(stderr)); // restore the stderr
close(fd);
clearerr(stderr);
// Move stderr to the normal location
fsetpos(stderr, &pos);
if (ec != LIBCR51SIGN_SUCCESS)
{
LOG(flashupdate::LogLevel::Notice, "CR51 Validate error: {}",
libcr51sign_errorcode_to_string(ec))
return std::nullopt;
}
return imageRegions;
}
} // namespace cr51
} // namespace google
namespace flashupdate
{
namespace validator
{
namespace cr51
{
using flasher::ModArgs;
using stdplus::fd::OpenAccess;
using stdplus::fd::OpenFlag;
using stdplus::fd::OpenFlags;
/** @brief Format the Image Version String
*
* @param[in] descriptor Cr51 image descriptor
*
* @return version of the image
*/
std::string formatImageVersion(const struct image_descriptor& descriptor)
{
return std::format("{}.{}.{}.{}", descriptor.image_major,
descriptor.image_minor, descriptor.image_point,
descriptor.image_subpoint);
}
bool Cr51::validateImage(flasher::Reader& reader,
const std::vector<std::string>& keys)
{
this->keys = keys;
return verify(reader);
}
std::string Cr51::imageVersion() const
{
return version;
}
bool Cr51::verify(flasher::Reader& reader)
{
LOG(LogLevel::Info, "Read CR51 Descriptor with size of {}",
reader.getSize());
cr51Reader = &reader;
valid = false;
context.start_offset = 0;
context.end_offset = static_cast<uint32_t>(reader.getSize());
context.current_image_family = static_cast<image_family>(imageFamily);
context.current_image_type = IMAGE_PROD;
context.keyring_len = kSignatureRsa4096Pkcs15KeyLength;
context.priv = &shaContext;
struct libcr51sign_validated_regions imageRegions;
if (keys.empty())
{
throw std::runtime_error("no valid validation key available");
}
std::unique_ptr<flasher::File> mauv = nullptr;
if (std::string(CR51_MAUV_PATH) != "")
{
auto mauvMod = ModArgs(CR51_MAUV_PATH);
mauv = flasher::openFile(mauvMod, OpenFlags(OpenAccess::ReadWrite));
mauvManager = mauv.get();
}
else
{
mauvManager = nullptr;
}
std::optional<libcr51sign_validated_regions> maybeImageRegions;
std::string usedKeys;
for (const auto& key : keys)
{
context.keyring = key.data();
maybeImageRegions = cr51Validator.validateDescriptor(&context);
if (maybeImageRegions)
{
LOG(LogLevel::Notice, "CR51 sign is valid using {}", key);
break;
}
usedKeys += key + ",";
}
mauvManager = nullptr;
if (!maybeImageRegions)
{
LOG(LogLevel::Crit, "CR51 sign is invalid for using all keys: {}",
usedKeys);
return false;
}
imageRegions = *maybeImageRegions;
LOG(LogLevel::Notice, "Passed CR51 Sign Validation");
valid = true;
regions = std::vector<struct image_region>(&imageRegions.image_regions[0],
&imageRegions.image_regions[0] +
imageRegions.region_count);
version = formatImageVersion(context.descriptor);
LOG(LogLevel::Notice, "BIOS Version: {}", version);
std::vector<std::byte> buf(context.descriptor.descriptor_area_size);
reader.readAt(buf, context.descriptor.descriptor_offset);
// Create the HASH of the CR51 descriptor on the BIOS
auto hashDescriptor = cr51Validator.hashDescriptor(&context, buf);
if (!hashDescriptor.empty())
{
hash =
std::vector<uint8_t>(hashDescriptor.begin(), hashDescriptor.end());
descriptorOffset = context.descriptor.descriptor_offset;
descriptorSize = context.descriptor.descriptor_area_size;
}
// Update the sign type for the BIOS image
this->prod = context.descriptor.image_type == image_type::IMAGE_PROD;
return true;
}
std::span<ImageRegion> Cr51::persistentRegions()
{
if (!valid)
return {};
persistentRegion.clear();
for (const auto& region : regions)
{
bool persistent =
(region.region_attributes &
(IMAGE_REGION_PERSISTENT | IMAGE_REGION_PERSISTENT_RELOCATABLE |
IMAGE_REGION_PERSISTENT_EXPANDABLE)) > 0;
auto region_name = reinterpret_cast<const char*>(region.region_name);
LOG(LogLevel::Debug,
"Partition Name: {}, Offset: {}, Size: {}, Persistent?: {}",
region_name, region.region_offset, region.region_size, persistent);
if (persistent)
{
persistentRegion.emplace_back(ImageRegion{
region_name, region.region_offset, region.region_size});
};
}
return persistentRegion;
}
std::pair<std::string, bool>
Cr51::validateHash(flasher::ModArgs mod, uint32_t offset, uint32_t size,
std::span<const uint8_t> expected)
{
auto readDev = flasher::openDevice(mod);
std::vector<std::byte> fileIn(size);
readDev->readAt(fileIn, offset);
struct image_descriptor descriptor =
*reinterpret_cast<struct image_descriptor*>(fileIn.data());
// Get the descriptor hash type from the target partition
context.descriptor.hash_type = descriptor.hash_type;
context.descriptor.descriptor_offset = 0,
context.descriptor.descriptor_area_size = size;
context.priv = &shaContext;
auto hashDescriptor = cr51Validator.hashDescriptor(&context, fileIn);
stdplus::println(
stderr, "validateHash: current {} vs. expected {}",
info::bytesToHex(std::span<const uint8_t>(
&hashDescriptor[0], &hashDescriptor[0] + SHA256_DIGEST_LENGTH)),
info::bytesToHex(std::span<const uint8_t>(
&expected[0], &expected[0] + SHA256_DIGEST_LENGTH)));
std::string version = formatImageVersion(descriptor);
bool validHash = hashDescriptor.size() == expected.size() &&
std::equal(hashDescriptor.begin(), hashDescriptor.end(),
expected.begin(), expected.end());
return {version, validHash};
}
std::string Cr51::fetchVersion(flasher::ModArgs mod, uint32_t offset,
uint32_t size)
{
return validator::cr51::formatImageVersion(
imageDescriptor(mod, offset, size));
}
bool Cr51::isProdImage(flasher::ModArgs mod, uint32_t offset, uint32_t size)
{
return imageDescriptor(mod, offset, size).image_type ==
image_type::IMAGE_PROD;
}
struct image_descriptor Cr51::imageDescriptor(flasher::ModArgs mod,
uint32_t offset, uint32_t size)
{
auto readDev = flasher::openDevice(mod);
std::vector<std::byte> fileIn(size);
readDev->readAt(fileIn, offset);
return stdplus::raw::copyFrom<struct image_descriptor>(fileIn);
}
} // namespace cr51
} // namespace validator
} // namespace flashupdate