Phase 2: Wrapper Read Overflow Fix — Test Results¶
Date: 2026-04-13 (session 7)
Commit tested: 59ca5cd — Fix wrapper read overflow at sdr+104 + research synthesis
Status: Fix works as intended but surfaced a NEW issue (CDS+0xB896 now blocks check-in)
Goal of this test¶
Deploy ONLY the wrapper read-overflow fix (without touching CDS+0xB896 or any other
hook) and observe behavior. The hypothesis was that some of the crashes previously
attributed to action dispatch were actually caused by g_hook_wrapper being a
garbage pointer read from sdr+104 (16 bytes beyond the 88-byte SDR object).
Deployment¶
scp iosmux_inject.m havoc:~/iosmux/inject/
ssh havoc "cd ~/iosmux/inject && make clean && make"
ssh havoc-root "cp .../iosmux_inject.dylib /Library/Developer/CoreDevice/..."
Build succeeded. dylib signed ad-hoc. Deployed to /Library/Developer/CoreDevice/iosmux_inject.dylib
with timestamp 2026-04-12 18:41.
Observed behavior¶
What works (new, positive)¶
- CDS process starts on
devicectltrigger (launchd spawns) - Our inject constructor runs, full init sequence completes
OS_remote_devicecreated with real RSD connectionRSDDeviceWrapper init returned: 0x600001448180— successg_hook_wrapper = 0x600001448180— real valid pointer, not garbageWrapper for CDS helper hook: 0x600001448180 (set in Step 9b)— Step 12 no longer reads fromsdr+104; takes the value directly from Step 9b's local variable- All hooks install without errors
PoC registration complete. Device visible in Xcode.logged- CDS stays alive across repeated
devicectlcalls (PID 1301 stable, exit code 0) - No crash reports for CoreDeviceService
- 62 services parsed from RSD Handshake (was 74 pre-iOS update — iPhone now on iOS 26.4.1)
What doesn't work (new issue)¶
devicectl list devices times out with XPCError 1000: connection interrupted
after ~15 seconds:
devicectl[1342:7112] DeviceManager sending check-in request: 3D152F75-...
(15 seconds of silence)
devicectl[1342:7113] Timed out waiting for CoreDeviceService to fully initialize
CoreDeviceService[1301:7118] [peer] invalidated because client process exited
System log analysis:
devicectlconnects to CDS XPC service — peer connection establisheddevicectlsendsDeviceManagerCheckInRequest { identifier: <uuid> }- CDS does not log any response
- No "Client connected: ... Handling DeviceManagerCheckInRequest" message
- No "Published DeviceManagerCheckInCompleteEvent" message
devicectltimes out after 15 seconds and disconnects- CDS sees the disconnect and logs the invalidation — but itself stays alive
Interpretation¶
The check-in request reaches CDS XPC layer but does NOT reach the Mercury
XPCMessageDispatcher that would normally produce the "Handling DeviceManagerCheckInRequest"
log line. Something in our hook set is swallowing it silently.
Leading hypothesis: CDS+0xB896 hook blocks check-in¶
Prior research (docs/research/action-interception-full-picture.md, E+F section)
identified CDS+0xB896 as the single callsite for CoreDeviceUtilities.invoke(anyOf:usingContentsOf:).
The current hook is mov al, 1; ret — return true unconditionally, never call the
original function.
If DeviceManagerCheckInRequest dispatch also goes through this same
invoke(anyOf:usingContentsOf:) path, then our hook intercepts it, returns "handled",
and Swift async continuation is left dangling. devicectl never gets a reply.
This matches the observed symptom: check-in request reaches CDS but never gets an async reply, so devicectl times out.
Why did this not affect earlier sessions?¶
Before the wrapper fix, g_hook_wrapper was a garbage pointer from the heap. CDS
would crash somewhere in the flow — often via CDS+0xCCB0 hook returning that
garbage to CDS, which then dereferenced it. The crash happened BEFORE check-in
dispatch could reach invoke(anyOf:usingContentsOf:), so the CDS+0xB896 hook
never became the blocker.
Now that the wrapper is valid, CDS does not crash, and check-in dispatch reaches
invoke(anyOf:usingContentsOf:) — where CDS+0xB896 swallows it.
In other words: the wrapper bug was masking the CDS+0xB896 bug. Fixing one exposes the other. This is exactly what E+F research predicted:
The
return truepath is the very bug that causes "error communicating" — it short-circuits the Swift async continuation and leaves Mercury holding an uncompleted reply context. Keeping it as a safety net makes things WORSE, not better, because the leak only matters for messages that actually reached Mercury.
What this means about previous "working" sessions¶
The sessions where we believed "device visible in Xcode as connected" may have been showing CACHED state from devicectl/Xcode, or the success window was very narrow between CDS startup and the crash. We do not have strong evidence that check-in ever actually succeeded through our injected CDS — we have log lines from the inject showing successful construction, but no system log from CDS showing a successful check-in response.
Need to verify against system log history from earlier sessions (may not be available after reboots).
Implications for trust in earlier findings¶
We found ONE fatal latent bug (wrapper read overflow) that hid for multiple sessions. It caused silent garbage-pointer returns to CDS via our hooks. Nothing in our test output or inject logs ever flagged it.
This means any other silent memory/ABI bugs in our inject code could be hiding behind crashes attributed to other causes. A systematic code audit is warranted before continuing to build on the current implementation.
See code-audit-findings.md (to be written after audit research completes) for
follow-up.
Next minimal test¶
Convert CDS+0xB896 hook from mov al, 1; ret (return true) to either:
- Remove the hook entirely (restore original callq)
- Passthrough trampoline (
movabs r10, <orig_invoke>; jmp r10) - Conditional: only intercept if XPC event has our device UUID
All three predict successful check-in. Predictions differ on what happens when Xcode Pair is clicked:
| Option | Expected Pair click behavior |
|---|---|
| Remove hook | Original invoke() runs — may crash as before (but on valid wrapper now) |
| Passthrough trampoline | Same as remove — original invoke() runs |
| Conditional intercept | Our device UUID → swallowed (may still break async); others → passthrough |
The passthrough trampoline is the recommended option from E+F research — it preserves the hook infrastructure for future evolution while making it benign.