audk/MdePkg/Library/BaseUeImageLib/UeImageLib.c

1393 lines
39 KiB
C

/** @file
UEFI image loader library implementation for UE images.
Copyright (c) 2021 - 2023, Marvin Häuser. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <Base.h>
#include <IndustryStandard/UeImage.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/BaseOverflowLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>
#include <Library/PeCoffLib2.h>
#include <Library/UefiImageLib.h>
#include <Library/UeImageLib.h>
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;
}