/** Temporary BS and RT overrides for boot.efi support. Unlike RtShims they do not affect the kernel. by dmazar **/ #include #include #include #include #include #include #include #include #include #include "Config.h" #include "ServiceOverrides.h" #include "BootArgs.h" #include "BootFixes.h" #include "CustomSlide.h" #include "MemoryMap.h" #include "RtShims.h" #include "UmmMalloc/UmmMalloc.h" // // Placeholders for storing original Boot and RT Services functions // STATIC EFI_ALLOCATE_PAGES mStoredAllocatePages; STATIC EFI_ALLOCATE_POOL mStoredAllocatePool; STATIC EFI_FREE_POOL mStoredFreePool; STATIC EFI_GET_MEMORY_MAP mStoredGetMemoryMap; STATIC EFI_EXIT_BOOT_SERVICES mStoredExitBootServices; STATIC EFI_SET_VIRTUAL_ADDRESS_MAP mStoredSetVirtualAddressMap; STATIC EFI_IMAGE_START mStoredStartImage; // // Original runtime services hash we restore at address virtualisation // STATIC UINT32 mRtPreOverridesCRC32; // // Location of memory allocated by boot.efi for hibernate image // STATIC EFI_PHYSICAL_ADDRESS mHibernateImageAddress; // // Saved exit boot services arguments // STATIC EFI_HANDLE mExitBSImageHandle; STATIC UINTN mExitBSMapKey; // // Dynamic memory allocation filtering status // STATIC BOOLEAN mFilterDynamicPoolAllocations; // // Minimum and maximum addresses allocated by AlocatePages // STATIC EFI_PHYSICAL_ADDRESS mMinAllocatedAddr; STATIC EFI_PHYSICAL_ADDRESS mMaxAllocatedAddr; // // Amount of nested boot.efi detected // UINTN gMacOSBootNestedCount; // // Last descriptor size obtained from GetMemoryMap // UINTN gMemoryMapDescriptorSize = sizeof(EFI_MEMORY_DESCRIPTOR); VOID InstallBsOverrides ( VOID ) { #if APTIOFIX_CUSTOM_POOL_ALLOCATOR == 1 EFI_STATUS Status; EFI_PHYSICAL_ADDRESS UmmHeap = BASE_4GB; UINTN PageNum = EFI_SIZE_TO_PAGES (APTIOFIX_CUSTOM_POOL_ALLOCATOR_SIZE); // // We do not uninstall our custom allocator to avoid memory corruption issues // when shimming AMI code. This check ensures that we do not install it twice. // See UninstallBsOverrides for more details. // if (!UmmInitialized ()) { // // Enforce memory pool creation when -aptiodump argument is used, but let it slip otherwise. // Status = AllocatePagesFromTop (EfiBootServicesData, PageNum, &UmmHeap, !gDumpMemArgPresent); if (!EFI_ERROR (Status)) { gBS->SetMem ((VOID *)UmmHeap, APTIOFIX_CUSTOM_POOL_ALLOCATOR_SIZE, 0); UmmSetHeap ((VOID *)UmmHeap); mStoredAllocatePool = gBS->AllocatePool; mStoredFreePool = gBS->FreePool; gBS->AllocatePool = MOAllocatePool; gBS->FreePool = MOFreePool; } else { // // This is undesired, but technically less fatal than attempting to reduce the number // of slides available when no memory map dumping is necessary, for example. // Print (L"AMF: Not using custom memory pool - %r\n", Status); } } #endif mStoredAllocatePages = gBS->AllocatePages; mStoredGetMemoryMap = gBS->GetMemoryMap; mStoredExitBootServices = gBS->ExitBootServices; mStoredStartImage = gBS->StartImage; gBS->AllocatePages = MOAllocatePages; gBS->GetMemoryMap = MOGetMemoryMap; gBS->ExitBootServices = MOExitBootServices; gBS->StartImage = MOStartImage; gBS->Hdr.CRC32 = 0; gBS->CalculateCrc32 (gBS, gBS->Hdr.HeaderSize, &gBS->Hdr.CRC32); } VOID InstallRtOverrides ( VOID ) { mRtPreOverridesCRC32 = gRT->Hdr.CRC32; mStoredSetVirtualAddressMap = gRT->SetVirtualAddressMap; gRT->SetVirtualAddressMap = MOSetVirtualAddressMap; gRT->Hdr.CRC32 = 0; gBS->CalculateCrc32 (gRT, gRT->Hdr.HeaderSize, &gRT->Hdr.CRC32); } VOID UninstallRtOverrides ( VOID ) { gRT->SetVirtualAddressMap = mStoredSetVirtualAddressMap; gRT->Hdr.CRC32 = mRtPreOverridesCRC32; } VOID DisableDynamicPoolAllocations ( VOID ) { mFilterDynamicPoolAllocations = TRUE; } VOID EnableDynamicPoolAllocations ( VOID ) { mFilterDynamicPoolAllocations = FALSE; } /** gBS->StartImage override: * Called to start an efi image. * * If this is boot.efi, then run it with our overrides. */ EFI_STATUS EFIAPI MOStartImage ( IN EFI_HANDLE ImageHandle, OUT UINTN *ExitDataSize, OUT CHAR16 **ExitData OPTIONAL ) { EFI_STATUS Status; EFI_LOADED_IMAGE_PROTOCOL *AppleLoadedImage = NULL; UINTN ValueSize = 0; VOID *Gop; DEBUG ((DEBUG_VERBOSE, "StartImage (%lx)\n", ImageHandle)); AppleLoadedImage = GetAppleBootLoadedImage (ImageHandle); // // Clear monitoring vars // mMinAllocatedAddr = 0; mMaxAllocatedAddr = 0; // // Request boot variable redirection if enabled. // SetBootVariableRedirect (TRUE); if (AppleLoadedImage) { // // Report about macOS being loaded. // gMacOSBootNestedCount++; // // Latest Windows brings Virtualization-based security and monitors // CR0 by launching itself under a hypevisor. Since we need WP disable // on macOS to let NVRAM work, and for the time being no other OS // requires it, here we decide to use it for macOS exclusively. // SetWriteUnprotectorMode (TRUE); // // Boot.efi requires EfiGraphicsOutputProtocol on ConOutHandle, but it is not present // there on Aptio 2.0. EfiGraphicsOutputProtocol exists on some other handle. // If this is the case, we'll intercept that call and return EfiGraphicsOutputProtocol // from that other handle. // Gop = NULL; Status = gBS->HandleProtocol (gST->ConsoleOutHandle, &gEfiGraphicsOutputProtocolGuid, &Gop); if (EFI_ERROR (Status)) { Status = gBS->LocateProtocol (&gEfiGraphicsOutputProtocolGuid, NULL, &Gop); if (!EFI_ERROR (Status)) { gBS->InstallMultipleProtocolInterfaces ( &gST->ConsoleOutHandle, &gEfiGraphicsOutputProtocolGuid, Gop, NULL ); } } // // This is reverse engineered from boot.efi. // To cancel hibernate wake it is enough to delete the variables. // Starting with 10.13.6 boot-switch-vars is no longer supported. // ValueSize = 0; if (gRT->GetVariable (L"boot-signature", &gEfiAppleBootGuid, NULL, &ValueSize, NULL) == EFI_BUFFER_TOO_SMALL) { ValueSize = 0; if (gRT->GetVariable (L"boot-image-key", &gEfiAppleBootGuid, NULL, &ValueSize, NULL) == EFI_BUFFER_TOO_SMALL) { gHibernateWake = TRUE; } } else { ValueSize = 0; if (gRT->GetVariable (L"boot-switch-vars", &gEfiAppleBootGuid, NULL, &ValueSize, NULL) == EFI_BUFFER_TOO_SMALL) { gHibernateWake = TRUE; } } // // Save current 64bit state - will be restored later in callback from kernel jump // and relocate JumpToKernel32 code to higher mem (for copying kernel back to // proper place and jumping back to it) // Status = PrepareJumpFromKernel (); if (!EFI_ERROR (Status)) { // // Force boot.efi to use our copy of system table // AppleLoadedImage->SystemTable = (EFI_SYSTEM_TABLE *)(UINTN)gSysTableRtArea; #if APTIOFIX_ALLOW_ASLR_IN_SAFE_MODE == 1 UnlockSlideSupportForSafeMode ((UINT8 *)AppleLoadedImage->ImageBase, AppleLoadedImage->ImageSize); #endif // // Read options // ReadBooterArguments ((CHAR16*)AppleLoadedImage->LoadOptions, AppleLoadedImage->LoadOptionsSize/sizeof (CHAR16)); } } Status = mStoredStartImage (ImageHandle, ExitDataSize, ExitData); if (AppleLoadedImage) { // // We failed but other operating systems should be loadable. // gMacOSBootNestedCount--; SetWriteUnprotectorMode (FALSE); } // // Disable redirect on failure, this is cleaner design-wise. // SetBootVariableRedirect (FALSE); return Status; } /** gBS->AllocatePages override: * Returns pages from free memory block to boot.efi for kernel boot image. */ EFI_STATUS EFIAPI MOAllocatePages ( IN EFI_ALLOCATE_TYPE Type, IN EFI_MEMORY_TYPE MemoryType, IN UINTN NumberOfPages, IN OUT EFI_PHYSICAL_ADDRESS *Memory ) { EFI_STATUS Status; EFI_PHYSICAL_ADDRESS UpperAddr; if (gMacOSBootNestedCount > 0 && Type == AllocateAddress && MemoryType == EfiLoaderData) { // // Called from boot.efi // UpperAddr = *Memory + EFI_PAGES_TO_SIZE (NumberOfPages); // // Store min and max mem - can be used later to determine start and end of kernel boot image // if (mMinAllocatedAddr == 0 || *Memory < mMinAllocatedAddr) mMinAllocatedAddr = *Memory; if (UpperAddr > mMaxAllocatedAddr) mMaxAllocatedAddr = UpperAddr; Status = mStoredAllocatePages (Type, MemoryType, NumberOfPages, Memory); } else if (gMacOSBootNestedCount > 0 && gHibernateWake && Type == AllocateAnyPages && MemoryType == EfiLoaderData) { // // Called from boot.efi during hibernate wake, // first such allocation is for hibernate image // Status = mStoredAllocatePages (Type, MemoryType, NumberOfPages, Memory); if (mHibernateImageAddress == 0 && Status == EFI_SUCCESS) { mHibernateImageAddress = *Memory; } } else { // // Generic page allocation // Status = mStoredAllocatePages (Type, MemoryType, NumberOfPages, Memory); } return Status; } /** gBS->AllocatePool override: * Allows us to use a custom allocator that uses a preallocated memory pool * for certain types of memory. See details in Print function. */ EFI_STATUS EFIAPI MOAllocatePool ( IN EFI_MEMORY_TYPE Type, IN UINTN Size, OUT VOID **Buffer ) { // // The code below allows us to more safely invoke Boot Services to perform onscreen // printing when no memory map modifications (pool memory allocation) is allowed. // While it is imperfect design-wise, it works very well on many ASUS Skylake boards // when performing memory map dumps via -aptiodump. // if (gMacOSBootNestedCount > 0 && Type == EfiBootServicesData && mFilterDynamicPoolAllocations) { *Buffer = UmmMalloc ((UINT32)Size); if (*Buffer) return EFI_SUCCESS; // // Dynamic pool allocations filtering should technically only be used when booting is more // important than not allocating the requested memory and failing to do something. // However, since we skip other types of allocations anyway, not falling back here to using // the default allocator may have its own consequences on other boards. // } return mStoredAllocatePool (Type, Size, Buffer); } /** gBS->FreePool override: * Allows us to use a custom allocator for certain types of memory. */ EFI_STATUS EFIAPI MOFreePool ( IN VOID *Buffer ) { // // By default it will return FALSE if Buffer was not allocated by us. // if (UmmFree (Buffer)) return EFI_SUCCESS; return mStoredFreePool (Buffer); } /** gBS->GetMemoryMap override: * Returns shrinked memory map. XNU can handle up to PMAP_MEMORY_REGIONS_SIZE (128) entries. */ EFI_STATUS EFIAPI MOGetMemoryMap ( IN OUT UINTN *MemoryMapSize, IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, OUT UINTN *MapKey, OUT UINTN *DescriptorSize, OUT UINT32 *DescriptorVersion ) { EFI_STATUS Status; Status = mStoredGetMemoryMap (MemoryMapSize, MemoryMap, MapKey, DescriptorSize, DescriptorVersion); if (gMacOSBootNestedCount > 0 && Status == EFI_SUCCESS) { if (gDumpMemArgPresent) { PrintMemMap (L"GetMemoryMap", *MemoryMapSize, *DescriptorSize, MemoryMap, gRtShims, gSysTableRtArea); } #if APTIOFIX_PROTECT_CSM_REGION == 1 ProtectCsmRegion (*MemoryMapSize, MemoryMap, *DescriptorSize); #endif ShrinkMemMap (MemoryMapSize, MemoryMap, *DescriptorSize); // // Remember some descriptor size, since we will not have it later // during hibernate wake to be able to iterate memory map. // gMemoryMapDescriptorSize = *DescriptorSize; } return Status; } EFI_STATUS EFIAPI OrgGetMemoryMap ( IN OUT UINTN *MemoryMapSize, IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, OUT UINTN *MapKey, OUT UINTN *DescriptorSize, OUT UINT32 *DescriptorVersion ) { return (mStoredGetMemoryMap ? mStoredGetMemoryMap : gBS->GetMemoryMap) ( MemoryMapSize, MemoryMap, MapKey, DescriptorSize, DescriptorVersion ); } /** gBS->ExitBootServices override: * Patches kernel entry point with jump to our KernelEntryPatchJumpBack(). */ EFI_STATUS EFIAPI MOExitBootServices ( IN EFI_HANDLE ImageHandle, IN UINTN MapKey ) { EFI_STATUS Status; UINTN SlideAddr = 0; VOID *MachOImage = NULL; IOHibernateImageHeader *ImageHeader = NULL; // // For non-macOS operating systems return directly. // if (gMacOSBootNestedCount == 0) { return mStoredExitBootServices (ImageHandle, MapKey); } // // We need hibernate image address for wake // if (gHibernateWake && mHibernateImageAddress == 0) { Print (L"AMF: Failed to find hibernate image address\n"); gBS->Stall (SECONDS_TO_MICROSECONDS (5)); return EFI_INVALID_PARAMETER; } // // We can just return EFI_SUCCESS and continue using Print for debug // if (gDumpMemArgPresent) { mExitBSImageHandle = ImageHandle; mExitBSMapKey = MapKey; Status = EFI_SUCCESS; } else { Status = ForceExitBootServices (mStoredExitBootServices, ImageHandle, MapKey); } if (EFI_ERROR (Status)) return Status; if (!gHibernateWake) { SlideAddr = mMinAllocatedAddr - 0x100000; MachOImage = (VOID*)(UINTN)(SlideAddr + SLIDE_GRANULARITY); KernelEntryFromMachOPatchJump (MachOImage, SlideAddr); } else { // // At this stage HIB section is not yet copied from sleep image to it's // proper memory destination. so we'll patch entry point in sleep image. // ImageHeader = (IOHibernateImageHeader *)(UINTN)mHibernateImageAddress; KernelEntryPatchJump ( ((UINT32)(UINTN)&(ImageHeader->fileExtentMap[0])) + ImageHeader->fileExtentMapSize + ImageHeader->restore1CodeOffset ); } return Status; } /** Helper function to call ExitBootServices that can handle outdated MapKey issues. */ EFI_STATUS ForceExitBootServices ( IN EFI_EXIT_BOOT_SERVICES ExitBs, IN EFI_HANDLE ImageHandle, IN UINTN MapKey ) { EFI_STATUS Status; EFI_MEMORY_DESCRIPTOR *MemoryMap; UINTN MemoryMapSize; UINTN DescriptorSize; UINT32 DescriptorVersion; // // Firstly try the easy way // Status = ExitBs (ImageHandle, MapKey); if (EFI_ERROR (Status)) { // // Just report error as var in nvram to be visible from macOS with "nvram -p" // gRT->SetVariable (L"aptiomemfix-exitbs", &gEfiAppleBootGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, 4, "fail" ); // // It is too late to free memory map here, but it does not matter, because boot.efi has an old one // and will freely use the memory. // It is technically forbidden to allocate pool memory here, but we should not hit this code // in the first place, and for older firmwares, where it was necessary (?), it worked just fine. // Status = GetMemoryMapAlloc (NULL, &MemoryMapSize, &MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion); if (Status == EFI_SUCCESS) { // // We have the latest memory map and its key, try again! // Status = ExitBs (ImageHandle, MapKey); if (EFI_ERROR (Status)) Print (L"AMF: ExitBootServices failed twice - %r\n", Status); } else { Print (L"AMF: Failed to get MapKey for ExitBootServices - %r\n", Status); Status = EFI_INVALID_PARAMETER; } if (EFI_ERROR (Status)) { Print (L"Waiting 10 secs...\n"); gBS->Stall (SECONDS_TO_MICROSECONDS (10)); } } return Status; } /** gRT->SetVirtualAddressMap override: * Fixes virtualizing of RT services. */ EFI_STATUS EFIAPI MOSetVirtualAddressMap ( IN UINTN MemoryMapSize, IN UINTN DescriptorSize, IN UINT32 DescriptorVersion, IN EFI_MEMORY_DESCRIPTOR *VirtualMap ) { EFI_STATUS Status; UINT32 EfiSystemTable; // // We do not need to recover BS, since they will be invalid. // UninstallRtOverrides (); // // Apply the necessary changes for macOS support // if (gMacOSBootNestedCount > 0) { if (gDumpMemArgPresent) { PrintMemMap (L"SetVirtualAddressMap", MemoryMapSize, DescriptorSize, VirtualMap, gRtShims, gSysTableRtArea); // // To print as much information as possible we delay ExitBootServices. // Most likely this will fail, but let's still try! // ForceExitBootServices (mStoredExitBootServices, mExitBSImageHandle, mExitBSMapKey); } // // Protect RT areas from relocation by marking then MemMapIO // ProtectRtMemoryFromRelocation (MemoryMapSize, DescriptorSize, DescriptorVersion, VirtualMap, gSysTableRtArea); // // Remember physical sys table addr // EfiSystemTable = (UINT32)(UINTN)gST; // // Virtualize RT services with all needed fixes // Status = ExecSetVirtualAddressesToMemMap (MemoryMapSize, DescriptorSize, DescriptorVersion, VirtualMap); CopyEfiSysTableToRtArea (&EfiSystemTable); } else { Status = gRT->SetVirtualAddressMap (MemoryMapSize, DescriptorSize, DescriptorVersion, VirtualMap); } // // Correct shim pointers right away // VirtualizeRtShims (MemoryMapSize, DescriptorSize, VirtualMap); return Status; }