Skip to content

OcAppleKernelLib: System KC loading and cross-KC dependency resolution#600

Open
MattJackson wants to merge 6 commits into
acidanthera:masterfrom
MattJackson:system-kc-loading
Open

OcAppleKernelLib: System KC loading and cross-KC dependency resolution#600
MattJackson wants to merge 6 commits into
acidanthera:masterfrom
MattJackson:system-kc-loading

Conversation

@MattJackson

@MattJackson MattJackson commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Adds optional System Kernel Collection (System KC) loading to
OcAppleKernelLib and wires it through OcMainLib, so OpenCore can
resolve Boot-KC kext OSBundleLibraries dependencies against classes
that live in the System KC (for example IOGraphicsFamily,
IOUSBFamily, AGPM). When no System KC is staged on the OpenCore
ESP, behaviour is identical to today's injection flow.

Why

Since macOS 11 the System KC hosts a large fraction of kext libraries
that used to be in the prelinkedkernel. OpenCore only injects into the
Boot KC, so any injected kext whose OSBundleLibraries names a
System-KC class fails at prelink with library kext ... not found.
Teaching OpenCore to optionally consult the System KC closes that gap
for any project whose kexts statically link against System-KC classes.

The feature is strictly opt-in: presence of
EFI/OC/SystemKernelExtensions.kc on the ESP activates it, and the
file is version-locked to the installed macOS build.

Test plan

Reviewer can reproduce with the following minimal flow. Pointers
indicate what output to expect in the OpenCore DEBUG log
(DisplayLevel=0x80000042, i.e. DEBUG_INFO | DEBUG_WARN).

  1. Unit-level (no macOS required).

    cd OpenCorePkg
    git am 0001-OcAppleKernelLib-Add-System-KC-context-loading.patch
    git am 0002-OcAppleKernelLib-Resolve-cross-KC-dependencies.patch
    git am 0003-OcAppleKernelLib-Translate-System-KC-symbols-at-link.patch
    git am 0004-OcMainLib-Stage-System-KC-for-prelinked-injection.patch
    git am 0005-Changelog-Note-System-KC-loading-support.patch
    
    make -C Utilities/TestProcessKernel
    Utilities/TestProcessKernel/ProcessKernel /path/to/prelinkedkernel

    Expect: exit 0, out.bin byte-identical to a run without the
    patches when no System KC is present. No new warnings.

  2. Integration-level (requires a macOS 11+ guest or host).

    # On macOS host
    sudo cp /System/Library/KernelCollections/SystemKernelExtensions.kc \
      /Volumes/EFI/EFI/OC/
    
    # Build OpenCore with the patches and install OpenCore.efi to the ESP.
    # Boot with DEBUG_XCODE5 build + DisplayLevel=0x80000042.

    Expect these log lines (order matters, format verbatim):

    OC: System KC loaded from EFI partition (NNN bytes)
    OCAK: System KC ready (NNN bytes, NNN chained fixups)
    OCAK: System KC resolved com.apple.iokit.IOGraphicsFamily (VA 0x...)
    OCAK: System KC resolved com.apple.driver.AGPM (VA 0x...)
    

    The OC: Prelinked status - Success line that usually terminates
    OcKernelFileOpen's cache work must remain. macOS boots normally.

  3. Regression test (no System KC staged).
    Remove EFI/OC/SystemKernelExtensions.kc, rebuild image or simply
    delete the file, reboot. Expect the DEBUG log to contain NONE of
    the OC: System KC ... or OCAK: System KC ... lines and
    macOS to still boot.

  4. Negative test (corrupt file).
    Stage a file named SystemKernelExtensions.kc that is not a
    Mach-O (echo not-a-macho > SystemKernelExtensions.kc). Expect:

    OC: System KC loaded from EFI partition (N bytes)
    OCAK: System KC header is not a valid Mach-O
    OC: System KC parse failed - Invalid Parameter
    

    No panic, no crash; Boot KC injection continues normally.

Review notes

