| // 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 |