/** @file UEFI image loader library implementation for UE images. Copyright (c) 2021 - 2023, Marvin Häuser. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include #include #include struct UE_LOADER_RUNTIME_CONTEXT_ { UINT8 Machine; UINT8 Reserved[7]; UINT32 FixupSize; UINT64 *FixupData; UINT32 UnchainedRelocsSize; UINT8 *UnchainedRelocs; }; typedef union { UINT32 Value32; UINT64 Value64; } UE_RELOC_FIXUP_VALUE; STATIC RETURN_STATUS InternalVerifySegments ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context ) { CONST UE_SEGMENT *Segments; UINT8 LastSegmentIndex; UINT8 SegmentIndex; UINT32 SegmentEndFileOffset; UINT32 SegmentEndImageAddress; BOOLEAN Overflow; UINT32 SegmentImageSize; Segments = Context->Segments; LastSegmentIndex = Context->LastSegmentIndex; // // As it holds that SegmentEndFileOffset - Context->SegmentsFileOffset <= SegmentEndImageAddress, // and it holds that SegmentEndImageAddress % 4 KiB = 0, SegmentEndFileOffset - Context->SegmentsFileOffset // can be at most 0xFFFFF000. As it holds that Context->SegmentsFileOffset <= MAX_SIZE_OF_UE_HEADER <= 0xFF0, // this cannot overflow. // STATIC_ASSERT ( MAX_SIZE_OF_UE_HEADER <= BASE_4KB - 8, "The arithmetic below may overflow." ); SegmentEndFileOffset = Context->SegmentsFileOffset; // // The first image segment must begin the image address space. // SegmentEndImageAddress = 0; for (SegmentIndex = 0; SegmentIndex <= LastSegmentIndex; ++SegmentIndex) { SegmentImageSize = UE_SEGMENT_SIZE (Segments[SegmentIndex].ImageInfo); if (Segments[SegmentIndex].FileSize > SegmentImageSize) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } // // Verify the image segments are aligned. // if (!IS_ALIGNED (SegmentImageSize, Context->SegmentAlignment)) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } // // Determine the end of the current image segment. // Overflow = BaseOverflowAddU32 ( SegmentEndImageAddress, SegmentImageSize, &SegmentEndImageAddress ); if (Overflow) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } // // As it holds that SegmentFileSize <= SegmentImageSize and thus // SegmentEndFileOffset - Context->SegmentsFileOffset <= SegmentEndImageAddress, // this cannot overflow (see above). // SegmentEndFileOffset += Segments[SegmentIndex].FileSize; } // // As it holds that SegmentEndFileOffset <= 0xFFFFFFF0, this cannot overflow. // SegmentEndFileOffset = ALIGN_VALUE ( SegmentEndFileOffset, UE_LOAD_TABLE_ALIGNMENT ); // // Verify all image segment data are in bounds of the file buffer. // ASSERT (Context->SegmentsFileOffset <= SegmentEndFileOffset); if (SegmentEndFileOffset > Context->UnsignedFileSize) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } Context->LoadTablesFileOffset = SegmentEndFileOffset; Context->ImageSize = SegmentEndImageAddress; if (Context->XIP) { Context->ImageSize += Context->SegmentsFileOffset; } return RETURN_SUCCESS; } STATIC RETURN_STATUS InternalVerifyLoadTables ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context ) { BOOLEAN Overflow; CONST UE_LOAD_TABLE *LoadTable; UINT8 LoadTableIndex; INT16 PrevLoadTableId; UINT32 LoadTableFileOffset; UINT32 LoadTableFileSize; UINT8 LoadTableId; UINT32 LoadTableEndFileOffset; UINT8 NumLoadTables; Context->RelocTableSize = 0; LoadTableEndFileOffset = Context->LoadTablesFileOffset; NumLoadTables = Context->NumLoadTables; PrevLoadTableId = -1; if (0 < NumLoadTables) { LoadTableIndex = 0; do { LoadTable = Context->LoadTables + LoadTableIndex; LoadTableId = UE_LOAD_TABLE_ID (LoadTable->FileInfo); if (PrevLoadTableId >= LoadTableId) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } LoadTableFileOffset = LoadTableEndFileOffset; LoadTableFileSize = UE_LOAD_TABLE_SIZE (LoadTable->FileInfo); Overflow = BaseOverflowAddU32 ( LoadTableFileOffset, LoadTableFileSize, &LoadTableEndFileOffset ); if (Overflow) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } PrevLoadTableId = LoadTableId; ++LoadTableIndex; } while (LoadTableIndex < NumLoadTables); if (UE_LOAD_TABLE_ID (Context->LoadTables[0].FileInfo) == UeLoadTableIdReloc) { if (Context->RelocsStripped) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } Context->RelocTableSize = UE_LOAD_TABLE_SIZE ( Context->LoadTables[0].FileInfo ); } } if (LoadTableEndFileOffset != Context->UnsignedFileSize) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } return RETURN_SUCCESS; } RETURN_STATUS UeInitializeContextPreHash ( OUT UE_LOADER_IMAGE_CONTEXT *Context, IN CONST VOID *FileBuffer, IN UINT32 FileSize ) { //BOOLEAN Overflow; CONST UE_HEADER *UeHdr; //UINT32 UnsignedFileSize; ASSERT (Context != NULL); ASSERT (FileBuffer != NULL || FileSize == 0); if (MIN_SIZE_OF_UE_HEADER > FileSize) { return RETURN_UNSUPPORTED; } UeHdr = (CONST UE_HEADER *) FileBuffer; if (UeHdr->Magic != UE_HEADER_MAGIC) { return RETURN_UNSUPPORTED; } ZeroMem (Context, sizeof (*Context)); /*UnsignedFileSize = UE_HEADER_FILE_SIZE (UeHdr->FileInfo); Overflow = BaseOverflowSubU32 ( FileSize, UnsignedFileSize, &Context->CertTableSize ); if (Overflow) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; }*/ Context->FileBuffer = (CONST UINT8 *)UeHdr; //Context->UnsignedFileSize = UnsignedFileSize; Context->UnsignedFileSize = FileSize; return RETURN_SUCCESS; } BOOLEAN UeHashImageDefault ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context, IN OUT VOID *HashContext, IN UE_LOADER_HASH_UPDATE HashUpdate ) { BOOLEAN Result; ASSERT (Context != NULL); ASSERT (HashContext != NULL); ASSERT (HashUpdate != NULL); Result = HashUpdate ( HashContext, Context->FileBuffer, Context->UnsignedFileSize ); if (!Result) { DEBUG_RAISE (); } return Result; } STATIC RETURN_STATUS InternalInitializeContextLate ( OUT UE_LOADER_IMAGE_CONTEXT *Context ) { if (Context->EntryPointAddress > Context->ImageSize) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } return InternalVerifyLoadTables (Context); } RETURN_STATUS UeInitializeContextPostHash ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context ) { CONST UE_HEADER *UeHdr; UINT8 LastSegmentIndex; UINT8 NumLoadTables; UINT32 LoadTablesFileOffset; UINT32 HeaderSize; UINT64 BaseAddress; RETURN_STATUS Status; ASSERT (Context != NULL); UeHdr = (CONST UE_HEADER *)Context->FileBuffer; Context->FixedAddress = (UeHdr->ImageInfo & UE_HEADER_IMAGE_INFO_FIXED_ADDRESS) != 0; Context->RelocsStripped = (UeHdr->ImageInfo & UE_HEADER_IMAGE_INFO_RELOCATION_FIXUPS_STRIPPED) != 0; Context->XIP = (UeHdr->ImageInfo & UE_HEADER_IMAGE_INFO_XIP) != 0; Context->Segments = UeHdr->Segments; Context->SegmentImageInfoIterSize = sizeof (*UeHdr->Segments); Context->SegmentAlignment = UE_HEADER_SEGMENT_ALIGNMENT (UeHdr->ImageInfo); LastSegmentIndex = UE_HEADER_LAST_SEGMENT_INDEX (UeHdr->TableCounts); NumLoadTables = UE_HEADER_NUM_LOAD_TABLES (UeHdr->TableCounts); LoadTablesFileOffset = MIN_SIZE_OF_UE_HEADER + (UINT32) LastSegmentIndex * sizeof (UE_SEGMENT); HeaderSize = LoadTablesFileOffset + (UINT32) NumLoadTables * sizeof (UE_LOAD_TABLE); ASSERT (HeaderSize <= MAX_SIZE_OF_UE_HEADER); if (Context->XIP) { HeaderSize = ALIGN_VALUE (HeaderSize, Context->SegmentAlignment); } if (HeaderSize > Context->UnsignedFileSize) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } ASSERT (IS_ALIGNED (LoadTablesFileOffset, ALIGNOF (UE_LOAD_TABLE))); Context->LoadTables = (CONST UE_LOAD_TABLE *)( (CONST UINT8 *) UeHdr + LoadTablesFileOffset ); Context->SegmentsFileOffset = HeaderSize; Context->LastSegmentIndex = LastSegmentIndex; Context->NumLoadTables = NumLoadTables; BaseAddress = UE_HEADER_BASE_ADDRESS (UeHdr->ImageInfo); if (!IS_ALIGNED (BaseAddress, Context->SegmentAlignment)) { DEBUG_RAISE (); return RETURN_UNSUPPORTED; } Context->FileBuffer = (CONST UINT8 *)UeHdr; Status = InternalVerifySegments (Context); if (RETURN_ERROR (Status)) { return Status; } Context->BaseAddress = BaseAddress; Context->EntryPointAddress = UeHdr->EntryPointAddress; Context->Subsystem = UE_HEADER_SUBSYSTEM (UeHdr->Type); Context->Machine = UE_HEADER_ARCH (UeHdr->Type); return InternalInitializeContextLate (Context); } RETURN_STATUS UeLoadImage ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context, OUT VOID *Destination, IN UINT32 DestinationSize ) { UINT8 SegmentIndex; UINT32 SegmentFileOffset; UINT32 SegmentFileSize; UINT32 SegmentImageAddress; UINT32 SegmentImageSize; UINT32 PrevSegmentDataEnd; CONST UE_SEGMENT *Segments; ASSERT (Context != NULL); ASSERT (Destination != NULL); ASSERT (ADDRESS_IS_ALIGNED (Destination, Context->SegmentAlignment)); Context->ImageBuffer = Destination; // // Load all image load tables into the address space. // Segments = Context->Segments; // // Start zeroing from the start of the destination buffer. // PrevSegmentDataEnd = 0; SegmentFileOffset = Context->SegmentsFileOffset; SegmentImageAddress = 0; SegmentIndex = 0; do { SegmentFileSize = Segments[SegmentIndex].FileSize; SegmentImageSize = UE_SEGMENT_SIZE (Segments[SegmentIndex].ImageInfo); // // Zero from the end of the previous image segment to the start of this // image segment. // ZeroMem ( (UINT8 *)Destination + PrevSegmentDataEnd, SegmentImageAddress - PrevSegmentDataEnd ); // // Load the current Image segment into the address space. // ASSERT (SegmentFileSize <= SegmentImageSize); CopyMem ( (UINT8 *)Destination + SegmentImageAddress, Context->FileBuffer + SegmentFileOffset, SegmentFileSize ); PrevSegmentDataEnd = SegmentImageAddress + SegmentFileSize; SegmentFileOffset += SegmentFileSize; SegmentImageAddress += SegmentImageSize; ++SegmentIndex; } while (SegmentIndex <= Context->LastSegmentIndex); // // Zero the trailing data after the last image segment. // ZeroMem ( (UINT8 *)Destination + PrevSegmentDataEnd, DestinationSize - PrevSegmentDataEnd ); return RETURN_SUCCESS; } RETURN_STATUS UeLoaderGetRuntimeContextSize ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context, OUT UINT32 *Size ) { ASSERT (Context != NULL); ASSERT (Size != NULL); *Size = sizeof (UE_LOADER_RUNTIME_CONTEXT); return RETURN_SUCCESS; } /** Apply an image relocation fixup. Only a subset of the PE/COFF relocation fixup types are permited. The relocation fixup target must be in bounds, aligned, and must not overlap with the Relocation Directory. @param[in] Context The context describing the image. Must have been loaded by PeCoffLoadImage(). @param[in] RelocIndex The index of the relocation fixup to apply. @param[in] Adjust The delta to add to the addresses. @retval RETURN_SUCCESS The relocation fixup has been applied successfully. @retval other The relocation fixup could not be applied successfully. **/ STATIC RETURN_STATUS InternalApplyRelocation ( IN OUT VOID *Image, IN UINT32 ImageSize, IN UINT8 Machine, IN UINT16 RelocType, IN UINT32 *RelocTarget, IN UINT64 Adjust, IN OUT UINT64 *FixupData, IN BOOLEAN IsRuntime ) { BOOLEAN Overflow; UINT32 RemFixupTargetSize; UINT32 FixupTarget; VOID *Fixup; UINT8 FixupSize; UE_RELOC_FIXUP_VALUE FixupValue; ASSERT (FixupData != NULL); FixupTarget = *RelocTarget; // // Verify the relocation fixup target address is in bounds of the image buffer. // Overflow = BaseOverflowSubU32 (ImageSize, FixupTarget, &RemFixupTargetSize); if (Overflow) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } Fixup = (UINT8 *)Image + FixupTarget; // // Apply the relocation fixup per type. // if (RelocType < UeRelocGenericMax) { if (RelocType == UeReloc32) { FixupSize = sizeof (UINT32); // // Verify the relocation fixup target is in bounds of the image buffer. // if (FixupSize > RemFixupTargetSize) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } // // Relocate the target instruction. // FixupValue.Value32 = ReadUnaligned32 (Fixup); // // If the Image relocation target value mismatches, skip or abort. // if (IsRuntime && (FixupValue.Value32 != (UINT32)*FixupData)) { if (PcdGetBool (PcdImageLoaderRtRelocAllowTargetMismatch)) { return RETURN_SUCCESS; } return RETURN_VOLUME_CORRUPTED; } FixupValue.Value32 += (UINT32) Adjust; WriteUnaligned32 (Fixup, FixupValue.Value32); } else if (RelocType == UeReloc64) { FixupSize = sizeof (UINT64); // // Verify the image relocation fixup target is in bounds of the image // buffer. // if (FixupSize > RemFixupTargetSize) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } // // Relocate target the instruction. // FixupValue.Value64 = ReadUnaligned64 (Fixup); // // If the Image relocation target value mismatches, skip or abort. // if (IsRuntime && (FixupValue.Value64 != *FixupData)) { if (PcdGetBool (PcdImageLoaderRtRelocAllowTargetMismatch)) { return RETURN_SUCCESS; } return RETURN_VOLUME_CORRUPTED; } FixupValue.Value64 += Adjust; WriteUnaligned64 (Fixup, FixupValue.Value64); } else if (RelocType == UeReloc32NoMeta) { FixupSize = sizeof (UINT32); // // Verify the relocation fixup target is in bounds of the image buffer. // if (FixupSize > RemFixupTargetSize) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } // // Relocate the target instruction. // FixupValue.Value32 = ReadUnaligned32 (Fixup); // // If the Image relocation target value mismatches, skip or abort. // if (IsRuntime && (FixupValue.Value32 != (UINT32)*FixupData)) { if (PcdGetBool (PcdImageLoaderRtRelocAllowTargetMismatch)) { return RETURN_SUCCESS; } return RETURN_VOLUME_CORRUPTED; } FixupValue.Value32 += (UINT32) Adjust; WriteUnaligned32 (Fixup, FixupValue.Value32); if (!IsRuntime) { *FixupData = FixupValue.Value32; } } else { // // The image relocation fixup type is unknown, disallow the image. // DEBUG_RAISE (); return RETURN_UNSUPPORTED; } } else { #if 0 if (Machine == UeMachineArmThumbMixed) { switch (RelocType) { // TODO: MOVW case (UeMachineArmThumbMixed << 4U) | UeRelocArmMovt: { // // Verify the relocation fixup target is in bounds of the image buffer. // if (sizeof (UINT64) > RemFixupTargetSize) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } // // Verify the relocation fixup target is sufficiently aligned. // The ARM Thumb instruction pait must start on a 16-bit boundary. // if (!IS_ALIGNED (RelocTarget, ALIGNOF (UINT16))) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } // // Relocate the target instruction. // PeCoffThumbMovwMovtImmediateFixup (Fixup, Adjust); break; } default: { // // The image relocation fixup type is unknown, disallow the image. // DEBUG_RAISE (); return RETURN_UNSUPPORTED; } } } #endif // // The image relocation fixup type is unknown, disallow the image. // DEBUG_RAISE (); return RETURN_UNSUPPORTED; } *RelocTarget = FixupTarget + FixupSize; return RETURN_SUCCESS; } STATIC RETURN_STATUS UnchainReloc ( IN OUT UE_LOADER_RUNTIME_CONTEXT *RuntimeContext OPTIONAL, IN CONST UINT8 *MetaSource OPTIONAL, IN UINT32 MetaSize, IN BOOLEAN IsRuntime, IN UINT16 RelocOffset, IN UINT64 *FixupData ) { UINT32 OldSize; UINT16 FixupHdr; UINT32 FixupIndex; UINT8 Addend; if ((RuntimeContext != NULL) && !IsRuntime) { if (MetaSource == NULL) { FixupIndex = RuntimeContext->UnchainedRelocsSize - sizeof (UINT16); FixupHdr = *(UINT16 *)&RuntimeContext->UnchainedRelocs[FixupIndex]; FixupHdr = (RelocOffset << 4U) | UE_RELOC_FIXUP_TYPE(FixupHdr); *(UINT16 *)&RuntimeContext->UnchainedRelocs[FixupIndex] = FixupHdr; Addend = ALIGN_VALUE_ADDEND(RuntimeContext->UnchainedRelocsSize, ALIGNOF(UE_FIXUP_ROOT)); if ((RelocOffset == UE_HEAD_FIXUP_OFFSET_END) && (Addend != 0)) { OldSize = RuntimeContext->UnchainedRelocsSize; RuntimeContext->UnchainedRelocs = ReallocateRuntimePool ( OldSize, OldSize + Addend, RuntimeContext->UnchainedRelocs ); if (RuntimeContext->UnchainedRelocs == NULL) { return RETURN_OUT_OF_RESOURCES; } ZeroMem (RuntimeContext->UnchainedRelocs + OldSize, Addend); RuntimeContext->UnchainedRelocsSize += Addend; } return RETURN_SUCCESS; } OldSize = RuntimeContext->UnchainedRelocsSize; RuntimeContext->UnchainedRelocs = ReallocateRuntimePool ( OldSize, OldSize + MetaSize, RuntimeContext->UnchainedRelocs ); if (RuntimeContext->UnchainedRelocs == NULL) { return RETURN_OUT_OF_RESOURCES; } CopyMem (RuntimeContext->UnchainedRelocs + OldSize, MetaSource, MetaSize); RuntimeContext->UnchainedRelocsSize += MetaSize; if (FixupData != NULL) { OldSize = RuntimeContext->FixupSize; RuntimeContext->FixupData = ReallocateRuntimePool ( OldSize, OldSize + sizeof (*FixupData), RuntimeContext->FixupData ); if (RuntimeContext->FixupData == NULL) { return RETURN_OUT_OF_RESOURCES; } CopyMem ((UINT8 *)RuntimeContext->FixupData + OldSize, FixupData, sizeof (*FixupData)); RuntimeContext->FixupSize += sizeof (*FixupData); } } return RETURN_SUCCESS; } STATIC RETURN_STATUS InternalProcessRelocChain ( IN OUT VOID *Image, IN UINT32 ImageSize, IN UINT8 Machine, IN UINT16 FirstRelocType, IN UINT32 *ChainStart, IN UINT64 Adjust, IN OUT UE_LOADER_RUNTIME_CONTEXT *RuntimeContext OPTIONAL ) { RETURN_STATUS Status; UINT16 RelocType; UINT16 RelocOffset; UINT32 RelocTarget; BOOLEAN Overflow; UINT32 RemFixupTargetSize; UINT8 *Fixup; UE_RELOC_FIXUP_VALUE FixupInfo; UINT8 FixupSize; UE_RELOC_FIXUP_VALUE FixupValue; UINT16 FixupHdr; UINT64 FixupData; RelocType = FirstRelocType; RelocTarget = *ChainStart; while (TRUE) { Overflow = BaseOverflowSubU32 (ImageSize, RelocTarget, &RemFixupTargetSize); if (Overflow) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } Fixup = (UINT8 *)Image + RelocTarget; if (RelocType < UeRelocGenericMax) { if (RelocType == UeReloc64) { FixupSize = sizeof (UINT64); // // Verify the relocation fixup target is in bounds of the image memory. // if (FixupSize > RemFixupTargetSize) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } // // Relocate the target instruction. // FixupInfo.Value64 = ReadUnaligned64 ((CONST VOID *)Fixup); FixupValue.Value64 = UE_CHAINED_RELOC_FIXUP_VALUE (FixupInfo.Value64); FixupValue.Value64 += Adjust; WriteUnaligned64 ((VOID *)Fixup, FixupValue.Value64); FixupData = FixupValue.Value64; } else if (RelocType == UeReloc32) { FixupSize = sizeof (UINT32); // // Verify the image relocation fixup target is in bounds of the image // buffer. // if (FixupSize > RemFixupTargetSize) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } // // Relocate the target instruction. // FixupInfo.Value32 = ReadUnaligned32 ((CONST VOID *)Fixup); FixupValue.Value32 = UE_CHAINED_RELOC_FIXUP_VALUE_32 (FixupInfo.Value32); FixupValue.Value32 += (UINT32) Adjust; WriteUnaligned32 ((VOID *)Fixup, FixupValue.Value32); FixupData = FixupValue.Value32; // // Imitate the common header of UE chained relocation fixups, // as for 32-bit files all relocs have the same type. // FixupInfo.Value32 = FixupInfo.Value32 << 4U; 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; RelocOffset = UE_CHAINED_RELOC_FIXUP_NEXT_OFFSET (FixupInfo.Value32); FixupHdr = (RelocOffset << 4U) | RelocType; Status = UnchainReloc ( RuntimeContext, (CONST UINT8 *)&FixupHdr, sizeof (FixupHdr), FALSE, 0, &FixupData ); if (RETURN_ERROR (Status)) { return Status; } 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 InternaRelocateImage ( IN OUT VOID *Image, IN UINT32 ImageSize, IN UINT8 Machine, IN UINT64 OldBaseAddress, IN CONST VOID *RelocTable, IN UINT32 RelocTableSize, IN BOOLEAN Chaining, IN UINT64 BaseAddress, OUT UE_LOADER_RUNTIME_CONTEXT *RuntimeContext OPTIONAL, IN BOOLEAN IsRuntime ) { RETURN_STATUS Status; BOOLEAN Overflow; UINT64 Adjust; UINT32 RootOffsetMax; UINT32 EntryOffsetMax; UINT32 TableOffset; CONST UE_FIXUP_ROOT *RelocRoot; UINT16 FixupInfo; UINT16 RelocType; UINT16 RelocOffset; UINT32 RelocTarget; UINT32 OldTableOffset; UINT64 FixupData; UINT64 *FixupPointer; ASSERT (Image != NULL); ASSERT (RelocTable != NULL || RelocTableSize == 0); // // Verify the Relocation Directory is not empty. // if (RelocTableSize == 0) { return RETURN_SUCCESS; } // // Calculate the image displacement from its prefered load address. // Adjust = BaseAddress - OldBaseAddress; // // FIXME: RT driver check is removed in the hope we can force no relocs in // writable segments. // // Skip explicit Relocation when the image is already loaded at its base // address. // if (Adjust == 0 && !Chaining) { return RETURN_SUCCESS; } RelocTarget = 0; if (IsRuntime) { FixupPointer = RuntimeContext->FixupData; } STATIC_ASSERT ( MIN_SIZE_OF_UE_FIXUP_ROOT <= UE_LOAD_TABLE_ALIGNMENT, "The following arithmetic may overflow." ); RootOffsetMax = RelocTableSize - MIN_SIZE_OF_UE_FIXUP_ROOT; EntryOffsetMax = RelocTableSize - sizeof (*RelocRoot->Heads); // // Apply all relocation fixups of the Image. // for (TableOffset = 0; TableOffset <= RootOffsetMax;) { RelocRoot = (CONST UE_FIXUP_ROOT *)( (CONST UINT8 *)RelocTable + TableOffset ); TableOffset += sizeof (*RelocRoot); Overflow = BaseOverflowAddU32 ( RelocTarget, RelocRoot->FirstOffset, &RelocTarget ); if (Overflow) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } Status = UnchainReloc ( RuntimeContext, (CONST UINT8 *)RelocRoot, sizeof (*RelocRoot), IsRuntime, 0, NULL ); if (RETURN_ERROR (Status)) { return Status; } // // Process all relocation fixups of the current root. // while (TRUE) { FixupInfo = *(CONST UINT16 *)((CONST UINT8 *)RelocTable + 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 && !IsRuntime && (RelocType != UeReloc32NoMeta)) { Status = InternalProcessRelocChain ( Image, ImageSize, Machine, RelocType, &RelocTarget, Adjust, RuntimeContext ); } else { Status = InternalApplyRelocation ( Image, ImageSize, Machine, RelocType, &RelocTarget, Adjust, IsRuntime ? FixupPointer : &FixupData, IsRuntime ); if (RETURN_ERROR (Status)) { return Status; } Status = UnchainReloc ( RuntimeContext, (CONST UINT8 *)&FixupInfo, sizeof (FixupInfo), IsRuntime, 0, &FixupData ); if (IsRuntime) { ++FixupPointer; } } if (RETURN_ERROR (Status)) { return Status; } RelocOffset = UE_RELOC_FIXUP_OFFSET (FixupInfo); Status = UnchainReloc ( RuntimeContext, NULL, 0, IsRuntime, RelocOffset, NULL ); if (RETURN_ERROR (Status)) { return Status; } 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 (); 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); } STATIC_ASSERT ( sizeof (UE_FIXUP_ROOT) <= UE_LOAD_TABLE_ALIGNMENT, "The following ASSERT may not hold." ); return RETURN_SUCCESS; } RETURN_STATUS UeRelocateImage ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context, IN UINT64 BaseAddress, OUT UE_LOADER_RUNTIME_CONTEXT *RuntimeContext OPTIONAL, IN UINT32 RuntimeContextSize ) { CONST UE_HEADER *UeHdr; BOOLEAN Chaining; CONST VOID *RelocTable; ASSERT (Context != NULL); ASSERT (IS_ALIGNED (Context->LoadTablesFileOffset, UE_LOAD_TABLE_ALIGNMENT)); ASSERT (IS_ALIGNED (Context->RelocTableSize, UE_LOAD_TABLE_ALIGNMENT)); ASSERT (IS_ALIGNED (BaseAddress, Context->SegmentAlignment)); ASSERT (RuntimeContext != NULL || RuntimeContextSize == 0); ASSERT (RuntimeContextSize == 0 || sizeof (*RuntimeContext) <= RuntimeContextSize); RelocTable = Context->FileBuffer + Context->LoadTablesFileOffset; UeHdr = (CONST UE_HEADER *)Context->FileBuffer; Chaining = (UeHdr->ImageInfo & UE_HEADER_IMAGE_INFO_CHAINED_FIXUPS) != 0; if (RuntimeContext != NULL) { RuntimeContext->Machine = Context->Machine; } if (Context->XIP) { Context->EntryPointAddress -= Context->SegmentsFileOffset; } return InternaRelocateImage ( Context->ImageBuffer, Context->ImageSize, Context->Machine, Context->XIP ? (Context->BaseAddress + Context->SegmentsFileOffset) : Context->BaseAddress, RelocTable, Context->RelocTableSize, Chaining, BaseAddress, RuntimeContext, FALSE ); } RETURN_STATUS UeRelocateImageForRuntime ( IN OUT VOID *Image, IN UINT32 ImageSize, IN CONST UE_LOADER_RUNTIME_CONTEXT *RuntimeContext, IN UINT64 BaseAddress ) { ASSERT (RuntimeContext != NULL); return InternaRelocateImage ( Image, ImageSize, RuntimeContext->Machine, (UINTN)Image, RuntimeContext->UnchainedRelocs, RuntimeContext->UnchainedRelocsSize, FALSE, BaseAddress, (UE_LOADER_RUNTIME_CONTEXT *)RuntimeContext, TRUE ); } STATIC RETURN_STATUS InternalGetDebugTable ( IN CONST UE_LOADER_IMAGE_CONTEXT *Context, OUT CONST UE_DEBUG_TABLE **DebugTable ) { BOOLEAN Overflow; CONST UE_LOAD_TABLE *LoadTable; UINT8 LoadTableIndex; UINT8 NumLoadTables; UINT32 LoadTableFileOffset; UINT32 LoadTableFileSize; CONST UE_DEBUG_TABLE *DbgTable; CONST UE_SEGMENT *Segments; UINT16 NumSegments; UINT32 LoadTableExtraSize; UINT32 MinLoadTableExtraSize; ASSERT (Context != NULL); ASSERT (DebugTable != NULL); LoadTableFileOffset = Context->LoadTablesFileOffset; NumLoadTables = Context->NumLoadTables; for (LoadTableIndex = 0; LoadTableIndex < NumLoadTables; ++LoadTableIndex) { LoadTable = Context->LoadTables + LoadTableIndex; if (UE_LOAD_TABLE_ID (LoadTable->FileInfo) != UeLoadTableIdDebug) { LoadTableFileOffset += UE_LOAD_TABLE_SIZE (LoadTable->FileInfo); continue; } ASSERT (IS_ALIGNED (LoadTableFileOffset, ALIGNOF (UE_DEBUG_TABLE))); LoadTableFileSize = UE_LOAD_TABLE_SIZE (LoadTable->FileInfo); Overflow = BaseOverflowSubU32 ( LoadTableFileSize, MIN_SIZE_OF_UE_DEBUG_TABLE, &LoadTableExtraSize ); if (Overflow) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } DbgTable = (CONST UE_DEBUG_TABLE *)(CONST VOID *)( Context->FileBuffer + LoadTableFileOffset ); NumSegments = UeGetSegments (Context, &Segments); MinLoadTableExtraSize = DbgTable->SymbolsPathLength + (UINT32)NumSegments * sizeof (UE_SEGMENT_NAME); if (MinLoadTableExtraSize > LoadTableExtraSize) { DEBUG_RAISE (); return RETURN_VOLUME_CORRUPTED; } *DebugTable = DbgTable; return RETURN_SUCCESS; } return RETURN_NOT_FOUND; } RETURN_STATUS UeGetSymbolsPath ( IN CONST UE_LOADER_IMAGE_CONTEXT *Context, OUT CONST CHAR8 **SymbolsPath, OUT UINT32 *SymbolsPathSize ) { RETURN_STATUS Status; CONST UE_DEBUG_TABLE *DebugTable; ASSERT (Context != NULL); ASSERT (SymbolsPath != NULL); ASSERT (SymbolsPathSize != NULL); Status = InternalGetDebugTable (Context, &DebugTable); if (RETURN_ERROR (Status)) { return Status; } if (DebugTable->SymbolsPathLength == 0) { return RETURN_NOT_FOUND; } if (DebugTable->SymbolsPath[DebugTable->SymbolsPathLength] != 0) { return RETURN_VOLUME_CORRUPTED; } *SymbolsPath = (CONST CHAR8 *)DebugTable->SymbolsPath; *SymbolsPathSize = (UINT32)DebugTable->SymbolsPathLength + 1; return RETURN_SUCCESS; } UINTN UeLoaderGetImageDebugAddress ( IN CONST UE_LOADER_IMAGE_CONTEXT *Context ) { RETURN_STATUS Status; CONST UE_DEBUG_TABLE *DebugTable; UINT8 SymOffsetFactor; UINT32 SymOffsetSubtrahend; ASSERT (Context != NULL); SymOffsetSubtrahend = 0; Status = InternalGetDebugTable (Context, &DebugTable); if (!RETURN_ERROR (Status)) { SymOffsetFactor = UE_DEBUG_TABLE_IMAGE_INFO_SYM_SUBTRAHEND_FACTOR ( DebugTable->ImageInfo ); SymOffsetSubtrahend = (UINT32)SymOffsetFactor * Context->SegmentAlignment; } return UeLoaderGetImageAddress (Context) - SymOffsetSubtrahend; } RETURN_STATUS UeGetSegmentNames ( IN CONST UE_LOADER_IMAGE_CONTEXT *Context, OUT CONST UE_SEGMENT_NAME **SegmentNames ) { RETURN_STATUS Status; CONST UE_DEBUG_TABLE *DebugTable; ASSERT (Context != NULL); ASSERT (SegmentNames != NULL); Status = InternalGetDebugTable (Context, &DebugTable); if (RETURN_ERROR (Status)) { return Status; } *SegmentNames = UE_DEBUG_TABLE_SEGMENT_NAMES (DebugTable); return RETURN_SUCCESS; } UINT32 UeGetEntryPointAddress ( IN CONST UE_LOADER_IMAGE_CONTEXT *Context ) { ASSERT (Context != NULL); return Context->EntryPointAddress; } UINT16 UeGetMachine ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context ) { ASSERT (Context != NULL); return Context->Machine; } UINT16 UeGetSubsystem ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context ) { ASSERT (Context != NULL); return Context->Subsystem; } UINT32 UeGetSegmentAlignment ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context ) { ASSERT (Context != NULL); return Context->SegmentAlignment; } UINT32 UeGetImageSize ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context ) { ASSERT (Context != NULL); return Context->ImageSize; } UINT64 UeGetBaseAddress ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context ) { ASSERT (Context != NULL); return Context->BaseAddress; } BOOLEAN UeGetRelocsStripped ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context ) { ASSERT (Context != NULL); return Context->RelocsStripped; } BOOLEAN UeGetFixedAddress( IN OUT UE_LOADER_IMAGE_CONTEXT *Context ) { ASSERT (Context != NULL); return Context->FixedAddress; } UINTN UeLoaderGetImageAddress ( IN CONST UE_LOADER_IMAGE_CONTEXT *Context ) { ASSERT (Context != NULL); return (UINTN) Context->ImageBuffer; } UINT16 UeGetSegments ( IN CONST UE_LOADER_IMAGE_CONTEXT *Context, OUT CONST UE_SEGMENT **Segments ) { ASSERT (Context != NULL); ASSERT (Segments != NULL); *Segments = Context->Segments; return (UINT16)Context->LastSegmentIndex + 1; } UINT16 UeGetSegmentImageInfos ( IN OUT UE_LOADER_IMAGE_CONTEXT *Context, OUT CONST UINT32 **SegmentImageInfos, OUT UINT8 *SegmentImageInfoIterSize ) { ASSERT (Context != NULL); ASSERT (SegmentImageInfos != NULL); ASSERT (SegmentImageInfoIterSize != NULL); ASSERT (IS_ALIGNED (Context->SegmentImageInfoIterSize, ALIGNOF (UINT32))); *SegmentImageInfos = Context->Segments; *SegmentImageInfoIterSize = Context->SegmentImageInfoIterSize; return (UINT16)Context->LastSegmentIndex + 1; }