mirror of https://github.com/acidanthera/audk.git
486 lines
14 KiB
C
486 lines
14 KiB
C
/** @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;
|
|
}
|