mirror of
https://github.com/acidanthera/audk.git
synced 2025-08-17 07:38:10 +02:00
509 lines
17 KiB
C
509 lines
17 KiB
C
/** @file
|
|
UEFI Image Loader library implementation for PE/COFF Images.
|
|
|
|
Copyright (c) 2021, Marvin Häuser. All rights reserved.<BR>
|
|
|
|
SPDX-License-Identifier: BSD-3-Clause
|
|
**/
|
|
|
|
#include <Base.h>
|
|
#include <Uefi/UefiBaseType.h>
|
|
#include <Uefi/UefiSpec.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/BaseOverflowLib.h>
|
|
#include <Library/CacheMaintenanceLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/PcdLib.h>
|
|
#include <Library/PeCoffLib2.h>
|
|
#include <Library/UefiImageLib.h>
|
|
|
|
/**
|
|
Retrieves the memory protection attributes corresponding to PE/COFF Image
|
|
section permissions.
|
|
|
|
@param[in] Characteristics The PE/COFF Image section permissions
|
|
|
|
@returns The memory protection attributes corresponding to the PE/COFF Image
|
|
section permissions.
|
|
**/
|
|
STATIC
|
|
UINT32
|
|
InternalCharacteristicsToAttributes (
|
|
IN UINT32 Characteristics
|
|
)
|
|
{
|
|
UINT32 Attributes;
|
|
|
|
if (PcdGetBool (PcdImageLoaderRemoveXForWX) && (Characteristics & (EFI_IMAGE_SCN_MEM_EXECUTE | EFI_IMAGE_SCN_MEM_WRITE)) == (EFI_IMAGE_SCN_MEM_EXECUTE | EFI_IMAGE_SCN_MEM_WRITE)) {
|
|
Characteristics &= ~EFI_IMAGE_SCN_MEM_EXECUTE;
|
|
}
|
|
|
|
Attributes = 0;
|
|
if ((Characteristics & EFI_IMAGE_SCN_MEM_READ) == 0) {
|
|
Attributes |= EFI_MEMORY_RP;
|
|
}
|
|
if ((Characteristics & EFI_IMAGE_SCN_MEM_EXECUTE) == 0) {
|
|
Attributes |= EFI_MEMORY_XP;
|
|
}
|
|
if ((Characteristics & EFI_IMAGE_SCN_MEM_WRITE) == 0) {
|
|
Attributes |= EFI_MEMORY_RO;
|
|
}
|
|
|
|
return Attributes;
|
|
}
|
|
|
|
/**
|
|
Index the read-only padding following an Image record section, if existent.
|
|
|
|
@param[in,out] RecordSegment The Image record section for the current memory
|
|
protection range. May be extended if it is of
|
|
the same type as the adjacent padding. At least
|
|
one more record section must be reserved after
|
|
it in order to index the read-only padding.
|
|
@param[in] NextAddress The start address of the next memory permission
|
|
range. This may be the end address of the Image
|
|
in order to cover the Image trailer.
|
|
@param[in] EndAddress The end address of the current memory permission
|
|
range. This also denotes the start of the added
|
|
read-only padding.
|
|
@param[in] Attributes The memory protection attributes of the current
|
|
memory permission range.
|
|
|
|
@returns The amount of Image record sections that have been appended.
|
|
**/
|
|
STATIC
|
|
UINT8
|
|
InternalInsertImageRecordSegmentPadding (
|
|
IN OUT UEFI_IMAGE_RECORD_SEGMENT *RecordSegment,
|
|
IN UINT32 EndAddress,
|
|
IN UINT32 NextAddress,
|
|
IN UINT32 Attributes
|
|
)
|
|
{
|
|
ASSERT (RecordSegment != NULL);
|
|
ASSERT (EndAddress <= NextAddress);
|
|
|
|
if (NextAddress == EndAddress) {
|
|
return 0;
|
|
}
|
|
//
|
|
// Add a new Image record section or expand the previous one depending on
|
|
// the the permissions of the previous Image record section.
|
|
//
|
|
if (Attributes == (EFI_MEMORY_XP | EFI_MEMORY_RO)) {
|
|
RecordSegment->Size += NextAddress - EndAddress;
|
|
|
|
return 0;
|
|
}
|
|
|
|
++RecordSegment;
|
|
RecordSegment->Size = NextAddress - EndAddress;
|
|
RecordSegment->Attributes = EFI_MEMORY_XP | EFI_MEMORY_RO;
|
|
|
|
return 1;
|
|
}
|
|
|
|
UEFI_IMAGE_RECORD *
|
|
UefiImageLoaderGetImageRecordPeCoff (
|
|
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context
|
|
)
|
|
{
|
|
UEFI_IMAGE_RECORD *ImageRecord;
|
|
UINT32 MaxNumRecordSegments;
|
|
UINT32 NumRecordSegments;
|
|
UEFI_IMAGE_RECORD_SEGMENT *RecordSegment;
|
|
UINTN ImageAddress;
|
|
UINT32 SizeOfImage;
|
|
UINT32 SectionAlignment;
|
|
CONST EFI_IMAGE_SECTION_HEADER *Sections;
|
|
UINT16 NumberOfSections;
|
|
UINT16 SectionIndex;
|
|
CONST EFI_IMAGE_SECTION_HEADER *Section;
|
|
UINT32 SectionAddress;
|
|
UINT32 SectionSize;
|
|
UINT32 SectionCharacteristics;
|
|
UINT32 StartAddress;
|
|
UINT32 EndAddress;
|
|
UINT32 Characteristics;
|
|
UINT32 Attributes;
|
|
|
|
ASSERT (Context != NULL);
|
|
//
|
|
// Determine the maximum amount of Image record sections and allocate the
|
|
// Image record.
|
|
//
|
|
NumberOfSections = PeCoffGetSectionTable (Context, &Sections);
|
|
|
|
STATIC_ASSERT (
|
|
MAX_UINT16 <= MAX_UINT32 / 2 - 1,
|
|
"The following arithmetic may overflow."
|
|
);
|
|
|
|
if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CONTIGUOUS_SECTIONS) == 0) {
|
|
//
|
|
// In case of contiguous Image sections, there can be two additional record
|
|
// sections (Image Headers and trailer, e.g. debug information).
|
|
//
|
|
MaxNumRecordSegments = (UINT32) NumberOfSections + 2;
|
|
} else {
|
|
//
|
|
// In case of possibly non-contiguous Image sections, there can be a trailer
|
|
// per Image section (the last Image section's trailer is the same as the
|
|
// Image trailer), as well as additionally the Image Headers.
|
|
//
|
|
MaxNumRecordSegments = (UINT32) NumberOfSections * 2 + 1;
|
|
}
|
|
|
|
ImageRecord = AllocatePool (
|
|
sizeof (*ImageRecord)
|
|
+ MaxNumRecordSegments * sizeof (*ImageRecord->Segments)
|
|
);
|
|
if (ImageRecord == NULL) {
|
|
DEBUG_RAISE ();
|
|
return NULL;
|
|
}
|
|
|
|
ImageRecord->Signature = UEFI_IMAGE_RECORD_SIGNATURE;
|
|
InitializeListHead (&ImageRecord->Link);
|
|
|
|
SectionAlignment = PeCoffGetSectionAlignment (Context);
|
|
//
|
|
// Map the Image Headers as read-only data. If the first Image section is
|
|
// loaded at the start of the Image memory space, the condition
|
|
// SectionAddress != StartAddress does not hold and these definitions will be
|
|
// ignored.
|
|
//
|
|
StartAddress = 0;
|
|
EndAddress = PeCoffGetSizeOfHeaders (Context);
|
|
Characteristics = EFI_IMAGE_SCN_MEM_READ;
|
|
Attributes = EFI_MEMORY_XP | EFI_MEMORY_RO;
|
|
ASSERT (Attributes == InternalCharacteristicsToAttributes (Characteristics));
|
|
//
|
|
// Create an Image record section for every permission-distinct range of the
|
|
// Image. The current range [StartAddress, EndAddress) shares the memory
|
|
// permissions Charactersitics/Attributes and is extended till a new
|
|
// memory permission configuration is required. Headers and trailers treated
|
|
// as read-only data.
|
|
//
|
|
NumRecordSegments = 0;
|
|
for (SectionIndex = 0; SectionIndex < NumberOfSections; ++SectionIndex) {
|
|
Section = Sections + SectionIndex;
|
|
//
|
|
// Skip empty Image sections to avoid unnecessary splitting.
|
|
//
|
|
if (Section->VirtualSize == 0) {
|
|
continue;
|
|
}
|
|
//
|
|
// These arithmetics are safe as guaranteed by PeCoffInitializeContext().
|
|
//
|
|
SectionAddress = Section->VirtualAddress;
|
|
SectionSize = ALIGN_VALUE (Section->VirtualSize, SectionAlignment);
|
|
SectionCharacteristics = Section->Characteristics & (EFI_IMAGE_SCN_MEM_EXECUTE | EFI_IMAGE_SCN_MEM_READ | EFI_IMAGE_SCN_MEM_WRITE);
|
|
//
|
|
// Skip Image sections with the same memory permissions as the current range
|
|
// as they can be merged. For this, the Image sections must be adjacent, or
|
|
// the range must have the same memory permissions as the padding inbetween
|
|
// (read-only).
|
|
//
|
|
if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CONTIGUOUS_SECTIONS) == 0
|
|
|| SectionAddress == EndAddress
|
|
|| Characteristics == EFI_IMAGE_SCN_MEM_READ) {
|
|
if (SectionCharacteristics == Characteristics) {
|
|
EndAddress = SectionAddress + SectionSize;
|
|
continue;
|
|
}
|
|
}
|
|
//
|
|
// Only create an entry if the range is not empty, otherwise discard it and
|
|
// start a new one. Even with skipping empty Image sections, this can still
|
|
// happen for the Image Headers when the first Image section starts at 0.
|
|
//
|
|
if (SectionAddress != StartAddress) {
|
|
//
|
|
// Create an Image record section for the current memory permission range.
|
|
//
|
|
RecordSegment = &ImageRecord->Segments[NumRecordSegments];
|
|
RecordSegment->Size = EndAddress - StartAddress;
|
|
RecordSegment->Attributes = Attributes;
|
|
++NumRecordSegments;
|
|
//
|
|
// If the previous range is not adjacent to the current Image section,
|
|
// report the padding as read-only data.
|
|
//
|
|
if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CONTIGUOUS_SECTIONS) != 0) {
|
|
NumRecordSegments += InternalInsertImageRecordSegmentPadding (
|
|
RecordSegment,
|
|
EndAddress,
|
|
SectionAddress,
|
|
Attributes
|
|
);
|
|
}
|
|
|
|
StartAddress = SectionAddress;
|
|
}
|
|
//
|
|
// Start a Image record section with the current Image section.
|
|
//
|
|
EndAddress = SectionAddress + SectionSize;
|
|
Characteristics = SectionCharacteristics;
|
|
Attributes = InternalCharacteristicsToAttributes (Characteristics);
|
|
}
|
|
//
|
|
// Image Record sections are only created once a non-empty Image section is
|
|
// encountered that requests a different memory permission configuration.
|
|
// As such, the last memory permission range is never converted in the loop.
|
|
// If the loop never produced such, this is true for the Image Headers, which
|
|
// cannot be empty.
|
|
//
|
|
ASSERT (StartAddress < EndAddress);
|
|
//
|
|
// Create an Image record section for the last Image memory permission range.
|
|
//
|
|
RecordSegment = &ImageRecord->Segments[NumRecordSegments];
|
|
RecordSegment->Size = EndAddress - StartAddress;
|
|
RecordSegment->Attributes = Attributes;
|
|
++NumRecordSegments;
|
|
|
|
ImageAddress = PeCoffLoaderGetImageAddress (Context);
|
|
SizeOfImage = PeCoffGetSizeOfImage (Context);
|
|
//
|
|
// The Image trailer, if existent, is treated as padding and as such is
|
|
// reported as read-only data, as intended. Because it is not part of the
|
|
// original Image memory space, this needs to happen whether Image sections
|
|
// are guaranteed to be contiguously form the entire Image memory space or
|
|
// not.
|
|
//
|
|
NumRecordSegments += InternalInsertImageRecordSegmentPadding (
|
|
RecordSegment,
|
|
EndAddress,
|
|
SizeOfImage,
|
|
Attributes
|
|
);
|
|
|
|
ImageRecord->NumSegments = NumRecordSegments;
|
|
ImageRecord->StartAddress = ImageAddress;
|
|
ImageRecord->EndAddress = ImageAddress + SizeOfImage;
|
|
//
|
|
// Zero the remaining array entries to avoid uninitialised data.
|
|
//
|
|
ZeroMem (
|
|
ImageRecord->Segments + NumRecordSegments,
|
|
(MaxNumRecordSegments - NumRecordSegments) * sizeof (*ImageRecord->Segments)
|
|
);
|
|
|
|
return ImageRecord;
|
|
}
|
|
|
|
// FIXME: Docs
|
|
STATIC
|
|
RETURN_STATUS
|
|
InternalDebugLocateImage (
|
|
OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context,
|
|
IN CHAR8 *Buffer,
|
|
IN UINTN Address,
|
|
IN BOOLEAN Recurse
|
|
)
|
|
{
|
|
RETURN_STATUS Status;
|
|
RETURN_STATUS DosStatus;
|
|
PE_COFF_LOADER_IMAGE_CONTEXT DosContext;
|
|
|
|
ASSERT (((UINTN) Buffer & 3U) == 0);
|
|
//
|
|
// Align the search buffer to a 4 Byte boundary.
|
|
//
|
|
// Search for the Image Header in 4 Byte steps. All dynamically loaded
|
|
// Images start at a page boundary to allow for Image section protection,
|
|
// but XIP Images may not.
|
|
//
|
|
// Do not attempt to access memory of the first page as it may be protected as
|
|
// part of NULL dereference detection.
|
|
//
|
|
for (; EFI_PAGE_SIZE <= (UINTN) Buffer; Buffer -= 4) {
|
|
//
|
|
// Try to parse the current memory as PE/COFF Image. Pass MAX_UINT32
|
|
// as the file size as there isn't any more information available. Only the
|
|
// Image Header memory will be accessed as part of initialisation.
|
|
//
|
|
Status = PeCoffInitializeContext (
|
|
Context,
|
|
Buffer,
|
|
MAX_UINT32
|
|
);
|
|
if (RETURN_ERROR (Status)) {
|
|
continue;
|
|
}
|
|
|
|
if (!Recurse) {
|
|
//
|
|
// For PE/COFF Images, the PE/COFF Image Header may be discovered while
|
|
// there may still be a preceeding DOS Image Header. All RVAs are relatvie
|
|
// to the start of the Image memory space, of which the DOS Image Header
|
|
// is a part of, if existent. Allow one level of recursion to find a lower
|
|
// Image Base including the DOS Image Header.
|
|
//
|
|
if (Context->ExeHdrOffset == 0) {
|
|
DosStatus = InternalDebugLocateImage (
|
|
&DosContext,
|
|
Buffer - 4,
|
|
Address,
|
|
TRUE
|
|
);
|
|
if (!RETURN_ERROR (DosStatus)) {
|
|
Buffer = DosContext.ImageBuffer;
|
|
CopyMem (Context, &DosContext, sizeof (*Context));
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// We know that (UINTN) Buffer <= Address from the initialisation.
|
|
//
|
|
if (Address < (UINTN) Buffer + PeCoffGetSizeOfImage (Context)) {
|
|
Context->ImageBuffer = Buffer;
|
|
//
|
|
// Zero the raw file information as we are initialising from a potentially
|
|
// non-XIP in-memory Image.
|
|
//
|
|
Context->FileBuffer = NULL;
|
|
Context->FileSize = 0;
|
|
|
|
return RETURN_SUCCESS;
|
|
}
|
|
//
|
|
// Continue for the unlikely case that a PE/COFF Image embeds another
|
|
// one within its data, the outer Image may still follow.
|
|
//
|
|
}
|
|
|
|
return RETURN_NOT_FOUND;
|
|
}
|
|
|
|
RETURN_STATUS
|
|
UefiImageDebugLocateImagePeCoff (
|
|
OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context,
|
|
IN UINTN Address
|
|
)
|
|
{
|
|
RETURN_STATUS Status;
|
|
|
|
Status = RETURN_NOT_FOUND;
|
|
//
|
|
// As this function is intrinsically unsafe, do not allow its usage outside of
|
|
// DEBUG-enabled code.
|
|
//
|
|
DEBUG_CODE_BEGIN ();
|
|
|
|
//
|
|
// If the Image Headers are not loaded explicitly, only XIP Images and Images
|
|
// that embed the Image Header in the first Image section can be located. As
|
|
// this is not the case for the majority of Images, don't attempt to locate
|
|
// the Image base to not access too much (potentially protected) memory.
|
|
//
|
|
if (!PcdGetBool (PcdImageLoaderLoadHeader)) {
|
|
DEBUG_RAISE ();
|
|
return RETURN_NOT_FOUND;
|
|
}
|
|
//
|
|
// Align the search buffer to a 4 Byte boundary.
|
|
//
|
|
Status = InternalDebugLocateImage (
|
|
Context,
|
|
(CHAR8 *) (Address & ~(UINTN) 3U),
|
|
Address,
|
|
FALSE
|
|
);
|
|
|
|
DEBUG_CODE_END ();
|
|
|
|
return Status;
|
|
}
|
|
|
|
RETURN_STATUS
|
|
UefiImageGetFixedAddressPeCoff (
|
|
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context,
|
|
OUT UINT64 *Address
|
|
)
|
|
{
|
|
CONST EFI_IMAGE_SECTION_HEADER *Sections;
|
|
UINT16 NumberOfSections;
|
|
UINT16 SectionIndex;
|
|
UINT64 FixedAddress;
|
|
|
|
ASSERT (Address != NULL);
|
|
//
|
|
// If this feature is enabled, the build tool will save the address in the
|
|
// PointerToRelocations and PointerToLineNumbers fields of the first Image
|
|
// section header that doesn't hold code. The 64-bit value across those fields
|
|
// will be non-zero if and only if the module has been assigned an address.
|
|
//
|
|
NumberOfSections = PeCoffGetSectionTable (Context, &Sections);
|
|
for (SectionIndex = 0; SectionIndex < NumberOfSections; ++SectionIndex) {
|
|
if ((Sections[SectionIndex].Characteristics & EFI_IMAGE_SCN_CNT_CODE) != 0) {
|
|
continue;
|
|
}
|
|
|
|
FixedAddress = ReadUnaligned64 (
|
|
(CONST VOID *) &Sections[SectionIndex].PointerToRelocations
|
|
);
|
|
if (FixedAddress != 0) {
|
|
if (!IS_ALIGNED (FixedAddress, Context->SectionAlignment)) {
|
|
return RETURN_UNSUPPORTED;
|
|
}
|
|
|
|
*Address = FixedAddress;
|
|
return RETURN_SUCCESS;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return RETURN_NOT_FOUND;
|
|
}
|
|
|
|
VOID
|
|
UefiImageDebugPrintSegmentsPeCoff (
|
|
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context
|
|
)
|
|
{
|
|
CONST EFI_IMAGE_SECTION_HEADER *Sections;
|
|
UINT16 NumberOfSections;
|
|
UINT16 SectionIndex;
|
|
CONST UINT8 *Name;
|
|
|
|
NumberOfSections = PeCoffGetSectionTable (Context, &Sections);
|
|
|
|
for (SectionIndex = 0; SectionIndex < NumberOfSections; ++SectionIndex) {
|
|
Name = Sections[SectionIndex].Name;
|
|
DEBUG ((
|
|
DEBUG_VERBOSE,
|
|
" Section - '%c%c%c%c%c%c%c%c'\n"
|
|
" VirtualSize - 0x%08x\n"
|
|
" VirtualAddress - 0x%08x\n"
|
|
" SizeOfRawData - 0x%08x\n"
|
|
" PointerToRawData - 0x%08x\n",
|
|
Name[0], Name[1], Name[2], Name[3], Name[4], Name[5], Name[6], Name[7],
|
|
Sections[SectionIndex].VirtualSize,
|
|
Sections[SectionIndex].VirtualAddress,
|
|
Sections[SectionIndex].SizeOfRawData,
|
|
Sections[SectionIndex].PointerToRawData
|
|
));
|
|
DEBUG ((
|
|
DEBUG_VERBOSE,
|
|
" PointerToRelocations - 0x%08x\n"
|
|
" PointerToLinenumbers - 0x%08x\n"
|
|
" NumberOfRelocations - 0x%08x\n"
|
|
" NumberOfLinenumbers - 0x%08x\n"
|
|
" Characteristics - 0x%08x\n",
|
|
Sections[SectionIndex].PointerToRelocations,
|
|
Sections[SectionIndex].PointerToLinenumbers,
|
|
Sections[SectionIndex].NumberOfRelocations,
|
|
Sections[SectionIndex].NumberOfLinenumbers,
|
|
Sections[SectionIndex].Characteristics
|
|
));
|
|
}
|
|
}
|