A few specific spots that might benefit from review eyes:

  • PrelinkedContext.c InternalApplyKernelCollectionFixups ():
    only the x86_64 and arm64e kernel-cache pointer formats are
    handled. Formats we have not observed in shipping kernel
    collections are deliberately skipped with continue - please
    confirm the list is complete enough for your intended targets.
  • PrelinkedKext.c InternalFindSystemKCDependency (): the fallback
    at the end of InternalCachedPrelinkedKext () still materialises a
    kernel-stub PRELINKED_KEXT when a bundle identifier is in
    neither collection. This preserves today's behaviour for non-KC
    dependencies (weak test symbols, etc.); open to changing that if
    you'd prefer the fallback path to disappear when
    IsKernelCollection is true.
  • Link.c: the translation in InternalSolveSymbolNonWeak () uses
    the same constant offsets as KcFixupValue (). If a future
    macOS changes the shared-KC KASLR convention, both sites would need
    updating in sync; consider whether they should share a helper.
  • OpenCoreKernel.c reads the System KC through mOcStorage, which
    anchors to the OpenCore ESP root. If a reviewer prefers a config
    knob (e.g. Kernel/Scheme/SystemKernelCollectionPath) I'm happy
    to add one; the current hard-coded filename felt like the minimal
    surface for a first cut.

Log snippet

With the patches applied and
EFI/OC/SystemKernelExtensions.kc staged from macOS 15.7.5:

OC: Request is usr-apfs (0), 0(0) for custom Hook
OC: System KC loaded from EFI partition (365821088 bytes)
OCAK: System KC ready (365821088 bytes, 81247 chained fixups)
OCAK: System KC resolved com.apple.iokit.IOGraphicsFamily (VA 0xffffff8149b20000, 1590 fixups)
OCAK: System KC resolved com.apple.driver.AppleGraphicsDeviceControl (VA 0xffffff8149c80000, 56 fixups)
OC: Prelinked status - Success

Checklist

  • Code compiles cleanly on XCODE5 DEBUG (Xcode 16.2, EDK2
    stable202502).
  • Utilities/TestProcessKernel runs unchanged in the no-System-KC
    case.
  • Changelog entry added.
  • Each commit is independently buildable for bisection.
  • Reviewed arm64e pointer-format behaviour end-to-end on physical
    Apple-silicon hardware. (Pointer-format path compiles; no
    Apple-silicon hardware on hand for a real boot test.)

Follow-up after #606

This PR contains its own inline chained-fixup walker code in
InternalApplyFilesetKextFixups (PrelinkedKext.c) and
InternalKcTranslateSystemKCFixups (PrelinkedContext.c). Both walkers
predate the public KcWalkChainedFixupsInImage API introduced by #603
and refined by #606 to be alignment-safe.

After #606 merges, I will follow up with a refactor PR that:

  • Replaces both private walkers with calls to
    KcWalkChainedFixupsInImage and a per-call visitor callback.
  • Adopts the same alignment-safe contract (CONST UINT8 * metadata,
    UINT8 *FixupLoc visitor parameter) so the same UB classes
    @vit9696 flagged on OcAppleKernelLib: chained-fixup pointer-format helpers + TestProcessKernel driver #603 (cast-and-deref of MACH_DYLD_CHAINED_STARTS_IN_SEGMENT *
    on a potentially-unaligned base, UINT64 * writes through unaligned
    fixup-slot pointers) are resolved here too.
  • Eliminates ~40 lines of duplicate walker logic in OcAppleKernelLib.

Sequencing #606 first keeps that PR's review focused on the API +
walker alignment fix, and lets #600 land on its own merits without
blocking on the consumer-side refactor.

@vit9696

vit9696 commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Thank you for your patch. It will take us some time to review, but could you first explain whether we absolutely need to copy the .kc file? The problem with this architecture is .kc desync, which happens:

  • after macOS upgrade;
  • when macOS loads recovery;
  • during macOS upgrade/install;
  • with multiple macOS installations.

I understand the problem with kext injection exists, but it feels like we ideally want a better approach than copying.

@MattJackson

Copy link
Copy Markdown
Contributor Author

Thanks for the early read.

