/** @file Copyright (c) 2021 - 2023, Marvin Häuser. All rights reserved. Copyright (c) 2022, Mikhail Krichanov. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "ImageTool.h" #include "UeScan.h" #include "DynamicBuffer.h" typedef union { UINT32 Value32; UINT64 Value64; } UE_RELOC_FIXUP_VALUE; static RETURN_STATUS InternalProcessRelocChain ( image_tool_dynamic_buffer *Buffer, const image_tool_segment_info_t *SegmentInfo, UINT16 FirstRelocType, UINT32 *ChainStart ) { uint32_t OffsetInSegment; const image_tool_segment_t *Segment; image_tool_reloc_t Reloc; uint32_t Offset; UINT16 RelocType; UINT16 RelocOffset; UINT32 RelocTarget; UINT32 RemRelocTargetSize; VOID *Fixup; UE_RELOC_FIXUP_VALUE FixupInfo; UINT8 FixupSize; UE_RELOC_FIXUP_VALUE FixupValue; memset (&Reloc, 0, sizeof (Reloc)); RelocType = FirstRelocType; RelocTarget = *ChainStart; while (TRUE) { OffsetInSegment = RelocTarget; Segment = ImageGetSegmentByAddress ( &OffsetInSegment, &RemRelocTargetSize, SegmentInfo ); if (Segment == NULL) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } Fixup = &Segment->Data[OffsetInSegment]; Reloc.Target = RelocTarget; if (RelocType < UeRelocGenericMax) { if (RelocType == UeReloc64) { FixupSize = sizeof (UINT64); // // Verify the relocation fixup target is in bounds of the Image buffer. // if (FixupSize > RemRelocTargetSize) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } // // Relocate the target instruction. // FixupInfo.Value64 = ReadUnaligned64 (Fixup); FixupValue.Value64 = UE_CHAINED_RELOC_FIXUP_VALUE (FixupInfo.Value64); WriteUnaligned64 (Fixup, FixupValue.Value64); Reloc.Type = EFI_IMAGE_REL_BASED_DIR64; } else if (RelocType == UeReloc32) { FixupSize = sizeof (UINT32); // // Verify the image relocation fixup target is in bounds of the image // buffer. // if (FixupSize > RemRelocTargetSize) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } // // Relocate the target instruction. // FixupInfo.Value32 = ReadUnaligned32 (Fixup); FixupValue.Value32 = UE_CHAINED_RELOC_FIXUP_VALUE_32 (FixupInfo.Value32); WriteUnaligned32 (Fixup, FixupValue.Value32); Reloc.Type = EFI_IMAGE_REL_BASED_HIGHLOW; // // Imitate the common header of UE chained relocation fixups, // as for 32-bit files all relocs have the same type. // FixupInfo.Value32 = FixupInfo.Value32 << 4; FixupInfo.Value32 |= UeReloc32; } else { // // The Image relocation fixup type is unknown, disallow the Image. // DEBUG_RAISE (); return RETURN_UNSUPPORTED; } } else { // // The Image relocation fixup type is unknown, disallow the Image. // DEBUG_RAISE (); return RETURN_UNSUPPORTED; } RelocTarget += FixupSize; Offset = ImageToolBufferAppend (Buffer, &Reloc, sizeof (Reloc)); if (Offset == MAX_UINT32) { DEBUG_RAISE (); return RETURN_OUT_OF_RESOURCES; } RelocOffset = UE_CHAINED_RELOC_FIXUP_NEXT_OFFSET (FixupInfo.Value32); if (RelocOffset == UE_CHAINED_RELOC_FIXUP_OFFSET_END) { *ChainStart = RelocTarget; return RETURN_SUCCESS; } // // It holds that ImageSize mod 4 KiB = 0, thus ImageSize <= 0xFFFFF000. // Furthermore, it holds that RelocTarget <= ImageSize. // Finally, it holds that RelocOffset <= 0xFFE. // It follows that this cannot overflow. // RelocTarget += RelocOffset; assert (RelocOffset <= RelocTarget); RelocType = UE_CHAINED_RELOC_FIXUP_NEXT_TYPE (FixupInfo.Value32); } } STATIC RETURN_STATUS InternalApplyRelocation ( image_tool_dynamic_buffer *Buffer, const image_tool_segment_info_t *SegmentInfo, UINT16 RelocType, UINT32 *RelocTarget ) { BOOLEAN Overflow; const image_tool_segment_t *LastSegment; uint32_t ImageSize; UINT32 RemRelocTargetSize; UINT32 FixupTarget; UINT8 FixupSize; image_tool_reloc_t Reloc; uint32_t Offset; FixupTarget = *RelocTarget; LastSegment = &SegmentInfo->Segments[SegmentInfo->NumSegments - 1]; ImageSize = LastSegment->ImageAddress + LastSegment->ImageSize; // // Verify the relocation fixup target address is in bounds of the image buffer. // Overflow = BaseOverflowSubU32 (ImageSize, FixupTarget, &RemRelocTargetSize); if (Overflow) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } memset (&Reloc, 0, sizeof (Reloc)); Reloc.Target = FixupTarget; // // Apply the relocation fixup per type. // if (RelocType < UeRelocGenericMax) { if ((RelocType == UeReloc32) || (RelocType == UeReloc32NoMeta)) { FixupSize = sizeof (UINT32); Reloc.Type = EFI_IMAGE_REL_BASED_HIGHLOW; } else { assert (RelocType == UeReloc64); FixupSize = sizeof (UINT64); Reloc.Type = EFI_IMAGE_REL_BASED_DIR64; } } else { // // The image relocation fixup type is unknown, disallow the image. // fprintf (stderr, "ImageTool: Unknown RelocType = 0x%x\n", RelocType); ImageToolBufferFree (Buffer); return RETURN_UNSUPPORTED; } // // Verify the relocation fixup target is in bounds of the image buffer. // if (FixupSize > RemRelocTargetSize) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } Offset = ImageToolBufferAppend (Buffer, &Reloc, sizeof (Reloc)); if (Offset == MAX_UINT32) { DEBUG_RAISE (); ImageToolBufferFree (Buffer); return RETURN_OUT_OF_RESOURCES; } *RelocTarget = FixupTarget + FixupSize; return RETURN_SUCCESS; } RETURN_STATUS ScanUeGetRelocInfo ( OUT image_tool_reloc_info_t *RelocInfo, IN const image_tool_segment_info_t *SegmentInfo, IN UE_LOADER_IMAGE_CONTEXT *Context ) { RETURN_STATUS Status; BOOLEAN Overflow; CONST UE_HEADER *UeHdr; BOOLEAN Chaining; UINT32 RootOffsetMax; UINT32 EntryOffsetMax; UINT32 EndOfRelocTable; UINT32 TableOffset; const UE_FIXUP_ROOT *RelocRoot; UINT16 FixupInfo; UINT16 RelocType; UINT16 RelocOffset; UINT32 RelocTarget; image_tool_dynamic_buffer Buffer; uint32_t RelocBufferSize; UINT32 OldTableOffset; // // Verify the Relocation Directory is not empty. // if (Context->RelocTableSize == 0) { return RETURN_SUCCESS; } ImageToolBufferInit (&Buffer); UeHdr = (CONST UE_HEADER *)Context->FileBuffer; Chaining = (UeHdr->ImageInfo & UE_HEADER_IMAGE_INFO_CHAINED_FIXUPS) != 0; EndOfRelocTable = Context->LoadTablesFileOffset + Context->RelocTableSize; RelocTarget = 0; RootOffsetMax = EndOfRelocTable - MIN_SIZE_OF_UE_FIXUP_ROOT; EntryOffsetMax = EndOfRelocTable - sizeof (*RelocRoot->Heads); // // Apply all Base Relocations of the Image. // for (TableOffset = Context->LoadTablesFileOffset; TableOffset <= RootOffsetMax;) { RelocRoot = (CONST UE_FIXUP_ROOT *)( (CONST UINT8 *)Context->FileBuffer + TableOffset ); // // This cannot overflow due to the TableOffset upper bound. // TableOffset += sizeof (*RelocRoot); Overflow = BaseOverflowAddU32 ( RelocTarget, RelocRoot->FirstOffset, &RelocTarget ); if (Overflow) { DEBUG_RAISE (); ImageToolBufferFree (&Buffer); return RETURN_VOLUME_CORRUPTED; } // // Process all relocation fixups of the current root. // while (TRUE) { FixupInfo = *(CONST UINT16 *)((CONST UINT8 *)Context->FileBuffer + TableOffset); // // This cannot overflow due to the upper bound of TableOffset. // TableOffset += sizeof (*RelocRoot->Heads); // // Apply the image relocation fixup. // RelocType = UE_RELOC_FIXUP_TYPE (FixupInfo); if (Chaining && (RelocType != UeReloc32NoMeta)) { Status = InternalProcessRelocChain ( &Buffer, SegmentInfo, RelocType, &RelocTarget ); } else { Status = InternalApplyRelocation ( &Buffer, SegmentInfo, RelocType, &RelocTarget ); } if (RETURN_ERROR (Status)) { DEBUG_RAISE (); ImageToolBufferFree (&Buffer); return Status; } RelocOffset = UE_RELOC_FIXUP_OFFSET (FixupInfo); if (RelocOffset == UE_HEAD_FIXUP_OFFSET_END) { break; } // // It holds that ImageSize mod 4 KiB = 0, thus ImageSize <= 0xFFFFF000. // Furthermore, it holds that RelocTarget <= ImageSize. // Finally, it holds that RelocOffset <= 0xFFE. // It follows that this cannot overflow. // RelocTarget += RelocOffset; assert (RelocOffset <= RelocTarget); if (TableOffset > EntryOffsetMax) { DEBUG_RAISE (); ImageToolBufferFree (&Buffer); return RETURN_VOLUME_CORRUPTED; } } // // This cannot overflow due to the TableOffset upper bounds and the // alignment guarantee of RelocTableSize. // OldTableOffset = TableOffset; TableOffset = ALIGN_VALUE (TableOffset, ALIGNOF (UE_FIXUP_ROOT)); assert (OldTableOffset <= TableOffset); } RelocInfo->Relocs = ImageToolBufferDump (&RelocBufferSize, &Buffer); ImageToolBufferFree (&Buffer); if (RelocInfo->Relocs == NULL) { fprintf (stderr, "ImageTool: Could not allocate memory for Relocs[]\n"); return RETURN_OUT_OF_RESOURCES; } assert (IS_ALIGNED (RelocBufferSize, sizeof (*RelocInfo->Relocs))); RelocInfo->NumRelocs = RelocBufferSize / sizeof (*RelocInfo->Relocs); return RETURN_SUCCESS; } RETURN_STATUS ScanUeGetSegmentInfo ( OUT image_tool_segment_info_t *SegmentInfo, IN UE_LOADER_IMAGE_CONTEXT *Context ) { RETURN_STATUS Status; const UE_SEGMENT *Segment; uint16_t NumSegments; image_tool_segment_t *ImageSegment; const char *ImageBuffer; uint16_t Index; uint32_t SegmentAddress; uint32_t SegmentSize; uint8_t SegmentPermissions; const UE_SEGMENT_NAME *SegmentNames; NumSegments = UeGetSegments (Context, &Segment); STATIC_ASSERT ( sizeof (*SegmentInfo->Segments) <= MAX_UINT16 / UE_HEADER_NUM_SEGMENTS_MAX, "The following arithmetics cannot overflow." ); SegmentInfo->Segments = AllocateZeroPool ( NumSegments * sizeof (*SegmentInfo->Segments) ); if (SegmentInfo->Segments == NULL) { fprintf (stderr, "ImageTool: Could not allocate memory for Segments[]\n"); return RETURN_OUT_OF_RESOURCES; } Status = UeGetSegmentNames (Context, &SegmentNames); if (RETURN_ERROR (Status)) { if (Status != RETURN_NOT_FOUND) { DEBUG_RAISE (); return Status; } SegmentNames = NULL; } ImageBuffer = (char *)UeLoaderGetImageAddress (Context); SegmentAddress = 0; ImageSegment = SegmentInfo->Segments; for (Index = 0; Index < NumSegments; ++Index, ++Segment) { if (SegmentNames != NULL) { ImageSegment->Name = AllocateCopyPool ( sizeof (SegmentNames[Index]), SegmentNames[Index] ); if (ImageSegment->Name == NULL) { fprintf (stderr, "ImageTool: Could not allocate memory for Segment Name\n"); return RETURN_OUT_OF_RESOURCES; } } else { assert (ImageSegment->Name == NULL); } SegmentSize = UE_SEGMENT_SIZE (Segment->ImageInfo); ImageSegment->Data = AllocateCopyPool ( SegmentSize, ImageBuffer + SegmentAddress ); if (ImageSegment->Data == NULL) { fprintf (stderr, "ImageTool: Could not allocate memory for Segment Data\n"); if (ImageSegment->Name != NULL) { FreePool (ImageSegment->Name); } return RETURN_OUT_OF_RESOURCES; } ImageSegment->ImageAddress = SegmentAddress; ImageSegment->ImageSize = SegmentSize; SegmentPermissions = UE_SEGMENT_PERMISSIONS (Segment->ImageInfo); switch (SegmentPermissions) { case UeSegmentPermX: { ImageSegment->Execute = true; assert (!ImageSegment->Read); assert (!ImageSegment->Write); break; } case UeSegmentPermRX: { ImageSegment->Read = true; ImageSegment->Execute = true; assert (!ImageSegment->Write); break; } case UeSegmentPermRW: { ImageSegment->Read = true; ImageSegment->Write = true; assert (!ImageSegment->Execute); break; } default: case UeSegmentPermR: { assert (SegmentPermissions == UeSegmentPermR); ImageSegment->Read = true; assert (!ImageSegment->Write); assert (!ImageSegment->Execute); break; } } SegmentAddress += SegmentSize; ++SegmentInfo->NumSegments; ++ImageSegment; } return RETURN_SUCCESS; }