/* fssbuilder.cpp Copyright (c) 2015, 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, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. */ #include "ffsbuilder.h" #include "descriptor.h" #include "ffs.h" #include "peimage.h" #include "utility.h" #include "nvram.h" #include USTATUS FfsBuilder::erase(const UModelIndex & index, UByteArray & erased) { // Sanity check if (!index.isValid()) return U_INVALID_PARAMETER; // Try to get emptyByte value from item's parsing data UINT8 emptyByte = 0xFF; if (!model->hasEmptyParsingData(index)) { if (model->type(index) == Types::Volume) { VOLUME_PARSING_DATA pdata = *(VOLUME_PARSING_DATA*)model->parsingData(index).constData(); emptyByte = pdata.emptyByte; } else if (model->type(index) == Types::File) { FILE_PARSING_DATA pdata = *(FILE_PARSING_DATA*)model->parsingData(index).constData(); emptyByte = pdata.emptyByte; } } erased = UByteArray(model->header(index).size() + model->body(index).size() + model->tail(index).size(), emptyByte); return U_SUCCESS; } USTATUS FfsBuilder::build(const UModelIndex & index, UByteArray & reconstructed) { if (!index.isValid()) return U_SUCCESS; USTATUS result; switch (model->type(index)) { case Types::Image: if (model->subtype(index) == Subtypes::IntelImage) { result = buildIntelImage(index, reconstructed); if (result) return result; } else { //Other images types can be reconstructed like regions result = buildRegion(index, reconstructed); if (result) return result; } break; case Types::Capsule: result = buildCapsule(index, reconstructed); if (result) return result; break; case Types::Region: result = buildRegion(index, reconstructed); if (result) return result; break; case Types::Padding: result = buildPadding(index, reconstructed); if (result) return result; break; case Types::Volume: // Nvram rebuild support if(model->subtype(index) == Subtypes::NvramVolume) result = buildNvramVolume(index, reconstructed); else result = buildVolume(index, reconstructed); if (result) return result; break; case Types::Section: result = buildSection(index, 0, reconstructed); if (result) return result; break; default: msg(usprintf("build: unknown item type %d", model->type(index)), index); return U_UNKNOWN_ITEM_TYPE; } return U_SUCCESS; } USTATUS FfsBuilder::buildRegion(const UModelIndex& index, UByteArray& reconstructed, bool includeHeader) { if (!index.isValid()) return U_SUCCESS; USTATUS result; // No action if (model->action(index) == Actions::NoAction) { reconstructed = model->header(index) + model->body(index); return U_SUCCESS; } else if (model->action(index) == Actions::Remove) { reconstructed.clear(); return U_SUCCESS; } else if (model->action(index) == Actions::Rebuild || model->action(index) == Actions::Replace) { if (model->rowCount(index)) { reconstructed.clear(); // Reconstruct children for (int i = 0; i < model->rowCount(index); i++) { UByteArray child; result = build(index.child(i, 0), child); if (result) return result; reconstructed += child; } } // Use stored item body else reconstructed = model->body(index); // Check size of reconstructed region, it must be same if (reconstructed.size() > model->body(index).size()) { msg("buildRegion: reconstructed region size is bigger then original ", index); return U_INVALID_PARAMETER; } else if (reconstructed.size() < model->body(index).size()) { msg("buildRegion: reconstructed region size is smaller then original ", index); return U_INVALID_PARAMETER; } // Reconstruction successful if (includeHeader) reconstructed = model->header(index) + reconstructed; return U_SUCCESS; } // All other actions are not supported return U_NOT_IMPLEMENTED; } USTATUS FfsBuilder::buildCapsule(const UModelIndex & index, UByteArray & capsule) { // Sanity check if (!index.isValid()) return U_INVALID_PARAMETER; // No action if (model->action(index) == Actions::NoAction) { // Use original item data capsule = model->header(index) + model->body(index) + model->tail(index); return U_SUCCESS; } // Rebuild or Replace else if (model->action(index) == Actions::Rebuild || model->action(index) == Actions::Replace) { if (model->rowCount(index)) { // Clear the supplied UByteArray capsule.clear(); // Right now there is only one capsule image element supported if (model->rowCount(index) != 1) { msg(usprintf("buildCapsule: building of capsules with %d items is not yet supported", model->rowCount(index)), index); return U_NOT_IMPLEMENTED; } // Build image UModelIndex imageIndex = index.child(0, 0); UByteArray imageData; // Check image type if (model->type(imageIndex) == Types::Image) { USTATUS result = U_SUCCESS; if (model->subtype(imageIndex) == Subtypes::IntelImage) { result = buildIntelImage(imageIndex, imageData); } else if (model->subtype(imageIndex) == Subtypes::UefiImage) { result = buildRawArea(imageIndex, imageData); } else { msg(UString("buildCapsule: unexpected item subtype ") + itemSubtypeToUString(model->type(imageIndex), model->subtype(imageIndex)), imageIndex); return U_UNKNOWN_ITEM_TYPE; } // Check build result if (result) { msg(UString("buildCapsule: building of ") + model->name(imageIndex) + UString(" failed with error ") + errorCodeToUString(result), imageIndex); return result; } else capsule += imageData; } else { msg(UString("buildCapsule: unexpected item type ") + itemTypeToUString(model->type(imageIndex)), imageIndex); return U_UNKNOWN_ITEM_TYPE; } // Check size of reconstructed capsule body, it must remain the same UINT32 newSize = capsule.size(); UINT32 oldSize = model->body(index).size(); if (newSize > oldSize) { msg(usprintf("buildCapsule: new capsule size %Xh (%u) is bigger than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index); return U_INVALID_CAPSULE; } else if (newSize < oldSize) { msg(usprintf("buildCapsule: new capsule size %Xh (%u) is smaller than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index); return U_INVALID_CAPSULE; } } else capsule = model->body(index); // Build successful, header and tail capsule = model->header(index) + capsule + model->tail(index); return U_SUCCESS; } msg(UString("buildCapsule: unexpected action " + actionTypeToUString(model->action(index))), index); return U_NOT_IMPLEMENTED; } USTATUS FfsBuilder::buildIntelImage(const UModelIndex & index, UByteArray & intelImage) { // Sanity check if (!index.isValid()) return U_SUCCESS; // No action if (model->action(index) == Actions::NoAction) { intelImage = model->header(index) + model->body(index) + model->tail(index); return U_SUCCESS; } // Remove else if (model->action(index) == Actions::Remove) { intelImage.clear(); return U_SUCCESS; } // Rebuild else if (model->action(index) == Actions::Rebuild) { // First child will always be descriptor for this type of image, and it's read only for now intelImage = model->header(index.child(0, 0)) + model->body(index.child(0, 0)) + model->tail(index.child(0, 0)); // Process other regions for (int i = 1; i < model->rowCount(index); i++) { UModelIndex currentRegion = index.child(i, 0); // Skip regions with Remove action if (model->action(currentRegion) == Actions::Remove) continue; // Check item type to be either region or padding UINT8 type = model->type(currentRegion); if (type == Types::Padding) { // Add padding as is intelImage += model->header(currentRegion) + model->body(currentRegion) + model->tail(currentRegion); continue; } // Check region subtype USTATUS result; UByteArray region; UINT8 regionType = model->subtype(currentRegion); switch (regionType) { case Subtypes::BiosRegion: case Subtypes::PdrRegion: result = buildRawArea(currentRegion, region); if (result) { msg(UString("buildIntelImage: building of region ") + regionTypeToUString(regionType) + UString(" failed with error ") + errorCodeToUString(result), currentRegion); return result; } break; case Subtypes::MeRegion: case Subtypes::GbeRegion: case Subtypes::DevExp1Region: case Subtypes::Bios2Region: case Subtypes::MicrocodeRegion: case Subtypes::EcRegion: case Subtypes::DevExp2Region: case Subtypes::IeRegion: case Subtypes::Tgbe1Region: case Subtypes::Tgbe2Region: case Subtypes::Reserved1Region: case Subtypes::Reserved2Region: case Subtypes::PttRegion: // Add region as is region = model->header(currentRegion) + model->body(currentRegion); break; default: msg(UString("buildIntelImage: unknown region type"), currentRegion); return U_UNKNOWN_ITEM_TYPE; } // the resulting region intelImage += region; } // Check size of new image, it must be same as old one UINT32 newSize = intelImage.size(); UINT32 oldSize = model->body(index).size(); if (newSize > oldSize) { msg(usprintf("buildIntelImage: new image size %Xh (%u) is bigger than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index); return U_INVALID_IMAGE; } else if (newSize < oldSize) { msg(usprintf("buildIntelImage: new image size %Xh (%u) is smaller than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index); return U_INVALID_IMAGE; } // Build successful, header and tail intelImage = model->header(index) + intelImage + model->tail(index); return U_SUCCESS; } msg(UString("buildIntelImage: unexpected action " + actionTypeToUString(model->action(index))), index); return U_NOT_IMPLEMENTED; } USTATUS FfsBuilder::buildRawArea(const UModelIndex & index, UByteArray & rawArea, bool includeHeader) { // Sanity check if (!index.isValid()) return U_INVALID_PARAMETER; // No action required if (model->action(index) == Actions::NoAction) { rawArea = model->header(index) + model->body(index) + model->tail(index); return U_SUCCESS; } // Remove else if (model->action(index) == Actions::Remove) { rawArea.clear(); return U_SUCCESS; } // Rebuild or Replace else if (model->action(index) == Actions::Rebuild || model->action(index) == Actions::Replace) { // Rebuild if there is at least 1 child if (model->rowCount(index)) { // Clear the supplied UByteArray rawArea.clear(); // Build children for (int i = 0; i < model->rowCount(index); i++) { USTATUS result = U_SUCCESS; UModelIndex currentChild = index.child(i, 0); UByteArray currentData; // Check child type if (model->type(currentChild) == Types::Volume) { result = buildVolume(currentChild, currentData); } else if (model->type(currentChild) == Types::Padding) { result = buildPadding(currentChild, currentData); } else { msg(UString("buildRawArea: unexpected item type ") + itemTypeToUString(model->type(currentChild)), currentChild); return U_UNKNOWN_ITEM_TYPE; } // Check build result if (result) { msg(UString("buildRawArea: building of ") + model->name(currentChild) + UString(" failed with error ") + errorCodeToUString(result), currentChild); return result; } // current data rawArea += currentData; } // Check size of new raw area, it must be same as original one UINT32 newSize = rawArea.size(); UINT32 oldSize = model->body(index).size(); if (newSize > oldSize) { msg(usprintf("buildRawArea: new area size %Xh (%u) is bigger than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index); return U_INVALID_RAW_AREA; } else if (newSize < oldSize) { msg(usprintf("buildRawArea: new area size %Xh (%u) is smaller than the original %Xh (%u)", newSize, newSize, oldSize, oldSize), index); return U_INVALID_RAW_AREA; } } // No need to rebuild a raw area with no children else { rawArea = model->body(index); } // Build successful, add header if needed if(includeHeader) rawArea = model->header(index) + rawArea + model->tail(index); else rawArea = rawArea + model->tail(index); return U_SUCCESS; } msg(UString("buildRawArea: unexpected action " + actionTypeToUString(model->action(index))), index); return U_NOT_IMPLEMENTED; } USTATUS FfsBuilder::buildPadding(const UModelIndex & index, UByteArray & padding) { // Sanity check if (!index.isValid()) return U_INVALID_PARAMETER; // No action required if (model->action(index) == Actions::NoAction) { padding = model->header(index) + model->body(index) + model->tail(index); return U_SUCCESS; } // Remove else if (model->action(index) == Actions::Remove) { padding.clear(); return U_SUCCESS; } // Erase else if (model->action(index) == Actions::Erase) { return erase(index, padding); } msg(UString("buildPadding: unexpected action " + actionTypeToUString(model->action(index))), index); return U_NOT_IMPLEMENTED; } USTATUS FfsBuilder::buildNonUefiData(const UModelIndex & index, UByteArray & data) { // Sanity check if (!index.isValid()) return U_INVALID_PARAMETER; // No action required if (model->action(index) == Actions::NoAction) { data = model->header(index) + model->body(index) + model->tail(index); return U_SUCCESS; } // Remove else if (model->action(index) == Actions::Remove) { data.clear(); return U_SUCCESS; } // Erase else if (model->action(index) == Actions::Erase) { return erase(index, data); } // TODO: rebuild properly msg(UString("buildNoUefiData: unexpected action " + actionTypeToUString(model->action(index))), index); return U_NOT_IMPLEMENTED; } USTATUS FfsBuilder::buildFreeSpace(const UModelIndex & index, UByteArray & freeSpace) { // Sanity check if (!index.isValid()) return U_INVALID_PARAMETER; // No actions possible for free space freeSpace = model->header(index) + model->body(index) + model->tail(index); return U_SUCCESS; } USTATUS FfsBuilder::buildVolume(const UModelIndex & index, UByteArray & volume) { if (!index.isValid()) return U_SUCCESS; USTATUS result; // No action if (model->action(index) == Actions::NoAction) { volume = model->header(index) + model->body(index); return U_SUCCESS; } else if (model->action(index) == Actions::Remove) { volume.clear(); return U_SUCCESS; } else if (model->action(index) == Actions::Replace || model->action(index) == Actions::Rebuild) { UByteArray header = model->header(index); UByteArray body = model->body(index); EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)header.data(); // Check sanity of HeaderLength if (volumeHeader->HeaderLength > header.size()) { msg(UString("buildVolume: invalid volume header length, reconstruction is not possible"), index); return U_INVALID_VOLUME; } // Recalculate volume header checksum volumeHeader->Checksum = 0; volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength); // Get volume size UINT32 volumeSize = header.size() + body.size(); // Reconstruct volume body UINT32 freeSpaceOffset = 0; if (model->rowCount(index)) { volume.clear(); UINT8 polarity = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY ? ERASE_POLARITY_TRUE : ERASE_POLARITY_FALSE; char empty = volumeHeader->Attributes & EFI_FVB_ERASE_POLARITY ? '\xFF' : '\x00'; // Calculate volume base for volume UINT32 volumeBase; UByteArray file; bool baseFound = false; // Search for VTF for (int i = 0; i < model->rowCount(index); i++) { file = model->header(index.child(i, 0)); // VTF found if (file.left(sizeof(EFI_GUID)) == EFI_FFS_VOLUME_TOP_FILE_GUID) { baseFound = true; volumeBase = (UINT32)(0x100000000 - volumeSize); break; } } // Determine if volume is inside compressed item if (!baseFound) { // Iterate up to the root, checking for compression type to be other then none for (UModelIndex parentIndex = index.parent(); model->type(parentIndex) != Types::Root; parentIndex = parentIndex.parent()) { UByteArray data = model->parsingData(parentIndex); const COMPRESSED_SECTION_PARSING_DATA* pdata = (const COMPRESSED_SECTION_PARSING_DATA*)data.constData(); if (pdata->algorithm != COMPRESSION_ALGORITHM_NONE) { // No rebase needed for compressed PEI files baseFound = true; volumeBase = 0; break; } } } // Find volume base address using first PEI file in it if (!baseFound) { // Search for first PEI-file and use it as base source UINT32 fileOffset = header.size(); for (int i = 0; i < model->rowCount(index); i++) { if ((model->subtype(index.child(i, 0)) == EFI_FV_FILETYPE_PEI_CORE || model->subtype(index.child(i, 0)) == EFI_FV_FILETYPE_PEIM || model->subtype(index.child(i, 0)) == EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER)){ UModelIndex 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 || model->subtype(peiFile.child(j, 0)) == EFI_SECTION_TE) { UModelIndex image = peiFile.child(j, 0); // Check for correct action if (model->action(image) == Actions::Remove || model->action(image) == Actions::Insert) continue; // Calculate relative base address UINT32 relbase = fileOffset + sectionOffset + model->header(image).size(); // Calculate offset of image relative to file base UINT32 imagebase = 0; result = getBase(model->body(image), imagebase); // imagebase passed by reference if (!result) { // Calculate volume base volumeBase = imagebase - relbase; baseFound = true; goto out; } } sectionOffset += model->header(peiFile.child(j, 0)).size() + model->body(peiFile.child(j, 0)).size(); sectionOffset = ALIGN4(sectionOffset); } } fileOffset += model->header(index.child(i, 0)).size() + model->body(index.child(i, 0)).size(); fileOffset = ALIGN8(fileOffset); } } out: // Do not set volume base if (!baseFound) volumeBase = 0; // Reconstruct files in volume UINT32 offset = 0; UByteArray padFileGuid = EFI_FFS_PAD_FILE_GUID; UByteArray vtf; UModelIndex vtfIndex; UINT32 nonUefiDataOffset = 0; UByteArray nonUefiData; for (int i = 0; i < model->rowCount(index); i++) { // Inside a volume can be files, free space or padding with non-UEFI data if (model->type(index.child(i, 0)) == Types::File) { // Next item is a file // Align to 8 byte boundary UINT32 alignment = offset % 8; if (alignment) { alignment = 8 - alignment; offset += alignment; volume += UByteArray(alignment, empty); } // Calculate file base UINT32 fileBase = volumeBase ? volumeBase + header.size() + offset : 0; // Reconstruct file result = buildFile(index.child(i, 0), volumeHeader->Revision, polarity, fileBase, file); if (result) return result; // Empty file if (file.isEmpty()) 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) { padFileGuid = file.left(sizeof(EFI_GUID)); // Parse non-empty pad file if (model->rowCount(index.child(i, 0))) { //TODO: handle it msg("buildVolume: non-empty pad-file contents will be destroyed after volume modifications"); continue; } // Skip empty pad-file else continue; } // Volume Top File if (file.left(sizeof(EFI_GUID)) == EFI_FFS_VOLUME_TOP_FILE_GUID) { vtf = file; vtfIndex = index.child(i, 0); continue; } // Normal file // Ensure correct alignment UINT8 alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3]; alignment = (UINT32)(1UL <Revision, polarity, pad); if (result) return result; // Append constructed pad file to volume body volume += pad; offset += size; } // Append current file to new volume body volume += file; // Change current file offset offset += file.size(); } else if (model->type(index.child(i, 0)) == Types::FreeSpace) { //Next item is a free space // Some data are located beyond free space if (offset + (UINT32)model->body(index.child(i, 0)).size() < (UINT32)model->body(index).size()) { // Get non-UEFI data and it's offset nonUefiData = model->body(index.child(i + 1, 0)); nonUefiDataOffset = body.size() - nonUefiData.size(); break; } } } // Check volume sanity if (!vtf.isEmpty() && !nonUefiData.isEmpty()) { msg(usprintf("buildVolume: both VTF and non-UEFI data found in the volume, reconstruction is not possible"), index); return U_INVALID_VOLUME; } // Check for free space offset in ZeroVector if (model->text(index).contains("AppleFSO ")) { // Align current offset to 8 byte boundary UINT32 alignment = offset % 8; freeSpaceOffset = model->header(index).size() + offset; if (alignment) { alignment = 8 - alignment; freeSpaceOffset += alignment; } } // Insert VTF or non-UEFI data to it's correct place if (!vtf.isEmpty()) { // VTF found // Determine correct VTF offset UINT32 vtfOffset = model->body(index).size() - vtf.size(); if (vtfOffset % 8) { msg(usprintf("buildVolume: wrong size of the Volume Top File"), index); return U_INVALID_FILE; } // Insert pad file to fill the gap if (vtfOffset > offset) { // Determine pad file size UINT32 size = vtfOffset - offset; // Construct pad file UByteArray pad; result = buildPadFile(padFileGuid, size, volumeHeader->Revision, polarity, pad); if (result) return result; // Append constructed pad file to volume body volume += pad; } // No more space left in volume else if (offset > vtfOffset) { msg(usprintf("buildVolume: no space left to insert VTF, need %xh (%d) byte(s) more", offset - vtfOffset, offset - vtfOffset), index); return U_INVALID_VOLUME; } // Calculate VTF base UINT32 vtfBase = volumeBase ? volumeBase + vtfOffset : 0; // Reconstruct VTF again result = buildFile(vtfIndex, volumeHeader->Revision, polarity, vtfBase, vtf); if (result) return result; // Patch VTF if (!parser->peiCoreEntryPoint) { msg("patchVtf: PEI Core entry point can't be determined. VTF can't be patched.", index); return U_PEI_CORE_ENTRY_POINT_NOT_FOUND; } if (parser->newPeiCoreEntryPoint && parser->peiCoreEntryPoint != parser->newPeiCoreEntryPoint) { // Replace last occurrence of oldPeiCoreEntryPoint with newPeiCoreEntryPoint UByteArray old((char*)&parser->peiCoreEntryPoint, sizeof(parser->peiCoreEntryPoint)); int i = vtf.lastIndexOf(old); if (i == -1) msg("patchVtf: PEI Core entry point can't be found in VTF. VTF not patched.", index); else { UINT32* data = (UINT32*)(vtf.data() + i); *data = parser->newPeiCoreEntryPoint; } } // Append VTF volume += vtf; } else if (!nonUefiData.isEmpty()) { //Non-UEFI data found // No space left if (offset > nonUefiDataOffset) { msg(usprintf("buildVolume: no space left to insert non-UEFI data, need %xh (%d) byte(s) more", offset - nonUefiDataOffset, offset - nonUefiDataOffset), index); return U_INVALID_VOLUME; } // Append additional free space else if (nonUefiDataOffset > offset) { volume += UByteArray(nonUefiDataOffset - offset, empty); } // Append VTF volume += nonUefiData; } else { // Fill the rest of volume space with empty char if (body.size() > volume.size()) { // Fill volume end with empty char volume += UByteArray(body.size() - volume.size(), empty); } else if (body.size() < volume.size()) { // Check if volume can be grown // Root volume can't be grown UINT8 parentType = model->type(index.parent()); if (parentType != Types::File && parentType != Types::Section) { msg("buildVolume: root volume can't be grown", index); return U_INVALID_VOLUME; } // Grow volume to fit new body UINT32 newSize = header.size() + volume.size(); result = growVolume(header, volumeSize, newSize); if (result) return result; // Fill volume end with empty char volume += UByteArray(newSize - header.size() - volume.size(), empty); volumeSize = newSize; } } } // Use current volume body else { volume = model->body(index); // BUGBUG: volume size may change during this operation for volumes withour files in them // but such volumes are fairly rare } // Check new volume size if ((UINT32)(header.size() + volume.size()) != volumeSize) { msg("buildVolume: volume size can't be changed", index); return U_INVALID_VOLUME; } // Reconstruction successful volume = header + volume; // Recalculate CRC32 in ZeroVector, if needed if (model->text(index).contains("AppleCRC32 ")) { // Get current CRC32 value from volume header const UINT32 currentCrc = *(const UINT32*)(volume.constData() + 8); // Calculate new value UINT32 crc = crc32(0, (const UINT8*)volume.constData() + volumeHeader->HeaderLength, volume.size() - volumeHeader->HeaderLength); // Update the value if (currentCrc != crc) { *(UINT32*)(volume.data() + 8) = crc; // Recalculate header checksum volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)volume.data(); volumeHeader->Checksum = 0; volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength); } } // Store new free space offset, if needed if (model->text(index).contains("AppleFSO ")) { // Get current CRC32 value from volume header const UINT32 currentFso = *(const UINT32*)(volume.constData() + 12); // Update the value if (freeSpaceOffset != 0 && currentFso != freeSpaceOffset) { *(UINT32*)(volume.data() + 12) = freeSpaceOffset; // Recalculate header checksum volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)volume.data(); volumeHeader->Checksum = 0; volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength); } } return U_SUCCESS; } return U_NOT_IMPLEMENTED; } USTATUS FfsBuilder::buildNvramVolume(const UModelIndex & index, UByteArray & volume) { if (!index.isValid()) return U_SUCCESS; USTATUS result; // No action if (model->action(index) == Actions::NoAction) { volume = model->header(index) + model->body(index); return U_SUCCESS; } else if (model->action(index) == Actions::Remove) { volume.clear(); return U_SUCCESS; } else if (model->action(index) == Actions::Replace || model->action(index) == Actions::Rebuild) { UByteArray header = model->header(index); UByteArray body = model->body(index); EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)header.data(); // Check sanity of HeaderLength if (volumeHeader->HeaderLength > header.size()) { msg(UString("buildNvramVolume: invalid volume header length, reconstruction is not possible"), index); return U_INVALID_VOLUME; } // Recalculate volume header checksum volumeHeader->Checksum = 0; volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength); volume.clear(); for (int i = 0; i < model->rowCount(index); i++) { UModelIndex currentIndex = index.child(i, 0); UByteArray store; result = buildNvramStore(currentIndex, store); if(result) return result; // Element reconstruct success volume += store; } // Volume reconstruct success volume = header + volume; return U_SUCCESS; } return U_NOT_IMPLEMENTED; } USTATUS FfsBuilder::buildNvramStore(const UModelIndex & index, UByteArray & store) { UByteArray header = model->header(index); UByteArray body = model->body(index); UINT8 type = model->type(index); if(model->action(index) == Actions::Remove) { header.clear(); body.clear(); } else if(model->action(index) == Actions::Replace || model->action(index) == Actions::Rebuild) { if(type == Types::FdcStore) { // Recalculate store header EFI_FIRMWARE_VOLUME_HEADER* volumeHeader = (EFI_FIRMWARE_VOLUME_HEADER*)header.data(); volumeHeader->Checksum = 0; volumeHeader->Checksum = calculateChecksum16((const UINT16*)volumeHeader, volumeHeader->HeaderLength); // Rebuild VSS or VSS2 volume inside UByteArray vssStore; buildNvramStore(index.child(0, 0), vssStore); body = vssStore; } else if(type == Types::VssStore || type == Types::Vss2Store) { if(model->rowCount(index)) { body.clear(); for (int i = 0; i < model->rowCount(index); i++) { UModelIndex currentIndex = index.child(i, 0); UByteArray currentHeader = model->header(currentIndex); UByteArray currentBody = model->body(currentIndex); UINT8 currentAction = model->action(currentIndex); if(currentAction == Actions::Remove) { currentHeader.clear(); currentBody.clear(); } else if(currentAction == Actions::Rebuild || currentAction == Actions::Replace) { // Recalculate all Apple variables crc's if(model->subtype(currentIndex) == Subtypes::AppleVssEntry) { VSS_APPLE_VARIABLE_HEADER* appleVariableHeader = (VSS_APPLE_VARIABLE_HEADER*)currentHeader.data(); appleVariableHeader->DataCrc32 = crc32(0, (const UINT8*)currentBody.constData(), currentBody.size()); //appleVariableHeader->DataSize = currentBody.size(); } } body += currentHeader + currentBody; } } } else if(type == Types::FsysStore) { UByteArray store = header + body; // Recalculate store checksum UINT32 calculatedCrc = crc32(0, (const UINT8*)store.constData(), (const UINT32)store.size() - sizeof(UINT32)); // Write new checksum UINT32 *crc = reinterpret_cast(body.data() + body.size() - sizeof(UINT32)); std::memcpy(crc, &calculatedCrc, sizeof(UINT32)); } else if(type == Types::EvsaStore) { UByteArray store = header + body; // Recalculate header checksum const EVSA_STORE_ENTRY* evsaStoreHeader = (const EVSA_STORE_ENTRY*)store.constData(); UINT8 storeCrc = calculateChecksum8(((const UINT8*)evsaStoreHeader) + 2, evsaStoreHeader->Header.Size - 2); // Write new checksum EVSA_ENTRY_HEADER* evsaEntryHeader = (EVSA_ENTRY_HEADER*)header.data(); evsaEntryHeader->Checksum = storeCrc; // Recalculate all crc's if(model->rowCount(index)) { body.clear(); for (int i = 0; i < model->rowCount(index); i++) { UModelIndex currentIndex = index.child(i, 0); UByteArray currentHeader = model->header(currentIndex); UByteArray currentBody = model->body(currentIndex); UINT8 currentSubtype = model->subtype(currentIndex); UINT8 currentAction = model->action(currentIndex); if(currentAction == Actions::Remove) { currentHeader.clear(); currentBody.clear(); } else if(currentAction == Actions::Rebuild || currentAction == Actions::Replace) { if(currentSubtype == Subtypes::DataEvsaEntry || currentSubtype == Subtypes::GuidEvsaEntry || currentSubtype == Subtypes::NameEvsaEntry) { UByteArray currentStore = currentHeader + currentBody; // Recalculate header checksum const EVSA_STORE_ENTRY* evsaStoreHeader = (const EVSA_STORE_ENTRY*)currentStore.constData(); UINT8 entryCrc = calculateChecksum8(((const UINT8*)evsaStoreHeader) + 2, evsaStoreHeader->Header.Size - 2); // Write new checksum EVSA_ENTRY_HEADER* evsaEntryHeader = (EVSA_ENTRY_HEADER*)currentHeader.data(); evsaEntryHeader->Checksum = entryCrc; } } body += currentHeader + currentBody; } } } else if(type == Types::FtwStore) { // Recalculate block header checksum EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32* crcFtwBlockHeader = (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER32*)header.data(); crcFtwBlockHeader->Crc = crc32(0, (const UINT8*)crcFtwBlockHeader, header.size()); } } // Rebuild end store.clear(); store = header + body; return U_SUCCESS; } USTATUS FfsBuilder::buildPadFile(const UByteArray & guid, const UINT32 size, const UINT8 revision, const UINT8 erasePolarity, UByteArray & pad) { if (size < sizeof(EFI_FFS_FILE_HEADER) || erasePolarity == ERASE_POLARITY_UNKNOWN) return U_INVALID_PARAMETER; if (size >= 0xFFFFFF) // TODO: large file support return U_INVALID_PARAMETER; pad = UByteArray(size - guid.size(), erasePolarity == ERASE_POLARITY_TRUE ? '\xFF' : '\x00'); pad = guid + pad; EFI_FFS_FILE_HEADER* header = (EFI_FFS_FILE_HEADER*)pad.data(); uint32ToUint24(size, header->Size); header->Attributes = 0x00; header->Type = EFI_FV_FILETYPE_PAD; header->State = EFI_FILE_HEADER_CONSTRUCTION | EFI_FILE_HEADER_VALID | EFI_FILE_DATA_VALID; // Invert state bits if erase polarity is true if (erasePolarity == ERASE_POLARITY_TRUE) header->State = ~header->State; // Calculate header checksum header->IntegrityCheck.Checksum.Header = 0; header->IntegrityCheck.Checksum.File = 0; header->IntegrityCheck.Checksum.Header = calculateChecksum8((const UINT8*)header, sizeof(EFI_FFS_FILE_HEADER) - 1); // Set data checksum if (revision == 1) header->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM; else header->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM2; return U_SUCCESS; } USTATUS FfsBuilder::buildFile(const UModelIndex & index, const UINT8 revision, const UINT8 erasePolarity, const UINT32 base, UByteArray & reconstructed) { if (!index.isValid()) return U_SUCCESS; USTATUS result; // No action if (model->action(index) == Actions::NoAction) { reconstructed = model->header(index) + model->body(index); const EFI_FFS_FILE_HEADER* fileHeader = (const EFI_FFS_FILE_HEADER*)model->header(index).constData(); // Append tail, if needed if (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) { UINT8 ht = ~fileHeader->IntegrityCheck.Checksum.Header; UINT8 ft = ~fileHeader->IntegrityCheck.Checksum.File; reconstructed += UByteArray(1, (char)ht); reconstructed += UByteArray(1, (char)ft); } return U_SUCCESS; } else if (model->action(index) == Actions::Remove) { reconstructed.clear(); return U_SUCCESS; } else if (model->action(index) == Actions::Insert || model->action(index) == Actions::Replace || model->action(index) == Actions::Rebuild) { UByteArray header = model->header(index); EFI_FFS_FILE_HEADER* fileHeader = (EFI_FFS_FILE_HEADER*)header.data(); // Check erase polarity if (erasePolarity == ERASE_POLARITY_UNKNOWN) { msg("buildFile: unknown erase polarity", index); return U_INVALID_PARAMETER; } // Check file state // Check top reserved bit of file state to determine it's original erase polarity UINT8 state = fileHeader->State; if (state & EFI_FILE_ERASE_POLARITY) state = ~state; // Order of this checks must be preserved // Check file to have valid state, or delete it otherwise if (state & EFI_FILE_HEADER_INVALID) { // File marked to have invalid header and must be deleted // Do not add anything to queue msg("buildFile: file is HEADER_INVALID state, and will be removed from reconstructed image", index); return U_SUCCESS; } else if (state & EFI_FILE_DELETED) { // File marked to have been deleted form and must be deleted // Do not add anything to queue msg("buildFile: file is in DELETED state, and will be removed from reconstructed image", index); return U_SUCCESS; } else if (state & EFI_FILE_MARKED_FOR_UPDATE) { // File is marked for update, the mark must be removed msg("buildFile: file's MARKED_FOR_UPDATE state cleared", index); } else if (state & EFI_FILE_DATA_VALID) { // File is in good condition, reconstruct it } else if (state & EFI_FILE_HEADER_VALID) { // Header is valid, but data is not, so file must be deleted msg("buildFile: file is in HEADER_VALID (but not in DATA_VALID) state, and will be removed from reconstructed image", index); return U_SUCCESS; } else if (state & EFI_FILE_HEADER_CONSTRUCTION) { // Header construction not finished, so file must be deleted msg("buildFile: file is in HEADER_CONSTRUCTION (but not in DATA_VALID) state, and will be removed from reconstructed image", index); return U_SUCCESS; } // Reconstruct file body if (model->rowCount(index)) { reconstructed.clear(); // Construct new file body // File contains raw data, must be parsed as region without header if (model->subtype(index) == EFI_FV_FILETYPE_ALL || model->subtype(index) == EFI_FV_FILETYPE_RAW) { result = buildRawArea(index, reconstructed, false); if (result) return result; } // 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 UINT8 alignment = offset % 4; if (alignment) { alignment = 4 - alignment; offset += alignment; reconstructed += UByteArray(alignment, '\x00'); } // Calculate section base UINT32 sectionBase = base ? base + headerSize + offset : 0; UINT8 alignmentPower = ffsAlignmentTable[(fileHeader->Attributes & FFS_ATTRIB_DATA_ALIGNMENT) >> 3]; UINT32 fileAlignment = (UINT32)(1UL << alignmentPower); UINT32 alignmentBase = base + headerSize; if (alignmentBase % fileAlignment) { // File will be unaligned if added as is, so we must add pad file before it // Determine pad file size UINT32 size = fileAlignment - (alignmentBase % fileAlignment); // Required padding is smaller then minimal pad file size while (size < sizeof(EFI_FFS_FILE_HEADER)) { size += fileAlignment; } // Adjust file base to incorporate pad file that will be added to align it sectionBase += size; } // Reconstruct section UByteArray section; result = buildSection(index.child(i, 0), sectionBase, section); if (result) return result; // Check for empty section if (section.isEmpty()) continue; // Append current section to new file body reconstructed += section; // Change current file offset offset += section.size(); } } } // Use current file body else reconstructed = model->body(index); // Correct file size UINT8 tailSize = (revision == 1 && (fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT)) ? sizeof(UINT16) : 0; 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("buildFile: resulting file size is too big", index); return U_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 = 0x100 - (calculateSum8((const UINT8*)header.constData(), header.size()) - fileHeader->State); // Recalculate data checksum, if needed if (fileHeader->Attributes & FFS_ATTRIB_CHECKSUM) { fileHeader->IntegrityCheck.Checksum.File = calculateChecksum8((const UINT8*)reconstructed.constData(), reconstructed.size()); } else if (revision == 1) fileHeader->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM; else fileHeader->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM2; // Append tail, if needed if (revision == 1 && fileHeader->Attributes & FFS_ATTRIB_TAIL_PRESENT) { UINT8 ht = ~fileHeader->IntegrityCheck.Checksum.Header; UINT8 ft = ~fileHeader->IntegrityCheck.Checksum.File; reconstructed += UByteArray(1, (char)ht); reconstructed += UByteArray(1, (char)ft); } // Set file state state = EFI_FILE_DATA_VALID | EFI_FILE_HEADER_VALID | EFI_FILE_HEADER_CONSTRUCTION; if (erasePolarity == ERASE_POLARITY_TRUE) state = ~state; fileHeader->State = state; // Reconstruction successful reconstructed = header + reconstructed; return U_SUCCESS; } // All other actions are not supported return U_NOT_IMPLEMENTED; } USTATUS FfsBuilder::buildSection(const UModelIndex & index, const UINT32 base, UByteArray & reconstructed) { if (!index.isValid()) return U_SUCCESS; USTATUS result; // No action if (model->action(index) == Actions::NoAction) { reconstructed = model->header(index) + model->body(index); return U_SUCCESS; } else if (model->action(index) == Actions::Remove) { reconstructed.clear(); return U_SUCCESS; } else if (model->action(index) == Actions::Insert || model->action(index) == Actions::Replace || model->action(index) == Actions::Rebuild || model->action(index) == Actions::Rebase) { UByteArray header = model->header(index); EFI_COMMON_SECTION_HEADER* commonHeader = (EFI_COMMON_SECTION_HEADER*)header.data(); bool extended = false; UByteArray data = model->parsingData(index); if (uint24ToUint32(commonHeader->Size) == 0xFFFFFF) { extended = true; } // Reconstruct section with children if (model->rowCount(index)) { reconstructed.clear(); // Construct new section body UINT32 offset = 0; // Reconstruct section body for (int i = 0; i < model->rowCount(index); i++) { // Align to 4 byte boundary UINT8 alignment = offset % 4; if (alignment) { alignment = 4 - alignment; offset += alignment; reconstructed += UByteArray(alignment, '\x00'); } // Reconstruct subsections UByteArray section; result = build(index.child(i, 0), section); if (result) return result; // Check for empty queue if (section.isEmpty()) continue; // Append current subsection to new section body reconstructed += section; // Change current file offset offset += section.size(); } // Only this 2 sections can have compressed body UINT8 compression = model->compressed(index) ? model->compression(index) : COMPRESSION_ALGORITHM_NONE; if (model->subtype(index) == EFI_SECTION_COMPRESSION) { EFI_COMPRESSION_SECTION* compessionHeader = (EFI_COMPRESSION_SECTION*)header.data(); // Set new uncompressed size compessionHeader->UncompressedLength = reconstructed.size(); // Compress new section body UByteArray compressed; result = compress(reconstructed, compression, compressed); if (result) return result; // Correct compression type if (compression == COMPRESSION_ALGORITHM_NONE) compessionHeader->CompressionType = EFI_NOT_COMPRESSED; else if (compression == COMPRESSION_ALGORITHM_LZMA || compression == COMPRESSION_ALGORITHM_IMLZMA) compessionHeader->CompressionType = EFI_CUSTOMIZED_COMPRESSION; else if (compression == COMPRESSION_ALGORITHM_EFI11 || compression == COMPRESSION_ALGORITHM_TIANO) compessionHeader->CompressionType = EFI_STANDARD_COMPRESSION; else return U_UNKNOWN_COMPRESSION_ALGORITHM; // Replace new section body reconstructed = compressed; } else if (model->subtype(index) == EFI_SECTION_GUID_DEFINED) { EFI_GUID_DEFINED_SECTION* guidDefinedHeader = (EFI_GUID_DEFINED_SECTION*)header.data(); // Compress new section body UByteArray compressed; result = compress(reconstructed, compression, compressed); if (result) return result; // Check for authentication status valid attribute if (guidDefinedHeader->Attributes & EFI_GUIDED_SECTION_AUTH_STATUS_VALID) { // CRC32 section if (UByteArray((const char*)&guidDefinedHeader->SectionDefinitionGuid, sizeof(EFI_GUID)) == EFI_GUIDED_SECTION_CRC32) { // Check header size if ((UINT32)header.size() != sizeof(EFI_GUID_DEFINED_SECTION) + sizeof(UINT32)) { msg("buildSection: invalid CRC32 section size", index); return U_INVALID_SECTION; } // Calculate CRC32 of section data UINT32 crc = crc32(0, (const UINT8*)compressed.constData(), compressed.size()); // Store new CRC32 *(UINT32*)(header.data() + sizeof(EFI_GUID_DEFINED_SECTION)) = crc; } else { const char *guidStr = guidToUString(guidDefinedHeader->SectionDefinitionGuid).toLocal8Bit(); msg(usprintf("buildSection: GUID defined section signature can become invalid (%s)", guidStr), index); } } // Check for Intel signed section if (guidDefinedHeader->Attributes & EFI_GUIDED_SECTION_PROCESSING_REQUIRED && UByteArray((const char*)&guidDefinedHeader->SectionDefinitionGuid, sizeof(EFI_GUID)) == EFI_FIRMWARE_CONTENTS_SIGNED_GUID) { const char *guidStr = guidToUString(guidDefinedHeader->SectionDefinitionGuid).toLocal8Bit(); msg(usprintf("buildSection: GUID defined section signature can become invalid (%s)", guidStr), index); } // Replace new section body reconstructed = compressed; } else if (compression != COMPRESSION_ALGORITHM_NONE) { msg(usprintf("buildSection: incorrectly required compression for section of type %d", model->subtype(index)), index); return U_INVALID_SECTION; } } // Leaf section else { reconstructed = model->body(index); } // Correct section 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); } // Rebase PE32 or TE image, if needed if ((model->subtype(index) == EFI_SECTION_PE32 || model->subtype(index) == EFI_SECTION_TE) && (model->subtype(index.parent()) == EFI_FV_FILETYPE_PEI_CORE || model->subtype(index.parent()) == EFI_FV_FILETYPE_PEIM || model->subtype(index.parent()) == EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER)) { UINT16 teFixup = 0; // Most EFI images today include teFixup in ImageBase value, // which doesn't follow the UEFI spec, but is so popular that // only a few images out of thousands are different // There are some heuristics possible here to detect if an entry point is calculated correctly // or needs a proper fixup, but new_engine already have them and it's better to work on proper // builder for it than trying to fix this mess //if (model->subtype(index) == EFI_SECTION_TE) { // const EFI_IMAGE_TE_HEADER* teHeader = (const EFI_IMAGE_TE_HEADER*)model->body(index).constData(); // teFixup = teHeader->StrippedSize - sizeof(EFI_IMAGE_TE_HEADER); // if (base) { result = rebase(reconstructed, base - teFixup + header.size()); if (result) { msg("buildSection: executable section rebase failed", index); return result; } // Special case of PEI Core rebase if (model->subtype(index.parent()) == EFI_FV_FILETYPE_PEI_CORE) { result = getEntryPoint(reconstructed, parser->newPeiCoreEntryPoint); if (result) msg("buildSection: can't get entry point of PEI core", index); } } } // Reconstruction successful reconstructed = header + reconstructed; return U_SUCCESS; } // All other actions are not supported return U_NOT_IMPLEMENTED; }