The copy-to-ESP design is driven by a constraint rather than a preference: the sealed APFS System volume is not readable from EFI. Only Apple's boot.efi can follow the firmlink from the preboot volume to /System/Library/KernelCollections/SystemKernelExtensions.kc. From the OpenCore driver stack (APFS JumpStart + the Apple partition driver) we can reach the Preboot volume, but the sealed System snapshot that actually stores the .kc is not surfaced. I did not find a pre-ExitBootServices path to the live file, which is why OcMainLib/OpenCoreKernel.c pulls it from EFI/OC/ via mOcStorage.

Your desync concerns are all real:

  1. Post-upgrade — the staged .kc is version-locked to the build it was copied from; booting a newer System volume against an older staged KC will mis-resolve symbols. The current patch does not validate build UUID against the running kernel; a hash/UUID check in PrelinkedContextLoadSystemKC() before trusting the context would be straightforward to add.
  2. Recovery — Recovery uses its own .kc, so the ESP copy is wrong by construction. The simplest containment is to skip PrelinkedContextLoadSystemKC() when the selected boot entry is Recovery; I can wire that through OcKernelProcessPrelinked() without touching the library core.
  3. In-progress install/upgrade — same class as (1); a UUID mismatch check catches it.
  4. Multi-install — one ESP, N System volumes, N .kc files. This is the hardest case; any per-install staging scheme leaks into the config schema.

If you would prefer a design that avoids ESP staging entirely, the two directions I can see are: (a) an APFS-snapshot-aware reader on top of the existing Apple partition driver that can open the sealed System volume read-only at EFI time; (b) deferring cross-KC resolution until after boot.efi has mapped the System KC, which changes the patch's shape considerably. Happy to go either way — which would you like me to pursue, or would a UUID-gated version of the current approach (with Recovery skipped) be acceptable as a first step?

@vit9696

vit9696 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Hmmm. Thank you for your analysis.

I think option (a) can be considered a fallback if we can implement it cleanly, but I have doubts we can. It requires verifying image seal, and I am afraid EFI driver is not ready for this.

I also think option (b) is not feasible as I believe EfiBoot does not load system kc at all. I may be wrong, but I have always thought it is part of XNU OSKext APIs, namely loadKCFileSet, which are not open-source. Are you sure EfiBoot does it?

I honestly wonder whether it makes sense to try option (c) revive https://github.com/acidanthera/Lilu/pull/86/changes and work in mixed model: OC prepares, Lilu injects later stage kexts.

@MattJackson

Copy link
Copy Markdown
Contributor Author

Thanks so much for digging into this — the analysis on all three
directions is really helpful and saved me from chasing paths that
wouldn't have worked out.

(a) — you're right that verifying the APFS image seal from an EFI
driver is a real concern, and I don't think there's a clean way to get
there without duplicating a non-trivial chunk of APFS's sealed-snapshot
logic into the Apple partition driver. The maintenance cost of tracking
APFS format changes over time would outweigh the benefit, so I'd agree
with your read that this isn't the direction to push.

(b) — you're correct, and I double-checked: boot.efi does not
load the System KC. System KC loading happens in XNU after VFS comes
up, via OSKext::loadKCFileSet(path, kc_kind_t) in
libkern/c++/OSKext.cpp (public header lives in the xnu source tree).
EfiBoot only maps the Boot KC. So option (b) was architecturally
misplaced on my part — cross-KC resolution is fundamentally an
XNU-time concern, not an EFI-time one. Appreciate you catching that.

(c) — thanks for pointing at Lilu/pull/86, that's a useful
reference. We're coming at this from a downstream context where a
Lilu-side solution wouldn't land on our own path, so we're probably
not the right folks to carry that revival ourselves. If someone in
the Lilu community ends up picking it up on its own merits, that'd
be great to see.

On this PR — I don't think it has to be all-or-nothing.

