From 73019876cf489c1b34639d8f67135c7b8e28ef2f Mon Sep 17 00:00:00 2001 From: Alex Matrosov Date: Sun, 10 Dec 2017 16:54:40 -0800 Subject: [PATCH] add FFSv3 support with large files and large sections --- UEFIPatch/uefipatch_main.cpp | 8 +- ffs.cpp | 23 ++- ffs.h | 14 +- ffsengine.cpp | 295 +++++++++++++++++++++-------------- ffsengine.h | 3 +- uefitool.cpp | 2 +- 6 files changed, 212 insertions(+), 133 deletions(-) diff --git a/UEFIPatch/uefipatch_main.cpp b/UEFIPatch/uefipatch_main.cpp index e14e59f..aac0ca7 100644 --- a/UEFIPatch/uefipatch_main.cpp +++ b/UEFIPatch/uefipatch_main.cpp @@ -1,6 +1,6 @@ /* uefipatch_main.cpp -Copyright (c) 2015, Nikolaj Schlej. All rights reserved. +Copyright (c) 2017, LongSoft. 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 @@ -19,8 +19,8 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); - a.setOrganizationName("CodeRush"); - a.setOrganizationDomain("coderush.me"); + a.setOrganizationName("LongSoft"); + a.setOrganizationDomain("longsoft.me"); a.setApplicationName("UEFIPatch"); UEFIPatch w; @@ -31,7 +31,7 @@ int main(int argc, char *argv[]) result = w.patchFromFile(a.arguments().at(1)); } else { - std::cout << "UEFIPatch 0.3.9 - UEFI image file patching utility" << std::endl << std::endl << + std::cout << "UEFIPatch 0.3.10 - UEFI image file patching utility" << std::endl << std::endl << "Usage: UEFIPatch image_file" << std::endl << std::endl << "Patches will be read from patches.txt file\n"; return ERR_SUCCESS; diff --git a/ffs.cpp b/ffs.cpp index 33410c3..6c3c773 100644 --- a/ffs.cpp +++ b/ffs.cpp @@ -30,7 +30,10 @@ const QVector FFSv3Volumes = const UINT8 ffsAlignmentTable[] = { 0, 4, 7, 9, 10, 12, 15, 16 }; -UINT8 calculateChecksum8(const UINT8* buffer, UINT32 bufferSize) +const UINT8 ffsAlignment2Table[] = +{ 17, 18, 19, 20, 21, 22, 23, 24 }; + +UINT8 calculateSum8(const UINT8* buffer, UINT32 bufferSize) { if (!buffer) return 0; @@ -40,7 +43,15 @@ UINT8 calculateChecksum8(const UINT8* buffer, UINT32 bufferSize) while (bufferSize--) counter += buffer[bufferSize]; - return (UINT8)0x100 - counter; + return counter; +} + +UINT8 calculateChecksum8(const UINT8* buffer, UINT32 bufferSize) +{ + if (!buffer) + return 0; + + return (UINT8)0x100 - calculateSum8(buffer, bufferSize); } UINT16 calculateChecksum16(const UINT16* buffer, UINT32 bufferSize) @@ -120,6 +131,8 @@ QString fileTypeToQString(const UINT8 type) case EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE: return QObject::tr("Volume image"); case EFI_FV_FILETYPE_COMBINED_SMM_DXE: return QObject::tr("Combined SMM/DXE"); case EFI_FV_FILETYPE_SMM_CORE: return QObject::tr("SMM core"); + case EFI_FV_FILETYPE_SMM_STANDALONE: return QObject::tr("SMM standalone"); + case EFI_FV_FILETYPE_SMM_CORE_STANDALONE: return QObject::tr("SMM core standalone"); case EFI_FV_FILETYPE_PAD: return QObject::tr("Pad"); default: return QObject::tr("Unknown"); }; @@ -155,10 +168,10 @@ UINT32 sizeOfSectionHeader(const EFI_COMMON_SECTION_HEADER* header) if (!header) return 0; - const bool extended = false; - /*if (uint24ToUint32(header->Size) == EFI_SECTION2_IS_USED) { + bool extended = false; + if (uint24ToUint32(header->Size) == EFI_SECTION2_IS_USED) { extended = true; - }*/ + } switch (header->Type) { diff --git a/ffs.h b/ffs.h index 946a141..c761551 100644 --- a/ffs.h +++ b/ffs.h @@ -292,7 +292,7 @@ UINT8 Type; UINT8 Attributes; UINT8 Size[3]; // Set to 0xFFFFFF UINT8 State; -UINT32 ExtendedSize; +UINT64 ExtendedSize; } EFI_FFS_FILE_HEADER2; // Standard data checksum, used if FFS_ATTRIB_CHECKSUM is clear @@ -314,6 +314,8 @@ UINT32 ExtendedSize; #define EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE 0x0B #define EFI_FV_FILETYPE_COMBINED_SMM_DXE 0x0C #define EFI_FV_FILETYPE_SMM_CORE 0x0D +#define EFI_FV_FILETYPE_SMM_STANDALONE 0x0E +#define EFI_FV_FILETYPE_SMM_CORE_STANDALONE 0x0F #define EFI_FV_FILETYPE_OEM_MIN 0xC0 #define EFI_FV_FILETYPE_OEM_MAX 0xDF #define EFI_FV_FILETYPE_DEBUG_MIN 0xE0 @@ -326,6 +328,7 @@ UINT32 ExtendedSize; #define FFS_ATTRIB_TAIL_PRESENT 0x01 // Valid only for revision 1 volumes #define FFS_ATTRIB_RECOVERY 0x02 // Valid only for revision 1 volumes #define FFS_ATTRIB_LARGE_FILE 0x01 // Valid only for FFSv3 volumes +#define FFS_ATTRIB_DATA_ALIGNMENT2 0x02 // Valid only for revision 2 volumes #define FFS_ATTRIB_FIXED 0x04 #define FFS_ATTRIB_DATA_ALIGNMENT 0x38 #define FFS_ATTRIB_CHECKSUM 0x40 @@ -333,6 +336,9 @@ UINT32 ExtendedSize; // FFS alignment table extern const UINT8 ffsAlignmentTable[]; +// Extended FFS alignment table +extern const UINT8 ffsAlignment2Table[]; + // File states #define EFI_FILE_HEADER_CONSTRUCTION 0x01 #define EFI_FILE_HEADER_VALID 0x02 @@ -360,7 +366,9 @@ const QByteArray EFI_FFS_PAD_FILE_GUID // FFS size conversion routines extern VOID uint32ToUint24(UINT32 size, UINT8* ffsSize); extern UINT32 uint24ToUint32(const UINT8* ffsSize); -// FFS file 8bit checksum calculation routine + +// FFS file 8bit checksum calculation routines +extern UINT8 calculateSum8(const UINT8* buffer, UINT32 bufferSize); extern UINT8 calculateChecksum8(const UINT8* buffer, UINT32 bufferSize); //***************************************************************************** @@ -528,7 +536,7 @@ typedef EFI_COMMON_SECTION_HEADER2 EFI_FIRMWARE_VOLUME_IMAGE_SECTION2; typedef EFI_COMMON_SECTION_HEADER EFI_USER_INTERFACE_SECTION; typedef EFI_COMMON_SECTION_HEADER2 EFI_USER_INTERFACE_SECTION2; -//Section routines +// Section routines extern UINT32 sizeOfSectionHeader(const EFI_COMMON_SECTION_HEADER* header); //***************************************************************************** diff --git a/ffsengine.cpp b/ffsengine.cpp index 7909f10..9c66dbf 100644 --- a/ffsengine.cpp +++ b/ffsengine.cpp @@ -287,15 +287,9 @@ UINT8 FfsEngine::parseIntelImage(const QByteArray & intelImage, QModelIndex & in const FLASH_DESCRIPTOR_COMPONENT_SECTION* componentSection = (const FLASH_DESCRIPTOR_COMPONENT_SECTION*)calculateAddress8(descriptor, descriptorMap->ComponentBase); // Check descriptor version by getting hardcoded value of FlashParameters.ReadClockFrequency - UINT8 descriptorVersion = 0; - if (componentSection->FlashParameters.ReadClockFrequency == FLASH_FREQUENCY_20MHZ) // Old descriptor + UINT8 descriptorVersion = 2; // Skylake+ by default + if (componentSection->FlashParameters.ReadClockFrequency == FLASH_FREQUENCY_20MHZ) // Old descriptor descriptorVersion = 1; - else if (componentSection->FlashParameters.ReadClockFrequency == FLASH_FREQUENCY_17MHZ) // Skylake+ descriptor - descriptorVersion = 2; - else { - msg(tr("parseIntelImage: unknown descriptor version with ReadClockFrequency %1h").hexarg(componentSection->FlashParameters.ReadClockFrequency)); - return ERR_INVALID_FLASH_DESCRIPTOR; - } // ME region QByteArray me; @@ -983,15 +977,16 @@ UINT8 FfsEngine::parseVolume(const QByteArray & volume, QModelIndex & index, co else headerSize = volumeHeader->HeaderLength; - // Sanity check after some new crazy MSI images + // Sanity check after some crazy MSI images headerSize = ALIGN8(headerSize); - // Check for volume structure to be known - bool volumeIsUnknown = true; - - // Check for FFS v2 volume - if (FFSv2Volumes.contains(QByteArray::fromRawData((const char*)volumeHeader->FileSystemGuid.Data, sizeof(EFI_GUID)))) { - volumeIsUnknown = false; + // Check for FFS v2/v3 volume + UINT8 subtype = Subtypes::UnknownVolume; + if (FFSv2Volumes.contains(QByteArray::fromRawData((const char*)volumeHeader->FileSystemGuid.Data, sizeof(EFI_GUID)))){ + subtype = Subtypes::Ffs2Volume; + } + else if (FFSv3Volumes.contains(QByteArray::fromRawData((const char*)volumeHeader->FileSystemGuid.Data, sizeof(EFI_GUID)))) { + subtype = Subtypes::Ffs3Volume; } // Check attributes @@ -1063,10 +1058,10 @@ UINT8 FfsEngine::parseVolume(const QByteArray & volume, QModelIndex & index, co // Add tree item QByteArray header = volume.left(headerSize); QByteArray body = volume.mid(headerSize, volumeSize - headerSize); - index = model->addItem(Types::Volume, volumeIsUnknown ? Subtypes::UnknownVolume : Subtypes::Ffs2Volume, COMPRESSION_ALGORITHM_NONE, name, text, info, header, body, parent, mode); + index = model->addItem(Types::Volume, subtype, COMPRESSION_ALGORITHM_NONE, name, text, info, header, body, parent, mode); // Show messages - if (volumeIsUnknown) { + if (subtype == Subtypes::UnknownVolume) { msg(tr("parseVolume: unknown file system %1").arg(guidToQString(volumeHeader->FileSystemGuid)), index); // Do not parse unknown volumes return ERR_SUCCESS; @@ -1100,19 +1095,42 @@ UINT8 FfsEngine::parseVolume(const QByteArray & volume, QModelIndex & index, co break; } - result = getFileSize(volume, fileOffset, fileSize); - if (result) - return result; + QByteArray tempFile = volume.mid(fileOffset, sizeof(EFI_FFS_FILE_HEADER)); + const EFI_FFS_FILE_HEADER* tempFileHeader = (const EFI_FFS_FILE_HEADER*)tempFile.constData(); + UINT32 fileHeaderSize = sizeof(EFI_FFS_FILE_HEADER); + fileSize = uint24ToUint32(tempFileHeader->Size); + if (volumeHeader->Revision > 1 && (tempFileHeader->Attributes & FFS_ATTRIB_LARGE_FILE)) { + // Check if it's possibly the latest file in the volume + if (volumeSize - fileOffset < sizeof(EFI_FFS_FILE_HEADER2)) { + // No files are possible after this point + // All the rest is either free space or non-UEFI data + QByteArray rest = volume.right(volumeSize - fileOffset); + if (rest.count(empty) == rest.size()) { // It's a free space + model->addItem(Types::FreeSpace, 0, COMPRESSION_ALGORITHM_NONE, tr("Volume free space"), "", tr("Full size: %1h (%2)").hexarg(rest.size()).arg(rest.size()), QByteArray(), rest, index); + } + else { //It's non-UEFI data + QModelIndex dataIndex = model->addItem(Types::Padding, Subtypes::DataPadding, COMPRESSION_ALGORITHM_NONE, tr("Non-UEFI data"), "", tr("Full size: %1h (%2)").hexarg(rest.size()).arg(rest.size()), QByteArray(), rest, index); + msg(tr("parseVolume: non-UEFI data found in volume's free space"), dataIndex); + } + // Exit from loop + break; + } - // Check file size to be at least size of EFI_FFS_FILE_HEADER - if (fileSize < sizeof(EFI_FFS_FILE_HEADER)) { + fileHeaderSize = sizeof(EFI_FFS_FILE_HEADER2); + tempFile = volume.mid(fileOffset, sizeof(EFI_FFS_FILE_HEADER2)); + const EFI_FFS_FILE_HEADER2* tempFileHeader2 = (const EFI_FFS_FILE_HEADER2*)tempFile.constData(); + fileSize = (UINT32)tempFileHeader2->ExtendedSize; + } + + // Check file size to be at least size of the header + if (fileSize < fileHeaderSize) { msg(tr("parseVolume: volume has FFS file with invalid size"), index); return ERR_INVALID_FILE; } - + QByteArray file = volume.mid(fileOffset, fileSize); - QByteArray header = file.left(sizeof(EFI_FFS_FILE_HEADER)); - + QByteArray header = file.left(fileHeaderSize); + // If we are at empty space in the end of volume if (header.count(empty) == header.size()) { // Check free space to be actually free @@ -1152,8 +1170,11 @@ UINT8 FfsEngine::parseVolume(const QByteArray & volume, QModelIndex & index, co // Check file alignment const EFI_FFS_FILE_HEADER* fileHeader = (const EFI_FFS_FILE_HEADER*)header.constData(); UINT8 alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3]; - UINT32 alignment = (UINT32)pow(2.0, alignmentPower); - if ((fileOffset + sizeof(EFI_FFS_FILE_HEADER)) % alignment) + if (volumeHeader->Revision > 1 && (fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT2)) + alignmentPower = ffsAlignment2Table[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3]; + + UINT32 alignment = (UINT32)(1UL << alignmentPower); + if ((fileOffset + fileHeaderSize) % alignment) msgUnalignedFile = true; // Check file GUID @@ -1165,7 +1186,7 @@ UINT8 FfsEngine::parseVolume(const QByteArray & volume, QModelIndex & index, co // Parse file QModelIndex fileIndex; - result = parseFile(file, fileIndex, empty == '\xFF' ? ERASE_POLARITY_TRUE : ERASE_POLARITY_FALSE, index); + result = parseFile(file, fileIndex, volumeHeader->Revision, empty == '\xFF' ? ERASE_POLARITY_TRUE : ERASE_POLARITY_FALSE, index); if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME) msg(tr("parseVolume: FFS file parsing failed with error \"%1\"").arg(errorMessage(result)), index); @@ -1183,16 +1204,7 @@ UINT8 FfsEngine::parseVolume(const QByteArray & volume, QModelIndex & index, co return ERR_SUCCESS; } -UINT8 FfsEngine::getFileSize(const QByteArray & volume, const UINT32 fileOffset, UINT32 & fileSize) -{ - if ((UINT32)volume.size() < fileOffset + sizeof(EFI_FFS_FILE_HEADER)) - return ERR_INVALID_VOLUME; - const EFI_FFS_FILE_HEADER* fileHeader = (const EFI_FFS_FILE_HEADER*)(volume.constData() + fileOffset); - fileSize = uint24ToUint32(fileHeader->Size); - return ERR_SUCCESS; -} - -UINT8 FfsEngine::parseFile(const QByteArray & file, QModelIndex & index, const UINT8 erasePolarity, const QModelIndex & parent, const UINT8 mode) +UINT8 FfsEngine::parseFile(const QByteArray & file, QModelIndex & index, const UINT8 revision, const UINT8 erasePolarity, const QModelIndex & parent, const UINT8 mode) { bool msgInvalidHeaderChecksum = false; bool msgInvalidDataChecksum = false; @@ -1200,43 +1212,32 @@ UINT8 FfsEngine::parseFile(const QByteArray & file, QModelIndex & index, const U bool msgInvalidType = false; // Populate file header + if ((UINT32)file.size() < sizeof(EFI_FFS_FILE_HEADER)) + return ERR_INVALID_FILE; const EFI_FFS_FILE_HEADER* fileHeader = (const EFI_FFS_FILE_HEADER*)file.constData(); - // Check file state // Construct empty byte for this file char empty = erasePolarity ? '\xFF' : '\x00'; - // Check header checksum + // Get file header QByteArray header = file.left(sizeof(EFI_FFS_FILE_HEADER)); - QByteArray tempHeader = header; - EFI_FFS_FILE_HEADER* tempFileHeader = (EFI_FFS_FILE_HEADER*)(tempHeader.data()); - tempFileHeader->IntegrityCheck.Checksum.Header = 0; - tempFileHeader->IntegrityCheck.Checksum.File = 0; - UINT8 calculated = calculateChecksum8((const UINT8*)tempFileHeader, sizeof(EFI_FFS_FILE_HEADER) - 1); - if (fileHeader->IntegrityCheck.Checksum.Header != calculated) + if (revision > 1 && (fileHeader->Attributes & FFS_ATTRIB_LARGE_FILE)) { + if ((UINT32)file.size() < sizeof(EFI_FFS_FILE_HEADER2)) + return ERR_INVALID_FILE; + header = file.left(sizeof(EFI_FFS_FILE_HEADER2)); + } + + // Check header checksum + UINT8 calculatedHeader = 0x100 -(calculateSum8((const UINT8*)header.constData(), header.size()) - fileHeader->IntegrityCheck.Checksum.Header - fileHeader->IntegrityCheck.Checksum.File - fileHeader->State); + if (fileHeader->IntegrityCheck.Checksum.Header != calculatedHeader) msgInvalidHeaderChecksum = true; - // Check data checksum - // Data checksum must be calculated - if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) { - UINT32 bufferSize = file.size() - sizeof(EFI_FFS_FILE_HEADER); - // Exclude file tail from data checksum calculation - if (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) - bufferSize -= sizeof(UINT16); - calculated = calculateChecksum8((const UINT8*)(file.constData() + sizeof(EFI_FFS_FILE_HEADER)), bufferSize); - if (fileHeader->IntegrityCheck.Checksum.File != calculated) - msgInvalidDataChecksum = true; - } - // Data checksum must be one of predefined values - else if (fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM && fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM2) - msgInvalidDataChecksum = true; - // Get file body - QByteArray body = file.right(file.size() - sizeof(EFI_FFS_FILE_HEADER)); + QByteArray body = file.mid(header.size()); + // Check for file tail presence QByteArray tail; - if (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) - { + if (revision == 1 && fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) { //Check file tail; tail = body.right(sizeof(UINT16)); UINT16 tailValue = *(UINT16*)tail.constData(); @@ -1247,48 +1248,47 @@ UINT8 FfsEngine::parseFile(const QByteArray & file, QModelIndex & index, const U body = body.left(body.size() - sizeof(UINT16)); } + // Check data checksum + // Data checksum must be calculated + UINT8 calculatedData = 0; + if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) { + calculatedData = calculateChecksum8((const UINT8*)body.constData(), body.size()); + if (fileHeader->IntegrityCheck.Checksum.File != calculatedData) + msgInvalidDataChecksum = true; + } + // Data checksum must be one of predefined values + else if ((revision == 1 && fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM) + || fileHeader->IntegrityCheck.Checksum.File != FFS_FIXED_CHECKSUM2) + msgInvalidDataChecksum = true; + // Parse current file by default - bool parseCurrentFile = true; + bool parseCurrentFile = false; bool parseAsBios = false; // Check file type - switch (fileHeader->Type) - { + switch (fileHeader->Type) { case EFI_FV_FILETYPE_ALL: - parseAsBios = true; - break; case EFI_FV_FILETYPE_RAW: parseAsBios = true; - break; case EFI_FV_FILETYPE_FREEFORM: - break; case EFI_FV_FILETYPE_SECURITY_CORE: - break; case EFI_FV_FILETYPE_PEI_CORE: - break; case EFI_FV_FILETYPE_DXE_CORE: - break; case EFI_FV_FILETYPE_PEIM: - break; case EFI_FV_FILETYPE_DRIVER: - break; case EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER: - break; case EFI_FV_FILETYPE_APPLICATION: - break; case EFI_FV_FILETYPE_SMM: - break; case EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE: - break; case EFI_FV_FILETYPE_COMBINED_SMM_DXE: - break; case EFI_FV_FILETYPE_SMM_CORE: - break; + case EFI_FV_FILETYPE_SMM_STANDALONE: + case EFI_FV_FILETYPE_SMM_CORE_STANDALONE: case EFI_FV_FILETYPE_PAD: + parseCurrentFile = true; break; default: msgInvalidType = true; - parseCurrentFile = false; }; // Check for empty file @@ -1310,25 +1310,27 @@ UINT8 FfsEngine::parseFile(const QByteArray & file, QModelIndex & index, const U else name = parseAsNonEmptyPadFile ? tr("Non-empty pad-file") : tr("Pad-file"); - info = tr("File GUID: %1\nType: %2h\nAttributes: %3h\nFull size: %4h (%5)\nHeader size: %6h (%7)\nBody size: %8h (%9)\nState: %10h") + info = tr("File GUID: %1\nType: %2h\nAttributes: %3h\nFull size: %4h (%5)\nHeader size: %6h (%7)\nBody size: %8h (%9)\nState: %10h\nHeader checksum: %11h\nData checksum: %12h") .arg(guidToQString(fileHeader->Name)) .hexarg2(fileHeader->Type, 2) .hexarg2(fileHeader->Attributes, 2) .hexarg(header.size() + body.size() + tail.size()).arg(header.size() + body.size() + tail.size()) .hexarg(header.size()).arg(header.size()) .hexarg(body.size()).arg(body.size()) - .hexarg2(fileHeader->State, 2); + .hexarg2(fileHeader->State, 2) + .hexarg2(fileHeader->IntegrityCheck.Checksum.Header, 2) + .hexarg2(fileHeader->IntegrityCheck.Checksum.File, 2); // Add tree item index = model->addItem(Types::File, fileHeader->Type, COMPRESSION_ALGORITHM_NONE, name, "", info, header, body, parent, mode); // Show messages if (msgInvalidHeaderChecksum) - msg(tr("parseFile: invalid header checksum"), index); + msg(tr("parseFile: invalid header checksum %1h, should be %2h").hexarg2(fileHeader->IntegrityCheck.Checksum.Header, 2).hexarg2(calculatedHeader, 2), index); if (msgInvalidDataChecksum) - msg(tr("parseFile: invalid data checksum"), index); + msg(tr("parseFile: invalid data checksum %1h, should be %2h").hexarg2(fileHeader->IntegrityCheck.Checksum.File, 2).hexarg2(calculatedData, 2), index); if (msgInvalidTailValue) - msg(tr("parseFile: invalid tail value"), index); + msg(tr("parseFile: invalid tail value %1h").hexarg(*(UINT16*)tail.data()), index); if (msgInvalidType) msg(tr("parseFile: unknown file type %1h").arg(fileHeader->Type, 2), index); @@ -1382,8 +1384,15 @@ UINT8 FfsEngine::getSectionSize(const QByteArray & file, const UINT32 sectionOff { if ((UINT32)file.size() < sectionOffset + sizeof(EFI_COMMON_SECTION_HEADER)) return ERR_INVALID_FILE; + const EFI_COMMON_SECTION_HEADER* sectionHeader = (const EFI_COMMON_SECTION_HEADER*)(file.constData() + sectionOffset); sectionSize = uint24ToUint32(sectionHeader->Size); + // This may introduce a very rare error with a non-extended section of size equal to 0xFFFFFF + if (sectionSize != 0xFFFFFF) + return ERR_SUCCESS; + + const EFI_COMMON_SECTION_HEADER2* sectionHeader2 = (const EFI_COMMON_SECTION_HEADER2*)(file.constData() + sectionOffset); + sectionSize = sectionHeader2->ExtendedSize; return ERR_SUCCESS; } @@ -1670,7 +1679,7 @@ UINT8 FfsEngine::parseSection(const QByteArray & section, QModelIndex & index, c } } else if (certificateHeader->CertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA) { - info += tr("\nSignature type: PCKS7"); + info += tr("\nSignature type: PKCS7"); // TODO: show signature info in Information panel } else { @@ -2219,28 +2228,51 @@ UINT8 FfsEngine::create(const QModelIndex & index, const UINT8 type, const QByte if (header.size() != sizeof(EFI_FFS_FILE_HEADER)) return ERR_INVALID_FILE; - QByteArray newHeader = header; - QByteArray newBody = body; - EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*)newHeader.data(); + QByteArray newObject = header + body; + EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*)newObject.data(); + + // Determine correct file header size + bool largeFile = false; + UINT32 headerSize = sizeof(EFI_FFS_FILE_HEADER); + if (revision == 2 && (fileHeader->Attributes & FFS_ATTRIB_LARGE_FILE)) { + largeFile = true; + headerSize = sizeof(EFI_FFS_FILE_HEADER2); + } + + QByteArray newHeader = newObject.left(headerSize); + QByteArray newBody = newObject.mid(headerSize); // Check if the file has a tail - UINT8 tailSize = fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT ? sizeof(UINT16) : 0; + UINT8 tailSize = (revision == 1 && (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT)) ? sizeof(UINT16) : 0; if (tailSize) { // Remove the tail, it will then be added back for revision 1 volumes newBody = newBody.left(newBody.size() - tailSize); - // Remove the attribute for rev2+ volumes - if (revision != 1) { - fileHeader->Attributes &= ~(FFS_ATTRIB_TAIL_PRESENT); - } } // Correct file size - uint32ToUint24(sizeof(EFI_FFS_FILE_HEADER) + newBody.size() + tailSize, fileHeader->Size); + if (!largeFile) { + if (newBody.size() >= 0xFFFFFF) { + return ERR_INVALID_FILE; + } + + uint32ToUint24(headerSize + newBody.size() + tailSize, fileHeader->Size); + } + else { + uint32ToUint24(0xFFFFFF, fileHeader->Size); + EFI_FFS_FILE_HEADER2* fileHeader2 = (EFI_FFS_FILE_HEADER2*)newHeader.data(); + fileHeader2->ExtendedSize = headerSize + newBody.size() + tailSize; + } + + // Set file state + UINT8 state = EFI_FILE_DATA_VALID | EFI_FILE_HEADER_VALID | EFI_FILE_HEADER_CONSTRUCTION; + if (erasePolarity) + state = ~state; + fileHeader->State = state; // Recalculate header checksum fileHeader->IntegrityCheck.Checksum.Header = 0; fileHeader->IntegrityCheck.Checksum.File = 0; - fileHeader->IntegrityCheck.Checksum.Header = calculateChecksum8((const UINT8*)fileHeader, sizeof(EFI_FFS_FILE_HEADER) - 1); + fileHeader->IntegrityCheck.Checksum.Header = 0x100 - (calculateSum8((const UINT8*)newHeader.constData(), headerSize) - fileHeader->State); // Recalculate data checksum, if needed if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) @@ -2254,23 +2286,17 @@ UINT8 FfsEngine::create(const QModelIndex & index, const UINT8 type, const QByte created.append(newBody); // Append tail, if needed - if (revision ==1 && tailSize) { + if (revision == 1 && tailSize) { UINT8 ht = ~fileHeader->IntegrityCheck.Checksum.Header; UINT8 ft = ~fileHeader->IntegrityCheck.Checksum.File; created.append(ht).append(ft); } - // Set file state - UINT8 state = EFI_FILE_DATA_VALID | EFI_FILE_HEADER_VALID | EFI_FILE_HEADER_CONSTRUCTION; - if (erasePolarity) - state = ~state; - fileHeader->State = state; - // Prepend header created.prepend(newHeader); // Parse file - result = parseFile(created, fileIndex, erasePolarity ? ERASE_POLARITY_TRUE : ERASE_POLARITY_FALSE, index, mode); + result = parseFile(created, fileIndex, revision, erasePolarity ? ERASE_POLARITY_TRUE : ERASE_POLARITY_FALSE, index, mode); if (result && result != ERR_VOLUMES_NOT_FOUND && result != ERR_INVALID_VOLUME) return result; @@ -2428,17 +2454,20 @@ UINT8 FfsEngine::insert(const QModelIndex & index, const QByteArray & object, co } else if (model->type(parent) == Types::File) { type = Types::Section; - const EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)object.constData(); + const EFI_COMMON_SECTION_HEADER* commonHeader = (const EFI_COMMON_SECTION_HEADER*)object.constData(); headerSize = sizeOfSectionHeader(commonHeader); } else if (model->type(parent) == Types::Section) { type = Types::Section; - const EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)object.constData(); + const EFI_COMMON_SECTION_HEADER* commonHeader = (const EFI_COMMON_SECTION_HEADER*)object.constData(); headerSize = sizeOfSectionHeader(commonHeader); } else return ERR_NOT_IMPLEMENTED; + if ((UINT32)object.size() < headerSize) + return ERR_BUFFER_TOO_SMALL; + return create(index, type, object.left(headerSize), object.right(object.size() - headerSize), mode, Actions::Insert); } @@ -2873,6 +2902,9 @@ UINT8 FfsEngine::constructPadFile(const QByteArray &guid, const UINT32 size, con if (size < sizeof(EFI_FFS_FILE_HEADER) || erasePolarity == ERASE_POLARITY_UNKNOWN) return ERR_INVALID_PARAMETER; + if (size >= 0xFFFFFF) // TODO: large file support + return ERR_INVALID_PARAMETER; + pad = QByteArray(size - guid.size(), erasePolarity == ERASE_POLARITY_TRUE ? '\xFF' : '\x00'); pad.prepend(guid); EFI_FFS_FILE_HEADER* header = (EFI_FFS_FILE_HEADER*)pad.data(); @@ -3265,6 +3297,7 @@ UINT8 FfsEngine::reconstructVolume(const QModelIndex & index, QByteArray & recon model->subtype(index.child(i, 0)) == EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER)){ QModelIndex peiFile = index.child(i, 0); UINT32 sectionOffset = sizeof(EFI_FFS_FILE_HEADER); + // BUGBUG: this parsing is bad and doesn't support large files, but it needs to be performed only for very old images with uncompressed DXE volumes, so whatever // Search for PE32 or TE section for (int j = 0; j < model->rowCount(peiFile); j++) { if (model->subtype(peiFile.child(j, 0)) == EFI_SECTION_PE32 || @@ -3330,6 +3363,9 @@ UINT8 FfsEngine::reconstructVolume(const QModelIndex & index, QByteArray & recon continue; EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*)file.data(); + UINT32 fileHeaderSize = sizeof(EFI_FFS_FILE_HEADER); + if (volumeHeader->Revision > 1 && (fileHeader->Attributes & FFS_ATTRIB_LARGE_FILE)) + fileHeaderSize = sizeof(EFI_FFS_FILE_HEADER2); // Pad file if (fileHeader->Type == EFI_FV_FILETYPE_PAD) { @@ -3357,8 +3393,8 @@ UINT8 FfsEngine::reconstructVolume(const QModelIndex & index, QByteArray & recon UINT8 alignmentPower; UINT32 alignmentBase; alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3]; - alignment = (UINT32)pow(2.0, alignmentPower); - alignmentBase = header.size() + offset + sizeof(EFI_FFS_FILE_HEADER); + alignment = (UINT32)(1UL <action(index) == Actions::Rebuild) { QByteArray header = model->header(index); EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*)header.data(); - + // Check erase polarity if (erasePolarity == ERASE_POLARITY_UNKNOWN) { msg(tr("reconstructFile: unknown erase polarity"), index); @@ -3635,6 +3671,10 @@ UINT8 FfsEngine::reconstructFile(const QModelIndex& index, const UINT8 revision, // File contains sections else { UINT32 offset = 0; + UINT32 headerSize = sizeof(EFI_FFS_FILE_HEADER); + if (revision > 1 && (fileHeader->Attributes & FFS_ATTRIB_LARGE_FILE)) { + headerSize = sizeof(EFI_FFS_FILE_HEADER2); + } for (int i = 0; i < model->rowCount(index); i++) { // Align to 4 byte boundary @@ -3646,7 +3686,7 @@ UINT8 FfsEngine::reconstructFile(const QModelIndex& index, const UINT8 revision, } // Calculate section base - UINT32 sectionBase = base ? base + sizeof(EFI_FFS_FILE_HEADER) + offset : 0; + UINT32 sectionBase = base ? base + headerSize + offset : 0; // Reconstruct section QByteArray section; @@ -3668,13 +3708,22 @@ UINT8 FfsEngine::reconstructFile(const QModelIndex& index, const UINT8 revision, // Correct file size UINT8 tailSize = (revision == 1 && (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT)) ? sizeof(UINT16) : 0; - - uint32ToUint24(sizeof(EFI_FFS_FILE_HEADER) + reconstructed.size() + tailSize, fileHeader->Size); + if (revision > 1 && (fileHeader->Attributes & FFS_ATTRIB_LARGE_FILE)) { + uint32ToUint24(EFI_SECTION2_IS_USED, fileHeader->Size); + EFI_FFS_FILE_HEADER2* fileHeader2 = (EFI_FFS_FILE_HEADER2*) fileHeader; + fileHeader2->ExtendedSize = sizeof(EFI_FFS_FILE_HEADER2) + reconstructed.size() + tailSize; + } else { + if (sizeof(EFI_FFS_FILE_HEADER) + reconstructed.size() + tailSize > 0xFFFFFF) { + msg(tr("reconstructFile: resulting file size is too big"), index); + return ERR_INVALID_FILE; + } + uint32ToUint24(sizeof(EFI_FFS_FILE_HEADER) + reconstructed.size() + tailSize, fileHeader->Size); + } // Recalculate header checksum fileHeader->IntegrityCheck.Checksum.Header = 0; fileHeader->IntegrityCheck.Checksum.File = 0; - fileHeader->IntegrityCheck.Checksum.Header = calculateChecksum8((const UINT8*)fileHeader, sizeof(EFI_FFS_FILE_HEADER) - 1); + fileHeader->IntegrityCheck.Checksum.Header = 0x100 - (calculateSum8((const UINT8*)header.constData(), header.size()) - fileHeader->State); } // Use current file body else @@ -3690,7 +3739,7 @@ UINT8 FfsEngine::reconstructFile(const QModelIndex& index, const UINT8 revision, fileHeader->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM2; // Append tail, if needed - if (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) { + if (revision == 1 && fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) { UINT8 ht = ~fileHeader->IntegrityCheck.Checksum.Header; UINT8 ft = ~fileHeader->IntegrityCheck.Checksum.File; reconstructed.append(ht).append(ft); @@ -3732,6 +3781,10 @@ UINT8 FfsEngine::reconstructSection(const QModelIndex& index, const UINT32 base, model->action(index) == Actions::Rebase) { QByteArray header = model->header(index); EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)header.data(); + bool extended = false; + if(uint24ToUint32(commonHeader->Size) == 0xFFFFFF) { + extended = true; + } // Reconstruct section with children if (model->rowCount(index)) { @@ -3832,7 +3885,13 @@ UINT8 FfsEngine::reconstructSection(const QModelIndex& index, const UINT32 base, } // Correct section size - uint32ToUint24(header.size() + reconstructed.size(), commonHeader->Size); + if (extended) { + EFI_COMMON_SECTION_HEADER2 * extHeader = (EFI_COMMON_SECTION_HEADER2*) commonHeader; + extHeader->ExtendedSize = header.size() + reconstructed.size(); + uint32ToUint24(0xFFFFFF, commonHeader->Size); + } else { + uint32ToUint24(header.size() + reconstructed.size(), commonHeader->Size); + } } // Leaf section else diff --git a/ffsengine.h b/ffsengine.h index 4fe5223..32a88ff 100644 --- a/ffsengine.h +++ b/ffsengine.h @@ -70,7 +70,7 @@ public: UINT8 parseEcRegion(const QByteArray & ec, QModelIndex & index, const QModelIndex & parent, const UINT8 mode = CREATE_MODE_APPEND); UINT8 parseBios(const QByteArray & bios, const QModelIndex & parent = QModelIndex()); UINT8 parseVolume(const QByteArray & volume, QModelIndex & index, const QModelIndex & parent = QModelIndex(), const UINT8 mode = CREATE_MODE_APPEND); - UINT8 parseFile(const QByteArray & file, QModelIndex & index, const UINT8 erasePolarity = ERASE_POLARITY_UNKNOWN, const QModelIndex & parent = QModelIndex(), const UINT8 mode = CREATE_MODE_APPEND); + UINT8 parseFile(const QByteArray & file, QModelIndex & index, const UINT8 revision = 2, const UINT8 erasePolarity = ERASE_POLARITY_UNKNOWN, const QModelIndex & parent = QModelIndex(), const UINT8 mode = CREATE_MODE_APPEND); UINT8 parseSections(const QByteArray & body, const QModelIndex & parent = QModelIndex()); UINT8 parseSection(const QByteArray & section, QModelIndex & index, const QModelIndex & parent = QModelIndex(), const UINT8 mode = CREATE_MODE_APPEND); @@ -117,7 +117,6 @@ private: UINT8 parseDepexSection(const QByteArray & body, QString & parsed); UINT8 findNextVolume(const QByteArray & bios, const UINT32 volumeOffset, UINT32 & nextVolumeOffset); UINT8 getVolumeSize(const QByteArray & bios, const UINT32 volumeOffset, UINT32 & volumeSize, UINT32 & bmVolumeSize); - UINT8 getFileSize(const QByteArray & volume, const UINT32 fileOffset, UINT32 & fileSize); UINT8 getSectionSize(const QByteArray & file, const UINT32 sectionOffset, UINT32 & sectionSize); // Reconstruction helpers diff --git a/uefitool.cpp b/uefitool.cpp index 34ca9e0..5fec49a 100644 --- a/uefitool.cpp +++ b/uefitool.cpp @@ -17,7 +17,7 @@ UEFITool::UEFITool(QWidget *parent) : QMainWindow(parent), ui(new Ui::UEFITool), -version(tr("0.21.5")) +version(tr("0.22.0")) { clipboard = QApplication::clipboard();