From 7e965893bdce6889f380b2be12b0aa3106a275de Mon Sep 17 00:00:00 2001 From: vit9696 <4348897+vit9696@users.noreply.github.com> Date: Sat, 8 Aug 2020 20:05:23 +0300 Subject: [PATCH] OcAppleKernelLib: Implement 64-bit cacheless kext injection (#96) references acidanthera/bugtracker#358 --- .../Acidanthera/Library/OcAppleKernelLib.h | 131 ++ Include/Acidanthera/Library/OcStringLib.h | 30 + Library/OcAppleKernelLib/CachelessContext.c | 1157 +++++++++++++++++ Library/OcAppleKernelLib/CachelessInternal.h | 187 +++ Library/OcAppleKernelLib/OcAppleKernelLib.inf | 1 + Library/OcStringLib/OcAsciiLib.c | 20 + Library/OcStringLib/OcUnicodeLib.c | 20 + Platform/OpenCore/OpenCoreKernel.c | 163 ++- 8 files changed, 1702 insertions(+), 7 deletions(-) create mode 100644 Library/OcAppleKernelLib/CachelessContext.c create mode 100644 Library/OcAppleKernelLib/CachelessInternal.h diff --git a/Include/Acidanthera/Library/OcAppleKernelLib.h b/Include/Acidanthera/Library/OcAppleKernelLib.h index 38063bd8..f51a09d9 100644 --- a/Include/Acidanthera/Library/OcAppleKernelLib.h +++ b/Include/Acidanthera/Library/OcAppleKernelLib.h @@ -42,6 +42,10 @@ #define INFO_BUNDLE_LIBRARIES_64_KEY "OSBundleLibraries_x86_64" #define INFO_BUNDLE_VERSION_KEY "CFBundleVersion" #define INFO_BUNDLE_COMPATIBLE_VERSION_KEY "OSBundleCompatibleVersion" +#define INFO_BUNDLE_OS_BUNDLE_REQUIRED_KEY "OSBundleRequired" + +#define OS_BUNDLE_REQUIRED_ROOT "Root" +#define OS_BUNDLE_REQUIRED_SAFE_BOOT "Safe Boot" #define PRELINK_INFO_INTEGER_ATTRIBUTES "size=\"64\"" @@ -251,6 +255,36 @@ typedef struct { UINT32 Limit; } PATCHER_GENERIC_PATCH; +// +// Context for cacheless boot (S/L/E). +// +typedef struct { + // + // Extensions directory EFI_FILE_PROTOCOL instance. + // + EFI_FILE_PROTOCOL *ExtensionsDir; + // + // Extensions directory filename. This is freed by the caller. + // + CONST CHAR16 *ExtensionsDirFileName; + // + // Injected kext list. + // + LIST_ENTRY InjectedKexts; + // + // Dependency bundle list for injected kexts. + // + LIST_ENTRY InjectedDependencies; + // + // List of built-in shipping kexts. + // + LIST_ENTRY BuiltInKexts; + // + // Flag to indicate if above list is valid. List is built during the first read from SLE. + // + BOOLEAN BuiltInKextsValid; +} CACHELESS_CONTEXT; + /** Read Apple kernel for target architecture (possibly decompressing) into pool allocated buffer. @@ -741,4 +775,101 @@ PatchAppleRtcChecksum ( IN OUT PRELINKED_CONTEXT *Context ); +/** + Initializes cacheless context for later modification. + Must be freed with CachelessContextFree on success. + + @param[in,out] Context Cacheless context. + @param[in] FileName Extensions directory filename. + @param[in] ExtensionsDir Extensions directory EFI_FILE_PROTOCOL. + + @return EFI_SUCCESS on success. +**/ +EFI_STATUS +CachelessContextInit ( + IN OUT CACHELESS_CONTEXT *Context, + IN CONST CHAR16 *FileName, + IN EFI_FILE_PROTOCOL *ExtensionsDir + ); + +/** + Frees cacheless context. + + @param[in,out] Context Cacheless context. + + @return EFI_SUCCESS on success. +**/ +VOID +CachelessContextFree ( + IN OUT CACHELESS_CONTEXT *Context + ); + +/** + Add kext to cacheless context to be injected later on. + + @param[in,out] Context Cacheless context. + @param[in] InfoPlist Kext Info.plist. + @param[in] InfoPlistSize Kext Info.plist size. + @param[in] Executable Kext executable, optional. + @param[in] ExecutableSize Kext executable size, optional. + + @return EFI_SUCCESS on success. +**/ +EFI_STATUS +CachelessContextAddKext ( + IN OUT CACHELESS_CONTEXT *Context, + IN CONST CHAR8 *InfoPlist, + IN UINT32 InfoPlistSize, + IN CONST UINT8 *Executable OPTIONAL, + IN UINT32 ExecutableSize OPTIONAL + ); + +/** + Creates virtual directory overlay EFI_FILE_PROTOCOL from cacheless context. + + @param[in,out] Context Cacheless context. + @param[out] File The virtual directory instance. + + @return EFI_SUCCESS on success. +**/ +EFI_STATUS +CachelessContextOverlayExtensionsDir ( + IN OUT CACHELESS_CONTEXT *Context, + OUT EFI_FILE_PROTOCOL **File + ); + +/** + Perform kext injection. + + @param[in,out] Context Prelinked context. + @param[in] FileName Filename of kext file to be injected. + @param[out] VirtualFile Newly created virtualised EFI_FILE_PROTOCOL instance. + + @return EFI_SUCCESS on success. +**/ +EFI_STATUS +CachelessContextPerformInject ( + IN OUT CACHELESS_CONTEXT *Context, + IN CONST CHAR16 *FileName, + OUT EFI_FILE_PROTOCOL **VirtualFile + ); + +/** + Apply patches to built-in kexts. + + @param[in,out] Context Prelinked context. + @param[in] FileName Filename of kext file to be injected. + @param[in] File EFI_FILE_PROTOCOL instance of kext file. + @param[out] VirtualFile Newly created virtualised EFI_FILE_PROTOCOL instance. + + @return EFI_SUCCESS on success. If no patches are applicable, VirtualFile will be NULL. +**/ +EFI_STATUS +CachelessContextHookBuiltin ( + IN OUT CACHELESS_CONTEXT *Context, + IN CONST CHAR16 *FileName, + IN EFI_FILE_PROTOCOL *File, + OUT EFI_FILE_PROTOCOL **VirtualFile + ); + #endif // OC_APPLE_KERNEL_LIB_H diff --git a/Include/Acidanthera/Library/OcStringLib.h b/Include/Acidanthera/Library/OcStringLib.h index 5e2ea21e..a94d011e 100755 --- a/Include/Acidanthera/Library/OcStringLib.h +++ b/Include/Acidanthera/Library/OcStringLib.h @@ -114,6 +114,21 @@ OcAsciiSafeSPrint ( ... ); +/** Check if ASCII string ends with another ASCII string. + + @param[in] String A pointer to a Null-terminated ASCII string. + @param[in] SearchString A pointer to a Null-terminated ASCII string + to compare against String. + + @retval TRUE if String ends with SearchString. +**/ +BOOLEAN +EFIAPI +OcAsciiEndsWith ( + IN CONST CHAR8 *String, + IN CONST CHAR8 *SearchString + ); + /** Performs a case insensitive comparison of two Null-terminated Unicode strings, and returns the difference between the first mismatched Unicode characters. @@ -269,6 +284,21 @@ OcUnicodeSafeSPrint ( ... ); +/** Check if Unicode string ends with another Unicode string. + + @param[in] String A pointer to a Null-terminated Unicode string. + @param[in] SearchString A pointer to a Null-terminated Unicode string + to compare against String. + + @retval TRUE if String ends with SearchString. +**/ +BOOLEAN +EFIAPI +OcUnicodeEndsWith ( + IN CONST CHAR16 *String, + IN CONST CHAR16 *SearchString + ); + /** Convert path with mixed slashes to UEFI slashes (\\). diff --git a/Library/OcAppleKernelLib/CachelessContext.c b/Library/OcAppleKernelLib/CachelessContext.c new file mode 100644 index 00000000..ea18d5fd --- /dev/null +++ b/Library/OcAppleKernelLib/CachelessContext.c @@ -0,0 +1,1157 @@ +/** @file + Cacheless boot (S/L/E) support. + + Copyright (c) 2020, Goldfish64. 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CachelessInternal.h" + +STATIC +VOID +FreeBuiltInKext ( + IN BUILTIN_KEXT *BuiltinKext + ) +{ + DEPEND_KEXT *DependKext; + LIST_ENTRY *KextLink; + + if (BuiltinKext->PlistPath != NULL) { + FreePool (BuiltinKext->PlistPath); + } + if (BuiltinKext->BundleId != NULL) { + FreePool (BuiltinKext->BundleId); + } + if (BuiltinKext->BinaryFileName != NULL) { + FreePool (BuiltinKext->BinaryFileName); + } + + while (!IsListEmpty (&BuiltinKext->Dependencies)) { + KextLink = GetFirstNode (&BuiltinKext->Dependencies); + DependKext = GET_DEPEND_KEXT_FROM_LINK (KextLink); + RemoveEntryList (KextLink); + + if (DependKext->BundleId != NULL) { + FreePool (DependKext->BundleId); + } + FreePool (DependKext); + } + + FreePool (BuiltinKext); +} + +STATIC +BOOLEAN +AddKextDependencies ( + IN OUT LIST_ENTRY *Dependencies, + IN XML_NODE *InfoPlistLibraries + ) +{ + DEPEND_KEXT *DependKext; + UINT32 ChildCount; + UINT32 ChildIndex; + CONST CHAR8 *ChildPlistKey; + + LIST_ENTRY *KextLink; + BOOLEAN DependencyExists; + + ChildCount = PlistDictChildren (InfoPlistLibraries); + for (ChildIndex = 0; ChildIndex < ChildCount; ChildIndex++) { + ChildPlistKey = PlistKeyValue (PlistDictChild (InfoPlistLibraries, ChildIndex, NULL)); + if (ChildPlistKey == NULL) { + continue; + } + + DependencyExists = FALSE; + KextLink = GetFirstNode (Dependencies); + while (!IsNull (Dependencies, KextLink)) { + DependKext = GET_DEPEND_KEXT_FROM_LINK (KextLink); + + if (AsciiStrCmp (DependKext->BundleId, ChildPlistKey) == 0) { + DependencyExists = TRUE; + break; + } + + KextLink = GetNextNode (Dependencies, KextLink); + } + + if (!DependencyExists) { + DependKext = AllocateZeroPool (sizeof (*DependKext)); + if (DependKext == NULL) { + return FALSE; + } + DependKext->Signature = DEPEND_KEXT_SIGNATURE; + DependKext->BundleId = AllocateCopyPool (AsciiStrSize (ChildPlistKey), ChildPlistKey); + + InsertTailList (Dependencies, &DependKext->Link); + } + } + + return TRUE; +} + +STATIC +EFI_STATUS +ScanExtensions ( + IN OUT CACHELESS_CONTEXT *Context, + IN EFI_FILE_PROTOCOL *File, + IN CONST CHAR16 *FilePath, + IN BOOLEAN ReadPlugins + ) +{ + EFI_STATUS Status; + EFI_FILE_PROTOCOL *FileKext; + EFI_FILE_PROTOCOL *FilePlist; + EFI_FILE_PROTOCOL *FilePlugins; + EFI_FILE_INFO *FileInfo; + UINTN FileInfoSize; + BOOLEAN UseContents; + + CHAR8 *InfoPlist; + UINT32 InfoPlistSize; + XML_DOCUMENT *InfoPlistDocument; + XML_NODE *InfoPlistRoot; + XML_NODE *InfoPlistValue; + XML_NODE *InfoPlistLibraries; + CONST CHAR8 *TmpKeyValue; + UINT32 FieldCount; + UINT32 FieldIndex; + + BUILTIN_KEXT *BuiltinKext; + CHAR16 TmpPath[256]; + + DEBUG ((DEBUG_INFO, "OCAK: Scanning %s...\n", FilePath)); + + FileInfo = AllocatePool (SIZE_1KB); + if (FileInfo == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + File->SetPosition (File, 0); + + do { + // + // Apple's HFS+ driver does not adhere to the spec and will return zero for + // EFI_BUFFER_TOO_SMALL. EFI_FILE_INFO structures larger than 1KB are + // unrealistic as the filename is the only variable. + // + FileInfoSize = SIZE_1KB - sizeof (CHAR16); + Status = File->Read (File, &FileInfoSize, FileInfo); + if (EFI_ERROR (Status)) { + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return Status; + } + + if (FileInfoSize > 0) { + if (OcUnicodeEndsWith (FileInfo->FileName, L".kext")) { + Status = File->Open (File, &FileKext, FileInfo->FileName, EFI_FILE_MODE_READ, EFI_FILE_DIRECTORY); + if (!EFI_ERROR (Status)) { + Status = FileKext->Open (FileKext, &FilePlist, L"Contents\\Info.plist", EFI_FILE_MODE_READ, 0); + UseContents = !EFI_ERROR (Status); + if (Status == EFI_NOT_FOUND) { + Status = FileKext->Open (FileKext, &FilePlist, L"Info.plist", EFI_FILE_MODE_READ, 0); + } + if (EFI_ERROR (Status)) { + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return Status; + } + + // + // Parse Info.plist. + // + Status = AllocateCopyFileData (FilePlist, (UINT8**)&InfoPlist, &InfoPlistSize); + FilePlist->Close (FilePlist); + if (EFI_ERROR (Status)) { + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return Status; + } + + InfoPlistDocument = XmlDocumentParse (InfoPlist, InfoPlistSize, FALSE); + if (InfoPlistDocument == NULL) { + FreePool (InfoPlist); + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return EFI_INVALID_PARAMETER; + } + + InfoPlistRoot = PlistNodeCast (PlistDocumentRoot (InfoPlistDocument), PLIST_NODE_TYPE_DICT); + if (InfoPlistRoot == NULL) { + XmlDocumentFree (InfoPlistDocument); + FreePool (InfoPlist); + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return EFI_INVALID_PARAMETER; + } + + // + // Add to built-in kexts list. + // + BuiltinKext = AllocateZeroPool (sizeof (*BuiltinKext)); + if (BuiltinKext == NULL) { + XmlDocumentFree (InfoPlistDocument); + FreePool (InfoPlist); + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return EFI_OUT_OF_RESOURCES; + } + BuiltinKext->Signature = BUILTIN_KEXT_SIGNATURE; + InitializeListHead (&BuiltinKext->Dependencies); + + // + // Search for plist properties. + // + FieldCount = PlistDictChildren (InfoPlistRoot); + for (FieldIndex = 0; FieldIndex < FieldCount; ++FieldIndex) { + TmpKeyValue = PlistKeyValue (PlistDictChild (InfoPlistRoot, FieldIndex, &InfoPlistValue)); + if (TmpKeyValue == NULL) { + continue; + } + + if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_EXECUTABLE_KEY) == 0) { + BuiltinKext->BinaryFileName = AsciiStrCopyToUnicode (XmlNodeContent (InfoPlistValue), 0); + if (BuiltinKext->BinaryFileName == NULL) { + FreeBuiltInKext (BuiltinKext); + XmlDocumentFree (InfoPlistDocument); + FreePool (InfoPlist); + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return EFI_OUT_OF_RESOURCES; + } + + } else if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_IDENTIFIER_KEY) == 0) { + BuiltinKext->BundleId = AllocateCopyPool (AsciiStrSize (XmlNodeContent (InfoPlistValue)), XmlNodeContent (InfoPlistValue)); + if (BuiltinKext->BundleId == NULL) { + FreeBuiltInKext (BuiltinKext); + XmlDocumentFree (InfoPlistDocument); + FreePool (InfoPlist); + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return EFI_OUT_OF_RESOURCES; + } + + } else if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_OS_BUNDLE_REQUIRED_KEY) == 0) { + // + // If OSBundleRequired is present and is not Safe Boot, no action is required. + // + if (AsciiStrCmp (XmlNodeContent (InfoPlistValue), OS_BUNDLE_REQUIRED_SAFE_BOOT) != 0) { + BuiltinKext->OSBundleRequiredValue = KEXT_OSBUNDLE_REQUIRED_VALID; + } else { + BuiltinKext->OSBundleRequiredValue = KEXT_OSBUNDLE_REQUIRED_INVALID; + } + + } else if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_LIBRARIES_KEY) == 0) { + InfoPlistLibraries = PlistNodeCast (InfoPlistValue, PLIST_NODE_TYPE_DICT); + if (InfoPlistLibraries == NULL) { + FreeBuiltInKext (BuiltinKext); + XmlDocumentFree (InfoPlistDocument); + FreePool (InfoPlist); + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return EFI_INVALID_PARAMETER; + } + + AddKextDependencies (&BuiltinKext->Dependencies, InfoPlistLibraries); + } + } + + XmlDocumentFree (InfoPlistDocument); + FreePool (InfoPlist); + + if (BuiltinKext->BundleId == NULL) { + FreeBuiltInKext (BuiltinKext); + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return EFI_INVALID_PARAMETER; + } + + Status = OcUnicodeSafeSPrint ( + TmpPath, + sizeof (TmpPath), + L"%s\\%s\\%s", + FilePath, + FileInfo->FileName, + UseContents ? L"Contents\\Info.plist" : L"Info.plist" + ); + if (EFI_ERROR (Status)) { + FreeBuiltInKext (BuiltinKext); + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return EFI_INVALID_PARAMETER; + } + + BuiltinKext->PlistPath = AllocateCopyPool (StrSize (TmpPath), TmpPath); + if (BuiltinKext->PlistPath == NULL) { + FreeBuiltInKext (BuiltinKext); + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return EFI_OUT_OF_RESOURCES; + } + + InsertTailList (&Context->BuiltInKexts, &BuiltinKext->Link); + DEBUG (( + DEBUG_VERBOSE, + "OCAK: Discovered bundle %a %s %s %u\n", + BuiltinKext->BundleId, + BuiltinKext->BinaryFileName, + BuiltinKext->PlistPath, + BuiltinKext->OSBundleRequiredValue + )); + + // + // Scan PlugIns directory. + // + if (ReadPlugins) { + Status = FileKext->Open (FileKext, &FilePlugins, UseContents ? L"Contents\\PlugIns" : L"PlugIns", EFI_FILE_MODE_READ, EFI_FILE_DIRECTORY); + if (Status == EFI_SUCCESS) { + Status = OcUnicodeSafeSPrint ( + TmpPath, + sizeof (TmpPath), + L"%s\\%s\\%s", + FilePath, + FileInfo->FileName, + UseContents ? L"Contents\\PlugIns" : L"PlugIns" + ); + + Status = ScanExtensions (Context, FilePlugins, TmpPath, FALSE); + FilePlugins->Close (FilePlugins); + if (EFI_ERROR (Status)) { + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return Status; + } + } else if (Status != EFI_NOT_FOUND) { + FileKext->Close (FileKext); + File->SetPosition (File, 0); + FreePool (FileInfo); + return Status; + } + } + + FileKext->Close (FileKext); + } + } + } + } while (FileInfoSize > 0); + + File->SetPosition (File, 0); + FreePool (FileInfo); + + return EFI_SUCCESS; +} + +STATIC +BUILTIN_KEXT* +LookupBuiltinKextForBundleId ( + IN OUT CACHELESS_CONTEXT *Context, + IN CONST CHAR8 *BundleId + ) +{ + BUILTIN_KEXT *BuiltinKext; + LIST_ENTRY *KextLink; + + KextLink = GetFirstNode (&Context->BuiltInKexts); + while (!IsNull (&Context->BuiltInKexts, KextLink)) { + BuiltinKext = GET_BUILTIN_KEXT_FROM_LINK (KextLink); + + if (AsciiStrCmp (BundleId, BuiltinKext->BundleId) == 0) { + return BuiltinKext; + } + + KextLink = GetNextNode (&Context->BuiltInKexts, KextLink); + } + + return NULL; +} + +STATIC +BUILTIN_KEXT* +LookupBuiltinKextForPlistPath ( + IN OUT CACHELESS_CONTEXT *Context, + IN CONST CHAR16 *PlistPath + ) +{ + BUILTIN_KEXT *BuiltinKext; + LIST_ENTRY *KextLink; + + KextLink = GetFirstNode (&Context->BuiltInKexts); + while (!IsNull (&Context->BuiltInKexts, KextLink)) { + BuiltinKext = GET_BUILTIN_KEXT_FROM_LINK (KextLink); + + if (StrCmp (PlistPath, BuiltinKext->PlistPath) == 0) { + return BuiltinKext; + } + + KextLink = GetNextNode (&Context->BuiltInKexts, KextLink); + } + + return NULL; +} + +STATIC +EFI_STATUS +ScanDependencies ( + IN OUT CACHELESS_CONTEXT *Context, + IN CHAR8 *BundleId + ) +{ + EFI_STATUS Status; + BUILTIN_KEXT *BuiltinKext; + DEPEND_KEXT *DependKext; + LIST_ENTRY *KextLink; + + BOOLEAN DependencyExists; + + BuiltinKext = LookupBuiltinKextForBundleId (Context, BundleId); + if (BuiltinKext == NULL || BuiltinKext->OSBundleRequiredValue == KEXT_OSBUNDLE_REQUIRED_VALID) { + // + // Injected kexts may have dependencies on other injected kexts, which we do not need to handle. + // We should be able to safely assume that any kext with the OSBundleRequired set correctly does not need to be handled either. + // + return EFI_SUCCESS; + } + + BuiltinKext->PatchValidOSBundleRequired = TRUE; + + // + // Add bundle to list. + // + DependencyExists = FALSE; + KextLink = GetFirstNode (&Context->InjectedDependencies); + while (!IsNull (&Context->InjectedDependencies, KextLink)) { + DependKext = GET_DEPEND_KEXT_FROM_LINK (KextLink); + + if (AsciiStrCmp (DependKext->BundleId, BundleId) == 0) { + DependencyExists = TRUE; + break; + } + + KextLink = GetNextNode (&Context->InjectedDependencies, KextLink); + } + + if (!DependencyExists) { + DependKext = AllocateZeroPool (sizeof (*DependKext)); + if (DependKext == NULL) { + return EFI_OUT_OF_RESOURCES; + } + DependKext->Signature = DEPEND_KEXT_SIGNATURE; + DependKext->BundleId = AllocateCopyPool (AsciiStrSize (BundleId), BundleId); + + DEBUG ((DEBUG_INFO, "OCAK: Adding built-in dependency %a\n", BundleId)); + InsertTailList (&Context->InjectedDependencies, &DependKext->Link); + } + + KextLink = GetFirstNode (&BuiltinKext->Dependencies); + while (!IsNull (&BuiltinKext->Dependencies, KextLink)) { + DependKext = GET_DEPEND_KEXT_FROM_LINK (KextLink); + + Status = ScanDependencies (Context, DependKext->BundleId); + if (EFI_ERROR (Status)) { + return Status; + } + + KextLink = GetNextNode (&BuiltinKext->Dependencies, KextLink); + } + + return EFI_SUCCESS; +} + +EFI_STATUS +CachelessContextInit ( + IN OUT CACHELESS_CONTEXT *Context, + IN CONST CHAR16 *FileName, + IN EFI_FILE_PROTOCOL *ExtensionsDir + ) +{ + ASSERT (Context != NULL); + ASSERT (FileName != NULL); + ASSERT (ExtensionsDir != NULL); + + ZeroMem (Context, sizeof (*Context)); + + Context->ExtensionsDir = ExtensionsDir; + Context->ExtensionsDirFileName = FileName; + + InitializeListHead (&Context->InjectedKexts); + InitializeListHead (&Context->InjectedDependencies); + InitializeListHead (&Context->BuiltInKexts); + + return EFI_SUCCESS; +} + +VOID +CachelessContextFree ( + IN OUT CACHELESS_CONTEXT *Context + ) +{ + CACHELESS_KEXT *CachelessKext; + BUILTIN_KEXT *BuiltinKext; + LIST_ENTRY *KextLink; + + ASSERT (Context != NULL); + + while (!IsListEmpty (&Context->InjectedKexts)) { + KextLink = GetFirstNode (&Context->InjectedKexts); + CachelessKext = GET_CACHELESS_KEXT_FROM_LINK (KextLink); + RemoveEntryList (KextLink); + + if (CachelessKext->PlistData != NULL) { + FreePool (CachelessKext->PlistData); + } + if (CachelessKext->BinaryData != NULL) { + FreePool (CachelessKext->BinaryData); + } + if (CachelessKext->BinaryFileName != NULL) { + FreePool (CachelessKext->BinaryFileName); + } + FreePool (CachelessKext); + } + + while (!IsListEmpty (&Context->BuiltInKexts)) { + KextLink = GetFirstNode (&Context->BuiltInKexts); + BuiltinKext = GET_BUILTIN_KEXT_FROM_LINK (KextLink); + RemoveEntryList (KextLink); + + if (BuiltinKext->PlistPath != NULL) { + FreePool (BuiltinKext->PlistPath); + } + if (BuiltinKext->BundleId != NULL) { + FreePool (BuiltinKext->BundleId); + } + if (BuiltinKext->BinaryFileName != NULL) { + FreePool (BuiltinKext->BinaryFileName); + } + FreePool (BuiltinKext); + } + + ZeroMem (Context, sizeof (*Context)); +} + +EFI_STATUS +CachelessContextAddKext ( + IN OUT CACHELESS_CONTEXT *Context, + IN CONST CHAR8 *InfoPlist, + IN UINT32 InfoPlistSize, + IN CONST UINT8 *Executable OPTIONAL, + IN UINT32 ExecutableSize OPTIONAL + ) +{ + CACHELESS_KEXT *NewKext; + + XML_DOCUMENT *InfoPlistDocument; + XML_NODE *InfoPlistRoot; + XML_NODE *InfoPlistValue; + XML_NODE *InfoPlistLibraries; + CHAR8 *TmpInfoPlist; + CONST CHAR8 *TmpKeyValue; + UINT32 FieldCount; + UINT32 FieldIndex; + + BOOLEAN Failed; + BOOLEAN IsLoadable; + BOOLEAN PlistHasChanges; + CHAR8 *NewPlistData; + UINT32 NewPlistDataSize; + + ASSERT (Context != NULL); + ASSERT (InfoPlist != NULL); + ASSERT (InfoPlistSize > 0); + + IsLoadable = FALSE; + PlistHasChanges = FALSE; + + NewKext = AllocateZeroPool (sizeof (*NewKext)); + if (NewKext == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + NewKext->Signature = CACHELESS_KEXT_SIGNATURE; + NewKext->PlistData = AllocateCopyPool (InfoPlistSize, InfoPlist); + if (NewKext->PlistData == NULL) { + FreePool (NewKext); + return EFI_OUT_OF_RESOURCES; + } + NewKext->PlistDataSize = InfoPlistSize; + + // + // Allocate Info.plist copy for XML_DOCUMENT. + // + TmpInfoPlist = AllocateCopyPool (InfoPlistSize, InfoPlist); + if (TmpInfoPlist == NULL) { + FreePool (NewKext->PlistData); + FreePool (NewKext); + return EFI_OUT_OF_RESOURCES; + } + + InfoPlistDocument = XmlDocumentParse (TmpInfoPlist, InfoPlistSize, FALSE); + if (InfoPlistDocument == NULL) { + FreePool (TmpInfoPlist); + FreePool (NewKext->PlistData); + FreePool (NewKext); + return EFI_INVALID_PARAMETER; + } + + InfoPlistRoot = PlistNodeCast (PlistDocumentRoot (InfoPlistDocument), PLIST_NODE_TYPE_DICT); + if (InfoPlistRoot == NULL) { + XmlDocumentFree (InfoPlistDocument); + FreePool (TmpInfoPlist); + FreePool (NewKext->PlistData); + FreePool (NewKext); + return EFI_INVALID_PARAMETER; + } + + // + // Search for plist properties. + // + FieldCount = PlistDictChildren (InfoPlistRoot); + for (FieldIndex = 0; FieldIndex < FieldCount; ++FieldIndex) { + TmpKeyValue = PlistKeyValue (PlistDictChild (InfoPlistRoot, FieldIndex, &InfoPlistValue)); + if (TmpKeyValue == NULL) { + continue; + } + + if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_EXECUTABLE_KEY) == 0) { + // + // We are not supposed to check for this, it is XNU responsibility, which reliably panics. + // However, to avoid certain users making this kind of mistake, we still provide some + // code in debug mode to diagnose it. + // + DEBUG_CODE_BEGIN (); + if (Executable == NULL) { + DEBUG ((DEBUG_ERROR, "OCAK: Plist-only kext has %a key\n", INFO_BUNDLE_EXECUTABLE_KEY)); + ASSERT (FALSE); + CpuDeadLoop (); + } + DEBUG_CODE_END (); + + NewKext->BinaryFileName = AsciiStrCopyToUnicode (XmlNodeContent (InfoPlistValue), 0); + if (NewKext->BinaryFileName == NULL) { + XmlDocumentFree (InfoPlistDocument); + FreePool (TmpInfoPlist); + FreePool (NewKext->PlistData); + FreePool (NewKext); + return EFI_INVALID_PARAMETER; + } + + } else if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_OS_BUNDLE_REQUIRED_KEY) == 0) { + // + // If OSBundleRequired is present and is not Safe Boot, no action is required. + // + if (AsciiStrCmp (XmlNodeContent (InfoPlistValue), OS_BUNDLE_REQUIRED_SAFE_BOOT) == 0) { + XmlNodeChangeContent (InfoPlistValue, OS_BUNDLE_REQUIRED_ROOT); + PlistHasChanges = TRUE; + } + IsLoadable = TRUE; + + } else if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_LIBRARIES_KEY) == 0) { + InfoPlistLibraries = PlistNodeCast (InfoPlistValue, PLIST_NODE_TYPE_DICT); + if (InfoPlistLibraries == NULL) { + XmlDocumentFree (InfoPlistDocument); + FreePool (TmpInfoPlist); + FreePool (NewKext->PlistData); + FreePool (NewKext); + return EFI_INVALID_PARAMETER; + } + + AddKextDependencies (&Context->InjectedDependencies, InfoPlistLibraries); + } + } + + // + // Add OSBundleRequired if not found. + // + if (!IsLoadable) { + Failed = FALSE; + Failed |= XmlNodeAppend (InfoPlistRoot, "key", NULL, INFO_BUNDLE_OS_BUNDLE_REQUIRED_KEY) == NULL; + Failed |= XmlNodeAppend (InfoPlistRoot, "string", NULL, OS_BUNDLE_REQUIRED_ROOT) == NULL; + + if (Failed) { + XmlDocumentFree (InfoPlistDocument); + FreePool (TmpInfoPlist); + if (NewKext->BinaryFileName != NULL) { + FreePool (NewKext->BinaryFileName); + } + FreePool (NewKext->PlistData); + FreePool (NewKext); + return EFI_OUT_OF_RESOURCES; + } + + PlistHasChanges = TRUE; + } + + if (PlistHasChanges) { + NewPlistData = XmlDocumentExport (InfoPlistDocument, &NewPlistDataSize, 0, TRUE); + if (NewPlistData == NULL) { + XmlDocumentFree (InfoPlistDocument); + FreePool (TmpInfoPlist); + if (NewKext->BinaryFileName != NULL) { + FreePool (NewKext->BinaryFileName); + } + FreePool (NewKext->PlistData); + FreePool (NewKext); + return EFI_OUT_OF_RESOURCES; + } + FreePool (NewKext->PlistData); + NewKext->PlistData = NewPlistData; + NewKext->PlistDataSize = NewPlistDataSize; + } + + XmlDocumentFree (InfoPlistDocument); + FreePool (TmpInfoPlist); + + if (Executable != NULL) { + ASSERT (ExecutableSize > 0); + + // + // Ensure a binary name was found. + // + ASSERT (NewKext->BinaryFileName != NULL); + + NewKext->BinaryData = AllocateCopyPool (ExecutableSize, Executable); + if (NewKext->BinaryData == NULL) { + if (NewKext->BinaryFileName != NULL) { + FreePool (NewKext->BinaryFileName); + } + FreePool (NewKext->PlistData); + FreePool (NewKext); + return EFI_OUT_OF_RESOURCES; + } + NewKext->BinaryDataSize = ExecutableSize; + } + + InsertTailList (&Context->InjectedKexts, &NewKext->Link); + return EFI_SUCCESS; +} + +EFI_STATUS +CachelessContextOverlayExtensionsDir ( + IN OUT CACHELESS_CONTEXT *Context, + OUT EFI_FILE_PROTOCOL **File + ) +{ + EFI_STATUS Status; + EFI_FILE_PROTOCOL *ExtensionsDirOverlay; + + CACHELESS_KEXT *Kext; + LIST_ENTRY *KextLink; + EFI_FILE_INFO *DirectoryEntry; + EFI_FILE_PROTOCOL *NewFile; + EFI_TIME ModificationTime; + UINT32 FileNameIndex; + + ASSERT (Context != NULL); + ASSERT (File != NULL); + + // + // Create directory overlay. + // + Status = GetFileModificationTime (Context->ExtensionsDir, &ModificationTime); + if (EFI_ERROR (Status)) { + ZeroMem (&ModificationTime, sizeof (ModificationTime)); + } + + Status = VirtualDirCreateOverlayFileNameCopy (Context->ExtensionsDirFileName, NULL, Context->ExtensionsDir, &ExtensionsDirOverlay); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Inject kexts. + // + FileNameIndex = 0; + KextLink = GetFirstNode (&Context->InjectedKexts); + while (!IsNull (&Context->InjectedKexts, KextLink)) { + Kext = GET_CACHELESS_KEXT_FROM_LINK (KextLink); + + // + // Generate next available filename, testing for an existing file to ensure no conflicts. + // + do { + if (FileNameIndex == MAX_UINT32) { + return EFI_DEVICE_ERROR; + } + + UnicodeSPrint (Kext->BundleFileName, KEXT_BUNDLE_NAME_SIZE, L"Oc%8X.kext", FileNameIndex++); + + Status = Context->ExtensionsDir->Open (Context->ExtensionsDir, &NewFile, Kext->BundleFileName, EFI_FILE_MODE_READ, 0); + if (!EFI_ERROR (Status)) { + NewFile->Close (NewFile); + } + } while (!EFI_ERROR (Status)); + + DirectoryEntry = AllocateZeroPool (KEXT_BUNDLE_INFO_SIZE); + if (DirectoryEntry == NULL) { + VirtualDirFree (ExtensionsDirOverlay); + return EFI_OUT_OF_RESOURCES; + } + + // + // Populate file information. + // + CopyMem (DirectoryEntry->FileName, Kext->BundleFileName, KEXT_BUNDLE_NAME_SIZE); + DirectoryEntry->Size = KEXT_BUNDLE_INFO_SIZE; + DirectoryEntry->Attribute = EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY; + DirectoryEntry->FileSize = SIZE_OF_EFI_FILE_INFO + L_STR_SIZE (L"Contents"); + DirectoryEntry->PhysicalSize = DirectoryEntry->FileSize; + + VirtualDirAddEntry (ExtensionsDirOverlay, DirectoryEntry); + + KextLink = GetNextNode (&Context->InjectedKexts, KextLink); + } + + // + // Return the new handle for the overlayed directory. + // + *File = ExtensionsDirOverlay; + return EFI_SUCCESS; +} + +EFI_STATUS +CachelessContextPerformInject ( + IN OUT CACHELESS_CONTEXT *Context, + IN CONST CHAR16 *FileName, + OUT EFI_FILE_PROTOCOL **File + ) +{ + EFI_STATUS Status; + EFI_FILE_PROTOCOL *VirtualFileHandle; + CHAR16 *RealFileName; + UINT8 *Buffer; + UINTN BufferSize; + + CACHELESS_KEXT *Kext; + LIST_ENTRY *KextLink; + + CHAR16 *BundleName; + CHAR16 *KextExtension; + CHAR16 *BundlePath; + CHAR16 *BundleBinaryPath; + UINTN BundleBinaryPathSize; + UINTN BundleLength; + + EFI_FILE_INFO *ContentsInfo; + EFI_FILE_INFO *ContentsMacOs; + UINTN ContentsInfoEntrySize; + UINTN ContentsMacOsEntrySize; + + ASSERT (Context != NULL); + ASSERT (FileName != NULL); + ASSERT (File != NULL); + + // + // Only process injected extensions. + // + BundleName = StrStr (FileName, L"Oc"); + if (BundleName == NULL) { + return EFI_NOT_FOUND; + } + KextExtension = StrStr (BundleName, L".kext"); + if (KextExtension == NULL) { + return EFI_NOT_FOUND; + } + BundlePath = KextExtension + L_STR_LEN (L".kext"); + BundleLength = BundlePath - BundleName; + + // + // Find matching kext. + // + KextLink = GetFirstNode (&Context->InjectedKexts); + while (!IsNull (&Context->InjectedKexts, KextLink)) { + Kext = GET_CACHELESS_KEXT_FROM_LINK (KextLink); + if (StrnCmp (BundleName, Kext->BundleFileName, BundleLength) == 0) { + // + // Contents is being requested. + // + if (StrCmp (BundlePath, L"\\Contents") == 0) { + // + // Create virtual Contents directory. + // + Status = VirtualDirCreateOverlayFileNameCopy (L"Contents", NULL, NULL, &VirtualFileHandle); + if (EFI_ERROR (Status)) { + return Status; + } + + ContentsInfoEntrySize = SIZE_OF_EFI_FILE_INFO + L_STR_SIZE (L"Info.plist"); + ContentsMacOsEntrySize = SIZE_OF_EFI_FILE_INFO + L_STR_SIZE (L"MacOS"); + + // + // Create Info.plist directory entry. + // + ContentsInfo = AllocateZeroPool (ContentsInfoEntrySize); + if (ContentsInfo == NULL) { + VirtualDirFree (VirtualFileHandle); + return EFI_OUT_OF_RESOURCES; + } + ContentsInfo->Size = ContentsInfoEntrySize; + CopyMem (ContentsInfo->FileName, L"Info.plist", L_STR_SIZE (L"Info.plist")); + ContentsInfo->Attribute = EFI_FILE_READ_ONLY; + ContentsInfo->PhysicalSize = ContentsInfo->FileSize = Kext->PlistDataSize; + + // + // Create MacOS directory entry. + // + ContentsMacOs = AllocateZeroPool (ContentsMacOsEntrySize); + if (ContentsMacOs == NULL) { + FreePool (ContentsInfo); + VirtualDirFree (VirtualFileHandle); + return EFI_OUT_OF_RESOURCES; + } + ContentsMacOs->Size = ContentsMacOsEntrySize; + CopyMem (ContentsMacOs->FileName, L"MacOS", L_STR_SIZE (L"MacOS")); + ContentsMacOs->Attribute = EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY; + if (OcOverflowAddU64 (SIZE_OF_EFI_FILE_INFO, StrSize (Kext->BinaryFileName), &ContentsMacOs->FileSize)) { + FreePool (ContentsInfo); + VirtualDirFree (VirtualFileHandle); + return EFI_INVALID_PARAMETER; + } + ContentsMacOs->PhysicalSize = ContentsMacOs->FileSize; + + VirtualDirAddEntry (VirtualFileHandle, ContentsInfo); + VirtualDirAddEntry (VirtualFileHandle, ContentsMacOs); + + } else { + if (OcOverflowAddUN (L_STR_SIZE (L"\\Contents\\MacOS\\"), StrSize (Kext->BinaryFileName), &BundleBinaryPathSize)) { + return EFI_OUT_OF_RESOURCES; + } + BundleBinaryPath = AllocateZeroPool (BundleBinaryPathSize); + StrCatS (BundleBinaryPath, BundleBinaryPathSize / sizeof (CHAR16), L"\\Contents\\MacOS\\"); + StrCatS (BundleBinaryPath, BundleBinaryPathSize / sizeof (CHAR16), Kext->BinaryFileName); + + // + // Contents/Info.plist is being requested. + // + if (StrCmp (BundlePath, L"\\Contents\\Info.plist") == 0) { + // Get Info.plist. + RealFileName = L"Info.plist"; + BufferSize = Kext->PlistDataSize; + Buffer = AllocateCopyPool (BufferSize, Kext->PlistData); + if (Buffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Contents/MacOS/BINARY is being requested. + // It should be safe to assume there will only be one binary ever requested per kext? + // + } else if (StrCmp (BundlePath, BundleBinaryPath) == 0) { + RealFileName = Kext->BinaryFileName; + BufferSize = Kext->BinaryDataSize; + Buffer = AllocateCopyPool (BufferSize, Kext->BinaryData); + if (Buffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + } else { + return EFI_NOT_FOUND; + } + + // + // Create virtual file. + // + Status = CreateVirtualFileFileNameCopy (RealFileName, Buffer, BufferSize, NULL, &VirtualFileHandle); + if (EFI_ERROR (Status)) { + FreePool (Buffer); + return EFI_OUT_OF_RESOURCES; + } + } + + // + // Return our handle. + // + *File = VirtualFileHandle; + return EFI_SUCCESS; + } + + KextLink = GetNextNode (&Context->InjectedKexts, KextLink); + } + + return EFI_NOT_FOUND; +} + +EFI_STATUS +CachelessContextHookBuiltin ( + IN OUT CACHELESS_CONTEXT *Context, + IN CONST CHAR16 *FileName, + IN EFI_FILE_PROTOCOL *File, + OUT EFI_FILE_PROTOCOL **VirtualFile + ) +{ + EFI_STATUS Status; + BUILTIN_KEXT *BuiltinKext; + DEPEND_KEXT *DependKext; + LIST_ENTRY *KextLink; + + CHAR8 *InfoPlist; + UINT32 InfoPlistSize; + XML_DOCUMENT *InfoPlistDocument; + XML_NODE *InfoPlistRoot; + XML_NODE *InfoPlistValue; + CONST CHAR8 *TmpKeyValue; + UINT32 FieldCount; + UINT32 FieldIndex; + + BOOLEAN Failed; + CHAR8 *NewPlistData; + UINT32 NewPlistDataSize; + + ASSERT (Context != NULL); + ASSERT (FileName != NULL); + ASSERT (File != NULL); + ASSERT (VirtualFile != NULL); + + // + // Scan built-in kexts if we have not yet done so. + // + if (!Context->BuiltInKextsValid) { + DEBUG ((DEBUG_INFO, "OCAK: Built-in kext cache is not yet built, building...\n")); + + // + // Build list of kexts in system Extensions directory. + // + Status = ScanExtensions (Context, Context->ExtensionsDir, Context->ExtensionsDirFileName, TRUE); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Scan dependencies, adding any others besides ones being injected. + // + KextLink = GetFirstNode (&Context->InjectedDependencies); + while (!IsNull (&Context->InjectedDependencies, KextLink)) { + DependKext = GET_DEPEND_KEXT_FROM_LINK (KextLink); + + Status = ScanDependencies (Context, DependKext->BundleId); + if (EFI_ERROR (Status)) { + return Status; + } + + KextLink = GetNextNode (&Context->InjectedDependencies, KextLink); + } + Context->BuiltInKextsValid = TRUE; + } + + // + // Info.plist. + // + if (OcUnicodeEndsWith (FileName, L"Info.plist")) { + BuiltinKext = LookupBuiltinKextForPlistPath (Context, FileName); + if (BuiltinKext != NULL && BuiltinKext->PatchValidOSBundleRequired) { + DEBUG ((DEBUG_INFO, "OCAK: Processing patches for %s\n", FileName)); + + // + // Open Info.plist + // + Status = AllocateCopyFileData (File, (UINT8**)&InfoPlist, &InfoPlistSize); + if (EFI_ERROR (Status)) { + return Status; + } + + InfoPlistDocument = XmlDocumentParse (InfoPlist, InfoPlistSize, FALSE); + if (InfoPlistDocument == NULL) { + FreePool (InfoPlist); + return EFI_INVALID_PARAMETER; + } + + InfoPlistRoot = PlistNodeCast (PlistDocumentRoot (InfoPlistDocument), PLIST_NODE_TYPE_DICT); + if (InfoPlistRoot == NULL) { + XmlDocumentFree (InfoPlistDocument); + FreePool (InfoPlist); + return EFI_INVALID_PARAMETER; + } + + // + // If kext is present but invalid, we need to change it. + // Otherwise add new property. + // + if (BuiltinKext->OSBundleRequiredValue == KEXT_OSBUNDLE_REQUIRED_INVALID) { + FieldCount = PlistDictChildren (InfoPlistRoot); + for (FieldIndex = 0; FieldIndex < FieldCount; ++FieldIndex) { + TmpKeyValue = PlistKeyValue (PlistDictChild (InfoPlistRoot, FieldIndex, &InfoPlistValue)); + if (TmpKeyValue == NULL) { + continue; + } + + if (AsciiStrCmp (TmpKeyValue, INFO_BUNDLE_OS_BUNDLE_REQUIRED_KEY) == 0) { + XmlNodeChangeContent (InfoPlistValue, OS_BUNDLE_REQUIRED_ROOT); + } + } + + } else if (BuiltinKext->OSBundleRequiredValue == KEXT_OSBUNDLE_REQUIRED_NONE) { + Failed = FALSE; + Failed |= XmlNodeAppend (InfoPlistRoot, "key", NULL, INFO_BUNDLE_OS_BUNDLE_REQUIRED_KEY) == NULL; + Failed |= XmlNodeAppend (InfoPlistRoot, "string", NULL, OS_BUNDLE_REQUIRED_ROOT) == NULL; + if (Failed) { + XmlDocumentFree (InfoPlistDocument); + FreePool (InfoPlist); + return EFI_OUT_OF_RESOURCES; + } + } + + // + // Export plist. + // + NewPlistData = XmlDocumentExport (InfoPlistDocument, &NewPlistDataSize, 0, TRUE); + if (NewPlistData == NULL) { + XmlDocumentFree (InfoPlistDocument); + FreePool (InfoPlist); + return EFI_OUT_OF_RESOURCES; + } + + XmlDocumentFree (InfoPlistDocument); + FreePool (InfoPlist); + + // + // Virtualize newly created Info.plist. + // + Status = CreateVirtualFileFileNameCopy (FileName, NewPlistData, NewPlistDataSize, NULL, VirtualFile); + if (EFI_ERROR (Status)) { + *VirtualFile = NULL; + FreePool (NewPlistData); + } + return Status; + } + } + + // + // TODO: hook binaries for patches here. + // + + *VirtualFile = NULL; + + return EFI_SUCCESS; +} diff --git a/Library/OcAppleKernelLib/CachelessInternal.h b/Library/OcAppleKernelLib/CachelessInternal.h new file mode 100644 index 00000000..d7a5fbce --- /dev/null +++ b/Library/OcAppleKernelLib/CachelessInternal.h @@ -0,0 +1,187 @@ +/** @file + Cacheless boot (S/L/E) support. + + Copyright (c) 2020, Goldfish64. 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. + +**/ + +#ifndef CACHELESS_INTERNAL_H +#define CACHELESS_INTERNAL_H + +#include + +// +// Names are of format OcXXXXXXXX.kext, where XXXXXXXX is a 32-bit hexadecimal number. +// +#define KEXT_BUNDLE_NAME L"OcXXXXXXXX.kext" +#define KEXT_BUNDLE_NAME_SIZE (L_STR_SIZE (KEXT_BUNDLE_NAME)) +#define KEXT_BUNDLE_NAME_LEN (L_STR_LEN (KEXT_BUNDLE_NAME)) +#define KEXT_BUNDLE_INFO_SIZE (SIZE_OF_EFI_FILE_INFO + KEXT_BUNDLE_NAME_SIZE) + +enum { + KEXT_OSBUNDLE_REQUIRED_NONE = 0, + KEXT_OSBUNDLE_REQUIRED_INVALID, + KEXT_OSBUNDLE_REQUIRED_VALID +}; + +// +// Kext dependency. +// +typedef struct { + // + // Signature. + // + UINT32 Signature; + // + // Link for global list. + // + LIST_ENTRY Link; + // + // Bundle ID. + // + CHAR8 *BundleId; +} DEPEND_KEXT; + +// +// Cacheless kext. +// +typedef struct { + // + // Signature. + // + UINT32 Signature; + // + // Link for global list (CACHELESS_CONTEXT -> InjectedKexts). + // + LIST_ENTRY Link; + // + // Bundle filename used during S/L/E overlay creation. + // + CHAR16 BundleFileName[KEXT_BUNDLE_NAME_LEN + 1]; + // + // Plist data. + // + CHAR8 *PlistData; + // + // Plist data size. + // + UINT32 PlistDataSize; + // + // Binary data. + // + UINT8 *BinaryData; + // + // Binary data size. + // + UINT32 BinaryDataSize; + // + // Binary file name. + // + CHAR16 *BinaryFileName; +} CACHELESS_KEXT; + +// +// Built-in kexts in SLE. +// +typedef struct { + // + // Signature. + // + UINT32 Signature; + // + // Link for global list (CACHELESS_CONTEXT -> BuiltInKexts). + // + LIST_ENTRY Link; + // + // Plist path. + // + CHAR16 *PlistPath; + // + // Bundle ID. + // + CHAR8 *BundleId; + // + // Binary file name. + // + CHAR16 *BinaryFileName; + // + // Dependencies. + // + LIST_ENTRY Dependencies; + // + // OSBundleRequired is valid? + // + UINT8 OSBundleRequiredValue; + // + // Needs OSBundleRequired override for dependency injection? + // + BOOLEAN PatchValidOSBundleRequired; + // + // Needs patches or blocks? + // + BOOLEAN PatchKext; +} BUILTIN_KEXT; + +// +// DEPEND_KEXT signature for list identification. +// +#define DEPEND_KEXT_SIGNATURE SIGNATURE_32 ('S', 'l', 'e', 'D') + +/** + Gets the next element in list of DEPEND_KEXT. + + @param[in] This The current ListEntry. +**/ +#define GET_DEPEND_KEXT_FROM_LINK(This) \ + (CR ( \ + (This), \ + DEPEND_KEXT, \ + Link, \ + DEPEND_KEXT_SIGNATURE \ + )) + +// +// CACHELESS_KEXT signature for list identification. +// +#define CACHELESS_KEXT_SIGNATURE SIGNATURE_32 ('S', 'l', 'e', 'X') + +/** + Gets the next element in InjectedKexts list of CACHELESS_KEXT. + + @param[in] This The current ListEntry. +**/ +#define GET_CACHELESS_KEXT_FROM_LINK(This) \ + (CR ( \ + (This), \ + CACHELESS_KEXT, \ + Link, \ + CACHELESS_KEXT_SIGNATURE \ + )) + +// +// BUILTIN_KEXT signature for list identification. +// +#define BUILTIN_KEXT_SIGNATURE SIGNATURE_32 ('S', 'l', 'e', 'B') + +/** + Gets the next element in BuiltInKexts list of CACHELESS_KEXT. + + @param[in] This The current ListEntry. +**/ +#define GET_BUILTIN_KEXT_FROM_LINK(This) \ + (CR ( \ + (This), \ + BUILTIN_KEXT, \ + Link, \ + BUILTIN_KEXT_SIGNATURE \ + )) + +#endif diff --git a/Library/OcAppleKernelLib/OcAppleKernelLib.inf b/Library/OcAppleKernelLib/OcAppleKernelLib.inf index 25972065..26b1d209 100644 --- a/Library/OcAppleKernelLib/OcAppleKernelLib.inf +++ b/Library/OcAppleKernelLib/OcAppleKernelLib.inf @@ -38,6 +38,7 @@ PrelinkedInternal.h PrelinkedKext.c Vtables.c + CachelessContext.c [Packages] MdePkg/MdePkg.dec diff --git a/Library/OcStringLib/OcAsciiLib.c b/Library/OcStringLib/OcAsciiLib.c index 3a8f1c1d..06d9f944 100755 --- a/Library/OcStringLib/OcAsciiLib.c +++ b/Library/OcStringLib/OcAsciiLib.c @@ -166,3 +166,23 @@ OcAsciiSafeSPrint ( return Status; } + +BOOLEAN +EFIAPI +OcAsciiEndsWith ( + IN CONST CHAR8 *String, + IN CONST CHAR8 *SearchString + ) +{ + UINTN StringLength; + UINTN SearchStringLength; + + ASSERT (String != NULL); + ASSERT (SearchString != NULL); + + StringLength = AsciiStrLen (String); + SearchStringLength = AsciiStrLen (SearchString); + + return StringLength >= SearchStringLength + && AsciiStrnCmp (&String[StringLength - SearchStringLength], SearchString, SearchStringLength) == 0; +} diff --git a/Library/OcStringLib/OcUnicodeLib.c b/Library/OcStringLib/OcUnicodeLib.c index fa0df514..913b412b 100755 --- a/Library/OcStringLib/OcUnicodeLib.c +++ b/Library/OcStringLib/OcUnicodeLib.c @@ -271,3 +271,23 @@ OcUnicodeSafeSPrint ( return Status; } + +BOOLEAN +EFIAPI +OcUnicodeEndsWith ( + IN CONST CHAR16 *String, + IN CONST CHAR16 *SearchString + ) +{ + UINTN StringLength; + UINTN SearchStringLength; + + ASSERT (String != NULL); + ASSERT (SearchString != NULL); + + StringLength = StrLen (String); + SearchStringLength = StrLen (SearchString); + + return StringLength >= SearchStringLength + && StrnCmp (&String[StringLength - SearchStringLength], SearchString, SearchStringLength) == 0; +} diff --git a/Platform/OpenCore/OpenCoreKernel.c b/Platform/OpenCore/OpenCoreKernel.c index 49b6b5ae..940087d9 100644 --- a/Platform/OpenCore/OpenCoreKernel.c +++ b/Platform/OpenCore/OpenCoreKernel.c @@ -29,6 +29,11 @@ STATIC OC_STORAGE_CONTEXT *mOcStorage; STATIC OC_GLOBAL_CONFIG *mOcConfiguration; STATIC OC_CPU_INFO *mOcCpuInfo; +STATIC UINT32 mOcDarwinVersion; + +STATIC CACHELESS_CONTEXT mOcCachelessContext; +STATIC BOOLEAN mOcCachelessInProgress; + STATIC UINT32 OcParseDarwinVersion ( @@ -720,6 +725,76 @@ OcKernelProcessPrelinked ( return Status; } +STATIC +EFI_STATUS +OcKernelInitCacheless ( + IN OC_GLOBAL_CONFIG *Config, + IN CACHELESS_CONTEXT *Context, + IN UINT32 DarwinVersion, + IN CHAR16 *FileName, + IN EFI_FILE_PROTOCOL *ExtensionsDir, + OUT EFI_FILE_PROTOCOL **File + ) +{ + EFI_STATUS Status; + UINT32 Index; + + OC_KERNEL_ADD_ENTRY *Kext; + CHAR8 *BundlePath; + CHAR8 *Comment; + UINT32 MaxKernel; + UINT32 MinKernel; + + Status = CachelessContextInit (Context, FileName, ExtensionsDir); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Add kexts into cacheless context. + // + for (Index = 0; Index < Config->Kernel.Add.Count; Index++) { + Kext = Config->Kernel.Add.Values[Index]; + + if (!Kext->Enabled || Kext->PlistDataSize == 0) { + continue; + } + + BundlePath = OC_BLOB_GET (&Kext->BundlePath); + Comment = OC_BLOB_GET (&Kext->Comment); + MaxKernel = OcParseDarwinVersion (OC_BLOB_GET (&Kext->MaxKernel)); + MinKernel = OcParseDarwinVersion (OC_BLOB_GET (&Kext->MinKernel)); + + if (!OcMatchDarwinVersion (DarwinVersion, MinKernel, MaxKernel)) { + DEBUG (( + DEBUG_INFO, + "OC: Cacheless injection skips %a (%a) kext at %u due to version %u <= %u <= %u\n", + BundlePath, + Comment, + Index, + MinKernel, + DarwinVersion, + MaxKernel + )); + continue; + } + + Status = CachelessContextAddKext ( + Context, + Kext->PlistData, + Kext->PlistDataSize, + Kext->ImageData, + Kext->ImageDataSize + ); + if (EFI_ERROR (Status)) { + CachelessContextFree (Context); + return Status; + } + } + + return CachelessContextOverlayExtensionsDir (Context, File); +} + STATIC EFI_STATUS EFIAPI @@ -740,12 +815,29 @@ OcKernelFileOpen ( EFI_FILE_PROTOCOL *VirtualFileHandle; EFI_STATUS PrelinkedStatus; EFI_TIME ModificationTime; - UINT32 DarwinVersion; UINT32 ReservedInfoSize; UINT32 ReservedExeSize; UINT32 LinkedExpansion; UINT32 ReservedFullSize; + // + // Hook injected OcXXXXXXXX.kext reads from /S/L/E. + // + if (mOcCachelessInProgress + && OpenMode == EFI_FILE_MODE_READ + && StrnCmp (FileName, L"System\\Library\\Extensions\\Oc", L_STR_LEN (L"System\\Library\\Extensions\\Oc")) == 0) { + Status = CachelessContextPerformInject (&mOcCachelessContext, FileName, NewHandle); + DEBUG (( + DEBUG_INFO, + "OC: Hooking SLE injected file %s with %u mode gave - %r\n", + FileName, + (UINT32) OpenMode, + Status + )); + + return Status; + } + Status = SafeFileOpen (This, NewHandle, FileName, OpenMode, Attributes); DEBUG (( @@ -802,12 +894,12 @@ OcKernelFileOpen ( DEBUG ((DEBUG_INFO, "OC: Result of XNU hook on %s is %r\n", FileName, Status)); if (!EFI_ERROR (Status)) { - DarwinVersion = OcKernelReadDarwinVersion (Kernel, KernelSize); - OcKernelApplyPatches (mOcConfiguration, DarwinVersion, NULL, Kernel, KernelSize); + mOcDarwinVersion = OcKernelReadDarwinVersion (Kernel, KernelSize); + OcKernelApplyPatches (mOcConfiguration, mOcDarwinVersion, NULL, Kernel, KernelSize); PrelinkedStatus = OcKernelProcessPrelinked ( mOcConfiguration, - DarwinVersion, + mOcDarwinVersion, Kernel, &KernelSize, AllocatedSize, @@ -850,6 +942,61 @@ OcKernelFileOpen ( } } + // + // Hook /S/L/E for cacheless boots. + // + if (OpenMode == EFI_FILE_MODE_READ + && StrCmp (FileName, L"System\\Library\\Extensions") == 0) { + + mOcCachelessInProgress = FALSE; + + OcKernelLoadKextsAndReserve ( + mOcStorage, + mOcConfiguration, + &ReservedExeSize, + &ReservedInfoSize + ); + + // + // Initialize Extensions directory overlay for cacheless injection. + // + Status = OcKernelInitCacheless ( + mOcConfiguration, + &mOcCachelessContext, + mOcDarwinVersion, + FileName, + *NewHandle, + &VirtualFileHandle + ); + + DEBUG ((DEBUG_INFO, "OC: Result of SLE hook on %s is %r\n", FileName, Status)); + + if (!EFI_ERROR (Status)) { + mOcCachelessInProgress = TRUE; + *NewHandle = VirtualFileHandle; + return EFI_SUCCESS; + } + } + + // + // Hook /S/L/E contents for processing during cacheless boots. + // + if (mOcCachelessInProgress + && OpenMode == EFI_FILE_MODE_READ + && StrnCmp (FileName, L"System\\Library\\Extensions\\", L_STR_LEN (L"System\\Library\\Extensions\\")) == 0) { + Status = CachelessContextHookBuiltin ( + &mOcCachelessContext, + FileName, + *NewHandle, + &VirtualFileHandle + ); + + if (!EFI_ERROR (Status) && VirtualFileHandle != NULL) { + *NewHandle = VirtualFileHandle; + return EFI_SUCCESS; + } + } + // // This is not Apple kernel, just return the original file. // We recurse the filtering to additionally catch com.apple.boot.[RPS] directories. @@ -869,9 +1016,11 @@ OcLoadKernelSupport ( Status = EnableVirtualFs (gBS, OcKernelFileOpen); if (!EFI_ERROR (Status)) { - mOcStorage = Storage; - mOcConfiguration = Config; - mOcCpuInfo = CpuInfo; + mOcStorage = Storage; + mOcConfiguration = Config; + mOcCpuInfo = CpuInfo; + mOcDarwinVersion = 0; + mOcCachelessInProgress = FALSE; } else { DEBUG ((DEBUG_ERROR, "OC: Failed to enable vfs - %r\n", Status)); } -- GitLab