Going back through the series with the System-KC-loading parts
peeled off, there are a few pieces I think could potentially stand
on their own merits regardless of how (or whether) the broader
cross-KC story gets resolved:

  • A small handful of pure observability / diagnostic improvements
    (a DEBUG_INFO log line on dependency-scan failures, a
    RIP-relative-displacement overflow log in
    InternalCalculateDisplacementIntel64, a missing include).
    Roughly 14 LOC of pure cleanup; happy to submit as a trivial
    separate PR if it'd be useful.
  • A latent bug fix nested inside one of the System-KC commits:
    in InternalCachedPrelinkedKext, when a referenced kext isn't
    found, the current behaviour returns NULL immediately, which
    silently breaks symbol-only dependents (weak references, build-time
    links). The patch adds a kernel-stub PRELINKED_KEXT fallback for
    that case. ~10 LOC, independent of System KC; could extract as
    part of the same trivial PR.
  • The chained-fixup pointer-format handlers (X86_64 + arm64e
    variants) inside InternalApplyKernelCollectionFixups. These are
    potentially reusable as generic helpers for any caller working with
    chained-fixup buffers, but you've been pretty consistent that
    OcAppleKernelLib doesn't take infrastructure with no in-tree user.
    I could pair the helpers with a Utilities/TestProcessKernel
    subcommand that exercises them, but at that point we're back to a
    meatier PR, ~150 LOC, and I'd want your read on whether that shape
    is worth the review cycles.

So three concrete paths, in increasing ambition:

  • B. Trivial cleanup PR. ~25 LOC: the 3 diagnostic / include nits
    plus the symbol-only-dependent stub fallback. No System-KC story,
    no library-without-caller concerns. Withdraw the rest of this PR.
  • C. Cleanup + chained-fixup helpers. ~150 LOC: B plus the
    pointer-format extraction with a TestProcessKernel driver.
    Withdraw the rest of this PR.
  • D. Pause this PR entirely. Leave the System-KC work on our
    downstream fork as reference, and let whoever cracks the Lilu
    side (or some future XNU-time approach) come back to it on their
    own timeline. We don't take any more of your review cycles on
    this thread until/unless that happens.

My instinct is B, but I'd genuinely rather defer to your read on
which (if any) of these is worth the round-trip from your side.
Happy to rebase to whichever you pick, or to just close this with
D and move on.

Thanks again for the careful review.

@vit9696

vit9696 commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Right. Lilu solution is rather heavy, and we are not yet in the state we can pursue it. On the other side given macOS 26 is the last one supporting Intel, I believe there is room for compromise.

I personally think we can definitely implement B + C. This will be helpful for other use cases of kernel injection. As for system KC loading — I am yet to decide whether we want it.

Could you please tell me more about your use case? I wonder whether System KC loading in your scenario is practically the only choice?

@MattJackson

Copy link
Copy Markdown
Contributor Author

Thanks again for taking the time on this — really glad to hear B + C sound useful on their own merits. Happy to spin those off as a standalone PR on whatever cadence works for you; no rush on our end.

On the use case for the System KC loading half: the work originated in a graphics context. We were injecting a couple of small kexts that hook into the IOKit graphics stack — concretely, their OSBundleLibraries references com.apple.iokit.IOAcceleratorFamily2, com.apple.iokit.IOSurface, and com.apple.iokit.IOGraphicsFamily, all of which live in SystemKernelExtensions.kc rather than the boot KC. With only the boot KC visible to OpenCore's prelinker, those dependencies fail to resolve at prelink time with the "library kext … not found" error, even though the same kext loads cleanly post-boot if the kernel itself can see the system KC. Teaching OpenCore to consult a staged System KC was the most direct way we found to make those graphics-stack-dependent kexts prelink without changing the guest's actual boot path or shipping a modified XNU.

It's not the only conceivable choice — a Lilu-side late-stage injection would reach the same end state, and you raised that alternative earlier in the thread re: Lilu PR #86. For our particular needs the OpenCore-prelink approach was the cleanest fit, since we're not the right folks to drive the Lilu side and we wanted the resulting boot to look as close to a stock OpenCore boot as possible.

That said: we're heads-down on a few other things right now and this isn't blocking us in any way. We mainly wanted to get the code into your tree as a contribution if it's useful to anyone else hitting the same prelink wall on graphics-stack kexts, and we're entirely happy to wait, iterate on whatever shape you'd prefer, or just close the System-KC half and go forward with B (or B + C) if you'd rather not carry the cross-KC machinery. Whichever way you'd like to take it is fine by us — please don't feel any pressure to merge on our account.

Thanks again for the review.

@MattJackson

Copy link
Copy Markdown
Contributor Author

