kernel: jb: Add container-manager exec-upcall force-success (iOS 27 wallpaper/temporary-sandbox fix)

The 26.4 vphone600 kernel resolves each process's containers at exec via a
synchronous MIG upcall to containermanagerd over the container-manager host
special port (HOST_CONTAINERD_PORT). iOS 27 DELETED this kernel-side upcall (the
stock 27 kernel has no HOST_CONTAINERD_PORT / CM_KERN_* protocol — container
resolution moved out of the kernel), so 27's containermanagerd no longer
implements the reply server. On the 26.4 kernel running a 27.0 userland the
upcall therefore fails (MACH_SEND_INVALID_DEST) for every platform app -> they
are autoboxed into the restrictive `temporary-sandbox` profile, which denies
mach-lookup com.apple.backboard.display.services -> Campo (the wallpaper
renderer) crash-loops (no wallpaper), plus intelligencetasksd / feedbackd.

Re-registering the host special port is a confirmed dead end: the 26.4 kernel
then SENDS the MIG request and BLOCKS for a reply 27 cannot produce -> early-boot
deadlock (SpringBoard never comes up).

Fix (patchContainerManagerUpcall): flip the `cbz w0,<success>` guard in
_hook_cred_label_update_execve (taken when the upcall returns 0) to an
unconditional `b <success>`, so a failed upcall takes the success path instead
of autobox/kill. Anchored structurally on the "failed to upcall to
containermanagerd" string xref (-> the cbz immediately before the string-load
adrp, preceded by the upcall bl, unique backward branch); replacement b from the
Keystone-backed ARM64Encoder. No-op-in-effect for version-matched userlands
(there the upcall succeeds, so the original cbz already branches to <success>).

- new: sources/FirmwarePatcher/Kernel/JBPatches/KernelJBPatchContainerUpcall.swift
- KernelJBPatcher.findAll: call patchContainerManagerUpcall after
  patchExecSecurityPolicyKill
- cfw_install.sh: document the port-25 launchd-cache approach as a dead end
  (no active code)
- research/0_binary_patch_comparison.md: add row JB-02d

