diff --git a/Changelog.md b/Changelog.md index ca93bf075ac..a1d9fbd8153 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ OpenCore Changelog - Added option to hide verbose output from any driver, thx @ilikesn0w #### v1.0.7 +- Added System KC loading and cross-KC dependency resolution in OcAppleKernelLib - Improved `XhciPortLimit` compatibility with macOS Tahoe, thx @laobamac - Updated builtin firmware versions for SMBIOS and the rest - Migrated to edk2-stable202511 diff --git a/Include/Acidanthera/Library/OcAppleKernelLib.h b/Include/Acidanthera/Library/OcAppleKernelLib.h index e097f7f3f24..527a0553807 100644 --- a/Include/Acidanthera/Library/OcAppleKernelLib.h +++ b/Include/Acidanthera/Library/OcAppleKernelLib.h @@ -340,6 +340,30 @@ typedef struct { // Prelinked is 32-bit. // BOOLEAN Is32Bit; + // + // System KC buffer used to resolve cross-KC kext dependencies + // (e.g. IOGraphicsFamily). Optional; set via PrelinkedContextLoadSystemKC. + // Ownership is held by the context and released in PrelinkedContextFree. + // + UINT8 *SystemKC; + // + // System KC buffer size in bytes. + // + UINT32 SystemKCSize; + // + // Mach-O context covering the System KC outer header. + // + OC_MACHO_CONTEXT SystemKCMachContext; + // + // Mach-O context covering the System KC inner kernel when present, + // otherwise a copy of SystemKCMachContext. Used as the symtab source + // for fileset-entry kexts. + // + OC_MACHO_CONTEXT SystemKCInnerMachContext; + // + // TRUE once a System KC has been parsed and fixed up successfully. + // + BOOLEAN SystemKCValid; } PRELINKED_CONTEXT; // @@ -822,6 +846,34 @@ PrelinkedDependencyInsert ( IN VOID *Buffer ); +/** + Associate a System Kernel Collection buffer with a prelinked context. + + On macOS 11+ common kext libraries such as IOGraphicsFamily live in + the System KC rather than the Boot KC. When this function succeeds, + InternalCachedPrelinkedKext () can satisfy such dependencies by + walking the System KC's LC_FILESET_ENTRY commands. + + LC_DYLD_CHAINED_FIXUPS on the outer collection are applied in place + so subsequent pointer reads observe resolved virtual addresses. + + @param[in,out] Context Initialized prelinked context. + @param[in] SystemKC System KC buffer. Ownership transfers to + the context and is released by + PrelinkedContextFree (). + @param[in] SystemKCSize System KC buffer size in bytes. + + @retval EFI_SUCCESS System KC associated successfully. + @retval EFI_INVALID_PARAMETER Buffer is not a valid Mach-O kernel + collection. +**/ +EFI_STATUS +PrelinkedContextLoadSystemKC ( + IN OUT PRELINKED_CONTEXT *Context, + IN UINT8 *SystemKC, + IN UINT32 SystemKCSize + ); + /** Drop current plist entry, required for kext injection. Ensure that prelinked text can grow with new kexts. diff --git a/Library/OcAppleKernelLib/Link.c b/Library/OcAppleKernelLib/Link.c index e84db58480d..9b91120f857 100644 --- a/Library/OcAppleKernelLib/Link.c +++ b/Library/OcAppleKernelLib/Link.c @@ -386,7 +386,30 @@ InternalSolveSymbolNonWeak ( OcGetSymbolFirstLevel ); if (ResolveSymbol != NULL) { - InternalSolveSymbolValue (Context->Is32Bit, ResolveSymbol->Value, Symbol); + UINT64 ResolvedValue; + + ResolvedValue = ResolveSymbol->Value; + + // + // System KC kexts expose symbols with raw fileset virtual addresses + // (e.g. 0x149xxxxx, in the 1 MB .. 512 MB range). These must be + // translated into the kernel address space before the link phase + // emits RIP-relative relocations, otherwise the 32-bit displacements + // cannot reach them. The translation mirrors KcFixupValue (), which + // performs the same adjustment for vtable pointers; on macOS 11+ + // all kernel collections share a single KASLR slide. + // + // Kernel-space values, zero, and weak test placeholders must not + // be translated. + // + if ( !Context->Is32Bit + && (ResolvedValue >= BASE_1MB) + && (ResolvedValue < BASE_1MB * 512ULL)) + { + ResolvedValue = ResolvedValue + KERNEL_FIXUP_OFFSET + KERNEL_ADDRESS_BASE; + } + + InternalSolveSymbolValue (Context->Is32Bit, ResolvedValue, Symbol); } return TRUE; diff --git a/Library/OcAppleKernelLib/PrelinkedContext.c b/Library/OcAppleKernelLib/PrelinkedContext.c index a2b3873bf07..3e87dd23247 100644 --- a/Library/OcAppleKernelLib/PrelinkedContext.c +++ b/Library/OcAppleKernelLib/PrelinkedContext.c @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -472,6 +473,15 @@ PrelinkedContextFree ( Context->PrelinkedStateKexts = NULL; } + // + // Free System KC buffer if loaded. + // + if (Context->SystemKC != NULL) { + FreePool (Context->SystemKC); + Context->SystemKC = NULL; + Context->SystemKCValid = FALSE; + } + while (!IsListEmpty (&Context->PrelinkedKexts)) { Link = GetFirstNode (&Context->PrelinkedKexts); Kext = GET_PRELINKED_KEXT_FROM_LINK (Link); @@ -487,6 +497,246 @@ PrelinkedContextFree ( ZeroMem (&Context->InjectedKexts, sizeof (Context->InjectedKexts)); } +/** + Apply DYLD chained fixups to an on-disk kernel collection Mach-O. + + A kernel collection ships with LC_DYLD_CHAINED_FIXUPS describing a chain + of rebase entries that dyld (or, at boot time, the kernel loader) walks + to materialise raw pointers in the collection's data segments. When we + want to consult the collection pre-boot we must perform the same walk + ourselves, otherwise the pointers we later read (symbol table entries, + vtables, etc.) are still encoded fixup values. + + Only the x86_64 and arm64e kernel-cache pointer formats are supported. + + @param[in] Buffer Pointer to the collection Mach-O buffer. + @param[in] MachContext Initialised Mach-O context for Buffer. + @param[out] FixupCountOut Optional, populated with the total number + of rebases that were resolved. + + @retval EFI_SUCCESS Fixups applied (or none present). + @retval EFI_UNSUPPORTED LC_DYLD_CHAINED_FIXUPS not present. +**/ +STATIC +EFI_STATUS +InternalApplyKernelCollectionFixups ( + IN UINT8 *Buffer, + IN OC_MACHO_CONTEXT *MachContext, + OUT UINT32 *FixupCountOut OPTIONAL + ) +{ + MACH_HEADER_64 *Header; + MACH_LOAD_COMMAND *LoadCmd; + MACH_LINKEDIT_DATA_COMMAND *ChainedFixupsCmd; + MACHO_DYLD_CHAINED_FIXUPS_HEADER *FixupsHeader; + MACH_DYLD_CHAINED_STARTS_IN_IMAGE *StartsInImage; + MACH_DYLD_CHAINED_STARTS_IN_SEGMENT *StartsInSeg; + MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE_REBASE *Fixup; + UINT32 CmdIndex; + UINT32 CmdOffset; + UINT32 SegIndex; + UINT32 PageIndex; + UINT32 FixupCount; + UINT16 PageStart; + UINT64 *FixupLocation; + + ChainedFixupsCmd = NULL; + Header = MachoGetMachHeader64 (MachContext); + if (Header == NULL) { + return EFI_UNSUPPORTED; + } + + CmdOffset = sizeof (MACH_HEADER_64); + for (CmdIndex = 0; CmdIndex < Header->NumCommands; ++CmdIndex) { + LoadCmd = (MACH_LOAD_COMMAND *)(Buffer + CmdOffset); + if (LoadCmd->CommandType == MACH_LOAD_COMMAND_DYLD_CHAINED_FIXUPS) { + ChainedFixupsCmd = (MACH_LINKEDIT_DATA_COMMAND *)LoadCmd; + break; + } + + CmdOffset += LoadCmd->CommandSize; + } + + if (ChainedFixupsCmd == NULL) { + if (FixupCountOut != NULL) { + *FixupCountOut = 0; + } + + return EFI_UNSUPPORTED; + } + + FixupsHeader = (MACHO_DYLD_CHAINED_FIXUPS_HEADER *)(Buffer + ChainedFixupsCmd->DataOffset); + StartsInImage = (MACH_DYLD_CHAINED_STARTS_IN_IMAGE *)( + (UINT8 *)FixupsHeader + FixupsHeader->StartsOffset + ); + + FixupCount = 0; + + for (SegIndex = 0; SegIndex < StartsInImage->NumSegments; ++SegIndex) { + if (StartsInImage->SegInfoOffset[SegIndex] == 0) { + continue; + } + + StartsInSeg = (MACH_DYLD_CHAINED_STARTS_IN_SEGMENT *)( + (UINT8 *)StartsInImage + StartsInImage->SegInfoOffset[SegIndex] + ); + + if ( (StartsInSeg->PointerFormat != MACH_DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE) + && (StartsInSeg->PointerFormat != MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE)) + { + continue; + } + + for (PageIndex = 0; PageIndex < StartsInSeg->PageCount; ++PageIndex) { + PageStart = StartsInSeg->PageStart[PageIndex]; + if (PageStart == MACH_DYLD_CHAINED_PTR_START_NONE) { + continue; + } + + FixupLocation = (UINT64 *)( + Buffer + StartsInSeg->SegmentOffset + + (UINT64)PageIndex * StartsInSeg->PageSize + + PageStart + ); + + while (TRUE) { + Fixup = (MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE_REBASE *)FixupLocation; + + // + // For cacheLevel 0 (this KC) the Target field is the resolved + // virtual address; for cacheLevel 1 the target is a virtual + // address in a different KC but the encoding is the same. In + // either case we write Target in place of the fixup slot. + // + *FixupLocation = Fixup->Target; + ++FixupCount; + + if (Fixup->Next == 0) { + break; + } + + // + // Stride is 1 byte for x86_64 kernel cache, 4 bytes for arm64e. + // + if (StartsInSeg->PointerFormat == MACH_DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE) { + FixupLocation = (UINT64 *)((UINT8 *)FixupLocation + Fixup->Next); + } else { + FixupLocation = (UINT64 *)((UINT8 *)FixupLocation + Fixup->Next * 4); + } + } + } + } + + if (FixupCountOut != NULL) { + *FixupCountOut = FixupCount; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +PrelinkedContextLoadSystemKC ( + IN OUT PRELINKED_CONTEXT *Context, + IN UINT8 *SystemKC, + IN UINT32 SystemKCSize + ) +{ + EFI_STATUS Status; + MACH_SEGMENT_COMMAND_64 *Segment; + UINT32 FixupCount; + + ASSERT (Context != NULL); + ASSERT (SystemKC != NULL); + ASSERT (SystemKCSize > 0); + + Context->SystemKC = SystemKC; + Context->SystemKCSize = SystemKCSize; + Context->SystemKCValid = FALSE; + + // + // Initialise the Mach-O context covering the entire System KC. + // + if (!MachoInitializeContext64 ( + &Context->SystemKCMachContext, + SystemKC, + SystemKCSize, + 0, + SystemKCSize + )) + { + DEBUG ((DEBUG_WARN, "OCAK: System KC header is not a valid Mach-O\n")); + return EFI_INVALID_PARAMETER; + } + + // + // Two supported KC shapes: + // 1. Boot KC - contains an embedded kernel in the __TEXT_EXEC segment. + // Its LC_SYMTAB lives in the inner (kernel) context. + // 2. System KC - kexts only, no embedded kernel. LC_SYMTAB lives in the + // outer Mach-O header. + // We probe for __TEXT_EXEC and set up the inner context accordingly so + // that MachoInitialiseSymtabsExternal () works in either case. + // + Segment = MachoGetSegmentByName64 ( + &Context->SystemKCMachContext, + "__TEXT_EXEC" + ); + + if ((Segment != NULL) && (Segment->VirtualAddress >= Segment->FileOffset)) { + if (!MachoInitializeContext64 ( + &Context->SystemKCInnerMachContext, + SystemKC, + SystemKCSize, + (UINT32)Segment->FileOffset, + (UINT32)(SystemKCSize - Segment->FileOffset) + )) + { + DEBUG ((DEBUG_WARN, "OCAK: System KC __TEXT_EXEC inner init failed\n")); + return EFI_INVALID_PARAMETER; + } + + MachoInitialiseSymtabsExternal ( + &Context->SystemKCMachContext, + &Context->SystemKCInnerMachContext + ); + } else { + // + // No __TEXT_EXEC: the outer header already owns LC_SYMTAB. Clone the + // outer context as the inner context so dependents can uniformly call + // MachoInitialiseSymtabsExternal (). + // + CopyMem ( + &Context->SystemKCInnerMachContext, + &Context->SystemKCMachContext, + sizeof (OC_MACHO_CONTEXT) + ); + } + + // + // Apply LC_DYLD_CHAINED_FIXUPS on the outer KC so downstream pointer + // reads (e.g. vtable patching) observe real virtual addresses. + // + FixupCount = 0; + Status = InternalApplyKernelCollectionFixups ( + SystemKC, + &Context->SystemKCMachContext, + &FixupCount + ); + if (EFI_ERROR (Status) && (Status != EFI_UNSUPPORTED)) { + return Status; + } + + Context->SystemKCValid = TRUE; + DEBUG (( + DEBUG_INFO, + "OCAK: System KC ready (%u bytes, %u chained fixups)\n", + SystemKCSize, + FixupCount + )); + + return EFI_SUCCESS; +} + EFI_STATUS PrelinkedDependencyInsert ( IN OUT PRELINKED_CONTEXT *Context, diff --git a/Library/OcAppleKernelLib/PrelinkedKext.c b/Library/OcAppleKernelLib/PrelinkedKext.c index 9c0975cbda3..b56372ece83 100644 --- a/Library/OcAppleKernelLib/PrelinkedKext.c +++ b/Library/OcAppleKernelLib/PrelinkedKext.c @@ -23,8 +23,244 @@ #include #include +#include + #include "PrelinkedInternal.h" +/** + Apply LC_DYLD_CHAINED_FIXUPS for a single fileset kext's own fixup chains. + + Each System KC fileset entry ships its own chained-fixup table covering + the kext's data segments. Applying these in-place converts encoded fixup + slots into resolved virtual addresses so later vtable patching observes + real pointers. + + @param[in] KCBuffer Pointer to the containing kernel collection buffer. + @param[in] KextCtx Mach-O context for the fileset kext. + + @return Count of fixup slots resolved (may be 0 if no fixup command). +**/ +STATIC +UINT32 +InternalApplyFilesetKextFixups ( + IN UINT8 *KCBuffer, + IN OC_MACHO_CONTEXT *KextCtx + ) +{ + MACH_HEADER_64 *Header; + MACH_LOAD_COMMAND *LoadCmd; + MACH_LINKEDIT_DATA_COMMAND *FixupsCmd; + MACHO_DYLD_CHAINED_FIXUPS_HEADER *FixupsHdr; + MACH_DYLD_CHAINED_STARTS_IN_IMAGE *Starts; + MACH_DYLD_CHAINED_STARTS_IN_SEGMENT *StartsSeg; + MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE_REBASE *Fixup; + UINT32 CmdIdx; + UINT32 CmdOff; + UINT32 SegIdx; + UINT32 PageIdx; + UINT32 Resolved; + UINT64 *FixupLoc; + + FixupsCmd = NULL; + Header = MachoGetMachHeader64 (KextCtx); + if (Header == NULL) { + return 0; + } + + CmdOff = (UINT32)((UINT8 *)Header - KCBuffer) + sizeof (MACH_HEADER_64); + for (CmdIdx = 0; CmdIdx < Header->NumCommands; ++CmdIdx) { + LoadCmd = (MACH_LOAD_COMMAND *)(KCBuffer + CmdOff); + if (LoadCmd->CommandType == MACH_LOAD_COMMAND_DYLD_CHAINED_FIXUPS) { + FixupsCmd = (MACH_LINKEDIT_DATA_COMMAND *)LoadCmd; + break; + } + + CmdOff += LoadCmd->CommandSize; + } + + if (FixupsCmd == NULL) { + return 0; + } + + FixupsHdr = (MACHO_DYLD_CHAINED_FIXUPS_HEADER *)(KCBuffer + FixupsCmd->DataOffset); + Starts = (MACH_DYLD_CHAINED_STARTS_IN_IMAGE *)( + (UINT8 *)FixupsHdr + FixupsHdr->StartsOffset + ); + + Resolved = 0; + + for (SegIdx = 0; SegIdx < Starts->NumSegments; ++SegIdx) { + if (Starts->SegInfoOffset[SegIdx] == 0) { + continue; + } + + StartsSeg = (MACH_DYLD_CHAINED_STARTS_IN_SEGMENT *)( + (UINT8 *)Starts + Starts->SegInfoOffset[SegIdx] + ); + + if ( (StartsSeg->PointerFormat != MACH_DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE) + && (StartsSeg->PointerFormat != MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE)) + { + continue; + } + + for (PageIdx = 0; PageIdx < StartsSeg->PageCount; ++PageIdx) { + if (StartsSeg->PageStart[PageIdx] == MACH_DYLD_CHAINED_PTR_START_NONE) { + continue; + } + + FixupLoc = (UINT64 *)( + KCBuffer + StartsSeg->SegmentOffset + + (UINT64)PageIdx * StartsSeg->PageSize + + StartsSeg->PageStart[PageIdx] + ); + + while (TRUE) { + Fixup = (MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE_REBASE *)FixupLoc; + + // + // CacheLevel 0 references the current KC, CacheLevel 1 references + // another KC in the same cold boot. Both encode the resolved + // virtual address directly in the Target bitfield. + // + *FixupLoc = (UINT64)Fixup->Target; + ++Resolved; + + if (Fixup->Next == 0) { + break; + } + + FixupLoc = (UINT64 *)((UINT8 *)FixupLoc + Fixup->Next); + } + } + } + + return Resolved; +} + +/** + Look up a kext by bundle identifier among the LC_FILESET_ENTRY load + commands of a System KC and materialise a PRELINKED_KEXT that aliases + the existing KC buffer. + + The resulting PRELINKED_KEXT borrows its Mach-O data from the System KC + and wires its symtab via MachoInitialiseSymtabsExternal () to the System + KC's shared inner context, matching how Boot KC dependents are set up + by InternalCreatePrelinkedKext (). + + @param[in,out] Prelinked Prelinked context with a loaded System KC. + @param[in] Identifier Bundle identifier to resolve. + + @retval Allocated PRELINKED_KEXT on success; caller owns the allocation. + @retval NULL if Identifier is not present in the System KC. +**/ +STATIC +PRELINKED_KEXT * +InternalFindSystemKCDependency ( + IN OUT PRELINKED_CONTEXT *Prelinked, + IN CONST CHAR8 *Identifier + ) +{ + MACH_HEADER_64 *Header; + MACH_LOAD_COMMAND *Command; + MACH_FILESET_ENTRY_COMMAND *FilesetEntry; + UINT32 CmdIndex; + UINT32 CmdOffset; + UINT32 FixupCount; + CONST CHAR8 *EntryName; + PRELINKED_KEXT *NewKext; + + if (!Prelinked->SystemKCValid || (Prelinked->SystemKC == NULL)) { + return NULL; + } + + Header = MachoGetMachHeader64 (&Prelinked->SystemKCMachContext); + if (Header == NULL) { + return NULL; + } + + CmdOffset = sizeof (MACH_HEADER_64); + + for (CmdIndex = 0; CmdIndex < Header->NumCommands; ++CmdIndex) { + if (CmdOffset + sizeof (MACH_LOAD_COMMAND) > Header->CommandsSize + sizeof (MACH_HEADER_64)) { + break; + } + + Command = (MACH_LOAD_COMMAND *)((UINT8 *)Header + CmdOffset); + + if (Command->CommandType != MACH_LOAD_COMMAND_FILESET_ENTRY) { + CmdOffset += Command->CommandSize; + continue; + } + + FilesetEntry = (MACH_FILESET_ENTRY_COMMAND *)Command; + EntryName = (CONST CHAR8 *)((UINT8 *)FilesetEntry + FilesetEntry->EntryId.Offset); + + if (AsciiStrCmp (EntryName, Identifier) != 0) { + CmdOffset += Command->CommandSize; + continue; + } + + NewKext = AllocateZeroPool (sizeof (*NewKext)); + if (NewKext == NULL) { + return NULL; + } + + // + // Initialise a Mach-O context covering the whole System KC but pointed + // at the fileset entry's header offset. This mirrors how Boot KC kexts + // are initialised so that LC_SYMTAB, LC_DYSYMTAB and friends resolve + // into the KC's shared __LINKEDIT data. + // + if (!MachoInitializeContext64 ( + &NewKext->Context.MachContext, + Prelinked->SystemKC, + Prelinked->SystemKCSize, + (UINT32)FilesetEntry->FileOffset, + (UINT32)(Prelinked->SystemKCSize - FilesetEntry->FileOffset) + )) + { + DEBUG ((DEBUG_WARN, "OCAK: System KC MachContext init failed for %a\n", Identifier)); + FreePool (NewKext); + return NULL; + } + + FixupCount = InternalApplyFilesetKextFixups ( + Prelinked->SystemKC, + &NewKext->Context.MachContext + ); + + // + // Wire the kext's symtab up to the System KC's inner context so + // later lookups reach the shared symbol table. + // + MachoInitialiseSymtabsExternal ( + &NewKext->Context.MachContext, + &Prelinked->SystemKCInnerMachContext + ); + + NewKext->Signature = PRELINKED_KEXT_SIGNATURE; + NewKext->Identifier = Identifier; + NewKext->BundleLibraries = NULL; + NewKext->Context.VirtualBase = FilesetEntry->VirtualAddress; + NewKext->Context.VirtualKmod = 0; + NewKext->Context.IsKernelCollection = TRUE; + NewKext->Context.Is32Bit = FALSE; + + DEBUG (( + DEBUG_INFO, + "OCAK: System KC resolved %a (VA 0x%Lx, %u fixups)\n", + Identifier, + FilesetEntry->VirtualAddress, + FixupCount + )); + + return NewKext; + } + + return NULL; +} + /** Creates new uncached PRELINKED_KEXT from pool. @@ -749,42 +985,52 @@ InternalCachedPrelinkedKext ( // // On kernel-collection boots (macOS 11+), bundle identifiers that the // injected kext lists in OSBundleLibraries may have no plist entry in - // the Boot KC because the providing module ships in a different KC or - // is plist-only. Returning NULL here silently breaks symbol-only - // dependents (weak references, build-time links): the caller skips - // the dependency entirely, then InternalSolveSymbol () later fails - // with "library kext ... not found". + // the Boot KC because the providing module ships in a different KC + // (e.g. IOGraphicsFamily lives in the System KC) or is plist-only. + // Returning NULL here silently breaks symbol-only dependents (weak + // references, build-time links): the caller skips the dependency + // entirely, then InternalSolveSymbol () later fails with "library + // kext ... not found". // - // Materialise a minimal kernel-stub PRELINKED_KEXT aliasing the - // kernel's own inner Mach-O context. The stub carries no vtable - // source, so it cannot satisfy subclassing, but it lets the kernel's - // symbol table answer weak/test lookups so the link can complete. - // Boot-KC dependents that need real vtables still fail loudly during - // linking, exactly as before. + // First, if a System KC has been loaded, walk its LC_FILESET_ENTRY + // table looking for Identifier; if present, borrow its Mach-O so + // symbol and vtable resolution can complete. + // + // If no matching fileset entry exists, materialise a minimal + // kernel-stub PRELINKED_KEXT aliasing the kernel's own inner Mach-O + // context. The stub carries no vtable source, so it cannot satisfy + // subclassing, but it lets the kernel's symbol table answer + // weak/test lookups so the link can complete. Boot-KC dependents + // that need real vtables still fail loudly during linking, exactly + // as before. // if (Prelinked->IsKernelCollection) { - NewKext = AllocateZeroPool (sizeof (*NewKext)); + NewKext = InternalFindSystemKCDependency (Prelinked, Identifier); + if (NewKext == NULL) { - return NULL; + DEBUG (( + DEBUG_INFO, + "OCAK: %a not in Boot KC or System KC, using kernel stub for symbol-only deps\n", + Identifier + )); + + NewKext = AllocateZeroPool (sizeof (*NewKext)); + if (NewKext != NULL) { + NewKext->Signature = PRELINKED_KEXT_SIGNATURE; + NewKext->Identifier = Identifier; + NewKext->BundleLibraries = NULL; + CopyMem ( + &NewKext->Context.MachContext, + &Prelinked->InnerMachContext, + sizeof (OC_MACHO_CONTEXT) + ); + NewKext->Context.VirtualBase = 0; + NewKext->Context.VirtualKmod = 0; + } } + } - NewKext->Signature = PRELINKED_KEXT_SIGNATURE; - NewKext->Identifier = Identifier; - NewKext->BundleLibraries = NULL; - CopyMem ( - &NewKext->Context.MachContext, - &Prelinked->InnerMachContext, - sizeof (OC_MACHO_CONTEXT) - ); - NewKext->Context.VirtualBase = 0; - NewKext->Context.VirtualKmod = 0; - - DEBUG (( - DEBUG_INFO, - "OCAK: %a not in Boot KC, using kernel stub for symbol-only deps\n", - Identifier - )); - } else { + if (NewKext == NULL) { return NULL; } } diff --git a/Library/OcMainLib/OpenCoreKernel.c b/Library/OcMainLib/OpenCoreKernel.c index 4837f0bab79..8514631224b 100644 --- a/Library/OcMainLib/OpenCoreKernel.c +++ b/Library/OcMainLib/OpenCoreKernel.c @@ -43,6 +43,14 @@ STATIC BOOLEAN mOcCachelessInProgress; STATIC EFI_FILE_PROTOCOL *mCustomKernelDirectory; STATIC BOOLEAN mCustomKernelDirectoryInProgress; +// +// System KC staged on the EFI partition, read on demand in OcKernelFileOpen +// and consumed by OcKernelProcessPrelinked. +// +STATIC UINT8 *mSystemKCData; +STATIC UINT32 mSystemKCDataSize; +STATIC BOOLEAN mSystemKCLoaded; + STATIC VOID OcKernelConfigureCapabilities ( @@ -776,11 +784,33 @@ OcKernelProcessPrelinked ( ) { EFI_STATUS Status; + EFI_STATUS SystemKCStatus; PRELINKED_CONTEXT Context; Status = PrelinkedContextInit (&Context, Kernel, *KernelSize, AllocatedSize, Is32Bit); if (!EFI_ERROR (Status)) { + // + // If a System KC was loaded from the EFI partition (see + // OcKernelFileOpen ()), hand ownership of the buffer to the prelinked + // context so cross-KC dependencies resolve at prelink time. On + // non-KC boots the data is absent and this is a no-op. + // + if (Context.IsKernelCollection && mSystemKCLoaded && (mSystemKCData != NULL)) { + SystemKCStatus = PrelinkedContextLoadSystemKC (&Context, mSystemKCData, mSystemKCDataSize); + if (EFI_ERROR (SystemKCStatus)) { + DEBUG ((DEBUG_WARN, "OC: System KC parse failed - %r\n", SystemKCStatus)); + } + + // + // Ownership of mSystemKCData transfers to the prelinked context + // (freed via PrelinkedContextFree). Clear the globals so we don't + // attempt a double-free or reuse stale data on the next pass. + // + mSystemKCData = NULL; + mSystemKCLoaded = FALSE; + } + OcKernelBlockKexts (Config, DarwinVersion, Is32Bit, CacheTypePrelinked, &Context); OcKernelInjectKexts (Config, CacheTypePrelinked, &Context, DarwinVersion, Is32Bit, LinkedExpansion, ReservedExeSize); @@ -1320,6 +1350,38 @@ OcKernelFileOpen ( KernelSize ); + // + // Optionally load the System KC from the OpenCore EFI partition. + // + // The sealed APFS system volume cannot be read from EFI, so the + // System KC must be staged on the OpenCore ESP as + // EFI/OC/SystemKernelExtensions.kc + // extracted from the booting macOS install. When absent OpenCore + // continues to behave as before (Boot KC only injection). + // + // The buffer's ownership is handed to OcKernelProcessPrelinked () + // below and freed through PrelinkedContextFree (). + // + if (!mSystemKCLoaded && (mOcStorage != NULL)) { + UINT32 SystemKCFileSize; + + mSystemKCData = OcStorageReadFileUnicode ( + mOcStorage, + L"SystemKernelExtensions.kc", + &SystemKCFileSize + ); + + if (mSystemKCData != NULL) { + mSystemKCDataSize = SystemKCFileSize; + mSystemKCLoaded = TRUE; + DEBUG (( + DEBUG_INFO, + "OC: System KC loaded from EFI partition (%u bytes)\n", + SystemKCFileSize + )); + } + } + PrelinkedStatus = OcKernelProcessPrelinked ( mOcConfiguration, mOcDarwinVersion,