Add Insyde Flash Device Map parser

This commit is contained in:
Nikolaj Schlej 2025-03-03 01:27:24 +07:00
parent 0b66c5c7ff
commit b3c68b7006
28 changed files with 1361 additions and 230 deletions

View file

@ -35,6 +35,10 @@
#include "digest/sha2.h"
#include "digest/sm3.h"
#include "umemstream.h"
#include "kaitai/kaitaistream.h"
#include "generated/insyde_fdm.h"
// Constructor
FfsParser::FfsParser(TreeModel* treeModel) : model(treeModel),
imageBase(0), addressDiff(0x100000000ULL), protectedRegionsBase(0) {
@ -948,6 +952,114 @@ USTATUS FfsParser::parseRawArea(const UModelIndex & index)
msg(usprintf("%s: BPDT store parsing failed with error ", __FUNCTION__) + errorCodeToUString(result), index);
}
}
else if (itemType == Types::InsydeFlashDeviceMapStore) {
UByteArray fdm = data.mid(itemOffset, itemSize);
umemstream is(fdm.constData(), fdm.size());
kaitai::kstream ks(&is);
insyde_fdm_t parsed(&ks);
UINT32 storeSize = (UINT32)fdm.size();
// Construct header and body
UByteArray header = fdm.left(parsed.data_offset());
UByteArray body = fdm.mid(header.size(), storeSize - header.size());
// Add info
UString name = UString("Insyde H2O FlashDeviceMap");
UString info = usprintf("Signature: HFDM\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nData offset: %Xh\nEntry size: %Xh (%u)\nEntry format: %02Xh\nRevision: %02Xh\nExtension count: %u\nFlash descriptor base address: %08Xh\nChecksum: %02Xh",
storeSize, storeSize,
(UINT32)header.size(), (UINT32)header.size(),
(UINT32)body.size(), (UINT32)body.size(),
parsed.data_offset(),
parsed.entry_size(), parsed.entry_size(),
parsed.entry_format(),
parsed.revision(),
parsed.num_extensions(),
(UINT32)parsed.fd_base_address(),
parsed.checksum());
// Check header checksum
{
UByteArray tempHeader = data.mid(itemOffset, sizeof(INSYDE_FLASH_DEVICE_MAP_HEADER));
INSYDE_FLASH_DEVICE_MAP_HEADER* tempFdmHeader = (INSYDE_FLASH_DEVICE_MAP_HEADER*)tempHeader.data();
tempFdmHeader->Checksum = 0;
UINT8 calculated = calculateChecksum8((const UINT8*)tempFdmHeader, (UINT32)tempHeader.size());
if (calculated == parsed.checksum()) {
info += UString(", valid");
}
else {
info += usprintf(", invalid, should be %02Xh", calculated);
}
}
// Add board IDs
if (parsed.revision() == 3) {
info += usprintf("\nRegion index: %Xh\nBoardId Count: %u",
parsed.board_ids()->region_index(),
parsed.board_ids()->num_board_ids());
UINT32 i = 0;
for (const auto & boardId : *parsed.board_ids()->board_ids()) {
info += usprintf("\nBoardId #%u: %" PRIX64 "\n", i++, boardId);
}
}
// Add header tree item
UModelIndex headerIndex = model->addItem(headerSize + itemOffset, Types::InsydeFlashDeviceMapStore, 0, name, UString(), info, header, body, UByteArray(), Fixed, index);
// Add entries
UINT32 entryOffset = parsed.data_offset();
bool protectedRangeFound = false;
for (const auto & entry : *parsed.entries()->entries()) {
const EFI_GUID guid = readUnaligned((const EFI_GUID*)entry->guid().c_str());
name = insydeFlashDeviceMapEntryTypeGuidToUString(guid);
UString text;
header = data.mid(itemOffset + entryOffset, sizeof(INSYDE_FLASH_DEVICE_MAP_ENTRY));
body = data.mid(itemOffset + entryOffset + header.size(), parsed.entry_size() - header.size());
// Add info
UINT32 entrySize = (UINT32)header.size() + (UINT32)body.size();
info = UString("Region type: ") + guidToUString(guid, false) + "\n";
info += UString("Region id: ");
for (UINT8 i = 0; i < 16; i++) {
info += usprintf("%02X", *(const UINT8*)(entry->region_id().c_str() + i));
}
info += usprintf("\nFull size: %Xh (%u)\nHeader size: %Xh (%u)\nBody size: %Xh (%u)\nRegion address: %08Xh\nRegion size: %08Xh\nAttributes: %08Xh",
entrySize, entrySize,
(UINT32)header.size(), (UINT32)header.size(),
(UINT32)body.size(), (UINT32)body.size(),
(UINT32)entry->region_base(),
(UINT32)entry->region_size(),
entry->attributes());
if ((entry->attributes() & INSYDE_FLASH_DEVICE_MAP_ENTRY_ATTRIBUTE_MODIFIABLE) == 0) {
if (!protectedRangeFound) {
securityInfo += usprintf("Insyde Flash Device Map found at base %08Xh\nProtected ranges:\n", model->base(headerIndex));
protectedRangeFound = true;
}
// TODO: make sure that the only hash possible here is SHA256
// Add this region to the list of Insyde protected regions
PROTECTED_RANGE range = {};
range.Offset = (UINT32)entry->region_base();
range.Size = (UINT32)entry->region_size();
range.AlgorithmId = TCG_HASH_ALGORITHM_ID_SHA256;
range.Type = PROTECTED_RANGE_VENDOR_HASH_INSYDE;
range.Hash = body;
protectedRanges.push_back(range);
securityInfo += usprintf("Address: %08Xh Size: %Xh\nHash: ", range.Offset, range.Size) + UString(body.toHex().constData()) + "\n";
}
// Add tree item
model->addItem(entryOffset, Types::InsydeFlashDeviceMapEntry, 0, name, text, info, header, body, UByteArray(), Fixed, headerIndex);
entryOffset += entrySize;
}
if (protectedRangeFound) {
securityInfo += "\n";
}
}
else {
return U_UNKNOWN_ITEM_TYPE;
}
@ -992,6 +1104,9 @@ USTATUS FfsParser::parseRawArea(const UModelIndex & index)
case Types::BpdtPartition:
// Parsing already done
break;
case Types::InsydeFlashDeviceMapStore:
// Parsing already done
break;
case Types::Padding:
// No parsing required
break;
@ -1293,7 +1408,7 @@ USTATUS FfsParser::findNextRawAreaItem(const UModelIndex & index, const UINT32 l
for (; offset < dataSize - sizeof(UINT32); offset++) {
const UINT32* currentPos = (const UINT32*)(data.constData() + offset);
UINT32 restSize = dataSize - offset;
if (readUnaligned(currentPos) == INTEL_MICROCODE_HEADER_VERSION_1) {// Intel microcode
if (readUnaligned(currentPos) == INTEL_MICROCODE_HEADER_VERSION_1) { // Intel microcode
// Check data size
if (restSize < sizeof(INTEL_MICROCODE_HEADER)) {
continue;
@ -1415,6 +1530,28 @@ continue_searching: {}
nextItemOffset = offset;
break;
}
else if (readUnaligned(currentPos) == INSYDE_FLASH_DEVICE_MAP_SIGNATURE) {
// Check data size
if (restSize < sizeof(INSYDE_FLASH_DEVICE_MAP_HEADER))
continue;
const INSYDE_FLASH_DEVICE_MAP_HEADER *fdmHeader = (const INSYDE_FLASH_DEVICE_MAP_HEADER *)currentPos;
if (restSize < fdmHeader->Size)
continue;
if (fdmHeader->Revision > 3) {
msg(usprintf("%s: Insyde Flash Device Map candidate with unknown revision %u", __FUNCTION__, fdmHeader->Revision), index);
continue;
}
// All checks passed, FDM found
nextItemType = Types::InsydeFlashDeviceMapStore;
nextItemSize = fdmHeader->Size;
nextItemAlternativeSize = fdmHeader->Size;
nextItemOffset = offset;
break;
}
}
// No more stores found
@ -3764,6 +3901,26 @@ USTATUS FfsParser::checkProtectedRanges(const UModelIndex & index)
// Do nothing, this range is likely not found in the image
}
}
else if (protectedRanges[i].Type == PROTECTED_RANGE_VENDOR_HASH_INSYDE) {
try {
protectedRanges[i].Offset -= (UINT32)addressDiff;
protectedParts = openedImage.mid(protectedRanges[i].Offset, protectedRanges[i].Size);
UByteArray digest(SHA256_HASH_SIZE, '\x00');
sha256(protectedParts.constData(), protectedParts.size(), digest.data());
if (digest != protectedRanges[i].Hash) {
msg(usprintf("%s: Insyde protected range [%Xh:%Xh] hash mismatch, opened image may refuse to boot", __FUNCTION__,
protectedRanges[i].Offset, protectedRanges[i].Offset + protectedRanges[i].Size),
model->findByBase(protectedRanges[i].Offset));
}
markProtectedRangeRecursive(index, protectedRanges[i]);
}
catch(...) {
// Do nothing, this range is likely not found in the image
}
}
}
return U_SUCCESS;