Included commits: - f8a54b8 Update JB kernel patch research notes Refresh and revalidate jailbreak kernel-patcher documentation and runtime-verification notes. Key updates: re-analyzed B13 (patch_bsd_init_auth) and retargeted recommended site to the FSIOC_KERNEL_ROOTAUTH return check in bsd_init rather than the old ldr/cbz/bl heuristic; clarified preferred NOP-of-CBNZ vs forcing ioctl return. Reworked C21 (patch_cred_label_update_execve) to preserve AMFI exec-time flow and instead clear restrictive csflags in a success-tail trampoline; disabled in default schedule until boot validation. Documented that C23 (patch_hook_cred_label_update_execve) was mis-targeting the wrapper (sub_FFFFFE00093D2CE4) instead of the real hook body (_hook_cred_label_update_execve), explaining boot failures and recommending retargeting. Noted syscallmask and vm_fault matcher problems (patch_syscallmask_apply_to_proc historical hit targeted _profile_syscallmask_destroy; patch_vm_fault_enter_prepare matcher resolves to pmap_lock_phys_page path), and updated the runtime-verification summary with follow-up findings and which methods are temporarily commented out/disabled in the default KernelJBPatcher schedule pending staged re-validation. - 6ebac65 fix: patch_bsd_init_auth - 5b224d3 fix: patch_io_secure_bsd_root - e6806bf docs: update patch notes - 0d89c5c Retarget vm_fault_enter_prepare jailbreak patch - 6b9d79b Rework C21 late-exit cred_label patch - ece8cc0 Clean C21 mov matcher encodings - ad2ea7c enabled fixed patch_cred_label_update_execve - c37b6b1 Rebuild syscallmask C22 patch - 363dd7a Rebuild JB C23 as faithful upstream trampoline - 129e648 Disable IOUC MACF; rebuild kcall10 & C22 docs Re-evaluate and rework several JB kernel patches and docs: mark patch_iouc_failed_macf as reverted/disabled (repo-local, over-broad early-return) and replace its patcher with a no-op implementation to emit zero writes by default; update research notes to explain the reanalysis and rationale. Rebuild patch_kcall10: replace the historical 10-arg design with an ABI-correct syscall-439 cave (target + 7 args -> uint64 return), add a new cave builder and munge32 reuse logic in the kcall10 patcher, and enable the method in KernelJBPatcher group. Clarify syscallmask (C22) semantics in docs: upstream C22 is an all-ones-mask retarget (not a NULL install) and keep the rebuilt all-ones wrapper as the authoritative baseline. Misc: minor refactors and helper additions (chained-pointer helpers, cave size/constants, validation and dry-run safeguards) to improve correctness and alignment with IDA/runtime verification. - e1b2365 Rebuild kcall10 as ABI-correct syscall cave - 23090d0 fix patch_iouc_failed_macf - 0056be2 Normalize formatting in research docs Apply whitespace and formatting cleanup across research markdown files for consistency and readability. Adjust table alignment and spacing in 00_patch_comparison_all_variants.md, normalize list/indentation spacing in patch_bsd_init_auth.md and patch_syscallmask_apply_to_proc.md, and add/clean blank lines and minor spacing in patch_kcall10.md. These are non-functional documentation changes only.
10 KiB
B19 patch_io_secure_bsd_root — 2026-03-06 reanalysis
Scope
- Kernel used for live reverse-engineering:
kernelcache.research.vphone600 - Kernel file used locally:
/Users/qaq/Documents/Firmwares/PCC-CloudOS-26.3-23D128/kernelcache.research.vphone600 - Base VA:
0xFFFFFE0007004000 - Ground-truth sources for this note:
- IDA-MCP on the loaded research kernel
- recovered symbol datasets in
research/kernel_info/json/ - open-source XNU in
research/reference/xnu
This document intentionally discards earlier B19 writeups as untrusted and restarts the analysis from first principles.
Executive Conclusion
patch_io_secure_bsd_root was previously targeting the wrong branch.
The disabled historical patch at 0xFFFFFE000836E1F0 / file offset 0x0136A1F0 does not patch the "SecureRootName" policy result used by IOSecureBSDRoot(). Instead, it patches the earlier "SecureRoot" name-match gate inside AppleARMPE::callPlatformFunction, which changes generic platform-function dispatch semantics and is a credible root cause for the early-boot failure.
The semantically correct deny path for the IOSecureBSDRoot(rootdevice) flow is the "SecureRootName" branch in AppleARMPE::callPlatformFunction, specifically the final return-value select at:
- VA:
0xFFFFFE000836E464 - file offset:
0x0136A464 - before:
f613891a/CSEL W22, WZR, W9, NE - recommended after:
16008052/MOV W22, #0
That patch preserves the compare, callback, wakeup, and state updates, and only forces the final policy return from kIOReturnNotPrivileged to success.
Implementation Status
scripts/patchers/kernel_jb_patch_secure_root.pywas retargeted on 2026-03-06 to emit this0x0136A464patch instead of the historical0x0136A1F0false-positive branch rewrite.scripts/patchers/kernel_jb.pynow includespatch_io_secure_bsd_rootagain in_GROUP_B_METHODSwith the retargeted matcher.- Local dry-run verification on the research kernel emits exactly one write:
0x0136A464/16008052/mov w22, #0 [_IOSecureBSDRoot SecureRootName allow].
Verified Call Chain
1. BSD boot calls IOSecureBSDRoot
IDA shows bsd_init calling IOSecureBSDRoot here:
bsd_initcall site:0xFFFFFE0007F7B7C4/ file offset0x00F777C4- instruction:
BL IOSecureBSDRoot
The nearby boot flow is:
IOFindBSDRootvfs_mountrootIOSecureBSDRoot(rootdevice)VFS_ROOT(...)- later
FSIOC_KERNEL_ROOTAUTH
This matches open-source XNU in research/reference/xnu/bsd/kern/bsd_init.c, where IOSecureBSDRoot(rootdevice); appears before VFS_ROOT() and well before the later root-authentication ioctl.
2. IOSecureBSDRoot calls platform expert with "SecureRootName"
Recovered symbol + IDA decompilation:
IOSecureBSDRoot:0xFFFFFE0008297FD8/ file offset0x01293FD8- research recovered symbol:
IOSecureBSDRoot - release recovered symbol:
IOSecureBSDRootat0xFFFFFE000825FFD8
The decompiled logic is straightforward:
- build
OSSymbol("SecureRootName") - wait for
IOPlatformExpert - call
pe->callPlatformFunction(functionName, false, rootName, NULL, NULL, NULL) - if result is
0xE00002C1(kIOReturnNotPrivileged), callmdevremoveall()
Open-source XNU confirms the intended semantics in research/reference/xnu/iokit/bsddev/IOKitBSDInit.cpp:
"SecureRootName"is the exact function namekIOReturnNotPrivilegedmeans the root device is not secure- on that return code,
mdevremoveall()is invoked
mdevremoveall() in research/reference/xnu/bsd/dev/memdev.c removes /dev/md* devices and clears the memory-device bookkeeping, so this path is directly relevant to ramdisk / custom-root boot flows.
3. The real secure-root decision is made in AppleARMPE::callPlatformFunction
Relevant function:
AppleARMPE::callPlatformFunction:0xFFFFFE000836E168/ file offset0x0136A168
Within this function, there are two different string-based branches that matter:
A. "SecureRoot" branch — callback/control path
At:
0xFFFFFE000836E1EC:BLRAAtoa2->isEqualTo("SecureRoot")0xFFFFFE000836E1F0:CBZ W0, loc_FFFFFE000836E394
If the name matches "SecureRoot", the function enters a branch that:
- waits on byte flag
[a1+0x118] - may call
"SecureRootCallBack" - sets / wakes byte flag
[a1+0x119] - optionally returns a boolean via
a5
This is not the direct IOSecureBSDRoot(rootName) policy result.
B. "SecureRootName" branch — actual policy decision path
At:
0xFFFFFE000836E3C0:BLRAAtoa2->isEqualTo("SecureRootName")0xFFFFFE000836E3C4:CBZ W0, loc_FFFFFE000836E46C
Then:
0xFFFFFE000836E3D4: call helper that behaves likestrlen0xFFFFFE000836E3E4: call helper that behaves likestrncmp0xFFFFFE000836E3E8:CMP W0, #00xFFFFFE000836E3EC:CSET W8, EQ0xFFFFFE000836E3F0: store secure-match bit to[a1+0x11A]- wake waiting threads / synchronize callback state
0xFFFFFE000836E450: reload[a1+0x11A]0xFFFFFE000836E454: loadW9 = 0xE00002C10xFFFFFE000836E464:CSEL W22, WZR, W9, NE
That final CSEL is the actual deny/success selector for the "SecureRootName" request:
- secure match -> return
0 - mismatch -> return
0xE00002C1/kIOReturnNotPrivileged
Why the Historical Patch Is Wrong
Root cause 1: live patcher has no symbol table to use
Running the existing KernelJBPatcher locally against the research kernel shows:
symbol_count = 0_resolve_symbol("_IOSecureBSDRoot") == -1_resolve_symbol("IOSecureBSDRoot") == -1
So the current code always falls back to a heuristic matcher on this kernel.
Root cause 2: the fallback heuristic picks the first BL* + CBZ W0 site
The current fallback logic looks for a function referencing both "SecureRoot" and "SecureRootName", then selects the first forward conditional branch shaped like:
- previous instruction is
BL* - current instruction is
CBZ/CBNZ W0, target
That heuristic lands on:
0xFFFFFE000836E1F0/CBZ W0, loc_FFFFFE000836E394
But this site is only the result of isEqualTo("SecureRoot"). It is not the final policy-return site for "SecureRootName".
Root cause 3: the old patch changes dispatch routing, not just the deny return
Historical patch:
- before:
200d0034/CBZ W0, loc_FFFFFE000836E394 - after:
69000014/B #0x1A4
Effect:
- previously: only true
"SecureRoot"requests enter theSecureRootbranch - after patch: non-
"SecureRoot"requests are also forced into that branch
Because this is inside generic AppleARMPE::callPlatformFunction dispatch, the patch can corrupt the control flow for unrelated platform-function calls that happen to reach this portion of the function. That is much broader than “skip secure-root denial” and is consistent with a boot-time regression.
What This Patch Actually Does
patch_io_secure_bsd_root does not replace the later sealed-root / root-authentication gate in bsd_init.
What it actually controls is earlier and narrower:
- determine whether the chosen BSD root name is platform-approved (
"SecureRootName") - if not approved, return
kIOReturnNotPrivileged IOSecureBSDRoot()maps that failure intomdevremoveall()
So the practical effect of a correct B19 bypass is:
- allow a non-approved/custom BSD root name to survive the platform secure-root policy step
- avoid the
kIOReturnNotPrivileged -> mdevremoveall()failure path - keep the rest of the boot moving toward
VFS_ROOTand the later rootauth check
This is why B19 and patch_bsd_init_auth are separate methods: they handle different stages of the boot chain.
Recommended Patch Strategy
Preferred site: final "SecureRootName" return select
Patch only the final result selector:
- VA:
0xFFFFFE000836E464 - file offset:
0x0136A464 - before bytes:
f613891a - before asm:
CSEL W22, WZR, W9, NE - after bytes:
16008052 - after asm:
MOV W22, #0
Why this site is preferred:
- preserves the string comparison logic
- preserves the
SecureRootCallBacksynchronization / wakeup handshake - preserves the state bytes at
[a1+0x118],[a1+0x119],[a1+0x11A] - changes only the final deny-vs-success return value
Secondary option: force the secure-match bit before the final select
- VA:
0xFFFFFE000836E3EC - file offset:
0x0136A3EC - before bytes:
e8179f1a - before asm:
CSET W8, EQ - after bytes:
28008052 - after asm:
MOV W8, #1
This is broader than the preferred patch because it changes the stored secure-match state itself, not just the returned result.
Tertiary option: suppress only IOSecureBSDRoot() cleanup
There is also a coarser site in IOSecureBSDRoot itself:
0xFFFFFE0008298144: compare against0xE00002C1followed byB.NE
That site can suppress mdevremoveall() without touching AppleARMPE::callPlatformFunction, but it is less attractive because it leaves the underlying "SecureRootName" failure semantics intact and only masks the wrapper-side cleanup.
Safer Matcher Recipe For Future Python Rework
If/when the Python patcher is reworked, the fallback should stop selecting the first BL* + CBZ W0 site in the shared function.
A safer matcher for stripped kernels is:
- locate the function referencing both
"SecureRoot"and"SecureRootName" - inside that function, find the
"SecureRootName"equality check block, not the"SecureRoot"block - from there, require the sequence:
- helper call 1 (length)
- helper call 2 (compare)
CMP W0, #0CSET W8, EQ- store to
[X19,#0x11A] - later
MOV W9, #0xE00002C1 - final
CSEL W22, WZR, W9, NE
- patch only that final
CSEL
This gives a unique, semantics-aware patch site for the actual deny return.
Local Reproduction Notes
Local dry analysis of the current patcher on the research kernel produced:
fallback_func = 0x136a168- emitted patch =
(0x0136A1F0, 69000014, 'b #0x1A4 [_IOSecureBSDRoot]')
This reproduces the disabled historical behavior and confirms that the current implementation does not yet target the correct deny site.
Confidence
- Confidence that the historical patch site is wrong: high
- Confidence that
0xFFFFFE000836E464is the correct minimal deny-return site: high - Confidence that this alone is sufficient for full jailbreak boot: medium
The last item stays medium because B19 only addresses the secure-root platform policy stage; it does not replace the later root-auth/sealedness work handled elsewhere.