From ac6e0c217a1b136d82f93b691aff1acb40009f26 Mon Sep 17 00:00:00 2001 From: Vikram Bodireddy Date: Thu, 5 Dec 2019 11:55:36 +0530 Subject: [PATCH] PFR image HASH verification This adds HASH verification on PFR images uploaded for firmware updates Tested: tested firmware update with good and bad HASH images. A) 1. Upload the corrupted image for fw update. 2. Image present in /tmp/images/ -rw-r--r-- 1 root root 22969344 Jun 3 09:27 5dea710b-8b85-4065-8af7-3149ada81edf 3. Journalctl logs during image verification Jun 03 09:27:20 intel-obmc phosphor-version-software-manager[4755]: Firmware image HASH verification failed Jun 03 09:27:20 intel-obmc phosphor-version-software-manager[4755]: Error verifying uploaded image Jun 03 09:27:20 intel-obmc phosphor-version-software-manager[4755]: Error processing image 4. image deleted from /tmp/images/ B) 1. Upload the correct image. POST: https:///redfish/v1/UpdateService/ with binary file 2. Image verification is success and proceeds with update. { "@odata.id": "/redfish/v1/TaskService/Tasks/0", "@odata.type": "#Task.v1_4_3.Task", "Id": "0", "TaskState": "Running", "TaskStatus": "OK" } Change-Id: I9336980bfb74c8136690024782bfef45f6b08d56 Signed-off-by: Chalapathi Venkataramashetty Signed-off-by: Vikram Bodireddy --- pfr_image_manager.cpp | 150 +++++++++++++++++++++++++++++++++---------- pfr_image_manager.hpp | 112 +++++++++++++++++++++++++++++-- 2 files changed, 222 insertions(+), 40 deletions(-) diff --git a/pfr_image_manager.cpp b/pfr_image_manager.cpp index 242a6ca..1a41cbe 100644 --- a/pfr_image_manager.cpp +++ b/pfr_image_manager.cpp @@ -5,6 +5,8 @@ #include "version.hpp" #include "watch.hpp" +#include +#include #include #include #include @@ -20,6 +22,7 @@ #include #include #include +#include #include #include @@ -34,12 +37,21 @@ using namespace sdbusplus::xyz::openbmc_project::Software::Image::Error; namespace Software = phosphor::logging::xyz::openbmc_project::Software; static constexpr const uint32_t pfmPos = 2054; +static constexpr const uint32_t block0Magic = 0xB6EAFD19; +static constexpr const uint32_t lengthBlk0Blk1 = 1024; -static int getPFRImgInfo(const std::filesystem::path imgPath, uint8_t& imgType, - std::string& version) +int Manager::verifyPFRImage(const std::filesystem::path imgPath, + std::string& version, std::string& purposeString) { - struct pfrImgBlock0 block0Data; - uint8_t verData[2]; + uint8_t imgType = 0; + uint32_t imgMagic = 0; + uint8_t verData[2] = {0}; + uint32_t hashLen = 0; + struct pfrImgBlock0 block0Data = {}; + + std::string imageName; + + EVP_MD_CTX* ctx; if (std::filesystem::exists(imgPath)) { @@ -56,17 +68,101 @@ static int getPFRImgInfo(const std::filesystem::path imgPath, uint8_t& imgType, imgFile.read(reinterpret_cast(&block0Data), sizeof(block0Data)); + + imgMagic = block0Data.tag; + + if (imgMagic != block0Magic) + { + phosphor::logging::log( + "Image magic number match failed", + phosphor::logging::entry("IMAGEMAGIC=0x%x", imgMagic)); + return -1; + } + imgType = block0Data.pcType[0]; + + phosphor::logging::log( + "Image Type", phosphor::logging::entry( + "IMAGETYPE=0x%x", static_cast(imgType))); + + if (imgType == pfrBMCUpdateCap || imgType == pfrBMCPFM) + { + imageName = "BMC"; + purposeString = + "xyz.openbmc_project.Software.Version.VersionPurpose.BMC"; + } + else if (imgType == pfrPCHUpdateCap || imgType == pfrPCHPFM) + { + imageName = "BIOS"; + purposeString = + "xyz.openbmc_project.Software.Version.VersionPurpose.Host"; + } + else if (imgType == pfrCPLDUpdateCap) + { + imageName = "CPLD"; + purposeString = + "xyz.openbmc_project.Software.Version.VersionPurpose.Other"; + } + else + { + purposeString = "xyz.openbmc_project.Software.Version." + "VersionPurpose.Unknown"; + + phosphor::logging::log( + "Unknown image type"); + return -1; + } + imgFile.seekg(pfmPos, std::ios::beg); // Version is at 0x806 in the PFM imgFile.read(reinterpret_cast(&verData), sizeof(verData)); imgFile.close(); - version = - std::to_string(verData[0]) + "." + std::to_string(verData[1]); + + auto size = std::filesystem::file_size(imgPath); + + phosphor::logging::log( + "Image Size", phosphor::logging::entry("IMAGESIZE=0x%x", + static_cast(size))); + + // Adds all digest algorithms to the internal table + OpenSSL_add_all_digests(); + + ctx = EVP_MD_CTX_create(); + EVP_DigestInit(ctx, EVP_sha256()); + + // Hash the image file and update the digest + auto dataPtr = mapFile(imgPath, size); + + EVP_DigestUpdate(ctx, ((uint8_t*)dataPtr() + lengthBlk0Blk1), + (size - lengthBlk0Blk1)); + + std::vector digest(EVP_MD_size(EVP_sha256())); + std::vector expectedDigest(block0Data.hash256, + &block0Data.hash256[0] + 32); + + EVP_DigestFinal(ctx, digest.data(), &hashLen); + EVP_MD_CTX_destroy(ctx); + + std::string redfishMsgID = "OpenBMC.0.1"; + + if (expectedDigest != digest) + { + redfishMsgID += ".GeneralFirmwareSecurityViolation"; + sd_journal_send("MESSAGE=%s", + "Firmware image HASH verification failed", + "PRIORITY=%i", LOG_ERR, "REDFISH_MESSAGE_ID=%s", + redfishMsgID.c_str(), "REDFISH_MESSAGE_ARGS=%s", + "Image HASH check fail", NULL); + return -1; + } + phosphor::logging::log( "PFR image", phosphor::logging::entry("PCType=%d", block0Data.pcType[0]), phosphor::logging::entry("VERSION=%s", version.c_str())); + + version = + std::to_string(verData[0]) + "." + std::to_string(verData[1]); } catch (std::exception& e) { @@ -80,20 +176,21 @@ static int getPFRImgInfo(const std::filesystem::path imgPath, uint8_t& imgType, int Manager::processImage(const std::string& imgFilePath) { + std::filesystem::path imgPath(imgFilePath); if (!std::filesystem::exists(imgPath)) return -1; - uint8_t imgType; int retry = 3; std::string ver; std::string purposeString; - if (0 != getPFRImgInfo(imgFilePath, imgType, ver)) + if (0 != verifyPFRImage(imgFilePath, ver, purposeString)) { phosphor::logging::log( - "Error reading uploaded image type and version"); + "Error verifying uploaded image"); + std::filesystem::remove_all(imgFilePath); return -1; } @@ -104,31 +201,6 @@ int Manager::processImage(const std::string& imgFilePath) return -1; } - if (imgType == pfrBMCUpdateCap) - { - purposeString = - "xyz.openbmc_project.Software.Version.VersionPurpose.BMC"; - } - else if (imgType == pfrPCHUpdateCap) - { - purposeString = - "xyz.openbmc_project.Software.Version.VersionPurpose.Host"; - } - else if (imgType == pfrCPLDUpdateCap) - { - purposeString = - "xyz.openbmc_project.Software.Version.VersionPurpose.Other"; - } - else - { - purposeString = - "xyz.openbmc_project.Software.Version.VersionPurpose.Unknown"; - - phosphor::logging::log( - "Unknown image type"); - return -1; - } - sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose purpose = Version::VersionPurpose::Unknown; try @@ -170,6 +242,7 @@ int Manager::processImage(const std::string& imgFilePath) std::filesystem::create_directory(imageDirPath); std::filesystem::path newFileName = imageDirPath / "image-runtime"; + std::filesystem::rename(imgFilePath, newFileName); // Create Version object @@ -213,6 +286,14 @@ void Manager::erase(std::string entryId) this->versions.erase(entryId); } +CustomMap Manager::mapFile(const std::filesystem::path& path, size_t size) +{ + + CustomFd fd(open(path.c_str(), O_RDONLY)); + + return CustomMap(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd(), 0), + size); +} } // namespace manager } // namespace software } // namespace phosphor diff --git a/pfr_image_manager.hpp b/pfr_image_manager.hpp index c6ee6a4..5b7b2c3 100644 --- a/pfr_image_manager.hpp +++ b/pfr_image_manager.hpp @@ -1,8 +1,16 @@ #pragma once #include "version.hpp" +#include +#include +#include +#include +#include + #include +#include + namespace phosphor { namespace software @@ -22,7 +30,7 @@ enum pfrImgPCType /* PFR image block 0 - As defined in HAS */ struct pfrImgBlock0 { - uint8_t tag[4]; + uint32_t tag; uint8_t pcLength[4]; uint8_t pcType[4]; uint8_t reserved1[4]; @@ -31,6 +39,82 @@ struct pfrImgBlock0 uint8_t reserved2[32]; } __attribute__((packed)); +/** @struct CustomFd + * + * RAII wrapper for file descriptor. + */ +struct CustomFd +{ + public: + CustomFd() = delete; + CustomFd(const CustomFd&) = delete; + CustomFd& operator=(const CustomFd&) = delete; + CustomFd(CustomFd&&) = default; + CustomFd& operator=(CustomFd&&) = default; + /** @brief Saves File descriptor and uses it to do file operation + * + * @param[in] fd - File descriptor + */ + CustomFd(int fd) : fd(fd) + {} + + ~CustomFd() + { + if (fd >= 0) + { + close(fd); + } + } + + int operator()() const + { + return fd; + } + + private: + /** @brief File descriptor */ + int fd = -1; +}; + +/** @struct CustomMap + * + * RAII wrapper for mmap. + */ +struct CustomMap +{ + private: + /** @brief starting address of the map */ + void* addr; + + /** @brief length of the mapping */ + size_t length; + + public: + CustomMap() = delete; + CustomMap(const CustomMap&) = delete; + CustomMap& operator=(const CustomMap&) = delete; + CustomMap(CustomMap&&) = default; + CustomMap& operator=(CustomMap&&) = default; + + /** @brief Saves starting address of the map and + * and length of the file. + * @param[in] addr - Starting address of the map + * @param[in] length - length of the map + */ + CustomMap(void* addr, size_t length) : addr(addr), length(length) + {} + + ~CustomMap() + { + munmap(addr, length); + } + + void* operator()() const + { + return addr; + } +}; + /** @class Manager * @brief Contains a map of Version dbus objects. * @details The software image manager class that contains the Version dbus @@ -63,6 +147,22 @@ class Manager void erase(std::string entryId); private: + /** + * @brief Memory map the file + * @param[in] - file path + * @param[in] - file size + * @param[out] - Custom Mmap address + */ + CustomMap mapFile(const std::filesystem::path& path, size_t size); + + /** + * @brief Verify the PFR image and return version and purpose + * @param[in] - file path + * @param[out] - version + * @param[out] - purpose + */ + int verifyPFRImage(const std::filesystem::path imgPath, + std::string& version, std::string& purposeString); /** @brief Persistent map of Version dbus objects and their * version id */ std::map> versions;