/** @file Copyright (C) 2013, dmazar. All rights reserved. Copyright (C) 2017, vit9696. All rights reserved. All rights reserved. This program and the accompanying materials are licensed and made available under the terms and conditions of the BSD License which accompanies this distribution. The full text of the license may be found at http://opensource.org/licenses/bsd-license.php THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. **/ #include "BootCompatInternal.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** Helper function to mark OpenRuntime as executable with proper permissions. @param[in] BootCompat Boot compatibility context. **/ STATIC VOID FixRuntimeAttributes ( IN BOOT_COMPAT_CONTEXT *BootCompat, IN UINT32 Type ) { EFI_STATUS Status; EFI_PHYSICAL_ADDRESS Address; UINTN Pages; if ((Type != EfiRuntimeServicesCode) && (Type != EfiRuntimeServicesData)) { return; } if (BootCompat->Settings.SyncRuntimePermissions && (BootCompat->ServiceState.FwRuntime != NULL)) { // // Be very careful of recursion here, who knows what the firmware can call. // BootCompat->Settings.SyncRuntimePermissions = FALSE; Status = BootCompat->ServiceState.FwRuntime->GetExecArea (&Address, &Pages); if (!EFI_ERROR (Status)) { OcRebuildAttributes (Address, BootCompat->ServicePtrs.GetMemoryMap); } // // Permit syncing runtime permissions again. // BootCompat->Settings.SyncRuntimePermissions = TRUE; } } /** Helper function to call ExitBootServices that can handle outdated MapKey issues. @param[in] ExitBootServices ExitBootServices function pointer, optional. @param[in] GetMemoryMap GetMemoryMap function pointer, optional. @param[in] ImageHandle Image handle to call ExitBootServices on. @param[in] MapKey MapKey to call ExitBootServices on. @retval EFI_SUCCESS on success. **/ STATIC EFI_STATUS ForceExitBootServices ( IN EFI_HANDLE ImageHandle, IN UINTN MapKey, IN EFI_EXIT_BOOT_SERVICES ExitBootServices OPTIONAL, IN EFI_GET_MEMORY_MAP GetMemoryMap OPTIONAL ) { EFI_STATUS Status; EFI_MEMORY_DESCRIPTOR *MemoryMap; UINTN MemoryMapSize; UINTN DescriptorSize; UINT32 DescriptorVersion; if (ExitBootServices == NULL) { ExitBootServices = gBS->ExitBootServices; } if (GetMemoryMap == NULL) { GetMemoryMap = gBS->GetMemoryMap; } // // Firstly try the easy way. // Status = ExitBootServices (ImageHandle, MapKey); if (EFI_ERROR (Status)) { // // 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 firmware, where it was necessary (?), it worked just fine. // Status = OcGetCurrentMemoryMapAlloc ( &MemoryMapSize, &MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion, GetMemoryMap, NULL ); if (Status == EFI_SUCCESS) { // // We have the latest memory map and its key, try again! // Status = ExitBootServices (ImageHandle, MapKey); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "OCABC: ExitBootServices failed twice - %r\n", Status)); } } else { DEBUG ((DEBUG_WARN, "OCABC: Failed to get MapKey for ExitBootServices - %r\n", Status)); Status = EFI_INVALID_PARAMETER; } if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "OCABC: Waiting 10 secs...\n")); gBS->Stall (SECONDS_TO_MICROSECONDS (10)); } } return Status; } /** Protect regions in memory map. @param[in,out] MemoryMapSize Memory map size in bytes, updated on shrink. @param[in,out] MemoryMap Memory map to shrink. @param[in] DescriptorSize Memory map descriptor size in bytes. **/ STATIC VOID ProtectMemoryRegions ( IN UINTN MemoryMapSize, IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, IN UINTN DescriptorSize ) { UINTN NumEntries; UINTN Index; EFI_MEMORY_DESCRIPTOR *Desc; UINTN PhysicalEnd; // // AMI CSM module allocates up to two regions for legacy video output. // 1. For PMM and EBDA areas. // On Ivy Bridge and below it ends at 0xA0000-0x1000-0x1 and has EfiBootServicesCode type. // On Haswell and above it is allocated below 0xA0000 address with the same type. // 2. For Intel RC S3 reserved area, fixed from 0x9F000 to 0x9FFFF. // On Sandy Bridge and below it is not present in memory map. // On Ivy Bridge and newer it is present as EfiRuntimeServicesData. // Starting from at least SkyLake it is present as EfiReservedMemoryType. // // Prior to AptioMemoryFix EfiRuntimeServicesData could have been relocated by boot.efi, // and the 2nd region could have been overwritten by the kernel. Now it is no longer the // case, and only the 1st region may need special handling. // For the 1st region there appear to be (unconfirmed) reports that it may still be accessed // after waking from sleep. This does not seem to be valid according to AMI code, but we still // protect it in case such systems really exist. // // Initially researched and fixed on GIGABYTE boards by Slice. // Desc = MemoryMap; NumEntries = MemoryMapSize / DescriptorSize; for (Index = 0; Index < NumEntries; ++Index) { if ((Desc->NumberOfPages > 0) && (Desc->Type == EfiBootServicesData)) { ASSERT (LAST_DESCRIPTOR_ADDR (Desc) < MAX_UINTN); PhysicalEnd = (UINTN)LAST_DESCRIPTOR_ADDR (Desc) + 1; if ((PhysicalEnd >= 0x9E000) && (PhysicalEnd < 0xA0000)) { Desc->Type = EfiACPIMemoryNVS; break; } } Desc = NEXT_MEMORY_DESCRIPTOR (Desc, DescriptorSize); } // // Some types of firmware may leave MMIO regions as reserved memory with runtime flag, // which will not get mapped by macOS kernel. This will cause boot failures due // to such firmware accessing these regions at runtime for NVRAM support. // REF: https://github.com/acidanthera/bugtracker/issues/791#issuecomment-608959387 // Desc = MemoryMap; for (Index = 0; Index < NumEntries; ++Index) { if ((Desc->Type == EfiReservedMemoryType) && ((Desc->Attribute & EFI_MEMORY_RUNTIME) != 0)) { Desc->Type = EfiMemoryMappedIO; } Desc = NEXT_MEMORY_DESCRIPTOR (Desc, DescriptorSize); } } /** Mark MMIO virtual memory regions as non-runtime to reduce the amount of virtual memory required by boot.efi. @param[in] Context Boot compatibility context. @param[in,out] MemoryMapSize Memory map size in bytes, updated on devirtualisation. @param[in,out] MemoryMap Memory map to devirtualise. @param[in] DescriptorSize Memory map descriptor size in bytes. **/ STATIC VOID DevirtualiseMmio ( IN VOID *Context, IN UINTN MemoryMapSize, IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, IN UINTN DescriptorSize ) { UINTN NumEntries; UINTN Index; UINTN Index2; EFI_MEMORY_DESCRIPTOR *Desc; CONST EFI_PHYSICAL_ADDRESS *Whitelist; UINTN WhitelistSize; BOOLEAN Skipped; UINT64 PagesSaved; Whitelist = ((BOOT_COMPAT_CONTEXT *)Context)->Settings.MmioWhitelist; WhitelistSize = ((BOOT_COMPAT_CONTEXT *)Context)->Settings.MmioWhitelistSize; // // Some types of firmware (typically Haswell and earlier) need certain MMIO areas to have // virtual addresses due to their firmware implementations to access NVRAM. // For example, on Intel Haswell with APTIO that would be: // 0xFED1C000 (SB_RCBA) is a 0x4 page memory region, containing SPI_BASE at 0x3800 (SPI_BASE_ADDRESS). // 0xFF000000 (PCI root) is a 0x1000 page memory region. // One can make exceptions with Whitelist, as it is required on certain laptops. // Desc = MemoryMap; NumEntries = MemoryMapSize / DescriptorSize; PagesSaved = 0; if (!((BOOT_COMPAT_CONTEXT *)Context)->ServiceState.ReportedMmio) { DEBUG ((DEBUG_INFO, "OCABC: MMIO devirt start\n")); } for (Index = 0; Index < NumEntries; ++Index) { if ( (Desc->NumberOfPages > 0) && (Desc->Type == EfiMemoryMappedIO) && ((Desc->Attribute & EFI_MEMORY_RUNTIME) != 0)) { Skipped = FALSE; for (Index2 = 0; Index2 < WhitelistSize; ++Index2) { if (AREA_WITHIN_DESCRIPTOR (Desc, Whitelist[Index2], 1)) { Skipped = TRUE; break; } } if (!((BOOT_COMPAT_CONTEXT *)Context)->ServiceState.ReportedMmio) { DEBUG (( DEBUG_INFO, "OCABC: MMIO devirt 0x%Lx (0x%Lx pages, 0x%Lx) skip %d\n", (UINT64)Desc->PhysicalStart, (UINT64)Desc->NumberOfPages, (UINT64)Desc->Attribute, Skipped )); } if (!Skipped) { Desc->Attribute &= ~EFI_MEMORY_RUNTIME; PagesSaved += Desc->NumberOfPages; } } Desc = NEXT_MEMORY_DESCRIPTOR (Desc, DescriptorSize); } if (!((BOOT_COMPAT_CONTEXT *)Context)->ServiceState.ReportedMmio) { DEBUG (( DEBUG_INFO, "OCABC: MMIO devirt end, saved %Lu KB\n", EFI_PAGES_TO_SIZE (PagesSaved) / BASE_1KB )); ((BOOT_COMPAT_CONTEXT *)Context)->ServiceState.ReportedMmio = TRUE; } } /** Apply single booter patch. @param[in,out] ImageBase Booter image to be patched. @param[in] ImageSize Size of booter image. @param[in] Patch Single patch to be applied to booter. **/ STATIC VOID ApplyBooterPatch ( IN OUT UINT8 *ImageBase, IN UINTN ImageSize, IN OC_BOOTER_PATCH *Patch ) { UINT32 ReplaceCount; if (ImageSize < Patch->Size) { DEBUG ((DEBUG_INFO, "OCABC: Image size is even smaller than patch size\n")); return; } if ((Patch->Limit > 0) && (Patch->Limit < ImageSize)) { ImageSize = Patch->Limit; } ReplaceCount = ApplyPatch ( Patch->Find, Patch->Mask, Patch->Size, Patch->Replace, Patch->ReplaceMask, ImageBase, (UINT32)ImageSize, Patch->Count, Patch->Skip ); if ((ReplaceCount > 0) && (Patch->Count > 0) && (ReplaceCount != Patch->Count)) { DEBUG (( DEBUG_INFO, "OCABC: Booter patch (%a) performed only %u replacements out of %u\n", Patch->Comment, ReplaceCount, Patch->Count )); } else if (ReplaceCount == 0) { DEBUG (( DEBUG_INFO, "OCABC: Failed to apply Booter patch (%a) - not found\n", Patch->Comment )); } else { DEBUG ((DEBUG_INFO, "OCABC: Booter patch (%a) replace count - %u\n", Patch->Comment, ReplaceCount)); } } /** Iterate through user booter patches and apply them. @param[in] ImageHandle Loaded image handle to patch. @param[in] IsApple Whether the booter is Apple-made. @param[in] Patches Array of patches to be applied. @param[in] PatchesCount Size of patches to be applied. **/ STATIC VOID ApplyBooterPatches ( IN EFI_HANDLE ImageHandle, IN BOOLEAN IsApple, IN OC_BOOTER_PATCH *Patches, IN UINT32 PatchCount ) { EFI_STATUS Status; EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; UINT32 Index; BOOLEAN UsePatch; CONST CHAR8 *UserIdentifier; CHAR16 *UserIdentifierUnicode; Status = gBS->HandleProtocol ( ImageHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "OCABC: Failed to handle LoadedImage protocol - %r\n", Status)); return; } for (Index = 0; Index < PatchCount; ++Index) { UserIdentifier = Patches[Index].Identifier; if ((UserIdentifier[0] == '\0') || (AsciiStrCmp (UserIdentifier, "Any") == 0)) { UsePatch = TRUE; } else if (AsciiStrCmp (UserIdentifier, "Apple") == 0) { UsePatch = IsApple; } else { UserIdentifierUnicode = AsciiStrCopyToUnicode (UserIdentifier, 0); if (UserIdentifierUnicode == NULL) { DEBUG ((DEBUG_INFO, "OCABC: Booter patch (%a) for %a is out of memory\n", Patches[Index].Comment, UserIdentifier)); continue; } UsePatch = OcDevicePathHasFilePathSuffix (LoadedImage->FilePath, UserIdentifierUnicode, StrLen (UserIdentifierUnicode)); FreePool (UserIdentifierUnicode); } if (UsePatch) { ApplyBooterPatch ( (UINT8 *)LoadedImage->ImageBase, (UINTN)LoadedImage->ImageSize, &Patches[Index] ); } } } /** UEFI Boot Services AllocatePages override. Returns pages from free memory block to boot.efi for kernel boot image. **/ STATIC EFI_STATUS EFIAPI OcAllocatePages ( IN EFI_ALLOCATE_TYPE Type, IN EFI_MEMORY_TYPE MemoryType, IN UINTN NumberOfPages, IN OUT EFI_PHYSICAL_ADDRESS *Memory ) { EFI_STATUS Status; BOOT_COMPAT_CONTEXT *BootCompat; BOOLEAN IsPerfAlloc; BOOLEAN IsCallGateAlloc; // // Filter out garbage right away. // if (Memory == NULL) { return EFI_INVALID_PARAMETER; } BootCompat = GetBootCompatContext (); IsPerfAlloc = FALSE; IsCallGateAlloc = FALSE; if (BootCompat->ServiceState.AwaitingPerfAlloc) { if (BootCompat->ServiceState.AppleBootNestedCount > 0) { if ( (Type == AllocateMaxAddress) && (MemoryType == EfiACPIReclaimMemory) && (*Memory == BASE_4GB - 1)) { IsPerfAlloc = TRUE; } } else { BootCompat->ServiceState.AwaitingPerfAlloc = FALSE; } } if ( (BootCompat->ServiceState.AppleBootNestedCount > 0) && (Type == AllocateMaxAddress) && (MemoryType == EfiLoaderCode) && (*Memory == BASE_4GB - 1) && (NumberOfPages == 1)) { IsCallGateAlloc = TRUE; } if ( BootCompat->Settings.AllowRelocationBlock && (BootCompat->ServiceState.AppleBootNestedCount > 0) && (Type == AllocateAddress) && (MemoryType == EfiLoaderData)) { Status = AppleRelocationAllocatePages ( BootCompat, BootCompat->ServicePtrs.GetMemoryMap, BootCompat->ServicePtrs.AllocatePages, NumberOfPages, Memory ); } else { Status = EFI_UNSUPPORTED; } if (EFI_ERROR (Status)) { Status = BootCompat->ServicePtrs.AllocatePages ( Type, MemoryType, NumberOfPages, Memory ); } DEBUG ((DEBUG_VERBOSE, "OCABC: AllocPages %u 0x%Lx (%u) - %r\n", Type, *Memory, NumberOfPages, Status)); if (!EFI_ERROR (Status)) { FixRuntimeAttributes (BootCompat, MemoryType); if (BootCompat->ServiceState.AppleBootNestedCount > 0) { if (IsCallGateAlloc) { // // Called from boot.efi. // Memory allocated for boot.efi to kernel trampoline. // BootCompat->ServiceState.OldKernelCallGate = *Memory; } else if (IsPerfAlloc) { // // Called from boot.efi. // New perf data, it can be reallocated multiple times. // OcAppleDebugLogPerfAllocated ((VOID *)(UINTN)*Memory, EFI_PAGES_TO_SIZE (NumberOfPages)); } } } return Status; } /** UEFI Boot Services FreePages override. Ensures synchronised memory attribute table. **/ STATIC EFI_STATUS EFIAPI OcFreePages ( IN EFI_PHYSICAL_ADDRESS Memory, IN UINTN Pages ) { EFI_STATUS Status; BOOT_COMPAT_CONTEXT *BootCompat; BootCompat = GetBootCompatContext (); Status = BootCompat->ServicePtrs.FreePages ( Memory, Pages ); if (!EFI_ERROR (Status)) { FixRuntimeAttributes (BootCompat, EfiRuntimeServicesData); } return Status; } /** UEFI Boot Services GetMemoryMap override. Returns shrinked memory map as XNU can handle up to PMAP_MEMORY_REGIONS_SIZE (128) entries. Also applies any further memory map alterations as necessary. **/ STATIC EFI_STATUS EFIAPI OcGetMemoryMap ( IN OUT UINTN *MemoryMapSize, IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, OUT UINTN *MapKey, OUT UINTN *DescriptorSize, OUT UINT32 *DescriptorVersion ) { EFI_STATUS Status; EFI_STATUS Status2; BOOT_COMPAT_CONTEXT *BootCompat; EFI_PHYSICAL_ADDRESS Address; UINTN Pages; UINTN OriginalSize; BootCompat = GetBootCompatContext (); OriginalSize = MemoryMapSize != 0 ? *MemoryMapSize : 0; Status = BootCompat->ServicePtrs.GetMemoryMap ( MemoryMapSize, MemoryMap, MapKey, DescriptorSize, DescriptorVersion ); // // Reserve larger area for the memory map when we need to split it. // if ((BootCompat->ServiceState.AppleBootNestedCount > 0) && (Status == EFI_BUFFER_TOO_SMALL)) { *MemoryMapSize += OcCountSplitDescriptors () * *DescriptorSize; return EFI_BUFFER_TOO_SMALL; } if (EFI_ERROR (Status)) { return Status; } if (BootCompat->Settings.SyncRuntimePermissions && (BootCompat->ServiceState.FwRuntime != NULL)) { // // Some types of firmware mark runtime drivers loaded after EndOfDxe as EfiRuntimeServicesData: // REF: https://github.com/acidanthera/bugtracker/issues/791#issuecomment-607935508 // Status2 = BootCompat->ServiceState.FwRuntime->GetExecArea (&Address, &Pages); if (!EFI_ERROR (Status2)) { OcUpdateDescriptors ( *MemoryMapSize, MemoryMap, *DescriptorSize, Address, EfiRuntimeServicesCode, 0, 0 ); } } if (BootCompat->ServiceState.AppleBootNestedCount > 0) { if (BootCompat->Settings.ProtectMemoryRegions) { ProtectMemoryRegions ( *MemoryMapSize, MemoryMap, *DescriptorSize ); } if (BootCompat->Settings.DevirtualiseMmio) { DevirtualiseMmio ( BootCompat, *MemoryMapSize, MemoryMap, *DescriptorSize ); } if (BootCompat->Settings.RebuildAppleMemoryMap) { OcSortMemoryMap (*MemoryMapSize, MemoryMap, *DescriptorSize); Status2 = OcSplitMemoryMapByAttributes ( OriginalSize, MemoryMapSize, MemoryMap, *DescriptorSize ); if (EFI_ERROR (Status2) && (Status2 != EFI_UNSUPPORTED)) { DEBUG ((DEBUG_INFO, "OCABC: Cannot rebuild memory map - %r\n", Status)); } OcShrinkMemoryMap ( MemoryMapSize, MemoryMap, *DescriptorSize ); } else if (BootCompat->Settings.AllowRelocationBlock) { // // A sorted memory map is required when using a relocation block. // OcSortMemoryMap (*MemoryMapSize, MemoryMap, *DescriptorSize); } // // Remember some descriptor size, since we will not have it later // during hibernate wake to be able to iterate memory map. // BootCompat->ServiceState.MemoryMapDescriptorSize = *DescriptorSize; } return Status; } /** UEFI Boot Services AllocatePool override. Ensures synchronised memory attribute table. **/ STATIC EFI_STATUS EFIAPI OcAllocatePool ( IN EFI_MEMORY_TYPE PoolType, IN UINTN Size, OUT VOID **Buffer ) { EFI_STATUS Status; BOOT_COMPAT_CONTEXT *BootCompat; BootCompat = GetBootCompatContext (); Status = BootCompat->ServicePtrs.AllocatePool ( PoolType, Size, Buffer ); if (!EFI_ERROR (Status)) { FixRuntimeAttributes (BootCompat, PoolType); } return Status; } /** UEFI Boot Services FreePool override. Ensures synchronised memory attribute table. **/ STATIC EFI_STATUS EFIAPI OcFreePool ( IN VOID *Buffer ) { EFI_STATUS Status; BOOT_COMPAT_CONTEXT *BootCompat; BootCompat = GetBootCompatContext (); Status = BootCompat->ServicePtrs.FreePool ( Buffer ); if (!EFI_ERROR (Status)) { FixRuntimeAttributes (BootCompat, EfiRuntimeServicesData); } return Status; } /* Verify whether a device path refers to EfiBootRt of a loaded EfiBoot image. @param[in] EfiBootRtDevicePath The device path to verify. @param[in] SourceBuffer The image buffer of EfiBootRtDevicePath. @param[in] SourceSize The size, in Bytes, of SourceBuffer. @param[in] EfiBootLoadedImage The loaded EfiBoot image. @returns Whether EfiBootRtDevicePath refers to an EfiBootRt instance. */ STATIC BOOLEAN InternalIsEfiBootRt ( IN EFI_DEVICE_PATH_PROTOCOL *EfiBootRtDevicePath OPTIONAL, IN VOID *SourceBuffer OPTIONAL, IN UINTN SourceSize, IN EFI_LOADED_IMAGE_PROTOCOL *EfiBootLoadedImage ) { INTN CmpResult; BOOLEAN Overflow; EFI_DEVICE_PATH_PROTOCOL *EfiBootDevicePath; EFI_DEVICE_PATH_PROTOCOL *DevicePathEnd; UINTN EfiBootRtDevicePathRemSize; UINTN ComponentSize; EFI_DEVICE_PATH_PROTOCOL *CurrentRtNode; ASSERT (EfiBootLoadedImage != NULL); if ( (EfiBootRtDevicePath == NULL) || (SourceBuffer == NULL) || (EfiBootLoadedImage->FilePath == NULL)) { DEBUG (( DEBUG_INFO, "OCABC: Cannot be EfiBootRt (DP %d, Buf %d, EfiBoot FP %d)\n", (EfiBootRtDevicePath == NULL), (SourceBuffer == NULL), (EfiBootLoadedImage->FilePath == NULL) )); return FALSE; } // // The EfiBootRt device path appends a MemMap device path node with its loaded // image information to the EfiBoot device path. Verify the EfiBoot device // path is its prefix. // EfiBootDevicePath = DevicePathFromHandle (EfiBootLoadedImage->DeviceHandle); if (EfiBootDevicePath == NULL) { DEBUG (( DEBUG_INFO, "OCABC: Failed to get EfiBoot DP from handle 0x%llx\n", (UINT64)(UINTN)EfiBootLoadedImage->DeviceHandle )); return FALSE; } DebugPrintDevicePath ( DEBUG_INFO, "OCABC: Current EfiBoot", EfiBootDevicePath ); EfiBootRtDevicePathRemSize = GetDevicePathSize (EfiBootRtDevicePath); DevicePathEnd = FindDevicePathEndNode (EfiBootDevicePath); ComponentSize = (UINTN)DevicePathEnd - (UINTN)EfiBootDevicePath; Overflow = OcOverflowSubUN ( EfiBootRtDevicePathRemSize, ComponentSize, &EfiBootRtDevicePathRemSize ); if (Overflow) { DEBUG (( DEBUG_INFO, "OCABC: EfiBootRt DP (0x%llx) is shorter than EfiBoot DP (0x%llx)\n", (UINT64)EfiBootRtDevicePathRemSize, (UINT64)ComponentSize )); return FALSE; } // // The EfiBoot device path is constructed from the device path of its device // and its file path node. Check them one by one. // CmpResult = CompareMem ( EfiBootRtDevicePath, EfiBootDevicePath, ComponentSize ); if (CmpResult != 0) { DEBUG ((DEBUG_INFO, "OCABC: EfiBoot DP is not a prefix of EfiBootRt DP\n")); return FALSE; } CurrentRtNode = (EFI_DEVICE_PATH_PROTOCOL *)((UINT8 *)EfiBootRtDevicePath + ComponentSize); DevicePathEnd = FindDevicePathEndNode (EfiBootLoadedImage->FilePath); ComponentSize = (UINTN)DevicePathEnd - (UINTN)EfiBootLoadedImage->FilePath; if (ComponentSize >= EfiBootRtDevicePathRemSize) { DEBUG (( DEBUG_INFO, "OCABC: EfiBootRt FP (0x%llx) is shorter than EfiBoot FP (0x%llx)\n", (UINT64)EfiBootRtDevicePathRemSize, (UINT64)ComponentSize )); return FALSE; } CmpResult = CompareMem ( CurrentRtNode, EfiBootLoadedImage->FilePath, ComponentSize ); if (CmpResult != 0) { DEBUG ((DEBUG_INFO, "OCABC: EfiBoot FP is not a prefix of EfiBootRt FP\n")); return FALSE; } CurrentRtNode = (EFI_DEVICE_PATH_PROTOCOL *)((UINT8 *)CurrentRtNode + ComponentSize); // // Verify the appended node is of MemMap type and matches the loaded image // information. // if ( (DevicePathType (CurrentRtNode) != HARDWARE_DEVICE_PATH) || (DevicePathSubType (CurrentRtNode) != HW_MEMMAP_DP)) { DEBUG (( DEBUG_INFO, "OCABC: EfiBootRt FP is not succeeded by a MemMap node\n" )); return FALSE; } MEMMAP_DEVICE_PATH *MemMapNode = (MEMMAP_DEVICE_PATH *)CurrentRtNode; if ( (MemMapNode->StartingAddress != (EFI_PHYSICAL_ADDRESS)(UINTN)SourceBuffer) || (MemMapNode->EndingAddress != (EFI_PHYSICAL_ADDRESS)((UINTN)SourceBuffer + SourceSize))) { DEBUG (( DEBUG_INFO, "OCABC: MemMap [0x%llx, 0x%llx) mismatches EfiBootRt [0x%llx, 0x%llx)\n", MemMapNode->StartingAddress, MemMapNode->EndingAddress, (EFI_PHYSICAL_ADDRESS)(UINTN)SourceBuffer, (EFI_PHYSICAL_ADDRESS)((UINTN)SourceBuffer + SourceSize) )); return FALSE; } return TRUE; } /* Retrieves the offset of the kernel call gate in EfiBootRt. @param[in] KcgSize On output, the maximum size, in Bytes, available to the kernel call gate. @param[in] BootCompat The Apple Boot Compatibility context. @param[in] DevicePath The device path of EfiBootRt. @param[in] SourceBuffer The image buffer of EfiBootRt. @param[in] SourceSize The size, in Bytes, of SourceBuffer. @retval 0 The function did not complete successfully. @retval other The offset of the kernel call gate in EfiBootRt. */ STATIC UINTN InternalEfiBootRtGetKcgOffset ( OUT UINTN *KcgSize, IN BOOT_COMPAT_CONTEXT *BootCompat, IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, IN VOID *SourceBuffer, IN UINTN SourceSize ) { EFI_STATUS Status; BOOLEAN Overflow; EFI_HANDLE EfiBootRtCopyHandle; APPLE_EFI_BOOT_RT_INFO EfiBootRtLoadOptions; UINTN KcgOffset; EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; UINTN ImageBase; // // Load a copy of EfiBootRt to retrieve its information structure. EfiBootRt // must be loaded with the OpenCore image loader as it is not signed. This is // not a security issue as it is implicitly signed via EfiBoot. // Status = OcImageLoaderLoad ( FALSE, gImageHandle, DevicePath, SourceBuffer, SourceSize, &EfiBootRtCopyHandle ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OCABC: KCG load - %r\n", Status)); return 0; } // // EfiBootRt publishes its information via a caller-allocated buffer wired to // its launch options. For this, it must be launched. // Status = gBS->HandleProtocol ( EfiBootRtCopyHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OCABC: Handle KCG LoadedImage - %r\n", Status)); gBS->UnloadImage (EfiBootRtCopyHandle); return 0; } ImageBase = (UINTN)LoadedImage->ImageBase; ZeroMem (&EfiBootRtLoadOptions, sizeof (EfiBootRtLoadOptions)); LoadedImage->LoadOptions = &EfiBootRtLoadOptions; LoadedImage->LoadOptionsSize = sizeof (EfiBootRtLoadOptions); // // The calls to these services are correct despite calling OcImageLoaderLoad() // directly, as they will be overridden versions that support OpenCore loaded // images. // Status = BootCompat->ServicePtrs.StartImage (EfiBootRtCopyHandle, NULL, NULL); gBS->UnloadImage (EfiBootRtCopyHandle); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OCABC: KCG start - %r\n", Status)); return 0; } // // Compute the remaining space to the end of the image. // Overflow = OcOverflowSubUN ( (UINTN)EfiBootRtLoadOptions.KernelCallGate, ImageBase, &KcgOffset ); Overflow |= OcOverflowSubUN ( SourceSize, KcgOffset, KcgSize ); if (Overflow) { DEBUG (( DEBUG_INFO, "OCABC: KCG OOB (0x%llx, [0x%llx, 0x%llx)\n", (UINT64)(UINTN)EfiBootRtLoadOptions.KernelCallGate, (UINT64)ImageBase, (UINT64)(ImageBase + SourceSize) )); return 0; } return KcgOffset; } /** UEFI Boot Services LoadImage override. Called to load an efi image. If this is bootrt.efi, then we patch its kernel call gate. **/ STATIC EFI_STATUS EFIAPI OcLoadImage ( IN BOOLEAN BootPolicy, IN EFI_HANDLE ParentImageHandle, IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, IN VOID *SourceBuffer OPTIONAL, IN UINTN SourceSize, OUT EFI_HANDLE *ImageHandle ) { EFI_STATUS LoadImageStatus; EFI_STATUS Status; BOOLEAN IsEfiBootRt; BOOT_COMPAT_CONTEXT *BootCompat; EFI_LOADED_IMAGE_PROTOCOL *LoadedImage; UINTN KcgOffset; UINTN KcgSize; BootCompat = GetBootCompatContext (); DebugPrintDevicePath (DEBUG_INFO, "OCABC: EfiBootRt candidate", DevicePath); // // Verify whether it's EfiBootRt that's being loaded. If it is, patch its // kernel call gate. This is relevant as of macOS 13 Developer Beta 1, as // Apple moved the kernel call gate from a direct to an indirect EfiBoot embed // in form of a separate driver, EfiBootRt. // IsEfiBootRt = FALSE; if (!BootPolicy && (BootCompat->ServiceState.AppleBootNestedCount > 0)) { IsEfiBootRt = InternalIsEfiBootRt ( DevicePath, SourceBuffer, SourceSize, BootCompat->ServiceState.LastAppleBootImage ); } DEBUG (( DEBUG_INFO, "OCABC: IsEfiBootRt %d (BP %d, Apple %d)\n", IsEfiBootRt, !BootPolicy, BootCompat->ServiceState.AppleBootNestedCount > 0 )); // // Anything but EfiBootRt can be loaded transparently. EfiBootRt must be // loaded with the OpenCore image loader as it is not signed. This is not a // security issue as it is implicitly signed via EfiBoot. // if (!IsEfiBootRt) { return BootCompat->ServicePtrs.LoadImage ( BootPolicy, ParentImageHandle, DevicePath, SourceBuffer, SourceSize, ImageHandle ); } LoadImageStatus = OcImageLoaderLoad ( BootPolicy, ParentImageHandle, DevicePath, SourceBuffer, SourceSize, ImageHandle ); if (EFI_ERROR (LoadImageStatus)) { DEBUG ((DEBUG_INFO, "OCABC: EfiBootRt load - %r\n", LoadImageStatus)); return LoadImageStatus; } // // Retrieve the EfiBootRt kernel call gate location. // KcgOffset = InternalEfiBootRtGetKcgOffset ( &KcgSize, BootCompat, DevicePath, SourceBuffer, SourceSize ); if ((KcgOffset == 0) || (KcgSize < CALL_GATE_MIN_SIZE)) { DEBUG (( DEBUG_INFO, "OCABC: Invalid KCG [0x%llx, 0x%llx)\n", (UINT64)KcgOffset, (UINT64)KcgSize )); return LoadImageStatus; } Status = gBS->HandleProtocol ( *ImageHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OCABC: Handle EfiBootRt LoadedImage - %r\n", Status)); return LoadImageStatus; } // // Patch the EfiBootRt kernel call gate. // AppleMapPrepareKernelJump ( BootCompat, (UINTN)LoadedImage->ImageBase + KcgOffset, (UINTN)AppleMapPrepareKernelStateNew ); DEBUG (( DEBUG_INFO, "OCABC: Patched KCG at 0x%llx\n", (UINTN)LoadedImage->ImageBase + KcgOffset )); return LoadImageStatus; } /** UEFI Boot Services StartImage override. Called to start an efi image. If this is boot.efi, then our overrides are enabled. **/ STATIC EFI_STATUS EFIAPI OcStartImage ( IN EFI_HANDLE ImageHandle, OUT UINTN *ExitDataSize, OUT CHAR16 **ExitData OPTIONAL ) { EFI_STATUS Status; EFI_LOADED_IMAGE_PROTOCOL *AppleLoadedImage; EFI_LOADED_IMAGE_PROTOCOL *LastAppleBootImageTmp; EFI_OS_INFO_PROTOCOL *OSInfo; BOOT_COMPAT_CONTEXT *BootCompat; OC_FWRT_CONFIG Config; UINTN DataSize; BootCompat = GetBootCompatContext (); AppleLoadedImage = OcGetAppleBootLoadedImage (ImageHandle); // // Recover firmware-replaced GetMemoryMap pointer. // if ( BootCompat->Settings.ProtectUefiServices && (BootCompat->ServicePtrs.GetMemoryMap != OcGetMemoryMap)) { DEBUG ((DEBUG_INFO, "OCABC: Recovering trashed GetMemoryMap pointer\n")); gBS->GetMemoryMap = OcGetMemoryMap; gBS->Hdr.CRC32 = 0; gBS->CalculateCrc32 (gBS, gBS->Hdr.HeaderSize, &gBS->Hdr.CRC32); } FixRuntimeAttributes (BootCompat, EfiRuntimeServicesData); // // Clear monitoring vars // BootCompat->ServiceState.OldKernelCallGate = 0; if (AppleLoadedImage != NULL) { // // Report about macOS being loaded. // ++BootCompat->ServiceState.AppleBootNestedCount; // // Remember the last started Apple booter to match its EfiBootRt instance. // LastAppleBootImageTmp = BootCompat->ServiceState.LastAppleBootImage; BootCompat->ServiceState.LastAppleBootImage = AppleLoadedImage; // // VMware uses OSInfo->SetName call by EfiBoot to ensure that we are allowed // to run this version of macOS on VMware. The relevant EfiBoot image handle // is determined by the return address of OSInfo->SetName call, which will // be found in the list they make within their StartImage wrapper. // // The problem here happens with Apple Secure Boot, which makes their StartImage // wrapper to not be called and therefore OSInfo->SetName to fail to install anything. // Simply install a protocol here. Maybe make it a quirk if necessary. // Status = gBS->InstallMultipleProtocolInterfaces ( &ImageHandle, &gVMwareMacProtocolGuid, NULL, NULL ); DEBUG ((DEBUG_INFO, "OCABC: VMware Mac installed on %p - %r\n", ImageHandle, Status)); BootCompat->ServiceState.AppleHibernateWake = OcIsAppleHibernateWake (); BootCompat->ServiceState.AppleCustomSlide = OcCheckArgumentFromEnv ( AppleLoadedImage, BootCompat->ServicePtrs.GetVariable, "slide=", L_STR_LEN ("slide="), NULL ); if (BootCompat->Settings.EnableSafeModeSlide) { ASSERT (AppleLoadedImage->ImageSize <= MAX_UINTN); AppleSlideUnlockForSafeMode ( (UINT8 *)AppleLoadedImage->ImageBase, (UINTN)AppleLoadedImage->ImageSize ); } AppleMapPrepareBooterState ( BootCompat, AppleLoadedImage, BootCompat->ServicePtrs.GetMemoryMap ); if ( (BootCompat->Settings.ResizeAppleGpuBars >= 0) && (BootCompat->Settings.ResizeAppleGpuBars < PciBarTotal)) { ResizeGpuBars (BootCompat->Settings.ResizeAppleGpuBars, FALSE); } } else if (BootCompat->Settings.SignalAppleOS) { Status = gBS->LocateProtocol ( &gEfiOSInfoProtocolGuid, NULL, (VOID *)&OSInfo ); if (!EFI_ERROR (Status)) { // // Older versions of the protocol have less fields. // For instance, VMware installs 0x1 version. // if (OSInfo->Revision >= EFI_OS_INFO_PROTOCOL_REVISION_VENDOR) { OSInfo->OSVendor (EFI_OS_INFO_APPLE_VENDOR_NAME); } if (OSInfo->Revision >= EFI_OS_INFO_PROTOCOL_REVISION_NAME) { OSInfo->OSName ("Mac OS X 10.15"); } } } if (BootCompat->ServiceState.FwRuntime != NULL) { BootCompat->ServiceState.FwRuntime->GetCurrent (&Config); // // Support for ReadOnly and WriteOnly variables is OpenCore & Lilu security basics. // For now always enable it. // Config.RestrictedVariables = TRUE; // // Restrict secure boot variables and never let them slip unless once restricted. // Config.ProtectSecureBoot = BootCompat->Settings.ProtectSecureBoot; // // Enable Boot#### variable redirection if OpenCore requested it. // Do NOT disable it once enabled for stability reasons. // DataSize = sizeof (Config.BootVariableRedirect); BootCompat->ServicePtrs.GetVariable ( OC_BOOT_REDIRECT_VARIABLE_NAME, &gOcVendorVariableGuid, NULL, &DataSize, &Config.BootVariableRedirect ); // // Enable Apple-specific changes if requested. // Disable them when this is no longer Apple. // if (BootCompat->ServiceState.AppleBootNestedCount > 0) { Config.WriteProtection = BootCompat->Settings.DisableVariableWrite; Config.WriteUnprotector = BootCompat->Settings.EnableWriteUnprotector; } else { Config.WriteProtection = FALSE; Config.WriteUnprotector = FALSE; } BootCompat->ServiceState.FwRuntime->SetMain ( &Config ); } // // Apply customised booter patches. // ApplyBooterPatches ( ImageHandle, AppleLoadedImage != NULL, BootCompat->Settings.BooterPatches, BootCompat->Settings.BooterPatchesSize ); Status = BootCompat->ServicePtrs.StartImage ( ImageHandle, ExitDataSize, ExitData ); if (AppleLoadedImage != NULL) { // // On exit, restore the previous last started Apple booter. // BootCompat->ServiceState.LastAppleBootImage = LastAppleBootImageTmp; // // We failed but other operating systems should be loadable. // --BootCompat->ServiceState.AppleBootNestedCount; if (BootCompat->ServiceState.AppleBootNestedCount == 0) { AppleRelocationRelease (BootCompat); } } return Status; } /** UEFI Boot Services ExitBootServices override. Patches kernel entry point with jump to our KernelEntryPatchJumpBack(). Notes: - Most OSes attempt to call ExitBootServices more than once if it fails initially (similar to OpenCore ForceExitBootServices) - Therefore, OcExitBootServices may get called more than once - However this should never be relied upon for correct operation - Any logging within this call but before original ExitBootServices is attempted (e.g. within a scheduled handler) may cause ExitBootServices to fail (e.g. it may change the memory map by allocating), and should only be done, if at all, in the case of unexpected errors - Never log after original ExitBootServices has been attempted, not even on error **/ STATIC EFI_STATUS EFIAPI OcExitBootServices ( IN EFI_HANDLE ImageHandle, IN UINTN MapKey ) { EFI_STATUS Status; BOOT_COMPAT_CONTEXT *BootCompat; UINTN Index; BootCompat = GetBootCompatContext (); // // Handle events in case we have any. // if (BootCompat->Settings.ExitBootServicesHandlers != NULL) { for (Index = 0; BootCompat->Settings.ExitBootServicesHandlers[Index] != NULL; ++Index) { BootCompat->Settings.ExitBootServicesHandlers[Index]( NULL, BootCompat->Settings.ExitBootServicesHandlerContexts[Index] ); // // Even if ExitBootServices fails, do not subsequently call the events we handled. // BootCompat->Settings.ExitBootServicesHandlers[Index] = NULL; } } FixRuntimeAttributes (BootCompat, EfiRuntimeServicesData); // // For non-macOS operating systems return directly. // if (BootCompat->ServiceState.AppleBootNestedCount == 0) { return BootCompat->ServicePtrs.ExitBootServices ( ImageHandle, MapKey ); } // // Enable custom SetVirtualAddressMap. // if (BootCompat->ServiceState.FwRuntime != NULL) { BootCompat->ServiceState.FwRuntime->OnSetAddressMap (NULL, TRUE); } if (BootCompat->Settings.ForceExitBootServices) { Status = ForceExitBootServices ( ImageHandle, MapKey, BootCompat->ServicePtrs.ExitBootServices, BootCompat->ServicePtrs.GetMemoryMap ); } else { Status = BootCompat->ServicePtrs.ExitBootServices ( ImageHandle, MapKey ); } // // Abort on error. // if (EFI_ERROR (Status)) { return Status; } AppleMapPrepareKernelJump ( BootCompat, (UINTN)BootCompat->ServiceState.OldKernelCallGate, (UINTN)AppleMapPrepareKernelStateOld ); return Status; } /** UEFI Runtime Services SetVirtualAddressMap override. Fixes virtualizing of RT services. **/ STATIC EFI_STATUS EFIAPI OcSetVirtualAddressMap ( IN UINTN MemoryMapSize, IN UINTN DescriptorSize, IN UINT32 DescriptorVersion, IN EFI_MEMORY_DESCRIPTOR *MemoryMap ) { EFI_STATUS Status; BOOT_COMPAT_CONTEXT *BootCompat; BootCompat = GetBootCompatContext (); ASSERT (BootCompat->ServiceState.AppleBootNestedCount > 0); Status = AppleMapPrepareMemState ( BootCompat, MemoryMapSize, DescriptorSize, DescriptorVersion, MemoryMap ); return Status; } /** UEFI Runtime Services GetVariable override. Used to return customised values for boot-args and csr-active-config variables. **/ STATIC EFI_STATUS EFIAPI OcGetVariable ( IN CHAR16 *VariableName, IN EFI_GUID *VendorGuid, OUT UINT32 *Attributes OPTIONAL, IN OUT UINTN *DataSize, OUT VOID *Data ) { EFI_STATUS Status; BOOT_COMPAT_CONTEXT *BootCompat; BOOLEAN IsApple; BootCompat = GetBootCompatContext (); IsApple = BootCompat->ServiceState.AppleBootNestedCount > 0; if (IsApple && (BootCompat->Settings.ProvideCustomSlide || BootCompat->Settings.AllowRelocationBlock)) { Status = AppleSlideGetVariable ( BootCompat, BootCompat->ServicePtrs.GetVariable, BootCompat->ServicePtrs.GetMemoryMap, BootCompat->Settings.DevirtualiseMmio ? DevirtualiseMmio : NULL, BootCompat, VariableName, VendorGuid, Attributes, DataSize, Data ); } else { Status = BootCompat->ServicePtrs.GetVariable ( VariableName, VendorGuid, Attributes, DataSize, Data ); } // // Catch performance record allocation. // if ( IsApple && (Status == EFI_BUFFER_TOO_SMALL) && CompareGuid (VendorGuid, &gAppleBootVariableGuid) && (StrCmp (VariableName, APPLE_EFI_BOOT_PERF_VARIABLE_NAME) == 0)) { BootCompat->ServiceState.AwaitingPerfAlloc = TRUE; DEBUG ((DEBUG_INFO, "OCABC: Caught successful request for %s\n", VariableName)); } return Status; } /** UEFI Runtime Services GetVariable override event handler. We do not override GetVariable ourselves but let our runtime do that. @param[in] Event Event handle. @param[in] Context Apple boot compatibility context. **/ STATIC VOID EFIAPI SetGetVariableHookHandler ( IN EFI_EVENT Event, IN VOID *Context ) { EFI_STATUS Status; OC_FIRMWARE_RUNTIME_PROTOCOL *FwRuntime; BOOT_COMPAT_CONTEXT *BootCompat; BootCompat = (BOOT_COMPAT_CONTEXT *)Context; if (BootCompat->ServicePtrs.GetVariable == NULL) { Status = gBS->LocateProtocol ( &gOcFirmwareRuntimeProtocolGuid, NULL, (VOID **)&FwRuntime ); if (!EFI_ERROR (Status)) { if (FwRuntime->Revision == OC_FIRMWARE_RUNTIME_REVISION) { DEBUG ((DEBUG_INFO, "OCABC: Got rendezvous with OpenRuntime r%u\n", OC_FIRMWARE_RUNTIME_REVISION)); DEBUG ((DEBUG_INFO, "OCABC: MAT support is %d\n", OcGetMemoryAttributes (NULL) != NULL)); Status = FwRuntime->OnGetVariable (OcGetVariable, &BootCompat->ServicePtrs.GetVariable); if (!EFI_ERROR (Status)) { // // We override virtual address mapping function to perform // runtime area protection to prevent boot.efi // defragmentation and setup virtual memory for firmware // accessing it after exit boot services. // // We cannot override it to non-RT area since this is prohibited // by spec and at least Linux does not support it. // Status = FwRuntime->OnSetAddressMap (OcSetVirtualAddressMap, FALSE); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OCABC: OnSetAddressMap failure - %r\n", Status)); } } else { DEBUG ((DEBUG_INFO, "OCABC: OnGetVariable failure - %r\n", Status)); } } else { DEBUG (( DEBUG_ERROR, "OCABC: Incompatible OpenRuntime r%u, require r%u\n", (UINT32)FwRuntime->Revision, (UINT32)OC_FIRMWARE_RUNTIME_REVISION )); CpuDeadLoop (); } } else { DEBUG ((DEBUG_INFO, "OCABC: Awaiting rendezvous with OpenRuntime r%u\n", OC_FIRMWARE_RUNTIME_REVISION)); Status = EFI_UNSUPPORTED; } // // Mark protocol as useable. // if (!EFI_ERROR (Status)) { BootCompat->ServiceState.FwRuntime = FwRuntime; } } } VOID InstallServiceOverrides ( IN OUT BOOT_COMPAT_CONTEXT *BootCompat ) { EFI_STATUS Status; VOID *Registration; UEFI_SERVICES_POINTERS *ServicePtrs; EFI_TPL OriginalTpl; ServicePtrs = &BootCompat->ServicePtrs; OriginalTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL); ServicePtrs->AllocatePages = gBS->AllocatePages; ServicePtrs->FreePages = gBS->FreePages; ServicePtrs->GetMemoryMap = gBS->GetMemoryMap; ServicePtrs->AllocatePool = gBS->AllocatePool; ServicePtrs->FreePool = gBS->FreePool; ServicePtrs->ExitBootServices = gBS->ExitBootServices; ServicePtrs->LoadImage = gBS->LoadImage; ServicePtrs->StartImage = gBS->StartImage; gBS->AllocatePages = OcAllocatePages; gBS->FreePages = OcFreePages; gBS->GetMemoryMap = OcGetMemoryMap; gBS->AllocatePool = OcAllocatePool; gBS->FreePool = OcFreePool; gBS->ExitBootServices = OcExitBootServices; gBS->LoadImage = OcLoadImage; gBS->StartImage = OcStartImage; gBS->Hdr.CRC32 = 0; gBS->Hdr.CRC32 = CalculateCrc32 (gBS, gBS->Hdr.HeaderSize); gBS->RestoreTPL (OriginalTpl); // // Update GetVariable handle with the help of external runtime services. // SetGetVariableHookHandler (NULL, BootCompat); if (BootCompat->ServicePtrs.GetVariable != NULL) { return; } Status = gBS->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_CALLBACK, SetGetVariableHookHandler, BootCompat, &BootCompat->ServiceState.GetVariableEvent ); if (!EFI_ERROR (Status)) { Status = gBS->RegisterProtocolNotify ( &gOcFirmwareRuntimeProtocolGuid, BootCompat->ServiceState.GetVariableEvent, &Registration ); if (EFI_ERROR (Status)) { gBS->CloseEvent (BootCompat->ServiceState.GetVariableEvent); } } }