Per your endorsement of B + C: opened standalone PRs as #602 (B path: ~25 LOC, prelink dependency-scan + RIP-relative-overflow DEBUG_INFO traces, plus the kernel-stub PRELINKED_KEXT fallback for symbol-only dependents in InternalCachedPrelinkedKext, no SystemKC machinery) and #603 (C path: ~150 LOC of code plus header/doc, depends on #602; reusable KcWalkChainedFixupsInSegment / KcWalkChainedFixupsInImage helpers with correct per-format stride handling, plus a TestProcessKernel --test-fixup-walk subcommand exercising both walks).

TestKextInject and TestProcessKernel both build clean with WERROR=1 USE_SHARED_OBJS=1 on each branch.

The originally proposed missing-include item turned out to be a non-issue on master (PrelinkedInternal.h already pulls <IndustryStandard/AppleMachoImage.h> into PrelinkedKext.c transitively), so I dropped it rather than invent a fix.

The arm64e stride bug from the original PR #600 commit B is fixed in the C-PR helpers: MACH_DYLD_CHAINED_PTR_64_KERNEL_CACHE is stride 4 (Fixup->Next * 4), not 1 byte. The synthetic test in --test-fixup-walk exercises both stride layouts and asserts the walker produces identical visit counts.

This PR's SystemKC machinery is deferred per your "yet to decide" comment — happy to leave it open for now and close it once #602 and #603 land, or to close it sooner if you'd prefer the cleaner queue. Equally happy to rebase or squash either side at your discretion.

MattJackson added a commit to MattJackson/OpenCorePkg that referenced this pull request Apr 26, 2026
…nel driver

This is the "C" path queued behind the "B" cleanup PR per the PR acidanthera#600
discussion. It introduces two static-lib helpers for walking
LC_DYLD_CHAINED_FIXUPS chains in a kernel collection, with correct
stride handling for both supported pointer formats, and exercises them
from a TestProcessKernel subcommand so the helpers ship with an
in-tree user.

Helpers (KernelCollection.c, OcAppleKernelLib.h):

* KcWalkChainedFixupsInSegment - given a containing buffer and a
  MACH_DYLD_CHAINED_STARTS_IN_SEGMENT, walk the chain on every
  populated page and invoke a caller-provided KC_CHAINED_FIXUP_VISIT
  callback per fixup slot. The visitor may rewrite the slot in place
  (translation) or just observe (counting/validation).

* KcWalkChainedFixupsInImage - walks every segment in a
  MACH_DYLD_CHAINED_STARTS_IN_IMAGE by delegating to the per-segment
  helper. Returns the total slot count.

Pointer-format support:

  X86_64_KERNEL_CACHE (11): stride 1 byte  (Fixup->Next * 1)
  64_KERNEL_CACHE      (8): stride 4 bytes (Fixup->Next * 4)
  64_OFFSET            (6): stride 4 bytes (also accepted)
  ARM64E_KERNEL        (7): stride 4 bytes (also accepted)

Per Apple's mach-o/fixup-chains.h, Fixup->Next is a stride count, not
a byte offset. The 64_KERNEL_CACHE variant is stride 4 (arm64e kernel
caches), not 1; advancing by Fixup->Next bytes mis-walks every chain
on those caches. The walker computes the correct stride per format.
Other formats (ARM64E with auth, 32-bit variants, userland 64) and
START_MULTI pages are not produced by current macOS kernel caches and
return 0 / are skipped defensively rather than mis-walking.

TestProcessKernel driver:

* New --test-fixup-walk subcommand. Builds a synthetic single-page
  STARTS_IN_SEGMENT with a 4-link chain and walks it three times:
  - As X86_64_KERNEL_CACHE (Next=16, stride 1) - expect 4 visits.
  - As 64_KERNEL_CACHE (Next=4, stride 4)      - expect 4 visits.
  - As ARM64E (unsupported)                    - expect 0 visits.

  Both stride layouts encode the same byte distance between slots, so
  the walker must produce identical visit counts and fixup-slot
  addresses for both. The test ASSERTs all three counts.

  Verified on x86_64+arm64e darwin: all three subtests pass.

Tested:
- TestKextInject and TestProcessKernel both build clean with
  WERROR=1 USE_SHARED_OBJS=1.
