CloverBootloader/MemoryFix/AptioMemoryFix/ServiceOverrides.c

639 lines
18 KiB
C

/**
Temporary BS and RT overrides for boot.efi support.
Unlike RtShims they do not affect the kernel.
by dmazar
**/
#include <IndustryStandard/AppleHibernate.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/OcDebugLogLib.h>
#include <Library/OcMiscLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Protocol/LoadedImage.h>
#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.
//
OcPrintScreen (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", &gAppleBootVariableGuid, NULL, &ValueSize, NULL) == EFI_BUFFER_TOO_SMALL) {
ValueSize = 0;
if (gRT->GetVariable (L"boot-image-key", &gAppleBootVariableGuid, NULL, &ValueSize, NULL) == EFI_BUFFER_TOO_SMALL) {
gHibernateWake = TRUE;
}
} else {
ValueSize = 0;
if (gRT->GetVariable (L"boot-switch-vars", &gAppleBootVariableGuid, 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 OcPrintScreen 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) {
OcPrintScreen (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",
&gAppleBootVariableGuid,
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))
OcPrintScreen (L"AMF: ExitBootServices failed twice - %r\n", Status);
} else {
OcPrintScreen (L"AMF: Failed to get MapKey for ExitBootServices - %r\n", Status);
Status = EFI_INVALID_PARAMETER;
}
if (EFI_ERROR (Status)) {
OcPrintScreen (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;
}