From 432340352cf7c1767c52ce8fe68cb7466ea3c152 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Wed, 29 Jan 2025 08:40:38 +0100 Subject: [PATCH] Add AMD firmware parser Based on the open source AMDFWTOOL available here: https://github.com/coreboot/coreboot/tree/main/util/amdfwtool TODO: - Merge duplicated regions There can be multiple L2 directory tables, for A/B recovery or to support different SoC SKUs. They point to the same regions, causing the same area to be shown multiple times in the parsed image. - Better quirks support PSP hardcodes certain sizes and address types for some files. The parser might thus fail, even though it works on real hardware. Signed-off-by: Patrick Rudolph --- UEFIExtract/CMakeLists.txt | 1 + UEFIFind/CMakeLists.txt | 1 + UEFITool/CMakeLists.txt | 1 + UEFITool/uefitool.pro | 1 + common/amd_descriptor.h | 258 +++++++++ common/ffsparser.cpp | 7 +- common/ffsparser.h | 23 + common/ffsparser_amd.cpp | 1030 ++++++++++++++++++++++++++++++++++++ common/meson.build | 1 + common/types.cpp | 38 +- common/types.h | 15 +- fuzzing/CMakeLists.txt | 1 + 12 files changed, 1366 insertions(+), 11 deletions(-) create mode 100644 common/amd_descriptor.h create mode 100644 common/ffsparser_amd.cpp diff --git a/UEFIExtract/CMakeLists.txt b/UEFIExtract/CMakeLists.txt index 97e8839..cf7f70c 100644 --- a/UEFIExtract/CMakeLists.txt +++ b/UEFIExtract/CMakeLists.txt @@ -19,6 +19,7 @@ SET(PROJECT_SOURCES ../common/nvramparser.cpp ../common/meparser.cpp ../common/ffsparser.cpp + ../common/ffsparser_amd.cpp ../common/ffsparser_intel.cpp ../common/fitparser.cpp ../common/ffsreport.cpp diff --git a/UEFIFind/CMakeLists.txt b/UEFIFind/CMakeLists.txt index 3cc723e..19a35f3 100644 --- a/UEFIFind/CMakeLists.txt +++ b/UEFIFind/CMakeLists.txt @@ -17,6 +17,7 @@ SET(PROJECT_SOURCES ../common/nvram.cpp ../common/nvramparser.cpp ../common/ffsparser.cpp + ../common/ffsparser_amd.cpp ../common/ffsparser_intel.cpp ../common/fitparser.cpp ../common/peimage.cpp diff --git a/UEFITool/CMakeLists.txt b/UEFITool/CMakeLists.txt index 1d44e77..4ea69ef 100644 --- a/UEFITool/CMakeLists.txt +++ b/UEFITool/CMakeLists.txt @@ -48,6 +48,7 @@ SET(PROJECT_SOURCES ../common/utility.cpp ../common/ffsbuilder.cpp ../common/ffsparser.cpp + ../common/ffsparser_amd.cpp ../common/ffsparser_intel.cpp ../common/ffsreport.cpp ../common/treeitem.cpp diff --git a/UEFITool/uefitool.pro b/UEFITool/uefitool.pro index 0887edd..41c18ed 100644 --- a/UEFITool/uefitool.pro +++ b/UEFITool/uefitool.pro @@ -109,6 +109,7 @@ SOURCES += uefitool_main.cpp \ ../common/utility.cpp \ ../common/ffsbuilder.cpp \ ../common/ffsparser.cpp \ + ../common/ffsparser_amd.cpp \ ../common/ffsparser_intel.cpp \ ../common/ffsreport.cpp \ ../common/treeitem.cpp \ diff --git a/common/amd_descriptor.h b/common/amd_descriptor.h new file mode 100644 index 0000000..58a9ddd --- /dev/null +++ b/common/amd_descriptor.h @@ -0,0 +1,258 @@ +/* amd_descriptor.h + +Copyright (c) 2025 Patrick Rudolph. All rights reserved. +This program and the accompanying materials +are licensed and made available under the terms and conditions of the BSD License +which accompanies this distribution. The full text of the license may be found at +http://opensource.org/licenses/bsd-license.php + +THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, +WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. +*/ + +#ifndef AMD_DESCRIPTOR_H +#define AMD_DESCRIPTOR_H + +#include "basetypes.h" +#include "ustring.h" +#include "ubytearray.h" + +// Make sure we use right packing rules +#pragma pack(push,1) + +typedef enum AMD_ADDR_MODE_ { + AMD_ADDR_PHYSICAL = 0, /* Physical address */ + AMD_ADDR_REL_BIOS, /* Relative to beginning of image */ + AMD_ADDR_REL_TAB, /* Relative to table */ + AMD_ADDR_REL_SLOT, /* Relative to slot */ +} AMD_ADDR_MODE; + +/* An address can be relative to the image/file start but it can also be the address when + * the image is mapped at 0xff000000. Used to ensure that we only attempt to read within + * the limits of the file. */ +#define SPI_ROM_BASE 0xff000000 +#define FILE_REL_MASK 0xffffff + +// Embedded firmware descriptor +typedef struct AMD_EMBEDDED_FIRMWARE_ { + UINT32 Signature; // 0x55aa55aa + UINT32 IMC_Entry; // Pointer to IMC blob + UINT32 GEC_Entry; // Pointer to GEC blob + UINT32 xHCI_Entry; // Pointer to xHCI blob + UINT32 PSP_Directory; // Use New_PSP_Directory when 0xffffffff + UINT32 New_PSP_Directory; // Could be upper 32-bit of PSP_Directory + UINT32 BIOS0_Entry; // Unused? + UINT32 BIOS1_Entry; // Used by EFS1.0 + // Might be a BIOS directory or Combo directory table + UINT32 BIOS2_Entry; // Unused? + UINT32 Efs_Generation; // only used after RAVEN/PICASSO + // EFS 1.0 + // PLATFORM_CARRIZO 15h (60-6fh) + // PLATFORM_STONEYRIDGE 15h (60-6fh) + // PLATFORM_RAVEN 17h (00-0fh) + // PLATFORM_PICASSO 17h (10-2fh) + // EFS 2.0 + // PLATFORM_RENOIR 17h (10-1fh) + // PLATFORM_LUCIENNE 17h (60-6fh) + // PLATFORM_CEZANNE 19h (50-5fh) + // PLATFORM_MENDOCINO 17h (A0-Afh) + // PLATFORM_PHOENIX 19h (70-7fh) + // PLATFORM_GLINDA 17h + // PLATFORM_GENOA 19h + UINT32 BIOS3_Entry; // only used when not using A/B recovery + // Might be a BIOS directory or Combo directory table + UINT32 Reserved_0; + UINT32 Promontory_FW_PTR; + UINT32 Reserved_1[6]; +} AMD_EMBEDDED_FIRMWARE; + +#define AMD_EFS_GEN1 0xFFFFFFFFUL + +// PSP directory header +typedef struct AMD_PSP_DIRECTORY_HEADER_ { + UINT32 Cookie; // 0x50535024 + UINT32 Checksum; + UINT32 Num_Entries; + UINT32 Additional_Info_Fields; +} AMD_PSP_DIRECTORY_HEADER; + +typedef struct AMD_PSP_DIRECTORY_ENTRY_ { + UINT8 Type; + UINT8 SubProg; + UINT16 Flags; + UINT32 Size; + UINT64 Address_AddressMode; +} AMD_PSP_DIRECTORY_ENTRY; + +// PSP combo directory header +typedef struct AMD_PSP_COMBO_DIRECTORY_HEADER_ { + UINT32 Cookie; // 0x50535032 + UINT32 Checksum; + UINT32 Num_Entries; + UINT32 Lookup; + UINT64 Reserved[2]; +} AMD_PSP_COMBO_DIRECTORY_HEADER; + +typedef struct AMD_PSP_COMBO_ENTRY_ { + UINT32 Id_Sel; + UINT32 Id; + UINT64 Lvl2_Address; +} AMD_PSP_COMBO_ENTRY; + +typedef enum AMD_BIOS_TYPE_ { + AMD_BIOS_SIG = 0x07, + AMD_BIOS_APCB = 0x60, + AMD_BIOS_APOB = 0x61, + AMD_BIOS_BIN = 0x62, + AMD_BIOS_APOB_NV = 0x63, + AMD_BIOS_PMUI = 0x64, + AMD_BIOS_PMUD = 0x65, + AMD_BIOS_UCODE = 0x66, + AMD_BIOS_APCB_BK = 0x68, + AMD_BIOS_EARLY_VGA = 0x69, + AMD_BIOS_MP2_CFG = 0x6a, + AMD_BIOS_PSP_SHARED_MEM = 0x6b, + AMD_BIOS_L2_PTR = 0x70, + AMD_BIOS_INVALID, + AMD_BIOS_SKIP +} AMD_BIOS_TYPE; + +typedef enum AMD_FW_TYPE_ { + AMD_FW_PSP_PUBKEY = 0x00, + AMD_FW_PSP_BOOTLOADER = 0x01, + AMD_FW_PSP_SECURED_OS = 0x02, + AMD_FW_PSP_RECOVERY = 0x03, + AMD_FW_PSP_NVRAM = 0x04, + AMD_FW_RTM_PUBKEY = 0x05, + AMD_FW_PSP_SMU_FIRMWARE = 0x08, + AMD_FW_PSP_SECURED_DEBUG = 0x09, + AMD_FW_ABL_PUBKEY = 0x0a, + AMD_PSP_FUSE_CHAIN = 0x0b, + AMD_FW_PSP_TRUSTLETS = 0x0c, + AMD_FW_PSP_TRUSTLETKEY = 0x0d, + AMD_FW_PSP_SMU_FIRMWARE2 = 0x12, + AMD_DEBUG_UNLOCK = 0x13, + AMD_FW_PSP_TEEIPKEY = 0x15, + AMD_BOOT_DRIVER = 0x1b, + AMD_SOC_DRIVER = 0x1c, + AMD_DEBUG_DRIVER = 0x1d, + AMD_INTERFACE_DRIVER = 0x1f, + AMD_HW_IPCFG = 0x20, + AMD_WRAPPED_IKEK = 0x21, + AMD_TOKEN_UNLOCK = 0x22, + AMD_SEC_GASKET = 0x24, + AMD_MP2_FW = 0x25, + AMD_DRIVER_ENTRIES = 0x28, + AMD_FW_KVM_IMAGE = 0x29, + AMD_FW_MP5 = 0x2a, + AMD_S0I3_DRIVER = 0x2d, + AMD_ABL0 = 0x30, + AMD_ABL1 = 0x31, + AMD_ABL2 = 0x32, + AMD_ABL3 = 0x33, + AMD_ABL4 = 0x34, + AMD_ABL5 = 0x35, + AMD_ABL6 = 0x36, + AMD_ABL7 = 0x37, + AMD_SEV_DATA = 0x38, + AMD_SEV_CODE = 0x39, + AMD_FW_PSP_WHITELIST = 0x3a, + AMD_VBIOS_BTLOADER = 0x3c, + AMD_FW_L2_PTR = 0x40, + AMD_FW_DXIO = 0x42, + AMD_FW_USB_PHY = 0x44, + AMD_FW_TOS_SEC_POLICY = 0x45, + AMD_FW_DRTM_TA = 0x47, + AMD_FW_RECOVERYAB_A = 0x48, + AMD_FW_RECOVERYAB_B = 0x4A, + AMD_FW_BIOS_TABLE = 0x49, + AMD_FW_KEYDB_BL = 0x50, + AMD_FW_KEYDB_TOS = 0x51, + AMD_FW_PSP_VERSTAGE = 0x52, + AMD_FW_VERSTAGE_SIG = 0x53, + AMD_RPMC_NVRAM = 0x54, + AMD_FW_SPL = 0x55, + AMD_FW_DMCU_ERAM = 0x58, + AMD_FW_DMCU_ISR = 0x59, + AMD_FW_MSMU = 0x5a, + AMD_FW_SPIROM_CFG = 0x5c, + AMD_FW_MPIO = 0x5d, + AMD_FW_TPMLITE = 0x5f, /* family 17h & 19h */ + AMD_FW_PSP_SMUSCS = 0x5f, /* family 15h & 16h */ + AMD_FW_DMCUB = 0x71, + AMD_FW_PSP_BOOTLOADER_AB = 0x73, + AMD_RIB = 0x76, + AMD_FW_AMF_SRAM = 0x85, + AMD_FW_AMF_DRAM = 0x86, + AMD_FW_MFD_MPM = 0x87, + AMD_FW_AMF_WLAN = 0x88, + AMD_FW_AMF_MFD = 0x89, + AMD_FW_MPDMA_TF = 0x8c, + AMD_TA_IKEK = 0x8d, + AMD_FW_MPCCX = 0x90, + AMD_FW_GMI3_PHY = 0x91, + AMD_FW_MPDMA_PM = 0x92, + AMD_FW_LSDMA = 0x94, + AMD_FW_C20_MP = 0x95, + AMD_FW_FCFG_TABLE = 0x98, + AMD_FW_MINIMSMU = 0x9a, + AMD_FW_GFXIMU_0 = 0x9b, + AMD_FW_GFXIMU_1 = 0x9c, + AMD_FW_GFXIMU_2 = 0x9d, + AMD_FW_SRAM_FW_EXT = 0x9d, + AMD_FW_UMSMU = 0xa2, + AMD_FW_S3IMG = 0xa0, + AMD_FW_USBDP = 0xa4, + AMD_FW_USBSS = 0xa5, + AMD_FW_USB4 = 0xa6, + AMD_FW_IMC = 0x200, /* Large enough to be larger than the top BHD entry type. */ + AMD_FW_GEC, + AMD_FW_XHCI, + AMD_FW_INVALID, /* Real last one to detect the last entry in table. */ + AMD_FW_SKIP /* This is for non-applicable options. */ +} AMD_FW_TYPE; + +#define AMD_MAX_PSP_ENTRIES 0xff + +typedef struct AMD_ISH_DIRECTORY_TABLE_ { + UINT32 Checksum; + UINT32 Boot_Priority; + UINT32 Update_Retry_Count; + UINT8 Glitch_Retry_Count; + UINT8 Glitch_Higherbits_Reserved[3]; + UINT32 Pl2_location; + UINT32 Psp_Id; + UINT32 Slot_Max_Size; + UINT32 Reserved; +} AMD_ISH_DIRECTORY_TABLE; + +// BIOS directory header +typedef struct AMD_BIOS_DIRECTORY_HEADER_ { + UINT32 Cookie; // 0x44484224 + UINT32 Checksum; + UINT32 Num_Entries; + UINT32 Additional_Info_Fields; +} AMD_BIOS_DIRECTORY_HEADER; + +typedef struct AMD_BIOS_DIRECTORY_ENTRY_ { + UINT8 Type; + UINT8 RegionType; + UINT16 Flags; + UINT32 Size; + UINT64 Address_AddressMode; + UINT64 Destination; +} AMD_BIOS_DIRECTORY_ENTRY; +#define AMD_MAX_BIOS_ENTRIES 0x2f + +// AMD signatures +#define AMD_EMBEDDED_FIRMWARE_SIGNATURE 0x55aa55aa +#define AMD_PSP_DIRECTORY_HEADER_SIGNATURE 0x50535024 +#define AMD_PSPL2_DIRECTORY_HEADER_SIGNATURE 0x324c5024 +#define AMD_BIOS_HEADER_SIGNATURE 0x44484224 +#define AMD_BHDL2_HEADER_SIGNATURE 0x324c4224 +#define AMD_PSP_COMBO_DIRECTORY_HEADER_SIGNATURE 0x50535032 +#define AMD_PSP_BHD2_DIRECTORY_HEADER_SIGNATURE 0x44484232 + +#define AMD_EMBEDDED_FIRMWARE_OFFSET 0x20000 + +#endif // AMD_DESCRIPTOR_H diff --git a/common/ffsparser.cpp b/common/ffsparser.cpp index 3821139..360be5e 100644 --- a/common/ffsparser.cpp +++ b/common/ffsparser.cpp @@ -114,7 +114,12 @@ USTATUS FfsParser::performFirstPass(const UByteArray & buffer, UModelIndex & ind if (buffer.isEmpty()) { return U_INVALID_PARAMETER; } - + + // Try parsing as AMD image + if (U_SUCCESS == parseAMDImage(buffer, 0, UModelIndex(), index)) { + return U_SUCCESS; + } + // Try parsing as UEFI Capsule if (U_SUCCESS == parseCapsule(buffer, 0, UModelIndex(), index)) { return U_SUCCESS; diff --git a/common/ffsparser.h b/common/ffsparser.h index 3117480..cba1bd2 100644 --- a/common/ffsparser.h +++ b/common/ffsparser.h @@ -128,6 +128,29 @@ private: USTATUS parseCapsule(const UByteArray & capsule, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index); USTATUS parseGenericImage(const UByteArray & intelImage, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index); + // AMD specific + USTATUS parseAMDImage(const UByteArray & amdImage, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index); + USTATUS parseEFTable(const UByteArray & amdImage, const UINT32 efOffset, const UModelIndex & parent, UModelIndex & index); + USTATUS extractTable(const UByteArray & amdImage, const UINT32 offset, UByteArray & table); + USTATUS decodePSPTableAny(const UByteArray & amdImage, const UINT32 offset, const UModelIndex & parent, UModelIndex & index); + + USTATUS isValidTable(const UByteArray & amdImage, const UINT32 offset); + USTATUS isSupportedCookie(const UByteArray & amdImage, const UINT32 offset); + USTATUS parsePSPDir(const UINT32 offset, const UModelIndex & parent, UModelIndex & index); + USTATUS parseComboDir(const UINT32 pspOffset, const UModelIndex & parent, UModelIndex & index); + USTATUS parseBiosDir(const UINT32 imageOffset, const UModelIndex & parent, UModelIndex & index); + USTATUS insertRegion(UINT32 imageOffset, const UINT32 size, const UString name, UINT8 type, UINT8 subType, const UModelIndex & parent, UModelIndex & index); + USTATUS findByRange(const UINT32 offset, const UINT32 size, const UModelIndex & index, UModelIndex & found); + + + USTATUS parseISH(const UByteArray & fileImage, const UModelIndex & parent, UModelIndex & index); + + USTATUS amdRelativeOffset(const UModelIndex& pspRegionIndex, const UINT64 addr, const UINT64 mode, UINT64 & outaddr); + UString pspFileName(const UINT8 type, const UINT8 subtype); + UINT32 fletcher32(const UByteArray &Image); + USTATUS decompressBios(UModelIndex& parent); + USTATUS insertDirectoryFile(const UINT32 imageOffset, const UINT32 size, const UString name, const UModelIndex & parent, UModelIndex & index); + // Intel specific USTATUS parseIntelImage(const UByteArray & intelImage, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index); USTATUS parseBpdtRegion(const UByteArray & region, const UINT32 localOffset, const UINT32 sbpdtOffsetFixup, const UModelIndex & parent, UModelIndex & index); diff --git a/common/ffsparser_amd.cpp b/common/ffsparser_amd.cpp new file mode 100644 index 0000000..fbac1a8 --- /dev/null +++ b/common/ffsparser_amd.cpp @@ -0,0 +1,1030 @@ +/* ffsparser_amd.cpp + + Copyright (c) 2025 Patrick Rudolph. All rights reserved. + This program and the accompanying materials + are licensed and made available under the terms and conditions of the BSD License + which accompanies this distribution. The full text of the license may be found at + http://opensource.org/licenses/bsd-license.php + + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, + WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. + */ + +#include "ffsparser.h" + +#include +#include +#include + +#include "amd_descriptor.h" +#include "utility.h" + +// Converts addresses to be relative to the start of the file (ROM addressing) +USTATUS FfsParser::amdRelativeOffset(const UModelIndex& parent, const UINT64 addr, const UINT64 mode, UINT64 & outaddr) +{ + switch (mode) { + /* Since this utility operates on the BIOS file, physical address is converted + relative to the start of the BIOS file. */ + case AMD_ADDR_PHYSICAL: + if (addr < SPI_ROM_BASE || addr > (SPI_ROM_BASE + FILE_REL_MASK)) { + msg(usprintf("%s: Invalid address(%lx) or mode(%lx)\n", __FUNCTION__, addr, mode)); + } + outaddr = addr & FILE_REL_MASK; + return U_SUCCESS; + case AMD_ADDR_REL_BIOS: + { + UModelIndex parentImageIndex = (model->type(parent) == Types::Image) ? parent : model->findParentOfType(parent, Types::Image); + UByteArray parentImage = model->header(parentImageIndex) + model->body(parentImageIndex) + model->tail(parentImageIndex); + if (addr >= parentImage.size()) { + msg(usprintf("%s: Invalid address(%lx) or mode(%lx)\n", __FUNCTION__, addr, mode)); + return U_INVALID_PARAMETER; + } + outaddr = addr; + return U_SUCCESS; + } + case AMD_ADDR_REL_TAB: + outaddr = addr + model->base(parent); + return U_SUCCESS; + default: + msg(usprintf("Unsupported mode %lu\n", mode)); + return U_INVALID_PARAMETER; + } +} + +// Convert the ID to known file names +UString FfsParser::pspFileName(const UINT8 type, const UINT8 subtype) +{ +#define ENUM_TO_STRING(x) case x: return usprintf("PSP file %02x - %02x (%s)", type, subtype, #x); + switch (type) { + ENUM_TO_STRING(AMD_FW_PSP_PUBKEY) + ENUM_TO_STRING(AMD_FW_PSP_BOOTLOADER) + ENUM_TO_STRING(AMD_FW_PSP_SECURED_OS) + ENUM_TO_STRING(AMD_FW_PSP_RECOVERY) + ENUM_TO_STRING(AMD_FW_PSP_NVRAM) + ENUM_TO_STRING(AMD_FW_RTM_PUBKEY) + ENUM_TO_STRING(AMD_FW_PSP_SMU_FIRMWARE) + ENUM_TO_STRING(AMD_FW_PSP_SECURED_DEBUG) + ENUM_TO_STRING(AMD_FW_PSP_TRUSTLETS) + ENUM_TO_STRING(AMD_FW_PSP_TRUSTLETKEY) + ENUM_TO_STRING(AMD_FW_PSP_SMU_FIRMWARE2) + ENUM_TO_STRING(AMD_DEBUG_UNLOCK) + ENUM_TO_STRING(AMD_FW_PSP_TEEIPKEY) + ENUM_TO_STRING(AMD_BOOT_DRIVER) + ENUM_TO_STRING(AMD_SOC_DRIVER) + ENUM_TO_STRING(AMD_DEBUG_DRIVER) + ENUM_TO_STRING(AMD_INTERFACE_DRIVER) + ENUM_TO_STRING(AMD_HW_IPCFG) + ENUM_TO_STRING(AMD_WRAPPED_IKEK) + ENUM_TO_STRING(AMD_TOKEN_UNLOCK) + ENUM_TO_STRING(AMD_SEC_GASKET) + ENUM_TO_STRING(AMD_MP2_FW) + ENUM_TO_STRING(AMD_DRIVER_ENTRIES) + ENUM_TO_STRING(AMD_FW_KVM_IMAGE) + ENUM_TO_STRING(AMD_FW_MP5) + ENUM_TO_STRING(AMD_S0I3_DRIVER) + ENUM_TO_STRING(AMD_FW_ABL_PUBKEY) + ENUM_TO_STRING(AMD_PSP_FUSE_CHAIN) + ENUM_TO_STRING(AMD_FW_BIOS_TABLE) + ENUM_TO_STRING(AMD_FW_RECOVERYAB_A) + ENUM_TO_STRING(AMD_FW_RECOVERYAB_B) + + // BIOS types + ENUM_TO_STRING(AMD_BIOS_SIG) + ENUM_TO_STRING(AMD_BIOS_APCB) + ENUM_TO_STRING(AMD_BIOS_APOB) + ENUM_TO_STRING(AMD_BIOS_BIN) + ENUM_TO_STRING(AMD_BIOS_APOB_NV) + ENUM_TO_STRING(AMD_BIOS_PMUI) + ENUM_TO_STRING(AMD_BIOS_PMUD) + ENUM_TO_STRING(AMD_BIOS_UCODE) + ENUM_TO_STRING(AMD_BIOS_APCB_BK) + ENUM_TO_STRING(AMD_BIOS_EARLY_VGA) + ENUM_TO_STRING(AMD_BIOS_MP2_CFG) + ENUM_TO_STRING(AMD_BIOS_PSP_SHARED_MEM) + ENUM_TO_STRING(AMD_BIOS_L2_PTR) + ENUM_TO_STRING(AMD_BIOS_INVALID) + default: + break; + } +#undef ENUM_TO_STRING + return usprintf("PSP file %02x - %02x", type, subtype); +} + +// Returns U_SUCCESS when the table is known, fully within the specified UByteArray +// and the checksum is valid +USTATUS FfsParser::isValidTable(const UByteArray & amdImage, const UINT32 offset) +{ + if (offset % 16) { + msg(usprintf("%s: Invalid offset specified: %x", __FUNCTION__, offset)); + return U_INVALID_PARAMETER; + } + + if ((offset + sizeof(UINT32)) > amdImage.size()) { + msg(usprintf("%s: Offset outside of image: %x", __FUNCTION__, offset)); + return U_BUFFER_TOO_SMALL; + } + + const UINT32 *cookie = (const UINT32 *)(amdImage.constData() + offset); + UINT32 headerSize = 0; + switch (*cookie) { + case AMD_PSP_COMBO_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSP_BHD2_DIRECTORY_HEADER_SIGNATURE: + headerSize = sizeof(AMD_PSP_COMBO_DIRECTORY_HEADER); + break; + case AMD_PSP_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSPL2_DIRECTORY_HEADER_SIGNATURE: + headerSize = sizeof(AMD_PSP_DIRECTORY_HEADER); + break; + case AMD_BIOS_HEADER_SIGNATURE: + case AMD_BHDL2_HEADER_SIGNATURE: + headerSize = sizeof(AMD_BIOS_DIRECTORY_HEADER); + break; + default: + msg(usprintf("%s: Unknown cookie 0x%x", __FUNCTION__, *cookie)); + return U_UNKNOWN_ITEM_TYPE; + } + + // Full header is part of image? + if ((offset + headerSize) > amdImage.size()) { + msg(usprintf("%s: Table header at 0x%X not within image", __FUNCTION__, offset)); + return U_BUFFER_TOO_SMALL; + } + + // Fill in table specific details + UINT32 checksumStart; + UINT32 checksumSize; + UINT32 size; + UINT32 checksum; + switch (*cookie) { + case AMD_PSP_COMBO_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSP_BHD2_DIRECTORY_HEADER_SIGNATURE: + { + const AMD_PSP_COMBO_DIRECTORY_HEADER* hdr = (const AMD_PSP_COMBO_DIRECTORY_HEADER*)(cookie); + size = hdr->Num_Entries * sizeof(AMD_PSP_COMBO_ENTRY) + sizeof(AMD_PSP_COMBO_DIRECTORY_HEADER); + checksum = hdr->Checksum; + + checksumStart = offset + 8; // Start after checksum field + checksumSize = size - 8; // Subtract cookie and checksum field + break; + } + case AMD_PSP_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSPL2_DIRECTORY_HEADER_SIGNATURE: + { + const AMD_PSP_DIRECTORY_HEADER* hdr = (const AMD_PSP_DIRECTORY_HEADER*)(cookie); + size = hdr->Num_Entries * sizeof(AMD_PSP_DIRECTORY_ENTRY) + sizeof(AMD_PSP_DIRECTORY_HEADER); + checksum = hdr->Checksum; + + checksumStart = offset + 8; // Start after checksum field + checksumSize = size - 8; // Subtract cookie and checksum field + break; + } + case AMD_BIOS_HEADER_SIGNATURE: + case AMD_BHDL2_HEADER_SIGNATURE: + { + const AMD_BIOS_DIRECTORY_HEADER* hdr = (const AMD_BIOS_DIRECTORY_HEADER*)(cookie); + size = hdr->Num_Entries * sizeof(AMD_BIOS_DIRECTORY_ENTRY) + sizeof(AMD_BIOS_DIRECTORY_HEADER); + checksum = hdr->Checksum; + + checksumStart = offset + 8; // Start after checksum field + checksumSize = size - 8; // Subtract cookie and checksum field + break; + } + default: + return U_UNKNOWN_ITEM_TYPE; + } + + // Full table is part of image? + if ((offset + size) > amdImage.size()) { + msg(usprintf("%s: Table at 0x%X not within image", __FUNCTION__, offset)); + return U_BUFFER_TOO_SMALL; + } + + // Validate table checksum + const UINT32 calcChecksum = fletcher32(amdImage.mid(checksumStart, checksumSize)); + if (calcChecksum != checksum) { + msg(usprintf("%s: Header at offset %x checksum mismatch, expected %x found %x", __FUNCTION__, offset, checksum, calcChecksum)); + return U_INVALID_IMAGE; + } + + return U_SUCCESS; +} + +// Returns U_SUCCESS when the table could be extracted from the image +USTATUS FfsParser::extractTable(const UByteArray & amdImage, const UINT32 offset, UByteArray & table) +{ + USTATUS result; + + result = isValidTable(amdImage, offset); + if (result != U_SUCCESS) { + return result; + } + + const UINT32 *cookie = (const UINT32 *)(amdImage.constData() + offset); + UINT32 size; + switch (*cookie) { + case AMD_PSP_COMBO_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSP_BHD2_DIRECTORY_HEADER_SIGNATURE: + { + const AMD_PSP_COMBO_DIRECTORY_HEADER* hdr = (const AMD_PSP_COMBO_DIRECTORY_HEADER*)(cookie); + size = hdr->Num_Entries * sizeof(AMD_PSP_COMBO_ENTRY) + sizeof(AMD_PSP_COMBO_DIRECTORY_HEADER); + break; + } + case AMD_PSP_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSPL2_DIRECTORY_HEADER_SIGNATURE: + { + const AMD_PSP_DIRECTORY_HEADER* hdr = (const AMD_PSP_DIRECTORY_HEADER*)(cookie); + size = hdr->Num_Entries * sizeof(AMD_PSP_DIRECTORY_ENTRY) + sizeof(AMD_PSP_DIRECTORY_HEADER); + break; + } + case AMD_BIOS_HEADER_SIGNATURE: + case AMD_BHDL2_HEADER_SIGNATURE: + { + const AMD_BIOS_DIRECTORY_HEADER* hdr = (const AMD_BIOS_DIRECTORY_HEADER*)(cookie); + size = hdr->Num_Entries * sizeof(AMD_BIOS_DIRECTORY_ENTRY) + sizeof(AMD_BIOS_DIRECTORY_HEADER); + break; + } + default: + return U_UNKNOWN_ITEM_TYPE; + } + + table = amdImage.mid(offset, size); + return U_SUCCESS; +} + +USTATUS FfsParser::parseISH(const UByteArray & fileImage, const UModelIndex & parent, UModelIndex & index) +{ + USTATUS result; + + // Parse ISH table + const AMD_ISH_DIRECTORY_TABLE* ishTable = (const AMD_ISH_DIRECTORY_TABLE*)fileImage.constData(); + UINTN length = sizeof(AMD_ISH_DIRECTORY_TABLE); + + if (fileImage.size() < length) { + return U_BUFFER_TOO_SMALL; + } + + // Checksum starts right after checksum field + UByteArray data = fileImage.mid(4, length - 4); + const UINT32 Checksum = fletcher32(data); + if (ishTable->Checksum != Checksum) { + msg(usprintf("%s: ISH table checksum mismatch, expected %x found %x", __FUNCTION__, ishTable->Checksum, Checksum)); + return U_INVALID_IMAGE; + } + + data = fileImage.mid(0, length); + + // ISH directory + UString name("ISH directory"); + UString info = usprintf("Full size : %Xh (%u)\nPl2_location : %Xh (%u)\nBoot_Priority: %Xh (%u)\nSlot_Max_Size: %Xh (%u)\nPsp_Id : %Xh (%u)\n", + (UINT32)length, (UINT32)length, + ishTable->Pl2_location, ishTable->Pl2_location, + ishTable->Boot_Priority, ishTable->Boot_Priority, + ishTable->Slot_Max_Size, ishTable->Slot_Max_Size, + ishTable->Psp_Id, ishTable->Psp_Id); + + // Add ISH directory image tree item + index = model->addItem(0, Types::DirectoryTable, Subtypes::ISHDirectory, name, UString(), info, UByteArray(), data, UByteArray(), Fixed, parent); + const UModelIndex ishIndex = index; + + // Add PSPL2 directory tree item + result = parsePSPDir(ishTable->Pl2_location, ishIndex, index); + if (result != U_SUCCESS) { + msg(usprintf("%s: Failed to parse PSPL2 pointed to by ISH", __FUNCTION__)); + return result; + } + + return U_SUCCESS; +} + +USTATUS FfsParser::findByRange(const UINT32 base, const UINT32 size, const UModelIndex & index, UModelIndex & found) +{ + // Sort by inserting + for (int i = 0; i < model->rowCount(index); i++) { + UModelIndex current = index.model()->index(i, 0, index); + UINT32 currentSize = (model->header(current).size() + model->body(current).size() + model->tail(current).size()); + + // Non overlapping case + if ((model->base(current) + currentSize) < base || model->base(current) > (base + size)) + continue; + // Expect range to start at base or earlier + if (model->base(current) > base) + continue; + // Existing region is too small + if (currentSize < size) + continue; + found = current; + + findByRange(base, size, current, found); + + return U_SUCCESS; + } + return U_ITEM_NOT_FOUND; +} + +USTATUS FfsParser::insertRegion(UINT32 imageOffset, const UINT32 size, const UString name, UINT8 type, UINT8 subType, const UModelIndex & parent, UModelIndex & index) +{ + UString parentName = (model->type(parent) != Types::Image) ? model->name(parent): UString(); + USTATUS result; + + UModelIndex parentImageIndex = (model->type(parent) != Types::Image) ? model->findParentOfType(parent, Types::Image) : parent; + UByteArray parentImage = model->header(parentImageIndex) + model->body(parentImageIndex) + model->tail(parentImageIndex); + UModelIndex containerIndex = parentImageIndex; + + msg(usprintf("%s: inserting %x-%x: ", __FUNCTION__, imageOffset, imageOffset + size) + name); + + UModelIndex findIndex; + result = findByRange(imageOffset, size, parentImageIndex, findIndex); + if (result == U_SUCCESS && findIndex.isValid()) { + msg(usprintf("\n%s: findByRange returned model type %x:%x at offset %x, size %x", __FUNCTION__, model->type(findIndex), model->subtype(findIndex), model->base(findIndex), model->body(findIndex).size())); + msg(usprintf("%s: was looking for model type %x:%x at offset %x, size %x\n", __FUNCTION__, type, subType, imageOffset, size)); + } + if (result == U_SUCCESS && findIndex.isValid() && model->type(findIndex) == type && model->subtype(findIndex) == subType && + model->base(findIndex) == imageOffset && (model->header(findIndex).size() + model->body(findIndex).size() + model->tail(findIndex).size()) == size) { + UString info; + info += UString("Parent : ") + parentName + UString("\n"); + info += usprintf("ParentOffset : %Xh\n", model->base(parent)); + + model->addInfo(findIndex, info); + + msg(usprintf("%s: Skipping already added model type %x:%x at offset %x", __FUNCTION__, type, subType, imageOffset)); + return U_SUCCESS; + } else if (result == U_SUCCESS && findIndex.isValid() && model->type(findIndex) != type && model->base(findIndex) <= imageOffset && + (model->header(findIndex).size() + model->body(findIndex).size() + model->tail(findIndex).size()) > size) { + containerIndex = findIndex; + } + + UString info = usprintf("Full size : %Xh (%u)\nOffset : %Xh (%u)\n", + size, size, imageOffset, imageOffset); + + if (model->type(parent) != Types::Image) { + info += UString("Parent : ") + parentName + UString("\n"); + info += usprintf("ParentOffset : %Xh\n", model->base(parent)); + } + + // Sort by inserting + for (int i = 0; i < model->rowCount(containerIndex); i++) { + UModelIndex current = index.model()->index(i, 0, containerIndex); + + if (model->base(current) > imageOffset) { + // Add directory file tree item + index = model->addItem(imageOffset - model->base(containerIndex), type, subType, name, parentName, info, UByteArray(), parentImage.mid(imageOffset, size), UByteArray(), Fixed, current, CREATE_MODE_BEFORE); + return U_SUCCESS; + } + } + + // Add directory file tree item + index = model->addItem(imageOffset - model->base(containerIndex), type, subType, name, parentName, info, UByteArray(), parentImage.mid(imageOffset, size), UByteArray(), Fixed, containerIndex); + return U_SUCCESS; +} + +USTATUS FfsParser::insertDirectoryFile(const UINT32 imageOffset, const UINT32 size, const UString name, const UModelIndex & parent, UModelIndex & index) +{ + /* + * Directories can reference that same BIOS region multiple + * times, due to A/B partition support or because multiple SoC + * versions are supported in one ROM. + * Check if the requested file already exists and do not create + * a new region. + */ + return insertRegion(imageOffset, size, name, Types::Region, Subtypes::PspDirectoryFile, parent, index); +} + +USTATUS FfsParser::parseComboDir(const UINT32 offset, const UModelIndex & parent, UModelIndex & index) +{ + UINTN level = 0; + USTATUS result; + + UModelIndex parentImageIndex = (model->type(parent) == Types::Image) ? parent : model->findParentOfType(parent, Types::Image); + UByteArray parentImage = model->header(parentImageIndex) + model->body(parentImageIndex) + model->tail(parentImageIndex); + UString parentName = (model->type(parent) != Types::Image) ? model->name(parent) : ""; + + UByteArray tableImage; + // extractTable also validates the input + result = extractTable(parentImage, offset, tableImage); + if (result != U_SUCCESS) { + return result; + } + + const AMD_PSP_COMBO_DIRECTORY_HEADER* hdr = (const AMD_PSP_COMBO_DIRECTORY_HEADER*)(tableImage.data()); + + // Check PSP signature + switch (hdr->Cookie) { + case AMD_PSP_COMBO_DIRECTORY_HEADER_SIGNATURE: + level = 1; + break; + case AMD_PSP_BHD2_DIRECTORY_HEADER_SIGNATURE: + level = 2; + break; + default: + msg(usprintf("%s: Combo header has invalid cookie 0x%x", __FUNCTION__, hdr->Cookie)); + return U_INVALID_IMAGE; + } + + // PSP combo directory table + UString name = usprintf("PSP %s directory table", (level == 1) ? "COMBO" : "BHD2"); + UString info = usprintf("Full size : %Xh (%u)\nOffset : %Xh (%u)\nEntry count : %u\n", + (UINT32)tableImage.size(), (UINT32)tableImage.size(), + (UINT32)offset, (UINT32)offset, + hdr->Num_Entries); + if (parentName != "") { + name += "(" + parentName + ")"; + } + + // Add PSP combo directory table + result = insertRegion(offset, tableImage.size(), name, Types::DirectoryTable, Subtypes::ComboDirectory, parent, index); + if (result != U_SUCCESS) { + return result; + } + const UModelIndex pspHeaderIndex = index; + + const AMD_PSP_COMBO_ENTRY* Entry = (const AMD_PSP_COMBO_ENTRY*)(hdr + 1); + for (UINTN i = 0; i < hdr->Num_Entries; i++) { + // Fill directory table + REGION_INFO psp_entry; + psp_entry.type = Entry[i].Id; + psp_entry.offset = sizeof(AMD_PSP_COMBO_DIRECTORY_HEADER) + i * sizeof(AMD_PSP_COMBO_ENTRY); + psp_entry.length = sizeof(AMD_PSP_COMBO_ENTRY); + psp_entry.data = tableImage.mid(psp_entry.offset, psp_entry.length); + + UString entryName = name + " entry"; + + // PSP table entry + info = usprintf("Full size : %Xh (%u)\nOffset : %Xh (%u)\nID Select : %Xh\nID : %Xh\nLvl2_Address : %llXh\n", + (UINT32)psp_entry.length, (UINT32)psp_entry.length, + (UINT32)psp_entry.offset, (UINT32)psp_entry.offset, + Entry[i].Id_Sel, Entry[i].Id, Entry[i].Lvl2_Address); + + // Add PSP table entry image tree item + index = model->addItem(psp_entry.offset, Types::DirectoryTableEntry, Subtypes::ComboDirectory, entryName, UString(), info, UByteArray(), psp_entry.data, UByteArray(), Fixed, pspHeaderIndex); + UModelIndex pspEntryIndex = index; + + if (isSupportedCookie(parentImage, Entry[i].Lvl2_Address) == U_SUCCESS) { + result = parsePSPDir(Entry[i].Lvl2_Address, pspEntryIndex, index); + } + + if (result != U_SUCCESS) { + return result; + } + } + return U_SUCCESS; +} + +USTATUS FfsParser::decompressBios(UModelIndex& parent) +{ + USTATUS result; + + UByteArray parentImage = model->header(parent) + model->body(parent) + model->tail(parent); + if (parentImage.size() < 256) { + return U_BUFFER_TOO_SMALL; + } + parentImage = parentImage.mid(256, parentImage.size() - 256); + UByteArray output; + + result = zlibDecompress(parentImage, output); + if (result) { + msg(usprintf("%s: decompression failed with error ", __FUNCTION__) + errorCodeToUString(result), parent); + return U_SUCCESS; + } + + model->setUncompressedData(parent, output); + model->setCompressed(parent, true); + + return U_SUCCESS; +} + +USTATUS FfsParser::parseBiosDir(const UINT32 imageOffset, const UModelIndex & parent, UModelIndex & index) +{ + UINTN level = 0; + USTATUS result; + + UModelIndex parentImageIndex = (model->type(parent) == Types::Image) ? parent : model->findParentOfType(parent, Types::Image); + UByteArray parentImage = model->header(parentImageIndex) + model->body(parentImageIndex) + model->tail(parentImageIndex); + UString parentName = (model->type(parent) != Types::Image) ? model->name(parent) : ""; + + UByteArray tableImage; + // extractTable also validates the input + result = extractTable(parentImage, imageOffset, tableImage); + if (result != U_SUCCESS) { + msg(usprintf("%s: Failed to extract table", __FUNCTION__)); + return result; + } + + const AMD_BIOS_DIRECTORY_HEADER* hdr = (const AMD_BIOS_DIRECTORY_HEADER*)(tableImage.data()); + + // Check Bios directory signature + switch (hdr->Cookie) { + case AMD_BIOS_HEADER_SIGNATURE: + level = 1; + break; + case AMD_BHDL2_HEADER_SIGNATURE: + level = 2; + break; + default: + msg(usprintf("%s: Bios header has invalid cookie 0x%x", __FUNCTION__, hdr->Cookie)); + return U_INVALID_IMAGE; + } + + // Bios directory table + UString name = usprintf("Bios %s directory table", (level == 1) ? "COMBO" : "BHD2"); + UString info = usprintf("Full size : %Xh (%u)\nOffset : %Xh (%u)\nEntry count : %u\n", + (UINT32)tableImage.size(), (UINT32)tableImage.size(), + (UINT32)imageOffset, (UINT32)imageOffset, + hdr->Num_Entries); + if (parentName != "") { + name += "(" + parentName + ")"; + } + + // Add Bios directory table + result = insertRegion(imageOffset, tableImage.size(), name, Types::DirectoryTable, Subtypes::BiosDirectory, parent, index); + if (result != U_SUCCESS) { + return result; + } + + const UModelIndex biosHeaderIndex = index; + + const AMD_BIOS_DIRECTORY_ENTRY* Entry = (const AMD_BIOS_DIRECTORY_ENTRY*)(hdr + 1); + for (UINTN i = 0; i < hdr->Num_Entries; i++) { + const UINT64 addr = Entry[i].Address_AddressMode & 0x3fffffffffffffffULL; + const UINT64 mode = Entry[i].Address_AddressMode >> 62; + const UINT32 size = Entry[i].Size; + + // Fill directory table + REGION_INFO bios_entry; + bios_entry.type = Entry[i].Type; + bios_entry.offset = sizeof(AMD_BIOS_DIRECTORY_HEADER) + i * sizeof(AMD_BIOS_DIRECTORY_ENTRY); + bios_entry.length = sizeof(AMD_BIOS_DIRECTORY_ENTRY); + bios_entry.data = tableImage.mid(bios_entry.offset, bios_entry.length); + const UString fileName = pspFileName(Entry[i].Type, Entry[i].RegionType); + + // Bios table entry + info = usprintf("Full size : %Xh (%u)\nOffset : %Xh (%u)\nType : %Xh\nRegionType : %Xh\nFlags : %Xh\nAddress : %llXh\nAddressMode : %Xh\nDestination : %llXh\n", + (UINT32)bios_entry.length, (UINT32)bios_entry.length, + (UINT32)bios_entry.offset, (UINT32)bios_entry.offset, + Entry[i].Type, Entry[i].RegionType, Entry[i].Flags, addr, mode, Entry[i].Destination); + + // Add Bios table entry image tree item + index = model->addItem(bios_entry.offset, Types::DirectoryTableEntry, Subtypes::BiosDirectory, fileName, UString(), info, UByteArray(), bios_entry.data, UByteArray(), Fixed, biosHeaderIndex); + + // Look for files based on directory table + UINT64 entry_offset = 0; + result = amdRelativeOffset(biosHeaderIndex, addr, mode, entry_offset); + if (result != U_SUCCESS) { + msg(usprintf("%s: amdRelativeOffset failed for file ", __FUNCTION__) + name); + continue; + } + if (size == 0x00000000 || size == 0xffffffff) { + msg(usprintf("%s: Skipping BIOS file %d with no size", __FUNCTION__, i)); + continue; + } + + // BIOS file entry + info = usprintf("Full size : %Xh (%u)\nOffset : %Xh (%u)\nType : %Xh\nRegionType : %Xh\nFlags : %Xh\n Reset Image : %s\n Copy Image : %s\n ReadOnly : %s\n Compressed : %s", + (UINT32)size, (UINT32)size, + (UINT32)entry_offset, (UINT32)entry_offset, + Entry[i].Type, Entry[i].RegionType, Entry[i].Flags, + (Entry[i].Flags & 1) ? "true" : "false", + (Entry[i].Flags & 2) ? "true" : "false", + (Entry[i].Flags & 4) ? "true" : "false", + (Entry[i].Flags & 8) ? "true" : "false" + ); + + UModelIndex findIndex = model->findByBase(entry_offset - model->base(parentImageIndex)); + if (findIndex.isValid() && model->type(findIndex) == Types::Region && model->subtype(findIndex) == Subtypes::BiosRegion) { + msg(usprintf("%s: Skipping already added BIOS file", __FUNCTION__) + fileName); + continue; + } + + result = insertDirectoryFile((UINT32)entry_offset, size, fileName, biosHeaderIndex, index); + if (result != U_SUCCESS) { + msg(usprintf("%s: Failed to create directory file", __FUNCTION__) + fileName); + } + const UModelIndex biosFileIndex = index; + + switch (Entry[i].Type) { + case AMD_BIOS_L2_PTR: + if (level == 1) { + result = parseBiosDir(entry_offset, parentImageIndex, index); + } + break; + case AMD_BIOS_BIN: + + if (Entry[i].Flags & 8) { + result = decompressBios(index); + parseGenericImage(model->uncompressedData(biosFileIndex), 0, biosFileIndex, index); + } else { + parseGenericImage(model->body(biosFileIndex), 0, biosFileIndex, index); + } + + break; + } + if (result != U_SUCCESS) { + msg(usprintf("%s: Failed to parse file", __FUNCTION__) + fileName); + } + } + return U_SUCCESS; +} + +USTATUS FfsParser::parsePSPDir(const UINT32 offset, const UModelIndex & parent, UModelIndex & index) +{ + UINTN level; + USTATUS result; + + UModelIndex parentImageIndex = (model->type(parent) == Types::Image) ? parent : model->findParentOfType(parent, Types::Image); + UByteArray parentImage = model->header(parentImageIndex) + model->body(parentImageIndex) + model->tail(parentImageIndex); + UString parentName = (model->type(parent) != Types::Image) ? model->name(parent) : ""; + + UByteArray tableImage; + // extractTable also validates the input + result = extractTable(parentImage, offset, tableImage); + if (result != U_SUCCESS) { + return result; + } + + const AMD_PSP_DIRECTORY_HEADER* hdr = (const AMD_PSP_DIRECTORY_HEADER*)(tableImage.constData()); + + // Check PSP signature + switch (hdr->Cookie) { + case AMD_PSP_DIRECTORY_HEADER_SIGNATURE: + level = 1; + break; + case AMD_PSPL2_DIRECTORY_HEADER_SIGNATURE: + level = 2; + break; + default: + msg(usprintf("%s: PSP header has invalid cookie 0x%x", __FUNCTION__, hdr->Cookie)); + return U_INVALID_IMAGE; + } + + // Update REGION_INFO + REGION_INFO psp_directory; + if (level == 1) + psp_directory.type = Subtypes::PspL1DirectoryRegion; + else + psp_directory.type = Subtypes::PspL2DirectoryRegion; + psp_directory.offset = offset; + psp_directory.length = (hdr->Additional_Info_Fields & 0x3ff) << 12; + + // Validate image length + if ((psp_directory.offset + psp_directory.length) > parentImage.size()) { + return U_INVALID_IMAGE; + } + psp_directory.data = parentImage.mid(psp_directory.offset, psp_directory.length); + + // PSP directory + UString name = usprintf("PSPL%d directory", level); + if (parentName != "") { + name += " (" + parentName + ")"; + } + UString info = usprintf("Full size : %Xh (%u)\nOffset : %Xh (%u)\nPSP Table Size: %Xh (%u)", + (UINT32)psp_directory.length, (UINT32)psp_directory.length, + (UINT32)psp_directory.offset, (UINT32)psp_directory.offset, + (UINT32)tableImage.size(), (UINT32)tableImage.size()); + + // Add PSP directory region + result = insertRegion(offset, (hdr->Additional_Info_Fields & 0x3ff) << 12, name, Types::Region, psp_directory.type, parent, index); + if (result != U_SUCCESS) { + return result; + } + const UModelIndex pspRegionIndex = index; + + // PSP directory table + name = UString("PSP directory table"); + info = usprintf("Full size : %Xh (%u)\nOffset : %Xh (%u)\nEntry count : %u\n", + (UINT32)tableImage.size(), (UINT32)tableImage.size(), + (UINT32)offset, (UINT32)offset, + hdr->Num_Entries); + + // Add PSP directory table + index = model->addItem(0, Types::DirectoryTable, Subtypes::PSPDirectory, name, UString(), info, UByteArray(), tableImage, UByteArray(), Fixed, pspRegionIndex); + UModelIndex pspHeaderIndex = index; + + const AMD_PSP_DIRECTORY_ENTRY* Entry = (const AMD_PSP_DIRECTORY_ENTRY*)(hdr + 1); + for (UINTN i = 0; i < hdr->Num_Entries; i++) { + const UINT8 type = Entry[i].Type; + const UINT8 subtype = Entry[i].SubProg; + const UINT16 flags = Entry[i].Flags; + const UINT64 addr = Entry[i].Address_AddressMode & 0x3fffffffffffffffULL; + const UINT64 mode = Entry[i].Address_AddressMode >> 62; + UINT32 size = Entry[i].Size; + const UString fileName = pspFileName(type, subtype); + + msg(usprintf("%s: Entry%d: ", __FUNCTION__, i) + fileName); + + // Fill directory table + + REGION_INFO psp_entry; + psp_entry.type = type; + psp_entry.offset = sizeof(AMD_PSP_DIRECTORY_HEADER) + i * sizeof(AMD_PSP_DIRECTORY_ENTRY); + psp_entry.length = sizeof(AMD_PSP_DIRECTORY_ENTRY); + psp_entry.data = psp_directory.data.mid(psp_entry.offset, psp_entry.length); + + // PSP table entry + UString info = usprintf("Full size : %Xh (%u)\nOffset : %Xh (%u)\nFileType : %Xh\nSubProg : %Xh\nFlags : %Xh\nFile Address : %llxh\nAddressMode : %xh\nFile Size : %xh (%u)\n", + (UINT32)psp_entry.length, (UINT32)psp_entry.length, + (UINT32)psp_entry.offset, (UINT32)psp_entry.offset, + type, subtype, flags, addr, mode, size, size); + + // Add PSP table entry image tree item + index = model->addItem(psp_entry.offset, Types::DirectoryTableEntry, Subtypes::PSPDirectory, fileName, UString(), info, UByteArray(), psp_entry.data, UByteArray(), Fixed, pspHeaderIndex); + + // Look for files based on directory table + UINT64 entry_offset = 0; + result = amdRelativeOffset(pspHeaderIndex, addr, mode, entry_offset); + if (result != U_SUCCESS) { + msg(usprintf("%s: amdRelativeOffset failed for file ", __FUNCTION__) + fileName); + continue; + } + + // Some firmwares are broken and set size 0 for ISH directory table + switch (type) { + case AMD_FW_RECOVERYAB_A: + case AMD_FW_RECOVERYAB_B: + size = 4096; + } + + if (size == 0x00000000 || size == 0xffffffff) { + msg(usprintf("%s: Skipping PSP file with no size ", __FUNCTION__) + fileName); + continue; + } + + // PSP file entry + info = usprintf("Full size : %Xh (%u)\nOffset : %Xh (%u)\nFileType : %Xh\nSubProg : %Xh\nFlags : %Xh", + (UINT32)size, (UINT32)size, + (UINT32)entry_offset, (UINT32)entry_offset, + type, subtype, flags); + + // Add PSP file tree item + UModelIndex parentIndex = model->findByBase(entry_offset); + if (!parentIndex.isValid()) { + parentIndex = model->findParentOfType(pspRegionIndex, Types::Image); + } + + result = insertDirectoryFile((UINT32)entry_offset, size, fileName, pspRegionIndex, index); + if (result != U_SUCCESS) { + msg(usprintf("%s: Failed to create directory file", __FUNCTION__) + fileName); + } + const UModelIndex pspFileIndex = index; + + switch (type) { + case AMD_FW_RECOVERYAB_A: + case AMD_FW_RECOVERYAB_B: + if (level == 1) { + // Can be a PSPL2 table or ISH directory + result = isValidTable(parentImage, model->base(pspFileIndex)); + if (result == U_SUCCESS) { + result = parsePSPDir(model->base(pspFileIndex), pspFileIndex, index); + } else { + result = parseISH(model->body(pspFileIndex), pspFileIndex, index); + } + } + break; + case AMD_FW_BIOS_TABLE: + //if (level == 2) { + result = parseBiosDir(entry_offset, pspFileIndex, index); + // } + break; + } + if (result != U_SUCCESS) { + msg(usprintf("%s: Failed to parse file ", __FUNCTION__) + fileName); + } + } + + return U_SUCCESS; +} + +// Returns U_SUCCESS when a supported cookie is found at the specified offset +USTATUS FfsParser::isSupportedCookie(const UByteArray & amdImage, const UINT32 offset) +{ + if (offset % 16) { + return U_INVALID_PARAMETER; + } + + if ((offset + sizeof(UINT32)) > amdImage.size()) { + return U_BUFFER_TOO_SMALL; + } + + const UINT32 *cookie = (const UINT32 *)(amdImage.constData() + offset); + + switch (*cookie) { + case AMD_PSP_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSPL2_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSP_COMBO_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSP_BHD2_DIRECTORY_HEADER_SIGNATURE: + case AMD_BIOS_HEADER_SIGNATURE: + case AMD_BHDL2_HEADER_SIGNATURE: + return U_SUCCESS; + } + return U_UNKNOWN_ITEM_TYPE; +} + +// Decodes any supported PSP table found at the specified offset +USTATUS FfsParser::decodePSPTableAny(const UByteArray & amdImage, const UINT32 offset, const UModelIndex & parent, UModelIndex & index) +{ + USTATUS result; + + if (offset % 16) { + return U_INVALID_PARAMETER; + } + + result = isValidTable(amdImage, offset); + if (result != U_SUCCESS) { + return result; + } + + const UINT32 *cookie = (const UINT32 *)(amdImage.constData() + offset); + switch (*cookie) { + case AMD_PSP_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSPL2_DIRECTORY_HEADER_SIGNATURE: + result = parsePSPDir(offset, parent, index); + break; + case AMD_PSP_COMBO_DIRECTORY_HEADER_SIGNATURE: + case AMD_PSP_BHD2_DIRECTORY_HEADER_SIGNATURE: + result = parseComboDir(offset, parent, index); + break; + case AMD_BIOS_HEADER_SIGNATURE: + case AMD_BHDL2_HEADER_SIGNATURE: + result = parseBiosDir(offset, parent, index); + break; + default: + result = U_UNKNOWN_ITEM_TYPE; + } + return result; +} + +USTATUS FfsParser::parseEFTable(const UByteArray & amdImage, const UINT32 efOffset, const UModelIndex & parent, UModelIndex & index) +{ + USTATUS result; + UString name("Embedded Firmware Table"); + if (efOffset + sizeof(AMD_EMBEDDED_FIRMWARE) > amdImage.size()) { + return U_INVALID_PARAMETER; + } + AMD_EMBEDDED_FIRMWARE* ef_descriptor = (AMD_EMBEDDED_FIRMWARE*)(amdImage.constData() + efOffset); + + msg(usprintf("%s: IMC_Entry 0x%x", __FUNCTION__, ef_descriptor->IMC_Entry)); + msg(usprintf("%s: GEC_Entry 0x%x", __FUNCTION__, ef_descriptor->GEC_Entry)); + msg(usprintf("%s: xHCI_Entry 0x%x", __FUNCTION__, ef_descriptor->xHCI_Entry)); + msg(usprintf("%s: Efs_Generation 0x%x", __FUNCTION__, ef_descriptor->Efs_Generation)); + msg(usprintf("%s: PSP_Directory 0x%x", __FUNCTION__, ef_descriptor->PSP_Directory)); + msg(usprintf("%s: New_PSP_Directory 0x%x", __FUNCTION__, ef_descriptor->New_PSP_Directory)); + msg(usprintf("%s: BIOS0_Entry 0x%x", __FUNCTION__, ef_descriptor->BIOS0_Entry)); + msg(usprintf("%s: BIOS1_Entry 0x%x", __FUNCTION__, ef_descriptor->BIOS1_Entry)); + msg(usprintf("%s: BIOS2_Entry 0x%x", __FUNCTION__, ef_descriptor->BIOS2_Entry)); + msg(usprintf("%s: BIOS3_Entry 0x%x", __FUNCTION__, ef_descriptor->BIOS3_Entry)); + + // The specification between SoCs changed a lot, and at this point the + // SoC/PSP ID isn't known. Attempt to decode all tables without assuming + // to find a specific type + if (isSupportedCookie(amdImage, ef_descriptor->PSP_Directory) == U_SUCCESS) { + result = decodePSPTableAny(amdImage, ef_descriptor->PSP_Directory, parent, index); + if (result != U_SUCCESS) { + return result; + } + } + if (isSupportedCookie(amdImage, ef_descriptor->New_PSP_Directory) == U_SUCCESS) { + result = decodePSPTableAny(amdImage, ef_descriptor->New_PSP_Directory, parent, index); + if (result != U_SUCCESS) { + return result; + } + } + if (isSupportedCookie(amdImage, ef_descriptor->BIOS0_Entry) == U_SUCCESS) { + result = decodePSPTableAny(amdImage, ef_descriptor->BIOS0_Entry, parent, index); + if (result != U_SUCCESS) { + return result; + } + } + if (isSupportedCookie(amdImage, ef_descriptor->BIOS1_Entry) == U_SUCCESS) { + result = decodePSPTableAny(amdImage, ef_descriptor->BIOS1_Entry, parent, index); + if (result != U_SUCCESS) { + return result; + } + } + if (isSupportedCookie(amdImage, ef_descriptor->BIOS2_Entry) == U_SUCCESS) { + result = decodePSPTableAny(amdImage, ef_descriptor->BIOS2_Entry, parent, index); + if (result != U_SUCCESS) { + return result; + } + } + if (isSupportedCookie(amdImage, ef_descriptor->BIOS3_Entry) == U_SUCCESS) { + result = decodePSPTableAny(amdImage, ef_descriptor->BIOS3_Entry, parent, index); + if (result != U_SUCCESS) { + return result; + } + } + return U_SUCCESS; +} + + +/* + * Creates the OSI Fletcher checksum. See 8473-1, Appendix C, section C.3. + * The checksum field of the passed PDU does not need to be reset to zero. + * + * The "Fletcher Checksum" was proposed in a paper by John G. Fletcher of + * Lawrence Livermore Labs. The Fletcher Checksum was proposed as an + * alternative to cyclical redundancy checks because it provides error- + * detection properties similar to cyclical redundancy checks but at the + * cost of a simple summation technique. Its characteristics were first + * published in IEEE Transactions on Communications in January 1982. One + * version has been adopted by ISO for use in the class-4 transport layer + * of the network protocol. + * + * This program expects: + * stdin: The input file to compute a checksum for. The input file + * not be longer than 256 bytes. + * stdout: Copied from the input file with the Fletcher's Checksum + * inserted 8 bytes after the beginning of the file. + * stderr: Used to print out error messages. + */ +UINT32 FfsParser::fletcher32(const UByteArray &Image) +{ + UINT32 c0; + UINT32 c1; + UINT32 checksum; + INTN index; + const UINT16 *pptr = (const UINT16 *)Image.constData(); + + INTN length = Image.size() / 2; + + c0 = 0xFFFF; + c1 = 0xFFFF; + + while (length) { + index = length >= 359 ? 359 : length; + length -= index; + do { + c0 += *(pptr++); + c1 += c0; + } while (--index); + c0 = (c0 & 0xFFFF) + (c0 >> 16); + c1 = (c1 & 0xFFFF) + (c1 >> 16); + } + + /* Sums[0,1] mod 64K + overflow */ + c0 = (c0 & 0xFFFF) + (c0 >> 16); + c1 = (c1 & 0xFFFF) + (c1 >> 16); + checksum = (c1 << 16) | c0; + + return checksum; +} + +USTATUS FfsParser::parseAMDImage(const UByteArray & amdImage, const UINT32 localOffset, const UModelIndex & parent, UModelIndex & index) +{ + AMD_EMBEDDED_FIRMWARE* ef_descriptor = NULL; + USTATUS result; + UINT32 ProbeOffset; + + // Probe all possible locations for the header + for (ProbeOffset = AMD_EMBEDDED_FIRMWARE_OFFSET; (ProbeOffset + sizeof(AMD_EMBEDDED_FIRMWARE)) < amdImage.size(); ProbeOffset += 0x80000) { + // Store the beginning of descriptor as descriptor base address + ef_descriptor = (AMD_EMBEDDED_FIRMWARE*)(amdImage.constData() + ProbeOffset); + + // Check descriptor signature + if (ef_descriptor->Signature == AMD_EMBEDDED_FIRMWARE_SIGNATURE) { + msg(usprintf("%s: Embedded firmware table found at offset 0x%x", __FUNCTION__, ProbeOffset)); + // The Embedded Firmware table has no checksum, thus test if there's a valid pointer + if (isSupportedCookie(amdImage, ef_descriptor->PSP_Directory) != U_SUCCESS && + isSupportedCookie(amdImage, ef_descriptor->New_PSP_Directory) != U_SUCCESS && + isSupportedCookie(amdImage, ef_descriptor->BIOS0_Entry) != U_SUCCESS && + isSupportedCookie(amdImage, ef_descriptor->BIOS1_Entry) != U_SUCCESS && + isSupportedCookie(amdImage, ef_descriptor->BIOS2_Entry) != U_SUCCESS && + isSupportedCookie(amdImage, ef_descriptor->BIOS3_Entry) != U_SUCCESS) { + ef_descriptor = NULL; + continue; + } + break; + } + } + if (ef_descriptor == NULL) { + msg(usprintf("%s: Embedded firmware table not found", __FUNCTION__)); + return U_ITEM_NOT_FOUND; + } + + // AMD image + UString name("AMD image"); + UString info = usprintf("Full size: %Xh (%u)\n", + (UINT32)amdImage.size(), (UINT32)amdImage.size()); + + // Set image base + imageBase = model->base(parent) + localOffset; + + // Add AMD image tree item + index = model->addItem(localOffset, Types::Image, Subtypes::AmdImage, name, UString(), info, UByteArray(), amdImage, UByteArray(), Fixed, parent); + UModelIndex parentIndex = index; + + result = parseEFTable(amdImage, ProbeOffset, parentIndex, index); + if (result != U_SUCCESS) { + return result; + } +#if 0 + // Sort regions in ascending order + std::sort(regions.begin(), regions.end()); + + // Add offsets of actual regions + for (size_t i = 0; i < regions.size(); i++) { + if (regions[i].type != Subtypes::ZeroPadding && regions[i].type != Subtypes::OnePadding && regions[i].type != Subtypes::DataPadding) + info += "\n" + itemSubtypeToUString(Types::Region, regions[i].type) + + usprintf(" region offset: %Xh", regions[i].offset + localOffset); + } +#endif + + return result; +} diff --git a/common/meson.build b/common/meson.build index 601464e..5491656 100644 --- a/common/meson.build +++ b/common/meson.build @@ -26,6 +26,7 @@ uefitoolcommon = static_library('uefitoolcommon', 'meparser.cpp', 'fitparser.cpp', 'ffsparser.cpp', + 'ffsparser_amd.cpp', 'ffsparser_intel.cpp', 'ffsreport.cpp', 'peimage.cpp', diff --git a/common/types.cpp b/common/types.cpp index 375e7a3..5bc2626 100755 --- a/common/types.cpp +++ b/common/types.cpp @@ -1,11 +1,11 @@ /* types.cpp - + Copyright (c) 2016, Nikolaj Schlej. All rights reserved. This program and the accompanying materials are licensed and made available under the terms and conditions of the BSD License which accompanies this distribution. The full text of the license may be found at http://opensource.org/licenses/bsd-license.php - + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHWARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. */ @@ -18,6 +18,7 @@ UString regionTypeToUString(const UINT8 type) { switch (type) { + // Intel specific case Subtypes::DescriptorRegion: return UString("Descriptor"); case Subtypes::BiosRegion: return UString("BIOS"); case Subtypes::MeRegion: return UString("ME"); @@ -34,8 +35,12 @@ UString regionTypeToUString(const UINT8 type) case Subtypes::Reserved1Region: return UString("Reserved1"); case Subtypes::Reserved2Region: return UString("Reserved2"); case Subtypes::PttRegion: return UString("PTT"); + // AMD specific + case Subtypes::PspL1DirectoryRegion:return UString("PSP L1 Directory"); + case Subtypes::PspL2DirectoryRegion:return UString("PSP L2 Directory"); + case Subtypes::PspDirectoryFile: return UString("PSP Directory File"); }; - + return usprintf("Unknown %02Xh", type); } @@ -83,8 +88,10 @@ UString itemTypeToUString(const UINT8 type) case Types::CpdExtension: return UString("CPD extension"); case Types::CpdSpiEntry: return UString("CPD SPI entry"); case Types::StartupApDataEntry: return UString("Startup AP data"); + case Types::DirectoryTable: return UString("AMD Directory Table"); + case Types::DirectoryTableEntry: return UString("AMD Directory Entry"); } - + return usprintf("Unknown %02Xh", type); } @@ -94,6 +101,7 @@ UString itemSubtypeToUString(const UINT8 type, const UINT8 subtype) case Types::Image: if (subtype == Subtypes::IntelImage) return UString("Intel"); else if (subtype == Subtypes::UefiImage) return UString("UEFI"); + else if (subtype == Subtypes::AmdImage) return UString("AMD"); break; case Types::Padding: if (subtype == Subtypes::ZeroPadding) return UString("Empty (0x00)"); @@ -172,8 +180,20 @@ UString itemSubtypeToUString(const UINT8 type, const UINT8 subtype) case Types::StartupApDataEntry: if (subtype == Subtypes::x86128kStartupApDataEntry) return UString("X86 128K"); break; + case Types::DirectoryTable: + if (subtype == Subtypes::PSPDirectory) return UString("PSP Directory Table"); + if (subtype == Subtypes::ComboDirectory) return UString("Combo Directory Table"); + if (subtype == Subtypes::BiosDirectory) return UString("BIOS Directory Table"); + if (subtype == Subtypes::ISHDirectory) return UString("ISH Directory Table"); + + break; + case Types::DirectoryTableEntry: + if (subtype == Subtypes::PSPDirectory) return UString("PSP Directory"); + if (subtype == Subtypes::ComboDirectory) return UString("Combo Directory"); + if (subtype == Subtypes::BiosDirectory) return UString("BIOS Directory"); + break; } - + return UString(); } @@ -190,7 +210,7 @@ UString compressionTypeToUString(const UINT8 algorithm) case COMPRESSION_ALGORITHM_GZIP: return UString("GZip"); case COMPRESSION_ALGORITHM_ZLIB: return UString("Zlib"); } - + return usprintf("Unknown %02Xh", algorithm); } @@ -205,7 +225,7 @@ UString actionTypeToUString(const UINT8 action) case Actions::Rebuild: return UString("Rebuild"); case Actions::Rebase: return UString("Rebase"); } - + return usprintf("Unknown %02Xh", action); } @@ -235,7 +255,7 @@ UString fitEntryTypeToUString(const UINT8 type) case INTEL_FIT_TYPE_JMP_DEBUG_POLICY: return UString("JMP Debug Policy"); case INTEL_FIT_TYPE_EMPTY: return UString("Empty"); } - + return usprintf("Unknown %02Xh", (type & 0x7F)); } @@ -249,7 +269,7 @@ UString hashTypeToUString(const UINT16 algorithm_id) case TCG_HASH_ALGORITHM_ID_NULL: return UString("NULL"); case TCG_HASH_ALGORITHM_ID_SM3: return UString("SM3"); } - + return usprintf("Unknown %04Xh", algorithm_id); } diff --git a/common/types.h b/common/types.h index 0ee8247..738fb4b 100755 --- a/common/types.h +++ b/common/types.h @@ -75,6 +75,8 @@ namespace Types { CpdExtension, CpdSpiEntry, StartupApDataEntry, + DirectoryTable, + DirectoryTableEntry, }; } @@ -82,6 +84,7 @@ namespace Subtypes { enum ImageSubtypes{ IntelImage = 90, UefiImage, + AmdImage, }; enum CapsuleSubtypes { @@ -116,6 +119,9 @@ namespace Subtypes { Reserved1Region, Reserved2Region, PttRegion, + PspL1DirectoryRegion, + PspL2DirectoryRegion, + PspDirectoryFile, }; enum PaddingSubtypes { @@ -144,7 +150,14 @@ namespace Subtypes { InvalidSysFEntry = 150, NormalSysFEntry, }; - + + enum DirectorySubtypes { + PSPDirectory = 150, + ComboDirectory, + BiosDirectory, + ISHDirectory, + }; + enum EvsaEntrySubtypes { InvalidEvsaEntry = 160, UnknownEvsaEntry, diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt index a950bba..ca3a116 100644 --- a/fuzzing/CMakeLists.txt +++ b/fuzzing/CMakeLists.txt @@ -19,6 +19,7 @@ SET(PROJECT_SOURCES ../common/nvramparser.cpp ../common/meparser.cpp ../common/ffsparser.cpp + ../common/ffsparser_amd.cpp ../common/ffsparser_intel.cpp ../common/fitparser.cpp ../common/peimage.cpp