- ./ProcessKernel --test-fixup-walk reports
    [OK] X86_64_KERNEL_CACHE walk visited 4 fixups (stride 1)
    [OK] 64_KERNEL_CACHE walk visited 4 fixups (stride 4)
    [OK] unsupported-format guard returned 0

Depends on the diagnostic + symbol-only fallback PR (B path).

Signed-off-by: Matthew Jackson <matthew@pq.io>
vit9696 pushed a commit that referenced this pull request May 3, 2026
…allback

This is the "B" cleanup path from the discussion in #600. It groups two
trivial DEBUG_INFO additions and one independent latent-bug fix into a
single drop with no SystemKC machinery.

* PrelinkedKext.c: in InternalInsertPrelinkedKextDependency (), log the
  dependency-scan failure path that currently returns a status code
  without naming the dependent or dependency. This turned up while
  reviewing prelink failures and is helpful in any kext-injection
  scenario, not just ours.

* PrelinkedKext.c: in InternalCachedPrelinkedKext (), when running on a
  kernel collection (macOS 11+) and the requested bundle identifier
  is not in the Boot KC plist, materialise a minimal kernel-stub
  PRELINKED_KEXT aliasing PRELINKED_CONTEXT::InnerMachContext instead
  of returning NULL. Returning NULL silently breaks symbol-only
  dependents (weak references, build-time links): the caller skips
  the dependency and InternalSolveSymbol () later fails with
  "library kext ... not found". The stub carries no vtable source so
  it cannot satisfy subclassing - dependents that need real vtables
  still fail loudly during linking, exactly as before. Pre-Big Sur
  boots take the unchanged code path because IsKernelCollection is
  FALSE.

* Link.c: in InternalCalculateDisplacementIntel64 (), downgrade the
  silent X86_64_RIP_RELATIVE_LIMIT no-op to a DEBUG_INFO trace that
  carries the kext identifier, source link PC, target VA and the
  computed difference. Currently the helper just returns FALSE; the
  caller propagates that to MAX_UINTN and the link fails without any
  log line identifying which kext or which target overflowed. The new
  log makes RIP-relative overflows debuggable from a single OCAK:
  trace without raising the message level.

Tested:
- TestKextInject and TestProcessKernel both build clean with
  WERROR=1 USE_SHARED_OBJS=1 against this tree (acidanthera/master,
  EDK2 stable202502, Xcode 16.4 / clang 17).

Signed-off-by: Matthew Jackson <matthew@pq.io>
Macs on macOS 11 and newer split the kernel extension inventory into
two immutable kernel collections: the Boot KC that OpenCore injects
into, and the System KC that ships on the system volume. Kext libraries
that used to live in the prelinkedkernel - for example IOGraphicsFamily,
AppleGraphicsDeviceControl, AGPM, IOUSBFamily - have moved to the
System KC on modern releases, which means a Boot KC kext that declares
one of their classes in OSBundleLibraries cannot be linked at prelink
time today (the symbol table and vtables are not reachable).

This commit grows PRELINKED_CONTEXT with optional System KC state:

  - SystemKC / SystemKCSize : raw buffer and length
  - SystemKCMachContext     : Mach-O context for the outer KC
  - SystemKCInnerMachContext: context for the __TEXT_EXEC embedded
                              kernel (Boot KC-style) or an alias of
                              the outer (System KC-style)
  - SystemKCValid           : set once parsing and fixup completed

A new PrelinkedContextLoadSystemKC () function wires the buffer up,
probes for the __TEXT_EXEC inner kernel, connects the shared symtab
via MachoInitialiseSymtabsExternal (), and applies LC_DYLD_CHAINED_FIXUPS
on the outer collection so later consumers observe resolved virtual
addresses instead of encoded fixup slots. Only the x86_64 and arm64e
kernel-cache pointer formats are handled; other formats are skipped.

PrelinkedContextFree () now releases the System KC buffer (and clears
SystemKCValid) so callers do not have to special-case it.

No caller consumes the new state yet; wiring into kext dependency
resolution and into OcKernelProcessPrelinked () comes in follow-up
patches so each step is individually bisectable.

Signed-off-by: Matthew Jackson <matthew@pq.io>
With the previous patch a PRELINKED_CONTEXT can hold an optional
System KC. This commit teaches the kext dependency resolver to look
into that collection when a bundle identifier is not present in the
Boot KC.

