audk/BaseTools/ImageTool/UeScan.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;
}