Validated on-device (iPhone17,3 27.0 24A5380h + cloudOS 26.4 c0ecdb4b, JB):
iOS 27 wallpaper renders, Campo/SpringBoard/backboardd stable, clean boot (no
freeze, no panic). Patcher-level (patch-component --component kernel-jb on the
pristine 26.4 kernelcache): finds 0x1AB0654 cbz w0 -> b 0x1ab0564, emits record
container_manager_upcall_force_success, byte-identical to the on-device-validated
host-poke.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_013pk5tsoBeuu3jhkmnRFtic
This commit is contained in:
zqxwce
2026-07-20 16:20:34 +03:00
committed by zqxwce
co-authored by Claude Opus 4.8
parent 9c1227f89b
commit 84f4887056
3 changed files with 123 additions and 27 deletions
+1
View File
@@ -127,6 +127,7 @@
| JB-01 | A | `patch_amfi_cdhash_in_trustcache` | `AMFIIsCDHashInTrustCache` | Always return true + store hash | Y |
| JB-02 | A | `patch_amfi_execve_kill_path` | AMFI execve kill return site | Convert shared kill return from deny to allow (superseded by C21; standalone only) | N |
| JB-02b| C | `patch_exec_security_policy_kill` | XNU exec `imgp->ip_mac_return` gate (kern_exec, `os_reason_create(OS_REASON_EXEC, EXEC_EXIT_REASON_SECURITY_POLICY)` site) | Flip `cbz wN, <skip>` → unconditional `b <skip>` so the exec-time MAC-verdict `SECURITY_POLICY` kill is unreachable. **Needed to run a userland NEWER than the kernel (iOS 27.0 on the 26.4 kernel):** AMFI's exec hooks reject the newer binaries' code-sign validation category, setting `ip_mac_return != 0` → core daemons (backboardd/cfprefsd/containermanagerd/…) die at exec (`namespace 9 / code 0x8`) → boot deadlock. Validated on 27.0/26.4: 0 SECURITY_POLICY kills, daemons launch, networking + SSH come up. No-op-in-effect for version-matched userlands (ip_mac_return == 0 there, so the original cbz already skips). | Y |
| JB-02d| C | `patch_container_manager_upcall` | sandbox `_hook_cred_label_update_execve` container-manager upcall guard: the `cbz w0,<success>` after `bl <container_manager_get_process_containers>`, on the `"failed to upcall to containermanagerd for a platform app"` fall-through | Flip `cbz w0,<success>` → unconditional `b <success>` so a FAILED exec-time container-manager upcall takes the success path instead of autobox/kill. **Needed to run iOS 27.0 on the 26.4 kernel:** iOS 27 DELETED the kernel-side containermanagerd upcall — the stock 27 kernel has no `HOST_CONTAINERD_PORT` / `CM_KERN_*` protocol (container resolution moved out of the kernel), so 27's containermanagerd no longer implements the reply server. On the 26.4 kernel the exec upcall therefore fails (`MACH_SEND_INVALID_DEST`) for every 27 platform app → they are autoboxed into the restrictive `temporary-sandbox` profile, which denies e.g. `mach-lookup com.apple.backboard.display.services` → Campo (the wallpaper renderer) crash-loops (no wallpaper) + intelligencetasksd/feedbackd. Re-registering the container-manager host special port is NOT viable: the 26.4 kernel then SENDS the synchronous `CM_KERN` MIG request and BLOCKS for a reply 27 cannot produce → early-boot deadlock (verified: boot hangs, never reaches SpringBoard). Anchor is structural (string xref to `"failed to upcall to containermanagerd"` → the `cbz w0` immediately preceding the string-load `adrp`, itself immediately preceded by the upcall `bl`; backward branch to the success continuation; unique). Replacement `b` from the Keystone-backed `ARM64Encoder`. **VALIDATED on-device (2026-07-15, `17,3_27.0_24A5380h` + cloudOS 26.4 `c0ecdb4b…`, JB): iOS 27 wallpaper renders, Campo/SpringBoard/backboardd stable, boot clean (no freeze, no panic).** No-op-in-effect for version-matched userlands (there the upcall succeeds → the original cbz already branches to `<success>`). | Y |
| JB-03 | C | `patch_cred_label_update_execve` | `_cred_label_update_execve` | Reworked C21-v3: C21-v1 already boots; v3 keeps split late exits and additionally ORs success-only helper bits `0xC` after clearing `0x3F00`; still disabled pending boot validation | N |
| JB-04 | C | `patch_hook_cred_label_update_execve` | sandbox `mpo_cred_label_update_execve` wrapper (`ops[18]` -> `sub_FFFFFE00093BDB64`) | Faithful upstream C23 trampoline: copy `VSUID`/`VSGID` owner state into pending cred, set `P_SUGID`, then branch back to wrapper | Y |
| JB-05 | C | `patch_kcall10` | `sysent[439]` (`SYS_kas_info` replacement) | Rebuilt ABI-correct kcall cave: `target + 7 args -> uint64 x0`; re-enabled after focused dry-run validation | Y |
@@ -0,0 +1,101 @@
// KernelJBPatchContainerUpcall.swift JB kernel patch: force the sandbox exec-time
// container-manager upcall onto its success path (neutralize the autobox / kill).
//
// At exec, the sandbox hook `_hook_cred_label_update_execve` resolves each process's
// containers by a SYNCHRONOUS MIG upcall to containermanagerd over the container-manager
// host special port:
//
// w0 = container_manager_get_process_containers(...) // 0 = success
// if (w0 != 0) { // upcall failed
// os_log("... failed to upcall to containermanagerd for a platform app");
// ... // KILL the process, or autobox it into the restrictive
// // `temporary-sandbox` profile
// }
// // success path: use the returned container-derived sandbox
//
// iOS 27 DELETED this kernel-side upcall entirely (the stock iOS 27 kernel has no
// HOST_CONTAINERD_PORT / CM_KERN_* protocol container resolution moved out of the
// kernel), so 27's containermanagerd no longer implements the reply server. On the 26.4
// vphone600 kernel running a 27.0 userland, the upcall therefore fails
// (MACH_SEND_INVALID_DEST) for every platform app they are autoboxed into
// `temporary-sandbox`, which denies e.g. `mach-lookup com.apple.backboard.display.services`
// Campo (the wallpaper renderer) crash-loops (no wallpaper), plus intelligencetasksd /
// feedbackd. Re-registering the host special port is NOT an option: it makes the kernel
// SEND the MIG request and BLOCK for a reply 27 cannot produce early-boot deadlock.
//
// Fix: flip the `cbz w0, <success>` guard (taken when the upcall returns 0) to an
// unconditional `b <success>`, so a failed upcall takes the same path as a successful one
// instead of autobox/kill. Safe for version-matched userlands: there the upcall succeeds
// (w0 == 0), so the original cbz already branches to <success> the unconditional b is
// behaviourally identical (no-op in effect). Validated on-device: iOS 27.0 wallpaper
// renders, Campo/SpringBoard/backboardd stable, boot clean (no freeze, no panic).
//
// Anchor (structural, no hardcoded offsets/bytes): the os_log format string
// "failed to upcall to containermanagerd for a platform app" is loaded (adrp+add) on the
// failure fall-through; the guard is the `cbz w0, <back-branch>` immediately preceding
// that load, itself immediately preceded by the `bl <container upcall>`. The branch is
// backward (the success continuation precedes the call site). Replacement bytes come from
// the Keystone-backed ARM64Encoder.
import Foundation
extension KernelJBPatcher {
@discardableResult
func patchContainerManagerUpcall() -> Bool {
log("\n[JB] container-manager exec upcall: cbz w0 -> b (force success; skip autobox/temporary-sandbox)")
guard let strOff = buffer.findString("failed to upcall to containermanagerd") else {
log(" [-] 'failed to upcall to containermanagerd' string not found")
return false
}
// The string ref (adrp+add loading the format string) sits on the failure
// fall-through. The guard we flip is the cbz two instructions before the adrp:
// bl <container upcall> ; adrpOff - 8
// cbz w0, <success> ; adrpOff - 4 <- patch to `b <success>`
// adrp xN, "failed to..." ; adrpOff <- the string ref
var hits: [(cbzOff: Int, target: Int)] = []
for ref in findStringRefs(strOff) {
let adrpOff = ref.adrpOff
let cbzOff = adrpOff - 4
let blOff = adrpOff - 8
guard blOff >= 0 else { continue }
guard let cbz = disasAt(cbzOff), cbz.mnemonic == "cbz",
cbz.operandString.hasPrefix("w0"), // upcall status is a 32-bit w0
let bl = disasAt(blOff), bl.mnemonic == "bl" // the container upcall call
else { continue }
// Decode the cbz forward/backward target (imm19 << 2).
let word = buffer.readU32(at: cbzOff)
let imm19 = Int((word >> 5) & 0x7FFFF)
let signed = imm19 >= (1 << 18) ? imm19 - (1 << 19) : imm19
let target = cbzOff + signed * 4
// The success continuation precedes the upcall call site (backward branch),
// and must land in executable code.
if target < cbzOff, codeRanges.contains(where: { target >= $0.start && target < $0.end }) {
hits.append((cbzOff, target))
}
}
guard hits.count == 1 else {
log(" [-] container-manager upcall guard not found uniquely (found \(hits.count))")
return false
}
let (cbzOff, target) = hits[0]
guard let bBytes = ARM64Encoder.encodeB(from: cbzOff, to: target) else {
log(" [-] failed to encode B to 0x\(String(target, radix: 16))")
return false
}
let va = fileOffsetToVA(cbzOff)
emit(
cbzOff,
bBytes,
patchID: "container_manager_upcall_force_success",
virtualAddress: va,
description: "cbz w0 -> b [force container-manager exec upcall success; skip autobox/temporary-sandbox]"
)
return true
}
}
@@ -65,33 +65,27 @@ public final class KernelJBPatcher: KernelJBPatcherBase, Patcher {
patchKcall10()
patchSyscallmaskApplyToProc()
// Neutralize the exec-time ip_mac_return SECURITY_POLICY kill so a userland
// newer than the kernel (iOS 27 on the 26.4 kernel) can launch: AMFI's exec
// hooks reject the newer binaries' validation category ip_mac_return != 0
// core daemons (backboardd, cfprefsd, ...) die at exec boot deadlock.
// No-op-in-effect for version-matched userlands (ip_mac_return == 0 there).
patchExecSecurityPolicyKill()
// iOS 27 VZ-view (host paravirt-GPU scanout) fix kernel half of the
// "force the kern present path" pair:
//
// The 26.4 kernel's paravirt GPU only scans a frame out to the host when
// the guest presents via the IOMFB userclient SwapEnd (external method 5).
// iOS 27 defaults the paravirt display's present to IOMFB's parallel
// `_virt_*` path (an in-process callback that never enters the userclient),
// so the paravirt GPU never scans out host VZ window is black (the guest
// still composites; visible over in-guest TrollVNC). cfw_patch_iomfb_force_kern
// (userland half) retargets IOMFB's public `_IOMobileFramebufferSwap*`
// trampolines to `_kern_Swap*`, forcing present back onto method 5. But
// iOS 27's native SwapEnd struct is 0x6e0 bytes (26.x sent 0x588), and the
// 26.4 userclient exact-checks 0x588 in TWO places, so method 5 would return
// kIOReturnBadArgument. These two patches relax both size gates to accept
// 27's 0x6e0 (its IOMFBSwapRec prefix matches 26.x, so the paravirt swap
// handler reads valid fields):
// 1. dispatch-table checkStructureInputSize 0x588 variable
patchIomfbSwapEndVariableSize()
// 2. the handler's internal `cmp w2,#0x588` gate 0x6e0
patchIomfbSwapEndHandlerSize()
// iOS-27-only (gated a 26.x base skips these entirely). All target a 27
// userland on the 26.4 kernel and are unnecessary or actively harmful on 26.x:
// - exec ip_mac_return SECURITY_POLICY kill bypass: AMFI's exec hooks reject a
// userland newer than the kernel (27 binaries' validation category)
// ip_mac_return != 0 core daemons die at exec boot deadlock. 26.x
// binaries pass (ip_mac_return == 0), so it is not needed there.
// - container-manager exec-upcall force-success: iOS 27 deleted the kernel-side
// containermanagerd upcall, so on the 26.4 kernel it fails for every 27 app
// autoboxed into temporary-sandbox Campo/intelligencetasksd/feedbackd
// crash-loop. On 26.x the upcall succeeds, so it is not needed.
// - IOMFB SwapEnd size gates: 27's force-kern present (cfw_patch_iomfb_force_kern)
// sends a 0x6e0 SwapEnd struct; the 26.4 userclient exact-checks 0x588 in two
// places, so both gates are relaxed to accept 0x6e0. HARMFUL on 26.x it
// sends the native 0x588, which the retargeted handler gate would then reject
// every framebuffer swap fails dead display (the 26.5 regression).
if applyIOS27 {
patchExecSecurityPolicyKill()
patchContainerManagerUpcall()
patchIomfbSwapEndVariableSize() // dispatch checkStructureInputSize variable
patchIomfbSwapEndHandlerSize() // handler cmp w2,#0x588 0x6e0
}
return patches
}