Hello all, This is a proof of concept patch that fixes the problems that occur when enabling the EFI_PROPERTIES_RUNTIME_MEMORY_PROTECTION_NON_EXECUTABLE_PE_DATA MemoryProtectionAttribute, which may split PE/COFF memory images into disjoint regions in virtual memory.
It is not a complete solution, but it works both on AARCH64 (ArmVirtQemu) and X64 (Ovmf). With the 4 KB alignment patch and Laszlo's end-of-DXE series applied, I can boot into the Linux kernel (Ubuntu 3.13.0-55-generic) without problems, while the memory map reveals that the splitting has in fact occurred. Note that this may require 64 KB section alignment for non-ELF based toolchains, if they may potentially emit EFI_IMAGE_REL_BASED_LOW or EFI_IMAGE_REL_BASED_HIGH relocations. For toolchains that rely on GenFw for the ELF to PE/COFF conversion, this is not required. ------------------8<---------------------- When reapplying PE/COFF relocations for the switch to virtual mode, we should not assume that the virtual memory image is identical to the PE/COFF file image, since PE/COFF may no longer be adjacent in virtual memory. So instead of using a fixed adjustment derived from the virtual address of ImageBase, apply ConvertPointer () to each relocation target to obtain the new value. Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Ard Biesheuvel <ard.biesheu...@linaro.org> --- MdeModulePkg/Core/RuntimeDxe/Runtime.c | 229 +++++++++++++++++++- 1 file changed, 227 insertions(+), 2 deletions(-) diff --git a/MdeModulePkg/Core/RuntimeDxe/Runtime.c b/MdeModulePkg/Core/RuntimeDxe/Runtime.c index c61301cf80d8..c8dbfdd5a3ef 100644 --- a/MdeModulePkg/Core/RuntimeDxe/Runtime.c +++ b/MdeModulePkg/Core/RuntimeDxe/Runtime.c @@ -213,6 +213,232 @@ RuntimeDriverConvertInternalPointer ( } /** + Reapply fixups on a fixed up PE32/PE32+ image to allow virutal calling at EFI + runtime. + + This function reapplies relocation fixups to the PE/COFF image specified by ImageBase + and ImageSize so the image will execute correctly when the PE/COFF image is mapped + to the address specified by VirtualImageBase. RelocationData must be identical + to the FixupData buffer from the PE_COFF_LOADER_IMAGE_CONTEXT structure + after this PE/COFF image was relocated with BasePecoffLib::PeCoffLoaderRelocateImage(). + + Note that if the platform does not maintain coherency between the instruction cache(s) and the data + cache(s) in hardware, then the caller is responsible for performing cache maintenance operations + prior to transferring control to a PE/COFF image that is loaded using this funation. + + @param ImageBase The base address of a PE/COFF image that has been loaded + and relocated into system memory. + @param ImageSize The size, in bytes, of the PE/COFF image. + @param RelocationData A pointer to the relocation data that was collected when the PE/COFF + image was relocated using PeCoffLoaderRelocateImage(). + + @return EFI_SUCCESS The fixups have been reapplied successfully. + + @return EFI_UNSUPPORTED ImageBase does not point to a valid PE image + @return EFI_UNSUPPORTED No reloc directory found or contents invalid + @return EFI_UNSUPPORTED Image uses high/low relocations and section alignment < 64 KB + +**/ +STATIC +EFI_STATUS +EFIAPI +RuntimeDriverRelocateImageForRuntime ( + IN PHYSICAL_ADDRESS ImageBase, + IN UINTN ImageSize, + IN VOID *RelocationData + ) +{ + CHAR8 *OldBase; + EFI_IMAGE_DOS_HEADER *DosHdr; + EFI_IMAGE_OPTIONAL_HEADER_PTR_UNION Hdr; + UINT32 NumberOfRvaAndSizes; + EFI_IMAGE_DATA_DIRECTORY *DataDirectory; + EFI_IMAGE_DATA_DIRECTORY *RelocDir; + EFI_IMAGE_BASE_RELOCATION *RelocBase; + EFI_IMAGE_BASE_RELOCATION *RelocBaseEnd; + UINT16 *Reloc; + UINT16 *RelocEnd; + CHAR8 *Fixup; + CHAR8 *FixupBase; + UINT16 *Fixup16; + UINT32 *Fixup32; + UINT64 *Fixup64; + CHAR8 *FixupData; + UINT16 Magic; + UINTN ConvertAddress; + + OldBase = (CHAR8 *)((UINTN)ImageBase); + + // + // Find the image's relocate dir info + // + DosHdr = (EFI_IMAGE_DOS_HEADER *)OldBase; + if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) { + // + // Valid DOS header so get address of PE header + // + Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *)(((CHAR8 *)DosHdr) + DosHdr->e_lfanew); + } else { + // + // No Dos header so assume image starts with PE header. + // + Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *)OldBase; + } + + if (Hdr.Pe32->Signature != EFI_IMAGE_NT_SIGNATURE) { + // + // Not a valid PE image so Exit + // + return EFI_UNSUPPORTED; + } + + Magic = Hdr.Pe32->OptionalHeader.Magic; + + if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + // + // Use PE32 offset + // + NumberOfRvaAndSizes = Hdr.Pe32->OptionalHeader.NumberOfRvaAndSizes; + DataDirectory = (EFI_IMAGE_DATA_DIRECTORY *)&(Hdr.Pe32->OptionalHeader.DataDirectory[0]); + } else { + // + // Use PE32+ offset + // + NumberOfRvaAndSizes = Hdr.Pe32Plus->OptionalHeader.NumberOfRvaAndSizes; + DataDirectory = (EFI_IMAGE_DATA_DIRECTORY *)&(Hdr.Pe32Plus->OptionalHeader.DataDirectory[0]); + } + + // + // Find the relocation block + // + // Per the PE/COFF spec, you can't assume that a given data directory + // is present in the image. You have to check the NumberOfRvaAndSizes in + // the optional header to verify a desired directory entry is there. + // + if (NumberOfRvaAndSizes > EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) { + RelocDir = DataDirectory + EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC; + RelocBase = (EFI_IMAGE_BASE_RELOCATION *)(UINTN)(ImageBase + RelocDir->VirtualAddress); + RelocBaseEnd = (EFI_IMAGE_BASE_RELOCATION *)(UINTN)(ImageBase + RelocDir->VirtualAddress + RelocDir->Size); + } else { + // + // Cannot find relocations, cannot continue to relocate the image, ASSERT for this invalid image. + // + ASSERT (FALSE); + return EFI_UNSUPPORTED; + } + + // + // ASSERT for the invalid image when RelocBase and RelocBaseEnd are both NULL. + // + ASSERT (RelocBase != NULL && RelocBaseEnd != NULL); + + // + // Run the whole relocation block. And re-fixup data that has not been + // modified. The FixupData is used to see if the image has been modified + // since it was relocated. This is so data sections that have been updated + // by code will not be fixed up, since that would set them back to + // defaults. + // + FixupData = RelocationData; + while (RelocBase < RelocBaseEnd) { + // + // Add check for RelocBase->SizeOfBlock field. + // + if ((RelocBase->SizeOfBlock == 0) || (RelocBase->SizeOfBlock > RelocDir->Size)) { + // + // Data invalid, cannot continue to relocate the image, just return. + // + return EFI_UNSUPPORTED; + } + + Reloc = (UINT16 *) ((UINT8 *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION)); + RelocEnd = (UINT16 *) ((UINT8 *) RelocBase + RelocBase->SizeOfBlock); + FixupBase = (CHAR8 *) ((UINTN)ImageBase) + RelocBase->VirtualAddress; + + // + // Run this relocation record + // + while (Reloc < RelocEnd) { + + Fixup = FixupBase + (*Reloc & 0xFFF); + switch ((*Reloc) >> 12) { + + case EFI_IMAGE_REL_BASED_ABSOLUTE: + break; + + case EFI_IMAGE_REL_BASED_HIGH: + // + // In order to be able to apply relocations to code and data regions + // that may be disjoint in the virtual mapping, we disallow relocations + // of type EFI_IMAGE_REL_BASED_HIGH or EFI_IMAGE_REL_BASED_LOW, unless + // the section alignment is at least 64 KB. + // + if (Hdr.Pe32->OptionalHeader.SectionAlignment < SIZE_64KB) { + return EFI_UNSUPPORTED; + } + Fixup16 = (UINT16 *) Fixup; + if (*(UINT16 *) FixupData == *Fixup16) { + ConvertAddress = (UINTN) *Fixup16 << 16; + RuntimeDriverConvertInternalPointer ((VOID **) &ConvertAddress); + *Fixup16 = (UINT16) (ConvertAddress >> 16); + } + + FixupData = FixupData + sizeof (UINT16); + break; + + case EFI_IMAGE_REL_BASED_LOW: + if (Hdr.Pe32->OptionalHeader.SectionAlignment < SIZE_64KB) { + return EFI_UNSUPPORTED; + } + // + // Since the adjustment is guaranteed to be a multiple of 64 KB, + // there is nothing to do here. + // + break; + + case EFI_IMAGE_REL_BASED_HIGHLOW: + Fixup32 = (UINT32 *) Fixup; + FixupData = ALIGN_POINTER (FixupData, sizeof (UINT32)); + if (*(UINT32 *) FixupData == *Fixup32) { + ConvertAddress = (UINTN) *Fixup32; + RuntimeDriverConvertInternalPointer ((VOID **) &ConvertAddress); + *Fixup32 = (UINT32) ConvertAddress; + } + + FixupData = FixupData + sizeof (UINT32); + break; + + case EFI_IMAGE_REL_BASED_DIR64: + Fixup64 = (UINT64 *)Fixup; + FixupData = ALIGN_POINTER (FixupData, sizeof (UINT64)); + if (*(UINT64 *) FixupData == *Fixup64) { + ConvertAddress = (UINTN) *Fixup64; + RuntimeDriverConvertInternalPointer ((VOID **) &ConvertAddress); + *Fixup64 = (UINT64) ConvertAddress; + } + + FixupData = FixupData + sizeof (UINT64); + break; + + default: + ASSERT (FALSE); + } + // + // Next relocation record + // + Reloc += 1; + } + // + // next reloc block + // + RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd; + } + + return EFI_SUCCESS; +} + + +/** Changes the runtime addressing mode of EFI firmware from physical to virtual. @@ -312,9 +538,8 @@ RuntimeDriverSetVirtualAddressMap ( Status = RuntimeDriverConvertPointer (0, (VOID **) &VirtImageBase); ASSERT_EFI_ERROR (Status); - PeCoffLoaderRelocateImageForRuntime ( + RuntimeDriverRelocateImageForRuntime ( (EFI_PHYSICAL_ADDRESS) (UINTN) RuntimeImage->ImageBase, - VirtImageBase, (UINTN) RuntimeImage->ImageSize, RuntimeImage->RelocationData ); -- 1.9.1 ------------------------------------------------------------------------------ Don't Limit Your Business. Reach for the Cloud. GigeNET's Cloud Solutions provide you with the tools and support that you need to offload your IT needs and focus on growing your business. Configured For All Businesses. Start Your Cloud Today. https://www.gigenetcloud.com/ _______________________________________________ edk2-devel mailing list edk2-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/edk2-devel