diff --git a/Changelog.md b/Changelog.md index abf32a20cf8b..f306b158ff95 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ OpenCore Changelog - Fixed hang while generating boot entries on some systems - Added `efidebug.tool` support for 32-bit on 32-bit using GDB or LLDB - Fixed potential incorrect values in kernel image capabilities calculation +- Added `FixupAppleEfiImages` quirk to allow booting Mac OS X 10.4 and 10.5 boot.efi images on modern secure image loaders #### v0.9.5 - Fixed GUID formatting for legacy NVRAM saving diff --git a/Docs/Configuration.tex b/Docs/Configuration.tex index c4ff79ab6a2e..721f4a5bdf0c 100755 --- a/Docs/Configuration.tex +++ b/Docs/Configuration.tex @@ -1610,6 +1610,34 @@ \subsection{Quirks Properties}\label{booterpropsquirks} \texttt{RebuildAppleMemoryMap} if the firmware supports memory attributes table (MAT). Refer to the \texttt{OCABC: MAT support is 1/0} log entry to determine whether MAT is supported. +\item + \texttt{FixupAppleEfiImages}\\ + \textbf{Type}: \texttt{plist\ boolean}\\ + \textbf{Failsafe}: \texttt{false}\\ + \textbf{Description}: Fix errors in early Mac OS X boot.efi images. + + Modern secure PE loaders will refuse to load \texttt{boot.efi} images from + Mac OS X 10.4 and 10.5 due to these files containing \texttt{W\^{}X} errors + and illegal overlapping sections. + + This quirk detects these issues and pre-processes such images in memory, + so that a modern loader can accept them. + + Pre-processing in memory is incompatible with secure boot, as the image loaded + is not the image on disk, so you cannot sign files which are loaded in this way + based on their original disk image contents. + Certain firmware will offer to register the hash of new, unknown images - this would + still work. On the other hand, it is not particularly realistic to want to + start such early, insecure images with secure boot anyway. + + \emph{Note 1}: The quirk is only applied to Apple-specific `fat' (both 32-bit and 64-bit + versions in one image) \texttt{.efi} files, and is never applied during the Apple secure + boot path for newer macOS. + + \emph{Note 2}: The quirk is only needed for loading Mac OS X 10.4 and 10.5, and even then + only if the firmware itself includes a modern, more secure PE COFF image loader. This includes + current builds of OpenDuet. + \item \texttt{ForceBooterSignature}\\ \textbf{Type}: \texttt{plist\ boolean}\\ diff --git a/Docs/Sample.plist b/Docs/Sample.plist index 607384342e7a..1c60f841a61c 100644 --- a/Docs/Sample.plist +++ b/Docs/Sample.plist @@ -319,6 +319,8 @@ EnableWriteUnprotector + FixupAppleEfiImages + ForceBooterSignature ForceExitBootServices diff --git a/Docs/SampleCustom.plist b/Docs/SampleCustom.plist index 3b8ee41942b8..61843ed69d5c 100644 --- a/Docs/SampleCustom.plist +++ b/Docs/SampleCustom.plist @@ -319,6 +319,8 @@ EnableWriteUnprotector + FixupAppleEfiImages + ForceBooterSignature ForceExitBootServices diff --git a/Include/Acidanthera/Library/OcBootManagementLib.h b/Include/Acidanthera/Library/OcBootManagementLib.h index b443863a85b5..e0599da3d61c 100644 --- a/Include/Acidanthera/Library/OcBootManagementLib.h +++ b/Include/Acidanthera/Library/OcBootManagementLib.h @@ -1814,7 +1814,8 @@ OcRegisterBootstrapBootOption ( **/ VOID OcImageLoaderInit ( - IN CONST BOOLEAN ProtectUefiServices + IN CONST BOOLEAN ProtectUefiServices, + IN CONST BOOLEAN FixupAppleEfiImages ); /** diff --git a/Include/Acidanthera/Library/OcConfigurationLib.h b/Include/Acidanthera/Library/OcConfigurationLib.h index bb2a04a11ecc..470f54e8ccb9 100644 --- a/Include/Acidanthera/Library/OcConfigurationLib.h +++ b/Include/Acidanthera/Library/OcConfigurationLib.h @@ -142,6 +142,7 @@ OC_DECLARE (OC_BOOTER_PATCH_ARRAY) _(BOOLEAN , DiscardHibernateMap , , FALSE , ()) \ _(BOOLEAN , EnableSafeModeSlide , , FALSE , ()) \ _(BOOLEAN , EnableWriteUnprotector , , FALSE , ()) \ + _(BOOLEAN , FixupAppleEfiImages , , FALSE , ()) \ _(BOOLEAN , ForceBooterSignature , , FALSE , ()) \ _(BOOLEAN , ForceExitBootServices , , FALSE , ()) \ _(BOOLEAN , ProtectMemoryRegions , , FALSE , ()) \ diff --git a/Include/Acidanthera/Library/OcPeCoffExtLib.h b/Include/Acidanthera/Library/OcPeCoffExtLib.h index 404ddf5564ab..09bd5263336f 100644 --- a/Include/Acidanthera/Library/OcPeCoffExtLib.h +++ b/Include/Acidanthera/Library/OcPeCoffExtLib.h @@ -43,4 +43,19 @@ PeCoffGetApfsDriverVersion ( OUT APFS_DRIVER_VERSION **DriverVersionPtr ); +/** + Detect and patch W^X and section overlap errors in legacy boot.efi. + Expected to make changes in 10.4 and 10.5 only. + + @param[in] DriverBuffer Image buffer. + @param[in] DriverSize Size of the image. + + @retval EFI_SUCCESS on success. +**/ +EFI_STATUS +OcPatchLegacyEfi ( + IN VOID *DriverBuffer, + IN UINT32 DriverSize + ); + #endif // OC_PE_COFF_EXT_LIB_H diff --git a/Library/OcBootManagementLib/ImageLoader.c b/Library/OcBootManagementLib/ImageLoader.c index 5971b140a867..baca32aa44b4 100644 --- a/Library/OcBootManagementLib/ImageLoader.c +++ b/Library/OcBootManagementLib/ImageLoader.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,7 @@ STATIC EFI_HANDLE mImageLoaderCapsHandle; STATIC BOOLEAN mImageLoaderEnabled; STATIC BOOLEAN mProtectUefiServices; +STATIC BOOLEAN mFixupAppleEfiImages; STATIC EFI_IMAGE_LOAD mPreservedLoadImage; STATIC EFI_IMAGE_START mPreservedStartImage; @@ -867,6 +869,26 @@ InternalEfiLoadImage ( // Determine its capabilities. // if (!EFI_ERROR (Status) && (RealSize != SourceSize) && (RealSize >= EFI_PAGE_SIZE)) { + if (mFixupAppleEfiImages) { + if (SecureBootStatus == EFI_SUCCESS) { + DEBUG ((DEBUG_INFO, "OCB: Secure boot, fixup legacy efi ignored\n")); + } else { + Status = OcPatchLegacyEfi (SourceBuffer, RealSize); + // + // Error can mean incompletely patched image, so we should fail. + // Any error not the result of incomplete patching would in general not load anyway. + // + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "OCB: PatchLegacyEfi - %r\n", Status)); + if (AllocatedBuffer != NULL) { + FreePool (AllocatedBuffer); + } + + return Status; + } + } + } + mImageLoaderCaps = DetectCapabilities (SourceBuffer, RealSize); } @@ -1089,10 +1111,12 @@ InternalEfiExit ( VOID OcImageLoaderInit ( - IN CONST BOOLEAN ProtectUefiServices + IN CONST BOOLEAN ProtectUefiServices, + IN CONST BOOLEAN FixupAppleEfiImages ) { mProtectUefiServices = ProtectUefiServices; + mFixupAppleEfiImages = FixupAppleEfiImages; mOriginalEfiLoadImage = gBS->LoadImage; mOriginalEfiStartImage = gBS->StartImage; diff --git a/Library/OcBootManagementLib/OcBootManagementLib.inf b/Library/OcBootManagementLib/OcBootManagementLib.inf index efb5743b9a32..f782dd757eef 100644 --- a/Library/OcBootManagementLib/OcBootManagementLib.inf +++ b/Library/OcBootManagementLib/OcBootManagementLib.inf @@ -110,6 +110,7 @@ OcFlexArrayLib OcMachoLib OcMiscLib + OcPeCoffExtLib OcRtcLib OcTypingLib OcVariableLib diff --git a/Library/OcConfigurationLib/OcConfigurationLib.c b/Library/OcConfigurationLib/OcConfigurationLib.c index 3d0df633100c..09749e8769a3 100644 --- a/Library/OcConfigurationLib/OcConfigurationLib.c +++ b/Library/OcConfigurationLib/OcConfigurationLib.c @@ -189,6 +189,7 @@ OC_SCHEMA OC_SCHEMA_BOOLEAN_IN ("DiscardHibernateMap", OC_GLOBAL_CONFIG, Booter.Quirks.DiscardHibernateMap), OC_SCHEMA_BOOLEAN_IN ("EnableSafeModeSlide", OC_GLOBAL_CONFIG, Booter.Quirks.EnableSafeModeSlide), OC_SCHEMA_BOOLEAN_IN ("EnableWriteUnprotector", OC_GLOBAL_CONFIG, Booter.Quirks.EnableWriteUnprotector), + OC_SCHEMA_BOOLEAN_IN ("FixupAppleEfiImages", OC_GLOBAL_CONFIG, Booter.Quirks.FixupAppleEfiImages), OC_SCHEMA_BOOLEAN_IN ("ForceBooterSignature", OC_GLOBAL_CONFIG, Booter.Quirks.ForceBooterSignature), OC_SCHEMA_BOOLEAN_IN ("ForceExitBootServices", OC_GLOBAL_CONFIG, Booter.Quirks.ForceExitBootServices), OC_SCHEMA_BOOLEAN_IN ("ProtectMemoryRegions", OC_GLOBAL_CONFIG, Booter.Quirks.ProtectMemoryRegions), diff --git a/Library/OcMainLib/OpenCoreUefi.c b/Library/OcMainLib/OpenCoreUefi.c index a85b39768838..35e26c736808 100644 --- a/Library/OcMainLib/OpenCoreUefi.c +++ b/Library/OcMainLib/OpenCoreUefi.c @@ -899,7 +899,7 @@ OcLoadUefiSupport ( OcReinstallProtocols (Config); - OcImageLoaderInit (Config->Booter.Quirks.ProtectUefiServices); + OcImageLoaderInit (Config->Booter.Quirks.ProtectUefiServices, Config->Booter.Quirks.FixupAppleEfiImages); OcLoadAppleSecureBoot (Config, CpuInfo); diff --git a/Library/OcPeCoffExtLib/BasePeCoffLib2Internals.h b/Library/OcPeCoffExtLib/BasePeCoffLib2Internals.h new file mode 100644 index 000000000000..3d7f00952863 --- /dev/null +++ b/Library/OcPeCoffExtLib/BasePeCoffLib2Internals.h @@ -0,0 +1,55 @@ +/** @file + Provides shared private definitions across this library. + + Copyright (c) 2020 - 2021, Marvin Häuser. All rights reserved.
+ Copyright (c) 2020, Vitaly Cheptsov. All rights reserved.
+ Copyright (c) 2020, ISP RAS. All rights reserved.
+ + SPDX-License-Identifier: BSD-3-Clause +**/ + +#ifndef BASE_PE_COFF_LIB2_INTERNALS_H_ +#define BASE_PE_COFF_LIB2_INTERNALS_H_ + +// +// PcdImageLoaderRelocTypePolicy bits. +// + +/// +/// If set, ARM Thumb Image relocations are supported. +/// +#define PCD_RELOC_TYPE_POLICY_ARM BIT0 + +/// +/// Denotes the alignment requirement for Image certificate sizes. +/// +#define IMAGE_CERTIFICATE_ALIGN 8U + +// +// The PE/COFF specification guarantees an 8 Byte alignment for certificate +// sizes. This is larger than the alignment requirement for WIN_CERTIFICATE +// implied by the UEFI ABI. ASSERT this holds. +// +STATIC_ASSERT ( + ALIGNOF (WIN_CERTIFICATE) <= IMAGE_CERTIFICATE_ALIGN, + "The PE/COFF specification guarantee does not suffice." + ); + +// +// The 4 Byte alignment guaranteed by the PE/COFF specification has been +// replaced with ALIGNOF (EFI_IMAGE_BASE_RELOCATION_BLOCK) for proof simplicity. +// This obviously was the original intention of the specification. ASSERT in +// case the equality is not given. +// +STATIC_ASSERT ( + sizeof (UINT32) == ALIGNOF (EFI_IMAGE_BASE_RELOCATION_BLOCK), + "The current model violates the PE/COFF specification" + ); + +// FIXME: +RETURN_STATUS +PeCoffLoadImageInplaceNoBase ( + IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context + ); + +#endif // BASE_PE_COFF_LIB_INTERNALS_H_ diff --git a/Library/OcPeCoffExtLib/OcPeCoffExtInternal.h b/Library/OcPeCoffExtLib/OcPeCoffExtInternal.h index e31d7eb0257f..647e6031b27b 100644 --- a/Library/OcPeCoffExtLib/OcPeCoffExtInternal.h +++ b/Library/OcPeCoffExtLib/OcPeCoffExtInternal.h @@ -29,4 +29,31 @@ typedef struct APPLE_SIGNATURE_CONTEXT_ { UINT8 Signature[256]; } APPLE_SIGNATURE_CONTEXT; +/** + Fix W^X and section overlap issues in loaded TE, PE32, or PE32+ Image in + memory while initialising Context. + + Closely based on PeCoffInitializeContext from PeCoffLib2. + + The approach of modifying the image in memory is basically incompatible + with secure boot, athough: + a) Certain firmware may allow optionally registering the hash of any + image which does not load, which would still work. + b) It is fairly crazy anyway to want to apply secure boot to the old, + insecure .efi files which need these fixups. + + @param[out] Context The context describing the Image. + @param[in] FileBuffer The file data to parse as PE Image. + @param[in] FileSize The size, in Bytes, of FileBuffer. + + @retval RETURN_SUCCESS The Image context has been initialised successfully. + @retval other The file data is malformed. +**/ +RETURN_STATUS +InternalPeCoffFixup ( + OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context, + IN CONST VOID *FileBuffer, + IN UINT32 FileSize + ); + #endif // OC_PE_COFF_EXT_INTERNAL_H diff --git a/Library/OcPeCoffExtLib/OcPeCoffExtLib.c b/Library/OcPeCoffExtLib/OcPeCoffExtLib.c index 8192e7fda99a..044cec857bf0 100644 --- a/Library/OcPeCoffExtLib/OcPeCoffExtLib.c +++ b/Library/OcPeCoffExtLib/OcPeCoffExtLib.c @@ -33,6 +33,7 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #include #include #include +#include #include #include "OcPeCoffExtInternal.h" @@ -495,7 +496,7 @@ PeCoffGetApfsDriverVersion ( || (ImageContext.ImageType != PeCoffLoaderTypePe32Plus) || (ImageContext.Subsystem != EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER)) { - DEBUG ((DEBUG_INFO, "OCPE: PeCoff unsupported image\n")); + DEBUG ((DEBUG_INFO, "OCPE: PeCoff apfs unsupported image\n")); return EFI_UNSUPPORTED; } @@ -543,3 +544,25 @@ PeCoffGetApfsDriverVersion ( *DriverVersionPtr = DriverVersion; return EFI_SUCCESS; } + +EFI_STATUS +OcPatchLegacyEfi ( + IN VOID *DriverBuffer, + IN UINT32 DriverSize + ) +{ + EFI_STATUS ImageStatus; + PE_COFF_LOADER_IMAGE_CONTEXT ImageContext; + + ImageStatus = InternalPeCoffFixup ( + &ImageContext, + DriverBuffer, + DriverSize + ); + if (EFI_ERROR (ImageStatus)) { + DEBUG ((DEBUG_WARN, "OCPE: PeCoff legacy patch failure - %r\n", ImageStatus)); + return EFI_UNSUPPORTED; + } + + return EFI_SUCCESS; +} diff --git a/Library/OcPeCoffExtLib/OcPeCoffExtLib.inf b/Library/OcPeCoffExtLib/OcPeCoffExtLib.inf index e5e14faddda1..5bb07f849329 100644 --- a/Library/OcPeCoffExtLib/OcPeCoffExtLib.inf +++ b/Library/OcPeCoffExtLib/OcPeCoffExtLib.inf @@ -25,12 +25,13 @@ # -# VALID_ARCHITECTURES = X64 +# VALID_ARCHITECTURES = IA32 X64 # [Sources] OcPeCoffExtInternal.h OcPeCoffExtLib.c + OcPeCoffFixup.c [Packages] MdePkg/MdePkg.dec @@ -48,7 +49,13 @@ DebugLib OcAppleKeysLib OcCryptoLib + OcStringLib [Guids] gAppleEfiCertificateGuid gEfiCertTypeRsa2048Sha256Guid + +[FixedPcd] + gEfiMdePkgTokenSpaceGuid.PcdImageLoaderAlignmentPolicy + gEfiMdePkgTokenSpaceGuid.PcdImageLoaderAllowMisalignedOffset + gEfiMdePkgTokenSpaceGuid.PcdDebugRaisePropertyMask diff --git a/Library/OcPeCoffExtLib/OcPeCoffFixup.c b/Library/OcPeCoffExtLib/OcPeCoffFixup.c new file mode 100644 index 000000000000..72142fd83222 --- /dev/null +++ b/Library/OcPeCoffExtLib/OcPeCoffFixup.c @@ -0,0 +1,805 @@ +/** @file + Implements APIs to fix certain issues in legacy EFI files in memory before loading. + + Very closely based on MdePkg/Library/BasePeCoffLib2/PeCoffInit.c, and intentionally + kept more similar to that file than it would otherwise need to be, to easily allow + diffing and importing future changes if required. + + Copyright (c) 2023, Mike Beaton, Vitaly Cheptsov. All rights reserved.
+ Copyright (c) 2020 - 2021, Marvin Häuser. All rights reserved.
+ Copyright (c) 2020, Vitaly Cheptsov. All rights reserved.
+ Copyright (c) 2020, ISP RAS. All rights reserved.
+ Portions copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.
+ Portions copyright (c) 2008 - 2010, Apple Inc. All rights reserved.
+ Portions copyright (c) 2020, Hewlett Packard Enterprise Development LP. All rights reserved.
+ + SPDX-License-Identifier: BSD-3-Clause +**/ + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "BasePeCoffLib2Internals.h" + +// +// FIXME: Provide an API to destruct the context? +// + +/** + Verify the Image section Headers and initialise the Image memory space size. + + The first Image section must be the beginning of the memory space, or be + contiguous to the aligned Image Headers. + Sections must be disjoint and, depending on the policy, contiguous in the + memory space space. + The section data must be in bounds bounds of the file buffer. + + @param[in,out] Context The context describing the Image. Must have been + initialised by PeCoffInitializeContext(). + @param[in] FileSize The size, in Bytes, of Context->FileBuffer. + @param[out] StartAddress On output, the RVA of the first Image section. + + @retval RETURN_SUCCESS The Image section Headers are well-formed. + @retval other The Image section Headers are malformed. +**/ +STATIC +RETURN_STATUS +InternalVerifySections ( + IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context, + IN UINT32 FileSize, + OUT UINT32 *StartAddress + ) +{ + BOOLEAN Overflow; + UINT32 NextSectRva; + UINT32 FixupOffset; + UINT32 FixupVirtualSize; + CHAR8 SectionName[EFI_IMAGE_SIZEOF_SHORT_NAME + 1]; + UINT32 SectRawEnd; + UINT16 SectionIndex; + EFI_IMAGE_SECTION_HEADER *Sections; + + ASSERT (Context != NULL); + ASSERT (IS_POW2 (Context->SectionAlignment)); + ASSERT (StartAddress != NULL); + // + // Images without Sections have no usable data, disallow them. + // + if (Context->NumberOfSections == 0) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + Sections = (EFI_IMAGE_SECTION_HEADER *)(VOID *)( + (CHAR8 *)Context->FileBuffer + Context->SectionsOffset + ); + // + // The first Image section must begin the Image memory space, or it must be + // adjacent to the Image Headers. + // + if (Sections[0].VirtualAddress == 0) { + // FIXME: Add PCD to disallow. + NextSectRva = 0; + } else { + // + // Choose the raw or aligned Image Headers size depending on whether loading + // unaligned Sections is allowed. + // + if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CONTIGUOUS_SECTIONS) == 0) { + Overflow = BaseOverflowAlignUpU32 ( + Context->SizeOfHeaders, + Context->SectionAlignment, + &NextSectRva + ); + if (Overflow) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + } else { + NextSectRva = Context->SizeOfHeaders; + } + } + + SectionName[L_STR_LEN (SectionName)] = '\0'; + *StartAddress = NextSectRva; + // + // Verify all Image sections are valid. + // + for (SectionIndex = 0; SectionIndex < Context->NumberOfSections; ++SectionIndex) { + AsciiStrnCpyS (SectionName, L_STR_SIZE (SectionName), (CHAR8 *)Sections[SectionIndex].Name, EFI_IMAGE_SIZEOF_SHORT_NAME); + // + // Fix up W^X errors in memory. + // + if ((Sections[SectionIndex].Characteristics & (EFI_IMAGE_SCN_MEM_EXECUTE | EFI_IMAGE_SCN_MEM_WRITE)) == (EFI_IMAGE_SCN_MEM_EXECUTE | EFI_IMAGE_SCN_MEM_WRITE)) { + Sections[SectionIndex].Characteristics &= ~EFI_IMAGE_SCN_MEM_EXECUTE; + DEBUG ((DEBUG_INFO, "OCPE: Fixup W^X for %a\n", SectionName)); + } + + // + // Verify the Image sections are disjoint (relaxed) or adjacent (strict) + // depending on whether unaligned Image sections may be loaded or not. + // Unaligned Image sections have been observed with iPXE Option ROMs and old + // Apple Mac OS X bootloaders. + // + if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CONTIGUOUS_SECTIONS) == 0) { + if (Sections[SectionIndex].VirtualAddress != NextSectRva) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + } else { + if (Sections[SectionIndex].VirtualAddress < NextSectRva) { + // + // Disallow overlap fixup unless we're ovelapping into an empty section. + // + if (Sections[SectionIndex].SizeOfRawData > 0) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Fix up section overlap errors in memory. + // + FixupOffset = NextSectRva - Sections[SectionIndex].VirtualAddress; + FixupVirtualSize = Sections[SectionIndex].VirtualSize; + if (FixupOffset > Sections[SectionIndex].VirtualSize) { + Sections[SectionIndex].VirtualSize = 0; + } else { + Sections[SectionIndex].VirtualSize -= FixupOffset; + } + + DEBUG (( + DEBUG_INFO, + "OCPE: Fixup section overlap for %a 0x%X(0x%X)->0x%X(0x%X)\n", + SectionName, + Sections[SectionIndex].VirtualAddress, + FixupVirtualSize, + NextSectRva, + Sections[SectionIndex].VirtualSize + )); + Sections[SectionIndex].VirtualAddress = NextSectRva; + } + + // + // If the Image section address is not aligned by the Image section + // alignment, fall back to important architecture-specific page sizes if + // possible, to ensure the Image can have memory protection applied. + // Otherwise, report no alignment for the Image. + // + if (!IS_ALIGNED (Sections[SectionIndex].VirtualAddress, Context->SectionAlignment)) { + STATIC_ASSERT ( + DEFAULT_PAGE_ALLOCATION_GRANULARITY <= RUNTIME_PAGE_ALLOCATION_GRANULARITY, + "This code must be adapted to consider the reversed order." + ); + + if (IS_ALIGNED (Sections[SectionIndex].VirtualAddress, RUNTIME_PAGE_ALLOCATION_GRANULARITY)) { + Context->SectionAlignment = RUNTIME_PAGE_ALLOCATION_GRANULARITY; + } else if ( (DEFAULT_PAGE_ALLOCATION_GRANULARITY < RUNTIME_PAGE_ALLOCATION_GRANULARITY) + && IS_ALIGNED (Sections[SectionIndex].VirtualAddress, DEFAULT_PAGE_ALLOCATION_GRANULARITY)) + { + Context->SectionAlignment = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + } else { + Context->SectionAlignment = 1; + } + } + } + + // + // Verify the Image sections with data are in bounds of the file buffer. + // + if (Sections[SectionIndex].SizeOfRawData > 0) { + Overflow = BaseOverflowAddU32 ( + Sections[SectionIndex].PointerToRawData, + Sections[SectionIndex].SizeOfRawData, + &SectRawEnd + ); + if (Overflow) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + if (SectRawEnd > FileSize) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + } + + // + // Determine the end of the current Image section. + // + Overflow = BaseOverflowAddU32 ( + Sections[SectionIndex].VirtualAddress, + Sections[SectionIndex].VirtualSize, + &NextSectRva + ); + if (Overflow) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // VirtualSize does not need to be aligned, so align the result if needed. + // + if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CONTIGUOUS_SECTIONS) == 0) { + Overflow = BaseOverflowAlignUpU32 ( + NextSectRva, + Context->SectionAlignment, + &NextSectRva + ); + if (Overflow) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + } + } + + // + // Set SizeOfImage to the aligned end address of the last ImageSection. + // + if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CONTIGUOUS_SECTIONS) == 0) { + Context->SizeOfImage = NextSectRva; + } else { + // + // Because VirtualAddress is aligned by SectionAlignment for all Image + // sections, and they are disjoint and ordered by VirtualAddress, + // VirtualAddress + VirtualSize must be safe to align by SectionAlignment for + // all but the last Image section. + // Determine the strictest common alignment that the last section's end is + // safe to align to. + // + Overflow = BaseOverflowAlignUpU32 ( + NextSectRva, + Context->SectionAlignment, + &Context->SizeOfImage + ); + if (Overflow) { + Context->SectionAlignment = RUNTIME_PAGE_ALLOCATION_GRANULARITY; + Overflow = BaseOverflowAlignUpU32 ( + NextSectRva, + Context->SectionAlignment, + &Context->SizeOfImage + ); + if ( (DEFAULT_PAGE_ALLOCATION_GRANULARITY < RUNTIME_PAGE_ALLOCATION_GRANULARITY) + && Overflow) + { + Context->SectionAlignment = DEFAULT_PAGE_ALLOCATION_GRANULARITY; + Overflow = BaseOverflowAlignUpU32 ( + NextSectRva, + Context->SectionAlignment, + &Context->SizeOfImage + ); + } + + if (Overflow) { + Context->SectionAlignment = 1; + } + } + } + + return RETURN_SUCCESS; +} + +/** + Verify the basic Image Relocation information. + + The preferred Image load address must be aligned by the section alignment. + The Relocation Directory must be contained within the Image section memory. + The Relocation Directory must be sufficiently aligned in memory. + + @param[in] Context The context describing the Image. Must have been + initialised by PeCoffInitializeContext(). + @param[in] StartAddress The RVA of the first Image section. + + @retval RETURN_SUCCESS The basic Image Relocation information is well-formed. + @retval other The basic Image Relocation information is malformed. +**/ +STATIC +RETURN_STATUS +InternalValidateRelocInfo ( + IN CONST PE_COFF_LOADER_IMAGE_CONTEXT *Context, + IN UINT32 StartAddress + ) +{ + BOOLEAN Overflow; + UINT32 SectRvaEnd; + + ASSERT (Context != NULL); + ASSERT (!Context->RelocsStripped || Context->RelocDirSize == 0); + // + // If the Base Relocations have not been stripped, verify their Directory. + // + if (Context->RelocDirSize != 0) { + // + // Verify the Relocation Directory is not empty. + // + if (sizeof (EFI_IMAGE_BASE_RELOCATION_BLOCK) > Context->RelocDirSize) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the Relocation Directory does not overlap with the Image Headers. + // + if (StartAddress > Context->RelocDirRva) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the Relocation Directory is contained in the Image memory space. + // + Overflow = BaseOverflowAddU32 ( + Context->RelocDirRva, + Context->RelocDirSize, + &SectRvaEnd + ); + if (Overflow || (SectRvaEnd > Context->SizeOfImage)) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the Relocation Directory start is sufficiently aligned. + // + if (!IS_ALIGNED (Context->RelocDirRva, ALIGNOF (EFI_IMAGE_BASE_RELOCATION_BLOCK))) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + } + + // + // Verify the preferred Image load address is sufficiently aligned. + // + // FIXME: Only with force-aligned sections? What to do with XIP? + if (!IS_ALIGNED (Context->ImageBase, (UINT64)Context->SectionAlignment)) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + return RETURN_SUCCESS; +} + +/** + Verify the PE32 or PE32+ Image and initialise Context. + + Used offsets and ranges must be aligned and in the bounds of the raw file. + Image section Headers and basic Relocation information must be Well-formed. + + @param[in,out] Context The context describing the Image. Must have been + initialised by PeCoffInitializeContext(). + @param[in] FileSize The size, in Bytes, of Context->FileBuffer. + + @retval RETURN_SUCCESS The PE Image is Well-formed. + @retval other The PE Image is malformed. +**/ +STATIC +RETURN_STATUS +InternalInitializePe ( + IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context, + IN UINT32 FileSize + ) +{ + BOOLEAN Overflow; + CONST EFI_IMAGE_NT_HEADERS_COMMON_HDR *PeCommon; + CONST EFI_IMAGE_NT_HEADERS32 *Pe32; + CONST EFI_IMAGE_NT_HEADERS64 *Pe32Plus; + CONST CHAR8 *OptHdrPtr; + UINT32 HdrSizeWithoutDataDir; + UINT32 MinSizeOfOptionalHeader; + UINT32 MinSizeOfHeaders; + CONST EFI_IMAGE_DATA_DIRECTORY *RelocDir; + CONST EFI_IMAGE_DATA_DIRECTORY *SecDir; + UINT32 SecDirEnd; + UINT32 NumberOfRvaAndSizes; + RETURN_STATUS Status; + UINT32 StartAddress; + + ASSERT (Context != NULL); + ASSERT (sizeof (EFI_IMAGE_NT_HEADERS_COMMON_HDR) + sizeof (UINT16) <= FileSize - Context->ExeHdrOffset); + if (!PcdGetBool (PcdImageLoaderAllowMisalignedOffset)) { + ASSERT (IS_ALIGNED (Context->ExeHdrOffset, ALIGNOF (EFI_IMAGE_NT_HEADERS_COMMON_HDR))); + } + + // + // Locate the PE Optional Header. + // + OptHdrPtr = (CONST CHAR8 *)Context->FileBuffer + Context->ExeHdrOffset; + OptHdrPtr += sizeof (EFI_IMAGE_NT_HEADERS_COMMON_HDR); + + STATIC_ASSERT ( + IS_ALIGNED (ALIGNOF (EFI_IMAGE_NT_HEADERS_COMMON_HDR), ALIGNOF (UINT16)) + && IS_ALIGNED (sizeof (EFI_IMAGE_NT_HEADERS_COMMON_HDR), ALIGNOF (UINT16)), + "The following operation might be an unaligned access." + ); + // + // Determine the type of and retrieve data from the PE Optional Header. + // Do not retrieve SizeOfImage as the value usually does not follow the + // specification. Even if the value is large enough to hold the last Image + // section, it may not be aligned, or it may be too large. No data can + // possibly be loaded past the last Image section anyway. + // + switch (*(CONST UINT16 *)(CONST VOID *)OptHdrPtr) { + case EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC: + // + // Verify the PE32 header is in bounds of the file buffer. + // + if (sizeof (*Pe32) > FileSize - Context->ExeHdrOffset) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // The PE32 header offset is always sufficiently aligned. + // + STATIC_ASSERT ( + ALIGNOF (EFI_IMAGE_NT_HEADERS32) <= ALIGNOF (EFI_IMAGE_NT_HEADERS_COMMON_HDR), + "The following operations may be unaligned." + ); + // + // Populate the common data with information from the Optional Header. + // + Pe32 = (CONST EFI_IMAGE_NT_HEADERS32 *)(CONST VOID *)( + (CONST CHAR8 *)Context->FileBuffer + Context->ExeHdrOffset + ); + + Context->ImageType = PeCoffLoaderTypePe32; + Context->Subsystem = Pe32->Subsystem; + Context->SizeOfHeaders = Pe32->SizeOfHeaders; + Context->ImageBase = Pe32->ImageBase; + Context->AddressOfEntryPoint = Pe32->AddressOfEntryPoint; + Context->SectionAlignment = Pe32->SectionAlignment; + + RelocDir = Pe32->DataDirectory + EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC; + SecDir = Pe32->DataDirectory + EFI_IMAGE_DIRECTORY_ENTRY_SECURITY; + + PeCommon = &Pe32->CommonHeader; + NumberOfRvaAndSizes = Pe32->NumberOfRvaAndSizes; + HdrSizeWithoutDataDir = sizeof (EFI_IMAGE_NT_HEADERS32) - sizeof (EFI_IMAGE_NT_HEADERS_COMMON_HDR); + + break; + + case EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC: + // + // Verify the PE32+ header is in bounds of the file buffer. + // + if (sizeof (*Pe32Plus) > FileSize - Context->ExeHdrOffset) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the PE32+ header offset is sufficiently aligned. + // + if ( !PcdGetBool (PcdImageLoaderAllowMisalignedOffset) + && !IS_ALIGNED (Context->ExeHdrOffset, ALIGNOF (EFI_IMAGE_NT_HEADERS64))) + { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Populate the common data with information from the Optional Header. + // + Pe32Plus = (CONST EFI_IMAGE_NT_HEADERS64 *)(CONST VOID *)( + (CONST CHAR8 *)Context->FileBuffer + Context->ExeHdrOffset + ); + + Context->ImageType = PeCoffLoaderTypePe32Plus; + Context->Subsystem = Pe32Plus->Subsystem; + Context->SizeOfHeaders = Pe32Plus->SizeOfHeaders; + Context->ImageBase = Pe32Plus->ImageBase; + Context->AddressOfEntryPoint = Pe32Plus->AddressOfEntryPoint; + Context->SectionAlignment = Pe32Plus->SectionAlignment; + + RelocDir = Pe32Plus->DataDirectory + EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC; + SecDir = Pe32Plus->DataDirectory + EFI_IMAGE_DIRECTORY_ENTRY_SECURITY; + + PeCommon = &Pe32Plus->CommonHeader; + NumberOfRvaAndSizes = Pe32Plus->NumberOfRvaAndSizes; + HdrSizeWithoutDataDir = sizeof (EFI_IMAGE_NT_HEADERS64) - sizeof (EFI_IMAGE_NT_HEADERS_COMMON_HDR); + + break; + + default: + // + // Disallow Images with unknown PE Optional Header signatures. + // + DEBUG_RAISE (); + return RETURN_UNSUPPORTED; + } + + // + // Disallow Images with unknown directories. + // + if (NumberOfRvaAndSizes > EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the Image alignment is a power of 2. + // + if (!IS_POW2 (Context->SectionAlignment)) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + STATIC_ASSERT ( + sizeof (EFI_IMAGE_DATA_DIRECTORY) <= MAX_UINT32 / EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES, + "The following arithmetic may overflow." + ); + // + // Calculate the offset of the Image sections. + // + // Context->ExeHdrOffset + sizeof (EFI_IMAGE_NT_HEADERS_COMMON_HDR) cannot overflow because + // * ExeFileSize > sizeof (EFI_IMAGE_NT_HEADERS_COMMON_HDR) and + // * Context->ExeHdrOffset + ExeFileSize = FileSize + // + Overflow = BaseOverflowAddU32 ( + Context->ExeHdrOffset + sizeof (*PeCommon), + PeCommon->FileHeader.SizeOfOptionalHeader, + &Context->SectionsOffset + ); + if (Overflow) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the Section Headers offset is sufficiently aligned. + // + if ( !PcdGetBool (PcdImageLoaderAllowMisalignedOffset) + && !IS_ALIGNED (Context->SectionsOffset, ALIGNOF (EFI_IMAGE_SECTION_HEADER))) + { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // This arithmetic cannot overflow because all values are sufficiently + // bounded. + // + MinSizeOfOptionalHeader = HdrSizeWithoutDataDir + + NumberOfRvaAndSizes * sizeof (EFI_IMAGE_DATA_DIRECTORY); + + ASSERT (MinSizeOfOptionalHeader >= HdrSizeWithoutDataDir); + + STATIC_ASSERT ( + sizeof (EFI_IMAGE_SECTION_HEADER) <= (MAX_UINT32 + 1ULL) / (MAX_UINT16 + 1ULL), + "The following arithmetic may overflow." + ); + // + // Calculate the minimum size of the Image Headers. + // + Overflow = BaseOverflowAddU32 ( + Context->SectionsOffset, + (UINT32)PeCommon->FileHeader.NumberOfSections * sizeof (EFI_IMAGE_SECTION_HEADER), + &MinSizeOfHeaders + ); + if (Overflow) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the Image Header sizes are sane. SizeOfHeaders contains all header + // components (DOS, PE Common and Optional Header). + // + if (MinSizeOfOptionalHeader > PeCommon->FileHeader.SizeOfOptionalHeader) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + if (MinSizeOfHeaders > Context->SizeOfHeaders) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the Image Headers are in bounds of the file buffer. + // + if (Context->SizeOfHeaders > FileSize) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Populate the Image context with information from the Common Header. + // + Context->NumberOfSections = PeCommon->FileHeader.NumberOfSections; + Context->Machine = PeCommon->FileHeader.Machine; + Context->RelocsStripped = + ( + PeCommon->FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED + ) != 0; + + if (EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC < NumberOfRvaAndSizes) { + Context->RelocDirRva = RelocDir->VirtualAddress; + Context->RelocDirSize = RelocDir->Size; + + if (Context->RelocsStripped && (Context->RelocDirSize != 0)) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + } else { + ASSERT (Context->RelocDirRva == 0); + ASSERT (Context->RelocDirSize == 0); + } + + if (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY < NumberOfRvaAndSizes) { + Context->SecDirOffset = SecDir->VirtualAddress; + Context->SecDirSize = SecDir->Size; + // + // Verify the Security Directory is in bounds of the Image buffer. + // + Overflow = BaseOverflowAddU32 ( + Context->SecDirOffset, + Context->SecDirSize, + &SecDirEnd + ); + if (Overflow || (SecDirEnd > FileSize)) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the Security Directory is sufficiently aligned. + // + if (!IS_ALIGNED (Context->SecDirOffset, IMAGE_CERTIFICATE_ALIGN)) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the Security Directory size is sufficiently aligned, and that if + // it is not empty, it can fit at least one certificate. + // + if ( (Context->SecDirSize != 0) + && ( !IS_ALIGNED (Context->SecDirSize, IMAGE_CERTIFICATE_ALIGN) + || (Context->SecDirSize < sizeof (WIN_CERTIFICATE)))) + { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + } else { + // + // The Image context is zero'd on allocation. + // + ASSERT (Context->SecDirOffset == 0); + ASSERT (Context->SecDirSize == 0); + } + + // + // Verify the Image sections are Well-formed. + // + Status = InternalVerifySections ( + Context, + FileSize, + &StartAddress + ); + if (Status != RETURN_SUCCESS) { + DEBUG_RAISE (); + return Status; + } + + // + // Verify the entry point is in bounds of the Image buffer. + // + if (Context->AddressOfEntryPoint >= Context->SizeOfImage) { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + // + // Verify the basic Relocation information is well-formed. + // + Status = InternalValidateRelocInfo (Context, StartAddress); + if (Status != RETURN_SUCCESS) { + DEBUG_RAISE (); + } + + return Status; +} + +RETURN_STATUS +InternalPeCoffFixup ( + OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context, + IN CONST VOID *FileBuffer, + IN UINT32 FileSize + ) +{ + RETURN_STATUS Status; + CONST EFI_IMAGE_DOS_HEADER *DosHdr; + + // + // Failure of these asserts can be fixed if needed by not using the Pcd + // values above, we do not do this initially to make it simpler to compare + // this file with BasePeCoffLib2/PeCoffInit.c. + // STATIC_ASSERT not suitable here: 'not an integral constant expression'. + // + if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CONTIGUOUS_SECTIONS) == 0) { + ASSERT (FALSE); + } + + if (PcdGetBool (PcdImageLoaderAllowMisalignedOffset)) { + ASSERT (FALSE); + } + + ASSERT (Context != NULL); + ASSERT (FileBuffer != NULL || FileSize == 0); + // + // Initialise the Image context with 0-values. + // + ZeroMem (Context, sizeof (*Context)); + + Context->FileBuffer = FileBuffer; + Context->FileSize = FileSize; + // + // Check whether the DOS Image Header is present. + // + if ( (sizeof (*DosHdr) <= FileSize) + && (*(CONST UINT16 *)(CONST VOID *)FileBuffer == EFI_IMAGE_DOS_SIGNATURE)) + { + DosHdr = (CONST EFI_IMAGE_DOS_HEADER *)(CONST VOID *)( + (CONST CHAR8 *)FileBuffer + ); + // + // Verify the DOS Image Header and the Executable Header are in bounds of + // the file buffer, and that they are disjoint. + // + if ( (sizeof (EFI_IMAGE_DOS_HEADER) > DosHdr->e_lfanew) + || (DosHdr->e_lfanew > FileSize)) + { + DEBUG_RAISE (); + return RETURN_VOLUME_CORRUPTED; + } + + Context->ExeHdrOffset = DosHdr->e_lfanew; + // + // Verify the Execution Header offset is sufficiently aligned. + // + if ( !PcdGetBool (PcdImageLoaderAllowMisalignedOffset) + && !IS_ALIGNED (Context->ExeHdrOffset, ALIGNOF (EFI_IMAGE_NT_HEADERS_COMMON_HDR))) + { + return RETURN_UNSUPPORTED; + } + } + + // + // Verify the file buffer can hold a PE Common Header. + // + if (FileSize - Context->ExeHdrOffset < sizeof (EFI_IMAGE_NT_HEADERS_COMMON_HDR) + sizeof (UINT16)) { + return RETURN_UNSUPPORTED; + } + + STATIC_ASSERT ( + ALIGNOF (UINT32) <= ALIGNOF (EFI_IMAGE_NT_HEADERS_COMMON_HDR), + "The following access may be performed unaligned" + ); + // + // Verify the Image Executable Header has a PE signature. + // + if (*(CONST UINT32 *)(CONST VOID *)((CONST CHAR8 *)FileBuffer + Context->ExeHdrOffset) != EFI_IMAGE_NT_SIGNATURE) { + return RETURN_UNSUPPORTED; + } + + // + // Verify the PE Image Header is well-formed. + // + Status = InternalInitializePe (Context, FileSize); + if (Status != RETURN_SUCCESS) { + return Status; + } + + return RETURN_SUCCESS; +} diff --git a/Utilities/AppleEfiSignTool/Makefile b/Utilities/AppleEfiSignTool/Makefile index 6c8b89d6c190..eb805411fc02 100644 --- a/Utilities/AppleEfiSignTool/Makefile +++ b/Utilities/AppleEfiSignTool/Makefile @@ -13,7 +13,8 @@ OBJS = $(PROJECT).o \ PeCoffInit.o \ PeCoffLoad.o \ PeCoffRelocate.o \ - OcPeCoffExtLib.o + OcPeCoffExtLib.o \ + OcPeCoffFixup.o VPATH = $(UDK_PATH)/MdePkg/Library/BasePeCoffLib2:$\ ../../Library/OcPeCoffExtLib:$\