InternalCachedPrelinkedKext () (the one call site for dependency
lookup used by InternalInsertPrelinkedKextDependency ()) gains a new
branch that runs only for kernel-collection boots: if the caller has
loaded a System KC, walk its LC_FILESET_ENTRY load commands looking
for the requested identifier. On a hit we materialise a PRELINKED_KEXT
aliasing the System KC buffer - the Mach-O context is initialised with
the fileset entry's FileOffset as HeaderOffset, exactly like Boot KC
dependents - and wire the kext's symtab up to the System KC's inner
context via MachoInitialiseSymtabsExternal ().

Each fileset entry ships its own LC_DYLD_CHAINED_FIXUPS covering the
kext's data segments; those are applied in place by a new static
helper InternalApplyFilesetKextFixups () so later vtable patching
sees resolved pointers instead of fixup slots.

If the identifier is not in the System KC either, we fall back to the
pre-existing kernel-stub path (CopyMem from InnerMachContext) which
keeps compatibility for dependents that only look for weak test
symbols and can live without a real vtable source.

No change for non-KC boots: IsKernelCollection is FALSE and the new
branch is skipped entirely.

Signed-off-by: Matthew Jackson <matthew@pq.io>
System KC kexts carry symbol values that reference fileset virtual
addresses (typically in the 1 MB .. 512 MB range on x86_64, e.g.
0x149xxxxx). When the Boot KC linker resolves a symbol against a
System KC dependency it receives this raw value; feeding it into
InternalSolveSymbolValue () unchanged produces RIP-relative relocations
whose 32-bit displacements cannot reach the final kernel address
space, so InternalCalculateDisplacementIntel64 () trips the
X86_64_RIP_RELATIVE_LIMIT check and the link fails.

The adjustment is the same one that KcFixupValue () performs for vtable
entries:

    address_in_kernel_space = raw + KERNEL_FIXUP_OFFSET + KERNEL_ADDRESS_BASE

On macOS 11+ all kernel collections share a single KASLR slide, so the
translation is a constant. Kernel-space values, zero, and the weak
test placeholder must not be translated, which is why the guard only
applies to values clearly in the fileset-VA band (1 MB .. 512 MB).

Also downgrade the RIP-relative displacement overflow log from an
unprinted no-op to a DEBUG_INFO trace with the target and difference,
which makes diagnosing related link failures practical without
introducing WARN-level noise for normal operation.

Signed-off-by: Matthew Jackson <matthew@pq.io>
Wire the new PrelinkedContextLoadSystemKC () API into the existing
file-open hook. When OcKernelFileOpen () handles an apfs/kernel-cache
request and a System KC has not yet been staged for this boot, attempt
to read EFI/OC/SystemKernelExtensions.kc from the OpenCore ESP via
mOcStorage. If present, remember the buffer and its size in three new
file-scope statics; if absent, continue silently (Boot-KC-only
injection is the long-standing default and must keep working).

When OcKernelProcessPrelinked () subsequently creates a prelinked
context, it now calls PrelinkedContextLoadSystemKC () to hand the
staged buffer off. Ownership transfers to the context, which frees the
buffer through PrelinkedContextFree (); the statics are cleared
immediately so we neither double-free nor re-parse on the next pass.

The System KC has to be staged on the ESP because APFS sealed-volume
data (where macOS stores its own copy) is not readable from EFI.
Administrators are expected to copy the file out of
/System/Library/KernelCollections/SystemKernelExtensions.kc during
OpenCore installation; doing so is version-locked to the installed
macOS build.

No behavioural change for boots where the file is absent:
mSystemKCLoaded stays FALSE, the new branch in
OcKernelProcessPrelinked () is skipped, and the existing Boot-KC-only
injection path runs unchanged.

Signed-off-by: Matthew Jackson <matthew@pq.io>
Signed-off-by: Matthew Jackson <matthew@pq.io>
Align multi-line parenthesized expression continuations per
acidanthera's uncrustify.cfg (column-align to opening paren,
not block-indent). No functional change.

Signed-off-by: Matthew Jackson <matthew@pq.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants