AUA history — D38..D40 (2026-05-02)¶
Status: verified — historical archive
Distilled from D38 G-4/G-1/G-1' devicectl-side falsifications + D39
Xcode-side H#2 deploy attempt + recovery + D40 state-aware
retrospective. Archived from
aua-side-channel-mechanism.md
on 2026-05-02 anchor refactor; the main doc retains the current
architectural verdict and links here for the empirical chain.
D38 update: three devicectl-side cause-side hooks all FALSIFIED — listener Swift wrappers do not hold the send right (2026-05-02)¶
D38 attempted three independent devicectl-side cause-side hooks against
the listener Swift class lifecycle, on the hypothesis (D37-D / D37-F)
that Mercury.SystemXPCListenerConnection's deinit chain is what
drops the last mach send right and triggers the kernel
MACH_NOTIFY_NO_SENDERS cascade. All three failed with byte-identical
Path-B outcome (errorCode 3, ~10 ms latency, no D24-era XPCError 1001
peer[2019] sentinel), on a freshly-rebooted apparatus.
Test apparatus (post-VM-reboot, 2026-05-02)¶
- iosmux_inject.dylib sha
52df2cc6...4803(D23 v2 baseline preserved) - CoreDevice sha
bea205e2...0495(unchanged) - Mercury sha
753f919e...cdbb(unchanged) - Tahoe 26.3.2 (25D2140) x86_64
- iPhone (iosmux)
E8A190DD-...connected (no DDI) - New devicectl-side dylib
iosmux_devicectl_inject.dylibdeployed via DYLD_INSERT_LIBRARIES wrapper at~/iosmux/devicectl-wrapper.sh(D33 P3 feasibility re-confirmed:codesign -dshowsflags=0x2000(library-validation)noruntimeflag; SIP-off; ad-hoc signed dylib loads cleanly).
G-4 — extra retain on AUA-completion context at CD+0x30791 (FALSIFIED)¶
- Patch: 5-byte trampoline
e8 <rel32>atCD+0x30791(the_swift_allocObjectcall creatinglVar17per D37-D ownership trace lines 184-217). Hook page mmap'd RWX, calls original_swift_allocObjectthen_swift_retain(rax)thenret. Bumps lVar17 refcount permanently. - Theory (per D37-F): retain lVar17 → context lives forever →
listener referenced at
lVar17+0x18stays alive → no NO_SENDERS. - Build: dylib sha
771a9471...673154, libSystem-only, ad-hoc-signed, 32 KB. - Sanity: 5 install banners fired correctly in pid 1387; patch site
bytes verified (
e8 36 7d 31 00); rel32 reach0xaf986a≈ 11 MiB;install complete. - Empirical result: Path-B fired 9.9 ms after
success()reply (unified.log 13:26:25.756664 → 13:26:25.766613).pathA_success_lines=0/pathB_failure_lines=1/XPCError1001_lines=0. - Conclusion: lVar17+0x18 is NOT a strong reference (likely raw pointer / unowned), OR a different holder releases the listener irrespective of lVar17.
G-1 — NOP listener deinit body at Mercury+0x4e930 (FALSIFIED)¶
- Patch: 5-byte direct write
c3 90 90 90 90at function headMercury.SystemXPCListenerConnection.Cfd(deinit body) at VMA 0x4e930. Verified 5 original bytes55 48 89 e5 49. Function 40 bytes total, endingc3at 0x4e957, 8-byte align nop pad to 0x4e960. Replacement =retat function head, deinit body never executes its release calls. - Theory (per D37-D): deinit body does
_swift_unknownObjectRelease(listener_xpc)→ drops devicectl's last strong ref to the underlyingOS_xpc_connection. Skip body → no release → no NO_SENDERS. - Build: dylib sha
501a6f7b...b5108, libSystem-only, 27.6 KB. Mercury found at Tahoe cryptex path/Library/Apple/System/Library/PrivateFrameworks/Mercury.framework/...(suffix-match onMercury.framework/Versions/A/Mercuryworks cleanly). - Sanity: install banners present, patch site at runtime VMA
0x10aab9000+0x4e930verified, 5-byte write succeeded. - Empirical result: Path-B fired 9.9 ms after
success()reply, byte-identical surface to G-4. - Conclusion: D37-D's claim that 0x4e930 body does the
_swift_unknownObjectRelease(listener_xpc)is FALSIFIED. Either release happens elsewhere in the deinit chain, OR the listener Swift wrapper isn't what holds the send right at all.
G-1' — NOP listener __deallocating_deinit at Mercury+0x4e960 (FALSIFIED)¶
- Patch: 5-byte direct write
c3 90 90 90 90at function headMercury.SystemXPCListenerConnection.CfD(__deallocating_deinit) at VMA 0x4e960. Verified 5 original bytes55 48 89 e5 49(identical prologue to body). Function does 3 helper-release calls (likely field releases via metadata) then tail-call toswift_deallocClassInstance(size=0x30, alignmask=0x7). Patch makes functionretimmediately, skipping all releases AND the dealloc. - Theory: if release isn't in body, it's in the auto-generated
__deallocating_deinitfield-walker. NOP'ing the entire dealloc trampoline skips ALL field releases → Swift instance leaks forever → underlyingOS_xpc_connectionnever released. - Build: dylib sha
17b2950a...4b3e2, libSystem-only, 27.6 KB. - Sanity: install banners present, patch at runtime
0x102a53000+0x4e960, 5-byte write succeeded. - Empirical result: Path-B fired 11.0 ms after
success()reply (unified.log 13:58:46.278517 → 13:58:46.289534).XPCError1001_lines=0, same surface as G-4 / G-1. - Conclusion: skipping the entire listener dealloc trampoline did NOT prevent NO_SENDERS. The Swift class wrapper at Mercury+0x4e930-0x4ea00 range is NOT what holds the send right that, when dropped, fires the kernel notification.
Empirical conclusion across G-4 / G-1 / G-1'¶
Three structurally independent attempts on listener Swift wrapper lifecycle (retain context referencing listener / NOP listener body / NOP entire listener dealloc) all fail with byte-identical Path-B surface. This is significant empirical evidence that:
- Listener Swift wrappers at Mercury+0x4e930-0x4ea00 do not hold the send right that triggers NO_SENDERS.
- The actual holder is elsewhere — possibilities include:
- Peer Swift wrapper (
Mercury.SystemXPCPeerConnectionat Mercury+0x4e9d0-0x4ea30 — the OPPOSITE wrapper class, not listener) - libxpc-internal storage held by a C-level
OS_xpc_connectionrefcount independent of any Swift wrapper - Continuation thread cleanup — the success-resume thread on devicectl side might be releasing the peer as part of async function teardown, not via listener deinit
DUA._IndexBox<peer>(the late HIT #23 holder per D37-D) — holds the peer strongly, but releases only AFTER errorCode 3 has already been constructed; not the trigger
- Peer Swift wrapper (
D37-D's static map is therefore INCOMPLETE — the listener-deinit chain it documented is real but is NOT on the critical path of the send right release. D37-F's G-4 alternative similarly missed.
Latency stability across attempts¶
| Probe | Hook target | Latency (success → fail) |
|---|---|---|
| D24 baseline | none | ~11 ms |
| D30 (CDS-side AFM peer keep-alive) | global g_afm_retained_peers |
9.9 ms |
| D37-H (post-reboot baseline) | none | 14.7 ms |
| D38 G-4 | CD+0x30791 trampoline | 9.9 ms |
| D38 G-1 | Mercury+0x4e930 NOP | ~10 ms |
| D38 G-1' | Mercury+0x4e960 NOP | 11.0 ms |
Latency does not change meaningfully across hooks → the cancel cascade is happening through a path entirely DISJOINT from our hook targets.
Implications for next research¶
- Cause-side via Swift class lifecycle hooks is empirically dead end for listener wrappers. Three failure modes don't disprove it exhaustively for ALL Swift wrappers — peer wrapper untested — but the user has explicitly directed against another iteration of forcing the wrapper-class lifecycle approach: this is an undesirable path; look for the gate, not break through the wall.
- Direction shift: instead of preventing the race that fires after
GAMBIT's
success()reply, look for an entirely different angle. Working hypotheses for the next probe (D39-research): - Hook the AUA wrapper FUNCTION ENTRY at CD+0x31750
(
acquireDeviceUsageAssertion(withReason:options:) async throws). Skip the entire dispatch chain by synthesising aDeviceUsageAssertionsuccess result directly. Bypass the race, not fight it. - Xcode-side direct intervention: hook DVTCoreDeviceCore's
_shadowUseAssertion.fulfilledsetter directly, set true without ever invokingacquireDeviceUsageAssertion. Pure UI-gate flip. - Real Apple flow capture: what does side-channel peer[1013] actually communicate in a working pair? Are there messages we need to send/respond-to that we're missing? D31 captured ONE success run; closer analysis of that side-channel transcript may reveal what we're not doing.
Apparatus integrity (D38 non-destructive)¶
- All Apple binaries unchanged on havoc; no Apple files modified
- iosmux_inject.dylib (CDS-side) unchanged at D23 v2 sha
52df2cc6...4803 - New devicectl-side dylib at
~/iosmux/iosmux_devicectl_inject.dylibis opt-in via wrapper script — directxcrun devicectlinvocations are unaffected - No CDS / devicectl crashes; zero new DiagnosticReports
- iPhone (iosmux) still
connected (no DDI)
Distillation status¶
D38 raw notes preserved in repo (gitignored): notes/d38-g4-impl-spec.md,
notes/d38-g4-impl-buildlog.md (with §G-4 / §G-1 / §G-1' subsections).
Will be deleted post-D39 research dispatch per
feedback_notes_are_temporary_buffer. Source files at
inject/iosmux_devicectl_inject.m (G-1' final state, sha
03dd3275...0c2e), scripts/iosmux-devicectl-wrapper.sh, and the new
Makefile target are committed and stay as the apparatus foundation
for future devicectl-side experiments.
D39 update: Xcode-side hasConnection getter override — deploy¶
attempt + Xcode integrity incident (2026-05-02)
After D38 falsified all three devicectl-side cause-side hooks
(G-4 / G-1 / G-1'), D39 research dispatch (notes file at
notes/d39-gate-research.md, gitignored) ranked Hypothesis #2 as
the top forward direction: hook
DVTCoreDeviceCore.DVTCoreDevice_Impl.hasConnection.getter
(Swift mangled _$s17DVTCoreDeviceCore0aB5_ImplC13hasConnectionSbvg)
inside Xcode IDE at DVT+0x19e10, replacing the function head with
6-byte b8 01 00 00 00 c3 (mov eax, 1; ret) so the getter
unconditionally returns true. Per
pair-button-and-cfnetwork.md
§"Pair button gating", this single Bool flip is the documented UI
gate — flipping it directly bypasses the entire AUA dispatch chain
that D38 cause-side hooks failed to suppress.
D39 deploy attempts¶
- Attempt 1 (15:42):
iosmux_xcode_inject.dylibbuild sha7f81c6c9...ddb02. Sanity passed but real Xcode launch via wrapper bailed:[iosmux_d39] DVTCoreDeviceCore not in dyld image list — refusing to install. Cause: Xcode lazy-loads DVTCoreDeviceCore (only when Devices and Simulators window first opens); the constructor's one-time_dyld_image_countscan happens before lazy-load. - Attempt 2 (post lazy-load fix): build sha
9c76c383.... Replaced one-time scan with_dyld_register_func_for_add_imagecallback. Callback fires synchronously for every already-loaded image AND for each newly-loaded image, so the patch happens whenever DVTCoreDeviceCore appears regardless of lazy-vs-eager load. Sanity passed. Not yet validated against full Xcode IDE launch due to incident below.
D39 §J.6 xref pre-impl task — _shadowUseAssertion reader survey, MVP-OK verdict¶
D39's research spec required a pre-impl xref task to enumerate every
reader of the _shadowUseAssertion ivar at field offset 0x400 on
DVTCoreDevice_Impl (field-offset variable at DVT+0x6d7f8). The spec's
MVP-OK precondition assumed only hasConnection.getter plus the four
canonical setter-chain functions touched the field; the xref pass
empirically falsified that assumption (12 callers, not 5) but the
underlying RISK the spec guarded against — force-unwrap of nil shadow
when our hook flips hasConnection.getter true while the ivar stays
nil — does NOT materialise: every one of the 12 callers null-checks
before any deref, so non-canonical readers continue down their nil
branch exactly as they do today; only hasConnection.getter flips.
Twelve callers identified via r2 axt @ 0x6d7f8 on
/home/op/dev/iosmux/binaries/DVTCoreDeviceCore x86_64 slice (host
sha 635fbe0c1f7e0c4daed9b537976425bb96e7bb707661d51f2a8233a59d5dfabe),
classified by null-check posture and UI-relevance:
| # | Caller VMA | Containing function | Null-check | UI-relevance | Crash risk |
|---|---|---|---|---|---|
| 1 | DVT+0x50a7 | canRename.getter |
yes (test r13,r13; je) |
Rename button | none |
| 2 | DVT+0x993a | canConnectOnDemand.getter |
yes | irrelevant for tethered iPhone | none |
| 3 | DVT+0xb213 | _connectAndPrepareImplicitly |
yes | internal connect path | none |
| 4 | DVT+0x14c07 | deviceSummaryInternalPropertyDictionaries.getter |
multiple | About box / diagnostic | none |
| 5 | DVT+0x174ab | unavailabilityErrors.getter |
yes | possibly UI — see caveat | none crash; caveat |
| 6 | DVT+0x199b5 | connectedDurationMonotonicNS.getter |
yes | uptime display | none |
| 7 | DVT+0x19e5e | hasConnection.getter |
KNOWN target | Pair button gate | N/A (patch site) |
| 8 | DVT+0x24429 | _connectWhileLocked(reason:) |
yes | internal lock-state path | none |
| 9 | DVT+0x44a58 | deinit (Cfd) |
release-or-skip | maintenance | none |
| 10 | DVT+0x44cc8 | __deallocating_deinit (CfETo) |
release-or-skip | maintenance | none |
| 11 | DVT+0x490b9 | init(_:) constructor |
WRITES *(self+0x400)=0 |
construction | N/A (write) |
| 12 | DVT+0x4baf1 | useAssertion.setter (Tf4gn_n) |
KNOWN canonical setter | setter chain | N/A |
For sanity reference, the parallel _lockUseAssertion field-offset
variable at DVT+0x6d7f0 has 16 callers — strict superset of shadow
callers, expected because every reader takes the lock then reads
shadow. Confirms the access pattern is uniform.
A separate byte-pattern search for inline 0x400 displacement returned
zero hits in __text (0x16b0..0x54810) — no site reads [reg+0x400]
with the offset baked in as immediate; every site goes through the
field-offset variable load. This is canonical Swift-property codegen
behaviour and confirms the xref pass had full coverage.
Caveats deferred to user IDE relaunch:
unavailabilityErrors.getter(#5) — names suggest Xcode UI may consult this to render "device unavailable" labels. When_shadowUseAssertionis nil it returns its nil-branch result — what it does today regardless of the hook. If after the hook the device still shows as "unavailable" despitehasConnection=true, this getter is the prime suspect for an H#4 escalation.canRename.getter(#1) — Rename button in Devices window may stay disabled. Cosmetic / feature-loss only; not a crash.connectedDurationMonotonicNS.getter(#6) — uptime display may read 0. Cosmetic only.
D39 incident — Xcode integrity check tripped¶
After the attempt-1 launch and subsequent quit (via osascript -e
'quit app "Xcode"' for redeploy), the next user-initiated Xcode
launch produced the macOS Gatekeeper alert:
Xcode can't be opened. "Xcode" is damaged and can't be opened.
Delete "Xcode" and download it again from the App Store.
Bundle on disk is presumed UNTOUCHED — H#2's mechanism is in-memory
mach_vm_protect VM_PROT_COPY (per-process COW, not disk write).
Sha verification post-recovery will confirm. Most likely cause: the
DYLD_INSERT_LIBRARIES launch against Xcode IDE
(flags=0x2000 library-validation, no hardened runtime) poisoned
macOS LaunchServices / taskgated / lsd / syspolicyd cache, even
though amfi_get_out_of_my_way=1 boot-arg is present and was
sufficient for D33 P3's devicectl injection.
Architectural lesson¶
amfi_get_out_of_my_way=1 is not a complete bypass of macOS
binary-protection layers when the target is Xcode-class (versus
devicectl-class). D33 P3 verified DYLD_INSERT_LIBRARIES feasibility
on the same boot-arg posture, but D33 tested ad-hoc-signed dylib
into the comparatively-unprotected devicectl. Xcode itself goes
through additional integrity layers (LaunchServices state, possibly
internal codesign self-checks) that the D33 result did not validate
against.
For any future Xcode-side inject work, the impl plan must include:
- Pre-launch sha-baseline of every Xcode binary the inject touches.
- Post-launch sha-verification + LaunchServices state inspection.
- Recovery procedure baked in (xattr clear + reboot rollback).
- Test the inject against a non-Xcode DVTCoreDeviceCore-loading
process FIRST if any exists (e.g.
xcrun simctlmay load it). - Don't rely on
amfi_get_out_of_my_way=1alone for Xcode-class targets — assume LaunchServices may still react.
The D39 H#2 source files (inject/iosmux_xcode_inject.m,
scripts/iosmux-xcode-wrapper.sh, Makefile target) are committed
and stay as the apparatus foundation for future state-tagged
retest, gated on user-driven recovery confirmation.
D39 incident recovery confirmed 2026-05-02 ~16:25¶
User rebooted havoc, ran iosmux-restore.sh, and Xcode launches
normally with synthetic iPhone (iosmux) visible in Xcode UI Devices
and Simulators. The "Xcode is damaged" alert was confirmed transient
LaunchServices state poisoning, not bundle corruption — bundle bytes
on disk are intact.
Comprehensive sha audit 2026-05-02 16:27 confirmed every Apple
binary on havoc matches the host research copy at
/home/op/dev/iosmux/binaries/ byte-for-byte:
| Binary | sha256 |
|---|---|
| CoreDevice.framework/Versions/A/CoreDevice | bea205e2c64622d144bcc7664ee104083d0e192aca206739cca345dc7c420495 |
| CoreDeviceUtilities | b7ce01c47018b9bcb903fe4a134ff4b59a3db4aba69c7fcd40a6e0d4d19a7204 |
| Mercury (cryptex) | 753f919e840726f944372ab0bcc8a4c4871790312f38c98dc04f8d0978c6cdbb |
| devicectl | 4fede2dd4fdbe542547b535f70ad320f8c33093cd23ff5b226aaad7c327cbf6c |
| DVTCoreDeviceCore | 9c539c78210e0ab462aebbdab8e499688c8dc71c5ecf165cac50cee93e4e4d1d |
| Xcode IDE binary | f681556c5a3d53286b50744bce3573126c3243f609125eab26db843588799b60 |
| CoreDeviceService.xpc binary (patched with iosmux LC_LOAD_DYLIB) | 9d95ad21fd81223a957ba7ef115ecbbdfb81342ec9aeb91370774e8c1ab60ac1 |
User-mandated comprehensive backup created at
/home/op/backups/iosmux/recovery-2026-05-02/ covering all Apple
bundles (binaries plus _CodeSignature/CodeResources files
including the 38 MB master Gatekeeper file
Xcode.app/Contents/_CodeSignature/CodeResources) plus mirrors of
all five iosmux-side dylibs from havoc. Full sha manifest at
recovery-2026-05-02/shas.txt.
Operational lessons distilled from the incident:
- In-memory
mach_vm_protect VM_PROT_COPYpatching is genuinely per-process COW; disk bytes are not modified. Confirmed empirically by the post-recovery sha audit. - macOS LaunchServices / taskgated / lsd / syspolicyd may still
poison process-launch state for a library-validated binary even
when
amfi_get_out_of_my_way=1boot-arg is active. The poisoning is reboot-volatile. - Pre-launch sha baseline of every Apple bundle that the inject can conceivably reach is now mandatory before any future Xcode-side inject deploy. The recovery-2026-05-02 backup is the new baseline.
D40 update: state-aware retrospective — past 0/N baselines may be¶
state-conditional (2026-05-02)
After D38's three-falsification cluster the user identified that across the D20..D39 probe history, probes ran in DIFFERENT apparatus states (Xcode UI engagement, CDS lifetime, recent Pair-click history) without recording it. The long-standing inconsistency between D31's Run #2 SUCCESS path observation and the D29/D37-G/D37-H 0/N "Path-A absent" baseline likely reflects state-difference, not race jitter.
D40 retrospective mapped every probe D19..D39 to its state-tuple where determinable, classifying findings as state-invariant (~14, durable binary facts / hardcoded asm / kernel mechanisms) vs state-dependent (~8, including D31 Run #2, D29 0/N, D37-G/H baselines, D38 G-x falsifications) vs unrecoverable (~4). The forward-looking notes-file template (Dimensions 1-8 capture pre- trigger, post-trigger delta, comparison rules disallowing cross-state generalisation) lives in the state-tagged probe template runbook.
Top three historical inconsistencies plausibly explained by state difference:
- D31 Run #2 SUCCESS vs D29/D37-G/H 0/N — most likely lldb perturbation difference plus uncaptured Xcode UI state delta.
- D37-D static prediction vs D38 G-1/G-1' empirical falsification — NOT a state delta but an incomplete static-map issue; listener Swift wrappers don't actually hold the send right.
- D24 peer[2019] sentinel observed vs D31/D37-G "1001 sentinel gone" — apparent inconsistency is per-session pid rotation; same architectural mechanism, refer by role not pid.
State dimensions enumerated (8): Xcode UI state, CDS state, devicectl state, apparatus age, trigger type, inject configuration, probe instrumentation, ambient log baseline. Notes-file template codified in §F (Dimension 0 capture pre-trigger, Dimension Z post-trigger delta, comparison rules disallowing cross-state generalisation).
State-tagged retest plan (§E) prioritises:
- P1: D31 Run #2 reproduction across 6-cell Xcode-state matrix.
- P2: D39 H#2 validation across multiple states (NOT a single cell — declaring "the gate works" from one cell would replicate the D38 mistake). Currently blocked by D39 Xcode integrity incident; resumes post-recovery.
- P3: D38 G-1' retest in Xcode-engaged cell (D38's "listener wrappers don't hold send right" verdict was Xcode=A only; may flip in hot state).
The user has explicitly assumed control of all GUI state transitions for forward retests; the dispatcher will ONLY run CLI batches on user signal "I'm in state X". No auto-launch of Xcode or auto-clicking of Devices window UI by the dispatcher.
Memory anchor feedback_state_tagged_testing.md adopted as the
project-wide rule: every probe MUST record state explicitly, and
findings cannot be generalised across states without empirical
evidence in each.
D41 G-2 update: peer __deallocating_deinit NOP at Mercury+0x4ea10 — FALSIFIED across S1+S3 (2026-05-02)¶
Following D38's three-falsification listener-wrapper cluster, D41 tested
the OPPOSITE Mercury Swift class — SystemXPCPeerConnection rather than
SystemXPCListenerConnection — to close out D37-F's "send right holder
is elsewhere" claim by exhausting the peer wrapper too. State-tagged
across S1 (cold reboot, Xcode not running) AND S3 (Xcode running,
Devices and Simulators open, Pair clicked) per D40 protocol. The S1+S2+S3
state-tagged retest of pre-existing baselines also ran (D40 P1
priority): naked + G-1' wrapped variants across S1, S2, S3 produced
36/36 Path-B identical to D29's historical 0/30 baseline — falsifying
"past 0/N was state-conditional" hypothesis.
Hook target — peer __deallocating_deinit at Mercury+0x4ea10¶
Symbol: _$s7Mercury23SystemXPCPeerConnectionCfD per nm -arch x86_64.
Same 5-byte patch pattern as G-1' (c3 90 90 90 90), same proven
mechanism (mach_vm_protect VM_PROT_COPY in-process COW, no disk
modification). Patch site verified 55 48 89 e5 49 8b 7d 20 matches
listener prologue (G-1') byte-for-byte except for captured-field offset
(r13+0x20 vs r13+0x10).
Mercury is FAT universal — x86_64 slice at file offset 0x4000. Required
0x4ea10 vmaddr = 0x52a10 file offset for static dd checks. First D41
agent halted on a SHA-1/SHA-256 false alarm before this became relevant;
re-dispatched with shasum -a 256 directive proceeded cleanly.
Implementation¶
- Source pivoted:
inject/iosmux_devicectl_inject.mconstantIOSMUX_D38_G1P_PATCH_VMA = 0x4e960→IOSMUX_D41_G2_PATCH_VMA = 0x4ea10. Log prefix[iosmux_d38_g1p]→[iosmux_d41_g2]. All renames internally consistent (no leftover G-1' tokens). - Built on havoc: sha256
ab178ee577a1d2805c382fba9e4b39234e9aa081da5fec53aaa925a540d172a4(G-1' was17b2950a...4b3e2; backup at~/backups/iosmux/d41-pre-deploy/iosmux_devicectl_inject.dylib.g1prime). - Deployed at
/Users/nullweft/iosmux/iosmux_devicectl_inject.dylib, reachable via existing/Users/nullweft/iosmux/devicectl-wrapper.sh(wrapper script content unchanged).
State-tagged results¶
| State | C1 naked info | C2 wrapped (G-2) info | C3 naked list | C4 wrapped (G-2) list |
|---|---|---|---|---|
| S1-G2 (cold, Xcode closed) | 0/3 + 3/3 | 0/3 + 3/3 | 0/3 + 0/3 listed 3/3 | 0/3 + 0/3 listed 3/3 |
| S3-G2 (warm Xcode + Devices&Sims + Pair clicked) | 0/3 + 3/3 | 0/3 + 3/3 | 0/3 + 0/3 listed 3/3 | 0/3 + 0/3 listed 3/3 |
Hook telemetry confirmed 6/6 install banners landed in S1-G2 and 6/6 in
S3-G2 (12 total wrapped invocations); zero patch-site mismatches; suffix
a10 in patch-site VMA logs confirms G-2 offset (not G-1' 960).
Empirical conclusion across D38 G-4/G-1/G-1' + D41 G-2¶
Four structurally independent Mercury Swift wrapper hooks all FALSIFIED with byte-identical Path-B outcome (errorCode 3, ~10 ms latency, no D24-era XPCError 1001 peer[2019] sentinel):
| # | Probe | Hook target | Mercury class |
|---|---|---|---|
| 1 | D38 G-4 | CD+0x30791 (AUA-completion ctx retain) | n/a — context held listener |
| 2 | D38 G-1 | Mercury+0x4e930 (deinit body NOP) | SystemXPCListenerConnection |
| 3 | D38 G-1' | Mercury+0x4e960 (__deallocating_deinit NOP) | SystemXPCListenerConnection |
| 4 | D41 G-2 | Mercury+0x4ea10 (__deallocating_deinit NOP) | SystemXPCPeerConnection |
State-tagged retest (D40 protocol) confirms apparatus state has zero influence: cumulative 66/66 Path-B deterministic across 6 state×inject-loadout combinations (S1+S2+S3 baselines + S1-G2+S3-G2 + 36 D29 historical). ZERO Path-A in any cell.
The send right that triggers MACH_NOTIFY_NO_SENDERS is empirically
NOT held by any Swift class wrapper in Mercury reachable via
DYLD_INSERT into devicectl. Holder must be in:
- libxpc-internal C state (the underlying
OS_xpc_connectionrefcount tracked by libxpc itself, opaque API) - a third Mercury class (e.g.
SystemXPCConnectionparent — but D37-F enumerated all Mercury deinits and ruled out cancel calls there) - CoreDevice-side Swift wrapper rather than Mercury-side (untested)
- the CDS-half of the peer mach port pair, where the cancel observer fires (suppress observation rather than cause)
Implications for forward research¶
Cause-side via Swift wrappers in devicectl: EXHAUSTED. Six independent cause-side attempts in cumulative project history (D23 cache eviction + D30 peer lifetime + D38 G-4/G-1/G-1' + D41 G-2) all failed. The wrapper-class lifecycle approach is empirically dead-end.
Selected forward direction: Direction C via Ghidra (focused static disasm probe extending the existing D36 Ghidra project with these research questions:
- DUA (
CoreDevice.DeviceUsageAssertion) construction site — D26's blocker. Likely inside CDS reply-decoder for AUA Output payload. - Side-channel peer setup in CDS — what API CDS exposes on the peer connection, what events it posts during a successful AUA flow.
- Cancel-trigger upstream in CDS application code — D32 proved
kernel-driven
MACH_NOTIFY_NO_SENDERS, but Ghidra may find an app-code call that DROPS the send right reference, suppressible via existing CDS LC_LOAD_DYLIB patching mechanism.
Direction C results unlock Direction A (daemon-side AUA forge in CDS) as the implementation step. Direction B (H#2 persistent UI gate) remains deferred per D39 integrity incident risk class.
Apparatus integrity (D41 also non-destructive)¶
All Apple binaries unchanged on havoc; no Apple files modified.
iosmux_inject.dylib (CDS-side) unchanged at D23 v2 sha
52df2cc6...4803. New devicectl-side dylib at
~/iosmux/iosmux_devicectl_inject.dylib (G-2 build sha ab178ee5...172a4)
is opt-in via wrapper — direct xcrun devicectl invocations unaffected
(verified by C1+C3 baseline producing identical results to S1/S2/S3
unmodified runs). No CDS or devicectl crashes; zero new DiagnosticReports
during S1-G2 or S3-G2 batches. iPhone (iosmux) connected (no DDI)
throughout. CDS PID 1535 stable across both batches.
Distillation status (D41)¶
D41 raw notes (notes/d41-g2-peer-wrapper.md, ~237 lines) deleted at
distillation time per feedback_notes_are_temporary_buffer. Source
files at inject/iosmux_devicectl_inject.m (G-2 final state). Backup
of G-1' at ~/backups/iosmux/d41-pre-deploy/iosmux_devicectl_inject.dylib.g1prime
(host-side, not in repo). Currently deployed dylib on havoc is G-2 —
dispatcher can revert via single scp+cp from the backup if a future
probe requires G-1' baseline.
D42 update: architectural inversion discovered + Q3 zero CDS-side cancel callers + DUA is NOT wire type — Direction A in original framing DEAD, Direction D (StateSnapshot probe → fix GAMBIT AFM structure) emerges (2026-05-02 evening)¶
State at probe: S3 (Xcode running, Devices and Simulators open,
Pair clicked, "Failed to acquire assertion" error visible in UI).
This is documentation discipline per
feedback_state_tagged_testing.md — even pure static disasm probes
record their apparatus state for later cross-correlation.
D42 was a static-only Ghidra probe extending the existing D36 project
(/home/op/dev/iosmux/ghidra/) with three documentation questions
about Apple's reference AUA protocol surface. First dispatch attempt
hit Anthropic usage-policy guardrails (initial brief framing read as
offensive-tooling enumeration); reframed brief emphasising
protocol-research-for-understanding completed cleanly under Opus
model. Findings empirically reframe the entire forward roadmap.
Q1 — DUA is a CLIENT-SIDE wrapper class, NOT the wire-format Codable¶
CoreDevice.DeviceUsageAssertion is a Swift CLASS (Hashable +
Equatable, NO Codable conformance). Stored properties: 4 fields
(_lockedMutableState, identifier: UUID,
options: UsageAssertionOptions, reason: String) plus a computed
state: State { case fulfilled / case invalidated(Error?) } that
dispatches via class-method-table to read _lockedMutableState.
The wire-format Codable that flows on the side-channel peer is
CoreDevice.AssertionFulfilledMessage (a struct, NOT a class):
struct CoreDevice.AssertionFulfilledMessage : Codable {
var identifier: Foundation.UUID
var updatedSnapshot: <SomeStateSnapshot> // !!! gap — TMA at CD+0x29bb30
}
The DUA wrapper is built CLIENT-SIDE in CD's listener-handler block
(at CD+0x2fb30) by decoding an inbound AFM. Construction site at
CD+0x2ff02 calls swift_allocObject with metadata from the type-
metadata accessor at CD+0xd3570, then writes 4 stored properties
to runtime-patched offset globals at 0x41e8b0..0x41e8c8 and
0x3e7d80.
Implication: Direction A in its original framing ("forge a
synthetic DUA in CDS") is empirically DEAD. DUA is not what flows
over XPC — AFM is. GAMBIT already emits AFM today via
gambit_emit_afm_via_endpoint in inject/iosmux_gambit.m.
Q2 — Architectural inversion: CDS is the CLIENT, listener is in devicectl¶
The side-channel peer's listener half lives in CD/devicectl, not in CDS. Per Q2 §D:
[devicectl/CD process] [CDS daemon process]
anonymous listener half ◄──── side-channel peer connection
(xpc_endpoint_create at CD+0x30afd) (xpc_connection_create_from_endpoint)
handler block: CD+0x2fb30 wrapped in Mercury.{System|Remote}XPCPeerConnection
├ event-handler installed via Mercury Swift API
└ sends AssertionFulfilledMessage outbound
CDS receives the listener endpoint INBOUND in the action's
Input.endpoint field (at action invocation time), then constructs
its peer half via xpc_connection_create_from_endpoint. It wraps
the raw OS_xpc_object peer into a Mercury class instance
(Mercury.SystemXPCPeerConnection or Mercury.RemoteXPCPeerConnection)
via swift_dynamicCastClassUnconditional. Three CFE call sites
exist in CDS at 0x100020481, 0x10002120d, 0x10005e461 (the
last is FetchDyldSharedCacheFiles, out of AUA scope).
Anonymous-listener provider in Mercury:
Mercury.XPCSideChannel.anonymousListener() — CD/devicectl reaches
this; CDS never references it.
Implication: MACH_NOTIFY_NO_SENDERS fires on the listener
side (in devicectl) when the last peer-side send-right (in CDS) is
dropped — confirming the D29-D40 mechanism model. The send-right
holder is unambiguously inside CDS's Mercury wrapper instance.
Q2 — Side-channel protocol map (asymmetric, single-shot, no graceful termination)¶
| Direction | Message | Type | Frequency |
|---|---|---|---|
| CDS → CD | AssertionFulfilledMessage(.success) |
Codable enum payload-1 | exactly once per acquire |
| CDS → CD | AssertionFulfilledMessage(.error) |
Codable enum payload-2 | once on failure |
| CD → CDS | (none) | — | — |
| Either side | (no termination msg) | — | lifecycle ends via send-right release |
CD's listener handler block (CD+0x2fb30) decodes the wire enum:
- payload 1 → AFM-success → materialize DUA (B.5 construction
sequence) →
Result.success(DUA)to awaiter completion - payload 2 → AFM-error →
Result.failure(error)to awaiter -
1 message → warning "Received > 1 assertion fulfillment for anonymous assertion listener"
- "Peer is unexpectedly nil" → if peer connection dropped before AFM arrives → error path
The protocol is single-shot per listener with NO graceful shutdown handshake. Lifecycle ends purely via send-right release triggered by Swift class deinit through ARC.
Q3 — ZERO CDS-side application-code cancel/release callers¶
Static enumeration of CDS binary (/home/op/dev/iosmux/symbols/d42-q3-cds-*.txt)
found 0 direct call sites to any of the requested cancel/release/
dispose API family. The full CDS XPC API surface is 11 stubs total:
xpc_array_create, xpc_connection_create_from_endpoint (3 sites),
xpc_dictionary_create_empty, xpc_dictionary_set_{bool,string,value},
xpc_main, xpc_remote_connection_create_with_remote_service,
xpc_strerror, xpc_transaction_exit_clean,
remote_device_get_xpc_remote_connection_version_flags
CRITICALLY ABSENT: xpc_release, xpc_connection_cancel,
xpc_remote_connection_cancel, xpc_connection_resume,
xpc_connection_set_event_handler, xpc_connection_send_message,
listener-creation APIs, dispatch_source_cancel, dispatch_mach_cancel,
_xpc_connection_close, _xpc_connection_send_termination_event,
_xpc_pipe_invalidate.
CDS uses pure Swift-ARC for lifecycle. Reference-counting drops
→ Mercury wrapper deinit fires → Mercury internally releases the
OS_xpc_object → libxpc's port-rights-release path → listener
side eventually receives MACH_NOTIFY_NO_SENDERS.
Updated cancel-callers matrix across the entire bundle:
| Binary | xpc_connection_cancel | xpc_remote_connection_cancel | dispatch_mach_cancel | Mercury cancel thunk callers |
|---|---|---|---|---|
| CoreDevice | 0 | 0 | 0 | 0 |
| CoreDeviceUtilities | 0 | 1 (DTRS-service-client cancel, not on AUA path) | 0 | 0 |
| Mercury | 1 (Mercury+0x4e589, unreachable) | 1 (Mercury+0x3d239, unreachable) | 0 | 0 |
| devicectl | 0 | 0 | 0 | 0 |
| CoreDeviceService | 0 | 0 | 0 | 0 |
The single xpc_connection_cancel at Mercury+0x4e589 is itself
unreachable from any binary (D37-F established this). The AUA peer
connection is NEVER explicitly cancelled in any reachable code
path. This strongly reinforces the D38+D41 conclusion that the
send-right holder cannot be reached via wrapper-level Swift hooks.
Implication: CDS-side cause-side hook is empirically DEAD — there is nothing to hook in CDS application code. Any future interception must operate either below the Swift wrapper layer (libxpc / mach port APIs in Mercury/libxpc, both UNVIABLE per D33), or at the listener side (CD/devicectl's listener handler block at CD+0x2fb30) where AFM arrival/decode can be observed and substituted.
Forward design pivot — Direction D emerges¶
Direction A (forge DUA in CDS) is DEAD per Q1. CDS-side cause-side hook is DEAD per Q3. The wrapper-class lifecycle approach has been exhaustively falsified (D38 G-4/G-1/G-1' + D41 G-2 + D42 Q3 = 5 empirical dead-ends).
The remaining hypothesis with the lowest cost and most direct gate- finding character is:
Direction D — document StateSnapshot field set + verify GAMBIT
AFM structural fidelity.
Premise: GAMBIT's current gambit_emit_afm_via_endpoint puts a
DeviceStateSnapshot (from PairAction schema) into AFM's
updatedSnapshot field, but the AFM Codable type expects some
<SomeStateSnapshot> whose actual identity is !!! gap — TMA
referenced at CD+0x29bb30 in the listener decode chain. If the
expected type is NOT DeviceStateSnapshot (most likely a different
type — perhaps AssertionStateSnapshot or similar), then Apple's
Codable decoder in CD's listener block fails on the type mismatch,
the decode throws, and the awaiter resolves with .failure →
"Failed to acquire assertion" UI label. This would be CONSISTENT
with the 66/66 Path-B determinism observed across all states ×
inject loadouts.
D43 next step: focused Ghidra probe documenting the
updatedSnapshot field's actual type + complete field set. After
that, regenerate GAMBIT's AFM emission with structurally-correct
payload and run the state-tagged batch. If AFM decode succeeds in
CD listener → DUA materializes → continuation resumes with success
→ no race because we already won.
Apparatus integrity (D42 also non-destructive, S3 state)¶
- All Apple binaries unchanged on havoc + host research copies. sha-confirmed identical to D41 baseline.
- Ghidra project at
/home/op/dev/iosmux/ghidra/preserved (D36 + D37-F + D42 work all coexisting). - libxpc.dylib x86_64 slice IS available at
/home/op/dev/iosmux/dsc/extracted-v3/libxpc.dylib— D37-F's libxpc gap was for arm64e only; partially closed for x86_64. - CoreDeviceService binary copied from recovery backup at
/home/op/backups/iosmux/recovery-2026-05-02/apple-bundles/...to/home/op/dev/iosmux/binaries/(sha-verified9d95ad21...0ac1). NOT YET imported into Ghidra project — CDS analysis done via radare2 + llvm-objdump command-line. - iPhone (iosmux)
connected (no DDI)throughout; CDS PID 1535 stable; zero new DiagnosticReports. - Apparatus state during D42: S3 (Xcode running from Dock, Devices and Simulators open, iPhone row clicked, Pair attempted, "Failed to acquire assertion" error visible in UI). Pure static disasm — no apparatus interaction during probe.
Distillation status (D42)¶
D42 raw notes (notes/d42-ghidra-c-probe.md, ~620 lines) deleted at
distillation time per feedback_notes_are_temporary_buffer. Per-
question text dumps preserved at
/home/op/dev/iosmux/symbols/d42-{q1,q2,q3}-*.txt (host-side, not
in repo) — 40+ files documenting DUA class metadata, construction
site, deinit chain, peer connection construction sites, CDS XPC
stub inventory, full CDS disasm. Verdict + architectural inversion
+ Q3 zero-callers finding + Direction D pivot preserved in this
section + the Q-D66-15 D42 Resolution log entry in
d66-history-q15-d34-d40.md.
D43 update: TMA at CD+0x29bb30 IS DeviceStateSnapshot — type identity matches GAMBIT byte-for-byte; D42 §G working hypothesis FALSIFIED (2026-05-03, S3 state)¶
D43 was the focused Ghidra static probe Direction D promised — answer
"what is <SomeStateSnapshot> at TMA CD+0x29bb30 and does it match
what GAMBIT puts in AFM updatedSnapshot?" Answer: same type. Working
hypothesis collapsed.
Type identity (resolved entirely from existing D42 dumps)¶
/home/op/dev/iosmux/symbols/CoreDevice-x86_64-demangled.txt:1724:
Same file line 5081 (AFM init demangled): init(identifier:
Foundation.UUID, updatedSnapshot: CoreDevice.DeviceStateSnapshot) —
confirms <SomeStateSnapshot> slot type. No new disasm needed for B.1.
Conformances (lines 1727-1729): Encodable, Equatable, Decodable.
Nominal type descriptor 0x38e524. Type metadata 0x3ee8f8. Module
CoreDevice (private framework).
Field set (3 stored properties = 3 CodingKeys)¶
D43 disassembled DSS auto-synth init(from:) at 0x29d850 and
encode(to:) at 0x29d6b0. The auto-synthesized container reads/writes
3 distinct byte-tag values (0, 1, 2) for KeyedDecodingContainer
keys — matches stored-property count exactly:
| offset | type | name | source |
|---|---|---|---|
| (runtime, dword[ntd+0x14]) | CoreDeviceProtocols.DeviceInfo |
deviceInfo |
demangled symbols 1708..1711, designated init signature |
| 0 | [CoreDevice.Capability : [Swift.String]] |
capabilityImplementations |
demangled symbols 1717..1720, designated init at 0x29d520 writes [r12+0] |
| (runtime, dword[ntd+0x18]) | Swift.Int64 |
monotonicIdentifier |
demangled symbols 1715..1716, designated init writes via runtime offset |
Plus one computed property capabilities: Set<Capability> (getter-only,
not stored, NOT in CodingKeys). NO state field on DSS.
GAMBIT comparison (mismatch analysis)¶
GAMBIT's gambit_build_dss_only (around inject/iosmux_gambit.m:490)
emits 4 keys: state (xpc_dictionary {"connected": {}}),
capabilityImplementations (empty xpc_array), deviceInfo (sub-dict),
monotonicIdentifier (xpc_int64 = 1). Real CodingKeys are 3.
Differences:
- Type mismatch (DSS slot itself): NONE — TMA at
CD+0x29bb30ISCoreDevice.DeviceStateSnapshot. D42 §G working hypothesis ("real type is some<SomeStateSnapshot>different fromDeviceStateSnapshot") FALSIFIED. - Missing fields: NONE — all 3 real CodingKeys present in GAMBIT's payload.
- Spurious field: 1 (
state) — Swift's auto-synthesizedKeyedDecodingContainer<CodingKeys>only reads keys in its own CodingKeys enum. Extra keys are ignored, not validated. Sostateis decoder-tolerated noise, NOT decoder-poison. - Field-shape concern (
capabilityImplementations): GAMBIT emits emptyxpc_array_create(NULL, 0). Real type isDictionary<Capability, [String]>whereCapabilityis non-String- keyed (Swift struct). Empty container shape is ambiguous. Recorded as "may be acceptable, needs falsification" — D44 closes this.
Apparatus integrity (D43 non-destructive)¶
Static disasm only. No apparatus interaction. iosmux_inject.dylib sha
unchanged (52df2cc6...4803); CoreDevice / Mercury / CDU SHA unchanged;
iPhone (iosmux) connected (no DDI); CDS PID stable.
Distillation status (D43)¶
Raw notes (notes/d43-statesnapshot-probe.md, 235 lines) deleted at
distillation time per feedback_notes_are_temporary_buffer. Disasm
artifacts preserved at /home/op/dev/iosmux/symbols/d43-dss-*.txt
(host-side, not in repo). Verdict + field set + mismatch analysis
preserved in this section + the Q-D66-15 D43 Resolution log entry in
d66-history-q15-d34-d40.md.
D44a update: capabilityImplementations target shape = xpc_array-of-pairs; Capability is auto-synth Codable struct in CDU with 2 String CodingKeys (2026-05-03, research)¶
D44a was the cheap follow-up research probe to D43's outstanding
field-shape concern. Goal: determine the exact xpc-Codable shape
Apple's auto-synth init(from:) decoder expects for
capabilityImplementations: [Capability:[String]] and produce a
ready-to-paste C/xpc-API recipe for D44b's empirical falsifier.
Top-level shape: xpc_array-of-pairs¶
Disasm of DSS encode(to:) at 0x29d6b0 shows the conformance
witness selected at 0x29dba0 is SDyxq_GSEsSERzSER_rlMc —
the Swift stdlib's UNCONSTRAINED-key Dictionary<x, q>: Encodable
where x: Encodable, q: Encodable. NOT the keyed-by-string variant
(SDyxq_GSEsSERzs0sB12RepresentableR_rlMc).
Cross-check: Capability's conformance witness table in CDU
(symtab lines 701-707) lists VSEAAMc, VSHAAMc, VSLAAMc, VSQAAMc,
VSeAAMc, Vs12IdentifiableAAMc, Vs23CustomStringConvertibleAAMc —
no StringProtocol, no CodingKeyRepresentable conformance.
Runtime cannot promote Capability into the keyed-by-string path,
must use UnkeyedContainer with alternating [K0, V0, K1, V1, ...].
Encoder + decoder agree → wire shape is xpc_array of alternating
Capability dicts and [String] arrays.
Capability identity: 2-String auto-synth Codable struct¶
Module: defined and exported by CoreDeviceUtilities (CDU);
CoreDevice imports via chained-fixup relocs. Bare nominal /
metadata-accessor symbols _$s10CoreDevice10CapabilityVMa (0x165af0)
and ...VMn (0x002695c4) live in CDU.
Stored properties (per field-descriptor cstring run at
CDU+0x0026df50):
name: Swift.StringfeatureIdentifier: Swift.String
CodingKeys (auto-synth, by stored-property name): name (tag 0),
featureIdentifier (tag 1). Confirmed via direct disasm of
Capability.encode(to:) at CDU+0x1654b0 and init(from:) at
CDU+0x165650 — exact KeyedEncodingContainer.encode<String>(_:forKey:)
sequences with the 2 byte tags.
Wire form: xpc_dictionary { "name": <xpc_string>, "featureIdentifier": <xpc_string> }.
Apparatus integrity (D44a non-destructive)¶
Static disasm only. iosmux_inject.dylib sha unchanged
(52df2cc6...4803); Apple binaries unchanged; iPhone state preserved.
Distillation status (D44a)¶
Raw notes (notes/d44a-capabilityimpl-shape.md, 257 lines) deleted at
distillation time. Disasm artifacts preserved at
/home/op/dev/iosmux/symbols/d44a-cap-*.txt (host-side, not in repo).
Recipe + shape determination preserved in this section + the
Q-D66-15 D44a Resolution log entry.
D44b update: env-gate via launchctl setenv does NOT propagate to CoreDeviceService XPC service post-killall on Tahoe 26.x — apparatus learning (2026-05-03)¶
D44b was the first attempt at the field-shape falsifier. Built D44a's
recipe under env-gate IOSMUX_D44_CAPIMPL=1 so the new path could be
toggled at runtime without rebuild. Patch compiled / linked / codesigned
clean. Build sha new from D23 v2. CDS respawned via killall.
Failure mode: env-gate never reached CDS¶
Agent set env via ssh havoc-root launchctl setenv IOSMUX_D44_CAPIMPL 1
BEFORE killall, then triggered xcrun devicectl device info details.
Verified empirically via launchctl procinfo on the respawned CDS
PID — IOSMUX_* was absent from the env vector. Apple-allowed
XPC service vars were present; iosmux ones were not.
The env-gated branch in gambit_build_dss_only therefore never
executed ((no D44b lines) in inject log). The unified-log result
is BY CONSTRUCTION identical to the empty-array baseline: single
line Failed to acquire usage assertion ... CoreDeviceError(errorCode: 3),
no Codable / typeMismatch / Capability decoder errors anywhere.
Falsifier never ran. No empirical signal.
Cause: NOT SIP¶
User explicitly clarified: SIP is OFF on havoc (csrutil status:
disabled). Earlier dispatcher speculation about "SIP-filtered env"
was incorrect. Actual cause unknown — possibilities include launchd
caching env at service registration (which killall does not
re-trigger), user-domain vs system-domain bootstrap mismatch, or
some other Tahoe 26.x-specific launchd behavior. No further
investigation done; symptom-only is sufficient apparatus learning.
Apparatus integrity (D44b non-destructive)¶
D23 v2 baseline restored cleanly via backup at
/home/op/backups/iosmux/d44b-pre-deploy/iosmux_inject.dylib.d23v2-presnapshot.
Post-restore sha 52df2cc6...4803 verified. iPhone (iosmux)
connected (no DDI) post. Working tree clean. 0 new
DiagnosticReports.
Distillation status (D44b)¶
Raw notes (notes/d44b-fieldshape-falsifier.md) deleted at distillation
time. Apparatus learning captured in memory rule
feedback_no_env_gate_for_cds_xpc. Captured artifacts at
/tmp/iosmux-d44b-*.{log,txt} host-side (transient, lost on host
reboot).
D44b' update: capabilityImplementations field-shape FALSIFIED — Direction D dead, reframe required (2026-05-03, unconditional retest)¶
D44b' re-ran D44b's falsifier with the env-gate removed — the new
non-empty capabilityImplementations path runs unconditionally on
every gambit_build_dss_only call. Mandatory restore via backup at
end of probe limits exposure window.
Test apparatus (S1 cold)¶
User-confirmed pre-trigger: Xcode NOT running, VM uptime 1:15, CDS
PID rotates 1961 → 2143 → 2222 across deploy cycles. iPhone (iosmux)
connected (no DDI). Pre-trigger inject sha = D23 v2 baseline. New
build sha a02fb802f1f4454d7bc01ce14d877cf9053a67982ff473f52c11112c417b2f03
(203424 bytes, 1 pre-existing warning unrelated to D44, codesign OK).
Inject log confirms new path executed¶
/home/op/backups/iosmux/d44b-pre-deploy/iosmux_inject.log.d44b-prime
lines 687-690:
687: [inject] === PoC registration complete. Device visible in Xcode. ===
688: [inject] GAMBIT: synthesised reply for aid=com.apple.coredevice.action.acquireusageassertion ...
689: [inject] GAMBIT: D44b unconditional capImpl entry emitted
690: [inject] GAMBIT: emitted AFM endpoint=0x7ff1e3822c20 did=E8A190DD-...
The xpc_array sent on the wire contained one (Capability, [String])
pair with name="iosmux.test.cap1", featureIdentifier="iosmux.test.cap1.id",
single string "impl1". D44b's apparatus gap is RESOLVED.
Unified log: ZERO Codable decoder errors¶
Verbatim grep of 60s window (/tmp/iosmux-d44b-prime-unified-log.txt,
29 lines) for Failed to acquire, typeMismatch, keyNotFound,
dataCorrupted, Codable, Capability, capabilityImplementations,
CoreDeviceError returned EXACTLY ONE matching line — the same
CoreDeviceError(errorCode: 3, ... "Failed to acquire assertion")
that fires for the empty-array baseline. No Codable decoder errors
anywhere.
Critical sequencing (lines 22-29)¶
22 11:32:06.598085 [action] Invoking AcquireDeviceUsageAssertion ... invocation=171380B5...
23 11:32:06.598847 [action] forward(...) Forwarding to ExecutionLocation.coreDeviceService.
24 11:32:06.599503 [action] Forwarding to ExecutionLocation.elsewhere(<SystemXPCPeerConnection ... pid = 2222 ...>).
25 11:32:06.602825 [action] Received reply from forwarded action: SUCCESS()
26 11:32:06.602851 [action] Received reply from action: SUCCESS()
27 11:32:06.605777 [analytics] Core Analytics ...
28 11:32:06.613866 [analytics] Reporting to CA ...
29 11:32:06.615570 [useassertion] Failed to acquire usage assertion ... CoreDeviceError(errorCode: 3, ...)
The xpc-Codable decoder accepted the GAMBIT reply (lines 25-26
success()). The error fires ~13 ms LATER on the useassertion
subsystem (devicectl-side), NOT the action / decoder subsystem. This
is post-decode logic that consumes the DSS payload and decides "no
assertion was acquired" — the failure is on a code path that runs
AFTER the auto-synth init(from:) decoder.
Verdict — Direction D dead¶
D43 §Recommendations option (1) FALSIFIED. The post-decode logic is
NOT inspecting capabilityImplementations for non-empty (D44b' would
have flipped the outcome; it did not). AUA Path-B determinism does
NOT live on the capabilityImplementations shape. Two surviving
candidate surfaces remain (per Forward direction in
aua-side-channel-mechanism.md):
(a) different DSS field consumed post-decode by useassertion
subsystem (e.g. DeviceInfo sub-field invariant, monotonicIdentifier
value-check); OR
(b) non-Codable devicectl-side state-machine check ignoring DSS shape
entirely (e.g. AFM identifier UUID compared against client-tracked
assertion-instance UUID — D30 admonition hypothesis #1, never
empirically tested).
Apparatus integrity (D44b' non-destructive)¶
D23 v2 baseline restored cleanly. Pre-sha = 52df2cc6...4803,
during-active sha = a02fb802...2f03, post-restore sha =
52df2cc6...4803 (MATCH). 0 new CDS / devicectl DiagnosticReports.
iPhone post-restore connected (no DDI). git status clean for
inject/iosmux_gambit.m.
Distillation status (D44b')¶
Raw notes (notes/d44b-prime-fieldshape-falsifier.md, 235 lines)
deleted at distillation time. Empirical record + apparatus telemetry
preserved at /tmp/iosmux-d44b-prime-*.{log,txt} host-side
(transient). Inject log archived at
/home/op/backups/iosmux/d44b-pre-deploy/iosmux_inject.log.d44b-prime.
Verdict + critical sequencing + remaining candidates preserved in this
section + the Q-D66-15 D44b' Resolution log entry.
D45 update: D30 hypothesis #1 FALSIFIED + Q4 architectural pivot — useassertion subsystem lives in CoreDeviceService, not in CD/devicectl (2026-05-03, Ghidra static, research-only)¶
D45 was the cheap occam's-razor probe closing the oldest standing AUA
hypothesis (D30 admonition #1, untested through ~14 follow-up probes
D31..D44b'). Goal: determine whether Apple's AUA wrapper generates
and tracks a per-assertion-instance UUID separate from
CoreDevice.deviceIdentifier that GAMBIT cannot know about. Verdict:
no such UUID exists on the AUA endpoint side-channel path, plus a
Q4 architectural pivot that reshapes the surviving candidate surface
from devicectl-side to CDS-side.
Q1 — AUA wrapper UUID construction sites¶
Confirmed Tahoe 26.x offsets:
0x317a0— body ofCoreDevice.RemoteDevice.acquireDeviceUsageAssertion(withReason:options:) async throws -> CoreDevice.DeviceUsageAssertion, entered viaswift_task_switchfrom prologue at0x31750. Sets upwithCheckedThrowingContinuation, allocates a Swift continuation buffer, callsfunc.000304a0.0x304a0— completion-handler-flavoured AUA entry. ReadsRemoteDevice.deviceIdentifierfromfield.class.CoreDevice.RemoteDevice.var.deviceIdentifier(offset global at0x41e8c8), bridges Strings, builds anAcquireDeviceUsageAssertionActionDeclarationcarrier, dispatches the action via0x3472d8.
Neither function calls uuid_generate_random /
CFUUIDCreate / Foundation.UUID.init(). Verified by exhaustive
inspection of all call instructions in
/home/op/dev/iosmux/symbols/d45-aua-async-body-clean.txt and
/home/op/dev/iosmux/symbols/d42-q1b-cb-acquire.txt — only swift_*
runtime, objc_* retain/release, NSProgress KVO bridge, and the
action-dispatch funnel. No UUID constructor of any flavour. No
per-call UUID minted client-side before the action goes on the
wire.
Q1 — DUA construction at AFM-decode site (CD+0x2fb30..0x2ff3d)¶
Decisive structure (D42 disasm dump
/home/op/dev/iosmux/symbols/d42-q1-fn-2fb30.txt):
0x2fc54 call swift_getEnumCaseMultiPayload(decoded_AFM, type_metadata)
0x2fc62 cmp eax, 1
0x2fc64 jne 0x2fde9 ; case 0 = .success(AFM)
0x2fde9 (case 0 entry — AFM-success payload)
0x2fdfb cmp byte [rcx + 8], 0 ; first arrival check
0x2fec5 je 0x300bc ; peer-pointer nil → "Peer is unexpectedly nil"
0x2fee4 mov byte [rcx + 8], 1
0x2fee8..0x2feff
AFM[+0x10] -> var_30h ; UUID lo64
AFM[+0x18] -> var_28h ; UUID hi64 (UUID is 16 bytes)
AFM[+0x20] -> r15 ; updatedSnapshot (DSS reference)
0x2ff02 call CD+0xd3570 ; type metadata accessor for DUA
0x2ff11 call swift_allocObject ; r14 = new DUA instance
0x2ff19 lea rax, field.class.CoreDevice.DeviceUsageAssertion.var.identifier
; runtime-patched offset global at 0x41e8b0
0x2ff23 mov rbx, [rax]
0x2ff26 add rbx, r14 ; rbx = &dua->identifier
0x2ff2b call 0x347110 ; UUID type metadata accessor
0x2ff30 mov rcx, [rax - 8] ; UUID value-witness table
0x2ff34 mov rdi, rbx ; dest = &dua->identifier
0x2ff37 mov rsi, r13 ; src = AFM identifier slot pointer
0x2ff3d call [rcx + 0x10] ; vwt[2] = initializeWithCopy(dest, src)
; COPY 16-byte UUID into DUA
There is NO equality compare against any pre-stored UUID at the
AFM-decode site. The UUID is whatever the AFM payload carried —
copied byte-for-byte into the DUA. A hypothetical client-side
comparison if afm.identifier != expected.identifier { fail }
would be visible as a swift_stdlib_memcmp / explicit cmp 16-byte
check before the DUA construct. None exists between 0x2fc54 and
0x2ff3d.
Q1 — IdentifiableAssertionDetails is parallel-surface, not on AUA path¶
CD does export
CoreDevice.IdentifiableAssertionDetails.init(identifier:owningProcess:processName:creationReason:hostName:preparednessDescription:invalidationHandler:)
at CD+0x290330 and
CoreDevice.TunnelAssertionGroup.identifiableAssertions: [UUID: IdentifiableAssertionDetails]
at CD+0x2904a0 — a UUID-keyed dictionary that is the kind of
side-state hypothesis #1 predicted.
But IAD is NOT on the AUA endpoint side-channel path:
CoreDevice.TunnelAssertionRequest.identifiable(IdentifiableAssertionDetails, () -> ())is one case of theTunnelAssertionRequestenum (the other is.persistent). The request enters viaTunnelAssertionGroup.add(pending: TunnelAssertionRequest)atCD+0x290f30— tunnel-management API, not the Mercury action-invocation surface AUA uses.- AUA action declaration's
Inputhas exactly 3 fields —reason: String,options: UsageAssertionOptions,endpoint: __C.OS_xpc_object— confirmed at/home/op/dev/iosmux/symbols/CoreDevice-x86_64-demangled.txt:1462-1471. Noidentifier, no IAD reference. The AUA action invocation envelope does NOT carry any per-assertion UUID. - Pattern scan for direct
call rel32toCD+0x290330returned 0 hits in CD's text segment — the IAD constructor is reached only via Swift PWT dispatch, on a code path that is parallel to (not part of) the AUA endpoint side-channel.
Q3 — reachability from GAMBIT¶
AUA invocation envelope has no per-assertion UUID. Top-level keys are
the standard 6 documented in
mercury-envelope-empirical.md
§"Pair action invocation envelope" (actionIdentifier,
invocationIdentifier, deviceIdentifier, coreDeviceVersion,
CoreDeviceDDIProtocolVersion, input).
Even if the dispatcher chose to swap GAMBIT's AFM identifier from
deviceIdentifier to invocationIdentifier, the post-decode
failure would not change — nothing on the CD-side AFM-decode path
compares this UUID to anything. The DUA gets whichever value was
sent; the downstream gate that fires errorCode 3 is unrelated to
this field.
Direction E (assertion-instance UUID mismatch) is therefore not a "one-line GAMBIT swap" fix and is not "architecturally unreachable" either — it simply does not exist as a mechanism. GAMBIT current behaviour is correct for what the AFM-decode site does with the identifier.
Q4 PIVOT — useassertion subsystem lives ONLY in CoreDeviceService¶
grep -ac useassertion enumeration across the bundle (artifact at
/tmp/claude-1000/.../tasks/{bv92g4hhs,b117pzx95}.output):
| Binary | hits |
|---|---|
| CoreDevice | 0 |
| CoreDeviceUtilities | 0 |
| Mercury | 0 |
| devicectl | 0 |
| CoreDeviceService | >0 (subsystem string com.apple.dt.coredevice.useassertion. at slice file offset 0xb7d90, VA 0x1000b7d90) |
The [useassertion] Failed to acquire usage assertion ... CoreDeviceError(errorCode: 3, ...)
log line in D44b' unified log is therefore emitted server-side
(CDS), NOT client-side (devicectl). This contradicts the D44b'
anchor framing of "non-Codable devicectl-side state-machine check"
candidate (b) — the surviving candidate is CDS-side.
CDS-side strong candidate strings on the AUA failure path:
| CDS+offset | String |
|---|---|
0xbbd30 |
"Cannot acquire a usage assertion on a device without an effective device identifier." — strongest gate candidate (post-success()-reply check, presumably the format string for the errorCode-3-throwing site's NSLocalizedFailureReasonErrorKey) |
0xbc030 |
"This device does not support acquiring a usage assertion." — capability gate |
0xbbef0 |
"Acquired usage assertion." — success log line |
0xc2e40 |
format: "Received request to create usage assertion %{public}s for device %{public}s, (required preparedness=%ld, owning pid=%d, reason=%s)" — server-side per-request log; assertion-id is generated server-side at request receipt, never echoed back to the client |
0xc3160 |
format: "Notifying client of invalidation for usage assertion %{public}s, device %{public}s. Error: %s" |
0xc3aa0 |
"Received unexpected message through usage assertion acquisition control channel: %s" |
0xc3b00 |
format: "Usage assertion %{public}s was invalidated by device %{public}s with error: %s" |
0xbfb20 |
"The tunnel was disconnected because no outstanding usage assertions were held." |
CDS-side handler symbols (in
/home/op/dev/iosmux/symbols/d45-cds-demangled-x86_64.txt):
- L2113
0x1000b6404—CoreDeviceService.AcquireDeviceUsageAssertionActionImplementationnominal type descriptor (mangled prefix_$s17CoreDeviceService07AcquireB34UsageAssertionActionImplementationV). - L2117
0x1000b641e— associated conformance toCoreDeviceUtilities.ActionImplementation-style protocol — confirms this is aCoreDeviceUtilities-action-system handler running in CDS, not in devicectl. - L2114
0x1000b640a—CoreDeviceService.ListUsageAssertionsActionImplementation(companion handler). - L2100
0x1000b6386—CoreDeviceService.InProgressServerAssertionObj-C class (__DATAat0x1000d2e68, IVARS at0x1000d2048) — the server-side per-assertion-bookkeeping object. The assertion-id from the format string at0xc2e40is presumably its identifier. - L2417
0x1000b7324—CoreDeviceService.RemotePairingDeviceRepresentationconforms toCoreDevice.UseAssertionProvidingDeviceRepresentation(the protocol withacquireUsageAssertion(forRequest:),releaseAssertion(identifiedBy: UUID),listAllAssertions()).
Tooling gap surfaced for D46¶
Tahoe 26.x CDS uses chained-fixup encoding for lea rip+rel32
references to __TEXT,__cstring and for cross-segment call rel32.
Raw byte-pattern xref scans to 0x1000b7d90 (subsystem string) and
to CD+0x290330 (IAD.init) returned 0 hits via direct
call rel32 / lea rip+rel byte patterns. D46 needs Ghidra (D36
project at /home/op/dev/iosmux/ghidra/D36.gpr already imports CDS)
or llvm-objdump --chained-fixups CoreDeviceService for
chained-fixup-aware xref analysis.
Verdict¶
D30 hypothesis #1 FALSIFIED. Assertion UUID == AFM-carried UUID; CD-side decoder accepts unconditionally; GAMBIT current behaviour is correct. Direction E (assertion-instance UUID mismatch) is empirically dead as a separate axis.
Q4 architectural pivot: errorCode 3 emission is CDS-side, not
devicectl-side. Forward narrows to Direction F = CDS-side
useassertion subsystem trace. Probable gate: post-success()-reply
check on the AcquireDeviceUsageAssertionActionImplementation run/execute
path that fires the 0xbbd30 "effective device identifier" log/error.
D46 prep checklist (next probe scope)¶
- Find direct callers of
0xbbd30("Cannot acquire a usage assertion on a device without an effective device identifier.") in CDS via Ghidra D36 project (chained-fixup-aware xref). - Confirm whether that string is the direct format consumed by the
errorCode 3log site, or whether the wrappingCoreDeviceError(errorCode: 3, ...)is constructed elsewhere and the string is only the user-facingNSLocalizedFailureReasonErrorKey. - Inspect
CoreDeviceService.AcquireDeviceUsageAssertionActionImplementation'srun/executebody (anonymous innmoutput — Ghidra address findable from the protocol-conformance descriptor at0x1000b641e) and trace the post-success()-reply path that leads to theuseassertionlog emission. - If candidate (1) is the firing site, characterise what
"effective device identifier" means semantically on the CDS side
— presumably a lookup keyed on the device record retrieved from
RSDDeviceWrapper, post-Pair. This is a different semantic axis from D30 hypothesis #1 entirely, and may surface a concrete inject-side fix target on the CDS side (where iosmux already has load-bearing interpose chains).
Apparatus integrity (D45 non-destructive)¶
Pure host-side static disasm; zero apparatus interaction. inject sha
unchanged at 52df2cc6...4803. iPhone untouched. Working tree clean
post-distill.
Distillation status (D45)¶
Raw notes (notes/d45-aua-identifier-mismatch.md, 367 lines) deleted
at distillation time per feedback_notes_are_temporary_buffer.
Disasm artifacts preserved at
/home/op/dev/iosmux/symbols/d45-{aua-async-body-clean,aua-input-encode-clean,aua-input-decode-clean,cds-nm-x86_64,cds-demangled-x86_64}.txt
(host-side, not in repo). Strings dump at
/tmp/claude-1000/.../tasks/b117pzx95.output (transient). Empirical
findings + Q4 architectural pivot + D46 prep checklist preserved in
this section + the Q-D66-15 D45 Resolution log entry +
aua-side-channel-mechanism.md
§"Cumulative falsifications" + §"Forward direction".
D46 update: Direction F CONFIRMED + bridgeable — predicate is _get_effectiveDeviceIdentifier getter in CoreDevice.framework (2026-05-03, Ghidra static probe)¶
D46 was the wide-variant Ghidra static probe Direction F promised
post-D45 Q4. Goal: trace the post-success()-reply CDS-side code
path that emits errorCode 3 ~13ms after the Codable decoder accepts
GAMBIT's reply, identify the failing predicate, characterise
"effective device identifier" semantics, and produce a D46b
implementation roadmap with concrete hook targets.
Q1 — Direct callers of the AUA-fail-EDI string¶
Calibration fix vs D45 anchor: the AUA-fail-EDI string lives at
slice VA 0x1000b7d30 (slice file offset 0xb7d30). D45's
CDS+0xbbd30 was the FAT-archive-relative offset; the x86_64 slice
starts at FAT offset 0x4000, so subtract 0x4000 to convert FAT →
slice. The D45 0xb7d90 subsystem string and 0xc2e40 log format
were already slice-relative and remain correct.
Direct xref count to 0x1000b7d30 = 0 for Swift-rendered error
strings. D46 Q1 explained why: the Swift compiler packs __cstring
densely and constructs the error message at runtime via Swift String
length-prefixed encoding from a SHARED SUFFIX 0x1000b89e0 ("an
effective device identifier."). The 8 LEAs into the suffix appear
across 4 sibling gates (DDI-disable, DDI-enable, Pair, Tunnel-create)
plus the AUA path itself.
LEA hit map (artifact: /tmp/iosmux-d46-q1g-rangerefs.txt):
| code-VA | enclosing function | LEA target |
|---|---|---|
10002eb15 |
FUN_10002e4b0 (DDI-disable) |
0x1000b89e0 shared suffix |
10002ef6e |
FUN_10002e4b0 (DDI-disable) |
same |
10002f1ea |
FUN_10002e4b0 (DDI-disable) |
same |
10002fea6 |
FUN_10002f950 (DDI-enable) |
same |
100030124 |
FUN_10002f950 (DDI-enable) |
same |
100030697 |
FUN_10002f950 (DDI-enable) |
same |
1000309dc |
FUN_10002f950 (DDI-enable) |
same |
100030f90 |
FUN_100030cf0 (DDI helper) |
same |
100040351 |
FUN_1000386d0 (Pair/Tunnel umbrella) |
0x1000b9200 sibling |
D46 used the #file literal LEAs to identify which function is the
AUA-specific run body:
| code-VA | function | #file literal |
|---|---|---|
10001e92f |
FUN_10001e120 |
CoreDeviceService/AcquireDeviceUsageAssertionActionImplementation.swift |
1000202d4 |
FUN_10001fa00 |
"uiring a usage assertion." (separate capability gate) |
10002157f |
FUN_100020700 |
"e device identifier." (AUA-specific tail) |
The combination of (a) IdentifiableAssertionDetails type-metadata
accessor calls, (b) AcquireDeviceUsageAssertionActionDeclaration::Input
field getter calls (get_options, get_reason, _get_endpoint),
© exclusive caller from FUN_100018ea0 (action dispatcher),
identifies FUN_100020700 as the AUA action run body.
Q2 — Firing function + predicate¶
Firing function: FUN_100020700 at slice VA 0x100020700,
size 0x11fc bytes.
Gate predicate (decompile citation: /tmp/iosmux-d46-q2-funcsearch.txt:3266-3354):
// 1. extract UUID from inbound action invocation
CoreDeviceUtilities::Context::get_deviceIdentifier(...);
// 2. look up CDS-internal SDR by UUID
___swift_instantiateConcreteTypeFromMangledNameV2(&DAT_1000d3590, &DAT_1000b3040);
CoreDevice::CoreDeviceService::_serviceDeviceRepresentation(<UUID>, conformingTo);
// 3. GATE — read Optional<DeviceIdentifier>
CoreDevice::ServiceDeviceRepresentation::_get_effectiveDeviceIdentifier(this, out);
// 4. enum-witness "isCase(.none)"
iVar14 = (*opt_witness_isCase_none)(...);
if (iVar14 == 1) { // .none → FAILURE PATH
CoreDevice::CoreDeviceError::get_deviceRepresentationMissingIdentity(...);
Swift::Error::_init(...);
_swift_allocError(...);
CoreDeviceUtilities::_Continuation::_resume(error); // throws errorCode 3
_swift_errorRelease(...);
return; // <-- this is errorCode 3 emitted ~13ms after success() reply
}
// gate passed → extract Input fields → dispatch to FUN_10001fa00 (AFM emit)
Predicate semantics in plain English:
let effectiveDeviceID: Optional<DeviceIdentifier> =
serviceDeviceRepresentation._effectiveDeviceIdentifier
if effectiveDeviceID == nil {
throw CoreDeviceError.deviceRepresentationMissingIdentity
}
Cited code-site addresses (slice VAs in CDS):
0x100020700—FUN_100020700AUA actionrunbody.0x100020cff— predicate call site.0x100020d5d—_get_deviceRepresentationMissingIdentityinvocation.0x100020df3—_Continuation::_resume(error)(throw point).0x100018ea0/100018ea5— action router → AUA-body dispatch.
Q3 — "Effective device identifier" semantics¶
ServiceDeviceRepresentation._effectiveDeviceIdentifier:
Optional<DeviceIdentifier> is a Swift property on the CDS-internal
SDR populated post-RSD-pair. Distinct from the inbound
Context.deviceIdentifier UUID and from the pre-pair
RemoteDevice.deviceIdentifier.
Returns .none when:
- (a, most likely) the SDR for the requested UUID is registered but its post-pair RSD lookup hasn't populated the field yet.
- (b, less likely) explicit invalidation marker is set.
iosmux's existing inject chain (MDRemoteServiceSupport,
iosmux_md_proxy.m, serviceDeviceRepresentations interpose)
supplies device data into CD-side / devicectl-side structures but
does NOT touch the CDS-side SDR's _effectiveDeviceIdentifier
field. This is a separate state surface that iosmux has not yet
touched — the architectural advantage D45 Q4 hinted at.
The conformance-witness chain is:
CoreDeviceService.RemotePairingDeviceRepresentation
→ CoreDevice.UseAssertionProvidingDeviceRepresentation
→ CoreDevice._RSDDeviceWrapperConvertible
→ CoreDevice.ServiceDeviceRepresentation. The
_get_effectiveDeviceIdentifier getter is EXTERNAL to CDS — defined
in CoreDevice.framework, called via the protocol-witness table.
Q4 — Full AUA action implementation body¶
FUN_100020700 body skeleton (~4.6 KB, source citation:
/tmp/iosmux-d46-q2-funcsearch.txt:2853-3911):
void FUN_100020700(undefined8 param_1, Context *param_2, undefined8 param_3) {
// Stage 1 — type metadata accessor instantiation (~20 calls)
CoreDevice::CoreDeviceError::typeMetadataAccessor();
CoreDevice::IdentifiableAssertionDetails::typeMetadataAccessor();
Mercury::Attributes::typeMetadataAccessor();
Dispatch::DispatchQoS::typeMetadataAccessor();
OS_dispatch_queue::AutoreleaseFrequency::typeMetadataAccessor();
CoreDevice::UsageAssertionInformation::typeMetadataAccessor();
// ...
// Stage 2 — extract Context::deviceIdentifier
CoreDeviceUtilities::Context::get_deviceIdentifier(...);
// Stage 3 — SDR lookup by UUID
CoreDevice::CoreDeviceService::_serviceDeviceRepresentation((UUID), conformingTo);
// Stage 4 — GATE
CoreDevice::ServiceDeviceRepresentation::_get_effectiveDeviceIdentifier(this, out);
if (Optional.isCase(.none)) {
// throw CoreDeviceError.deviceRepresentationMissingIdentity → errorCode 3
return;
}
// Stage 5 — extract Input fields (gate passed)
CoreDevice::AcquireDeviceUsageAssertionActionDeclaration::Input::get_options(...);
CoreDevice::AcquireDeviceUsageAssertionActionDeclaration::Input::get_reason(...);
CoreDevice::AcquireDeviceUsageAssertionActionDeclaration::Input::_get_endpoint(...);
// Stage 6 — dispatch to AFM-emit happy path
// (FUN_10001fa00: builds InProgressServerAssertion, opens side-channel
// via xpc_connection_create_from_endpoint, emits AFM via Mercury)
}
The 13 ms gap between D44b' line 25 success() (synchronous routing
reply from the forward(...) infrastructure) and line 29 errorCode 3
is exactly the time FUN_100020700 spends doing Stages 1-4 above
plus the error construction + continuation throw at the gate trip.
Q5 — InProgressServerAssertion lifecycle correlation¶
The Obj-C class at CDS+0x1000b6386 is the server-side
per-assertion-bookkeeping object. It is allocated in
FUN_10001fa00 AFTER the gate at Stage 4 passes — it does NOT
exist at gate-trip time. Hooking IPSA's construction or its IVAR
getters does NOT help; the gate is upstream of any IPSA allocation.
IVAR layout (at 0x1000d2048, base 0x1000d2e68):
assertionIdentifier/peerConnection/deviceIdentifier/
serviceDeviceRepresentation/requiredPreparedness/assertionDetails/
invalidationHandler/invalidated/queue/devicePowerAssertion
at offsets +0x3420..+0x3468.
FUN_10001ad60 is the invalidation handler (calls
CoreDevice::UseAssertionProvidingDeviceRepresentation::releaseAssertion(UUID)
+ acquires os_unfair_lock on devicePowerAssertion + 0x18). Out of
D46 scope; documented for D46b implementation reference.
Q6 — D46b implementation roadmap¶
Recommended hook target #1: _get_effectiveDeviceIdentifier
getter (EXTERNAL CoreDevice.framework symbol).
| Property | Value |
|---|---|
| Reachability | dlsym(RTLD_DEFAULT, "_$s10CoreDevice32ServiceDeviceRepresentationP012_effectiveB10IdentifierAA0B10IdentifierVSgvg") (mangled candidate; D46b prep verifies exact mangling) |
| Calling convention | Swift class-method ABI; this in rdi, indirect-result buffer in rsi. C-compatible: void hook(void *out, SDR *this) |
| Hook payload | call original; if out tag byte = 1 (.none), copy <context.deviceIdentifier> UUID into out, set tag = 0 (.some) |
| Trampoline | S1.B pattern proven at inject/iosmux_inject.m:1463-1525 |
| Hook recipe pseudocode | call original; if (*((uint8_t *)out + 16)) == 1 |
| Failure surface | If next-layer consumer can't find real SDR for fabricated UUID, downstream lookup may fail differently — moves gate-trip downstream, gives diagnostic visibility |
| Major risk | hook fires for ALL 4 sibling gates (DDI-disable/enable, Pair, Tunnel-create) plus AUA — may need stack-frame discriminator on hook entry |
Hook target #2 (FUN_100020700 prologue replacement) and #3
(_get_deviceRepresentationMissingIdentity constructor hook)
evaluated and rejected as more brittle.
D46b prep gaps (research-only dispatch needed before D46c impl):
- Exact mangled symbol via
swift demangleovernm CoreDevice. - Disassembly of getter prologue (need ≥13 clean linear bytes).
- Confirm getter is not fully inlined under -O3.
- Verify exact
Optional<DeviceIdentifier>ABI shape (assumed 16+1 bytes; needs disassembly verification of FUN_100020700 at the predicate-load site0x100020cff).
After D46b prep closes, D46c is the implementation dispatch.
Architectural advantage — bridgeable from existing inject¶
_get_effectiveDeviceIdentifier is in CoreDevice.framework. CDS
imports CoreDevice via LC_LOAD_DYLIB — and so does our existing
iosmux_inject.dylib. Same address space. The getter is reachable
via standard dlsym(RTLD_DEFAULT, ...) from inside the inject. No
new injection target required — the same mechanism that's already
proven for the S1.B trampoline pattern works here.
Tooling stack (D46)¶
- 8 Java GhidraScripts authored (PyGhidra mode required Java port — Jython removed in Ghidra 12).
- Ghidra D36 project at
/home/op/dev/iosmux/ghidra/D36.gprnow contains imported CoreDeviceService (x86_64, all 2987 chained-fixup pointers resolved at import). - 150 423 instructions in
__textwalked. - 269 distinct cstring xref targets enumerated.
- 10 enclosing AUA-relevant functions decompiled (8344 lines of decompile output).
llvm-objdump --chained-fixupsnot used — Ghidra resolved fixups at import.
Why this finally found the gate after 8 falsifications¶
All prior probes (D23..D45) operated on CD-side / devicectl-side /
Mercury-side surfaces because the failure presentation
([useassertion] Failed to acquire ... errorCode 3 arriving at the
awaiter) suggested a client-observable mechanism. D45 Q4 inverted
that assumption empirically (subsystem string only in CDS), and D46
followed the trail into the CDS-internal action implementation where
the predicate actually lives. The gate is neither a connection
lifetime issue (D23..D42 Q3), nor a Codable shape issue (D43..D44b'),
nor a UUID-comparison issue (D45 D30 #1) — it is server-side
device-record state validation that iosmux has never touched.
Apparatus integrity (D46 non-destructive)¶
Pure host-side static disasm; zero apparatus interaction. inject sha
unchanged at 52df2cc6...4803. iPhone untouched. CDS unchanged.
Apple binaries unchanged. Working tree clean post-distill.
Distillation status (D46)¶
Raw notes (notes/d46-cds-useassertion-trace.md, ~430 lines)
deleted at distillation time per feedback_notes_are_temporary_buffer.
Ghidra-script artifacts under /tmp/iosmux-d46-*.{sh,java,py,txt}
(transient; lost on host reboot). Full empirical decompile at
/tmp/iosmux-d46-q2-funcsearch.txt (8344 lines, transient).
Empirical findings + D46b prep gaps + recommended hook target
preserved in this section + the Q-D66-15 D46 Resolution log entry +
aua-side-channel-mechanism.md
§"Forward direction — Direction F CONFIRMED + bridgeable post-D46".
D46b update: D46c READY-WITH-CAVEATS — ¾ prep gaps closed; D46 hook recipe overridden via capture-and-replay (2026-05-03, Ghidra static probe, research-only)¶
D46b was the prep dispatch closing the 4 verification gaps D46
surfaced. Goal: confirm exact mangled symbol of
_get_effectiveDeviceIdentifier, byte-level prologue disassembly,
inline status under -O3, and exact Optional<DeviceIdentifier> ABI
shape — so D46c implementation can be specified without remaining
unknowns. Verdict: ¾ RESOLVED + 1 PARTIAL (with empirical fallback);
several CRITICAL CORRECTIONS to D46's plan.
Q1 RESOLVED — exact mangled symbol¶
Demangled (via swift demangle):
dispatch thunk of CoreDevice.ServiceDeviceRepresentation.effectiveDeviceIdentifier.getter : CoreDevice.DeviceIdentifier?
Critical correction vs D46 candidate symbol: D46 proposed
_$s10CoreDevice32ServiceDeviceRepresentationP012_effectiveB10IdentifierAA0B10IdentifierVSgvg
(with P for protocol and V for struct). This was WRONG in 3
places:
| Component | D46 guess | D46b reality |
|---|---|---|
| Type kind | P (protocol) |
C (Swift class) |
| DeviceIdentifier kind | V (struct) |
O (Swift enum, per D45 §Q1.3) |
| Property name | _effectiveDeviceIdentifier (leading underscore) |
effectiveDeviceIdentifier (no underscore) |
| Symbol form | bare getter vg |
dispatch thunk vgTj (only externally exported) |
Three errors in a single guessed symbol — none would have linked at
dlsym. The bare vg getter is NOT externally exported (verified
via llvm-nm -a /home/op/dev/iosmux/binaries/CoreDevice listing all
6 effective-related symbols). Only the Tj dispatch thunk is
externally callable.
CDS imports the thunk by name: chained-fixup table entry
name_offset = 29559 (_$s10CoreDevice07ServiceB14RepresentationC09effectiveB10IdentifierAA0bF0OSgvgTj)
at /tmp/iosmux-d46b-q1-cds-fixups-grep.txt:5. CDS's call site at
0x100020cff resolves at load time to the thunk's address in the
loaded CoreDevice image.
Address: thunk at slice VA 0x28db10 in CoreDevice x86_64 slice.
Q2 RESOLVED — prologue disasm + trampoline viability¶
Thunk prologue at 0x28db10:
0x28db10 [ 1] 55 PUSH RBP cumulative 1
0x28db11 [ 3] 48 89 e5 MOV RBP, RSP cumulative 4
0x28db14 [ 4] 49 8b 4d 00 MOV RCX, [R13] ; R13 = self cumulative 8
0x28db18 [ 7] 48 8b 89 d8 00 00 00 MOV RCX, [RCX + 0xd8] ; vtable slot cumulative 15
0x28db1f [ 1] 5d POP RBP cumulative 16
0x28db20 [ 2] ff e1 JMP RCX ; tail call cumulative 18
15 contiguous clean linear bytes before the JMP RCX
terminator. No PC-rel, no PAC, no branch, no return. Sufficient
headroom for a 12-byte absolute-jump trampoline (MOV RAX, imm64
(10 bytes) + JMP RAX (2 bytes) = 12). S1.B trampoline pattern at
inject/iosmux_inject.m:1463-1525 is viable with no modification.
Critical caveat: the thunk is a tail-call dispatcher, not the real implementation. Hooking the thunk replaces the entire vtable indirection — when our hook is called, we must implement the entire getter contract (including avoiding infinite recursion with the trampoline-saved-bytes patched-back original).
Sample bare getter at 0x28a440 (selfReportedDeviceIdentifier):
prologue contains PC-relative ADD at offset 7 + LEA at offset 14 —
only ~6 clean bytes before first PC-rel. Bare getters are NOT
trampoline-safe; thunks ARE. Empirical confirmation that hook target
must be the thunk, not the bare getter.
Q3 RESOLVED — getter callable via thunk¶
Bare getter (concrete vtable-slot implementation) is NOT externally
exported. However, the dispatch thunk at 0x28db10 IS exported
(T text symbol), externally linkable, imported by CDS, called from
CDS via CALL 0x1000b0a56 (CDS-internal stub → thunk in CoreDevice)
at CDS:0x100020cfa.
Inlining of the bare getter is irrelevant for D46c — the thunk is the canonical externally-callable dispatch entry. Even if Apple's optimizer inlined some callers' invocations of the bare getter internally to CoreDevice, the thunk symbol still exists for any cross-image consumer (like CDS).
Witness-table alternative NOT NEEDED: D46 contemplated a
"conformance-witness-table slot" alternative if the getter was
inlined. This is moot — ServiceDeviceRepresentation is a Swift
class (mangling C), not a protocol (P). Class method dispatch
goes through a vtable, not a witness table. The thunk IS the
vtable-dispatch entry; no separate witness slot exists.
Q4 PARTIAL — Optional uses VWT runtime tag access (NOT fixed byte layout)¶
The naive D46 hook recipe if (out[16] == 1) { memcpy(out, uuid, 16); out[16] = 0; } will NOT work. D46 assumed a fixed
"16+1" layout. Empirical disasm at CDS:0x100020d20:
0x100020d07 MOV RDI, R15 ; arg 0 = Optional buffer ptr
0x100020d0a MOV ESI, 0x1 ; arg 1 = numEmptyCases = 1
0x100020d16 MOV RDX, RBX ; arg 2 = DeviceIdentifier type metadata ptr
0x100020d19 MOV R12, [RBP + -0x138] ; R12 = VWT pointer
0x100020d20 CALL [R12 + 0x30] ; VWT slot +0x30 = getEnumTagSinglePayload
0x100020d25 CMP EAX, 0x1 ; if EAX == 1 → .none
0x100020d2c JNZ 0x100020e1e ; not-none → success branch
; fall-through → error/throw branch
Swift uses runtime VWT call to getEnumTagSinglePayload (slot
+0x30) for Optional tag inspection. There is no fixed byte-tag
offset. The actual byte layout depends on
DeviceIdentifier's spare-bits flags which cannot be statically
determined without disassembling its nominal type descriptor + VWT
contents.
getEnumTagSinglePayload returns:
- 0 →
.some(<payload>)— payload bytes valid at offset 0 - 1 →
.none— first (and only) empty case for Optional
Confirmed by CDS's CMP EAX, 0x1; JNZ <not-none-branch>.
DeviceIdentifier payload sizing (from D45 §Q1.3):
2-case enum — case ecid(Swift.UInt64) (8 bytes) and
case uuid(Foundation.UUID, Swift.String) (~32 bytes). Multi-payload
enum layout: max-payload + case-tag. Optional packing on top adds
either spare-bits encoding (no size increase) or appended tag (+1-8
bytes). Static disasm cannot definitively determine which.
Stack-frame buffer at [RBP + -0xd0] in FUN_100020700 — exact size
not directly visible from snapshot.
D46c must use either:
- Capture-and-replay (preferred — layout-agnostic): capture a
known-good
.somebuffer at runtime from a sibling gate's natural success, replay on.nonevia VWTdestroy+initializeWithCopy. Avoids tag-byte writes entirely. - VWT-mediated tag manipulation: call
getEnumTagSinglePayloadto read tag,storeEnumTagSinglePayloadto write — heavier and requires constructing a validDeviceIdentifierpayload from scratch (which needs the.uuidcase-constructor mangled symbol).
D46c hook payload is documented in aua-side-channel-mechanism.md
§"Hook payload — capture-and-replay strategy".
Calling convention (Swift class-method getter on x86_64) — non-cdecl¶
Per the thunk disasm at 0x28db10, the calling convention is:
R13=self(SDR class instance pointer; thunk reads[R13]to load class metadata, then[metadata + 0xd8]for the bare getter address).RAX= indirect-result buffer pointer forOptional<DeviceIdentifier>return value.
This is NOT standard SysV AMD64 cdecl. D46 had assumed
this in rdi and indirect-result in rsi — this is also wrong.
D46c hook entry MUST use inline-asm preamble to capture R13 and RAX into C-accessible variables before any C code runs:
void *self_ptr;
void *out_buf;
__asm__ volatile (
"mov %%r13, %0\n"
"mov %%rax, %1\n"
: "=r" (self_ptr), "=r" (out_buf)
:
: "memory"
);
Updated risk register (D46 R1 + new D46b R5-R11)¶
| Risk ID | Description | D46 status | D46b status |
|---|---|---|---|
| R1 | Hook fires for ALL 4 sibling gate paths plus AUA | OPEN | OPEN — concrete mitigation drafted (TODO-D return-address discriminator); ~10 lines C |
| R2 | Mangled symbol guess wrong | OPEN | RESOLVED — exact thunk symbol confirmed |
| R3 | Prologue not trampoline-friendly | OPEN | RESOLVED — 15 contiguous clean bytes |
| R4 | Getter inlined under -O3 | OPEN | RESOLVED — bare getter not exported but thunk is |
| R5 (NEW) | Optional |
n/a | OPEN — capture-and-replay sidesteps |
| R6 (NEW) | Non-cdecl calling convention (R13=self, RAX=indirect-result) | n/a | OPEN — inline-asm preamble at hook entry |
| R7 (NEW) | Need known-good .some buffer to bootstrap capture-and-replay | n/a | OPEN — capture from first natural .some on sibling gate |
| R8 (NEW) | Hook recurses if buffer construction triggers another effectiveDeviceIdentifier call | n/a | OPEN — per-thread in_hook flag |
| R9 (NEW) | dlsym(RTLD_DEFAULT, |
n/a | OPEN — fallback via dyld_get_image* exports trie walk |
| R10 (NEW) | Constructing DeviceIdentifier from scratch needs case-constructor mangled symbol | n/a | OPEN — capture-and-replay obviates the need |
| R11 (NEW) | selfReportedDeviceIdentifier sibling could be backup target if effective fails | n/a | OPEN — documented; primary remains effective thunk |
D46c implementation dispatch — open TODOs¶
| TODO | Description |
|---|---|
| TODO-A | Capture-and-replay primary path (capture .some from sibling gate, replay on AUA) |
| TODO-B | dlsym(RTLD_DEFAULT, mangled) fallback if Swift mangled symbol resolution fails |
| TODO-C | Resolve Optional<DeviceIdentifier> size at runtime via swift_getGenericMetadata |
| TODO-D | Sibling-gate return-address discriminator (FUN_100020700 range hardcoded) |
| TODO-E | Per-thread recursion guard |
Apparatus integrity (D46b non-destructive)¶
Pure host-side static disasm + Ghidra invocation. Zero apparatus
interaction. Zero binaries modified. inject sha unchanged at
52df2cc6...4803. Working tree clean post-distill.
Distillation status (D46b)¶
Raw notes (notes/d46b-prep-gaps.md, 915 lines) deleted at
distillation time per feedback_notes_are_temporary_buffer. Ghidra
script artifacts at /tmp/iosmux-d46b-*.{sh,java,txt,log}
(transient). Verdict + corrections to D46 + D46c TODOs preserved in
this section + the Q-D66-15 D46b Resolution log entry +
aua-side-channel-mechanism.md
§"D46b-resolved hook target" + §"Hook payload — capture-and-replay
strategy".
D46c + D46c-fix update: hook code authored + build/deploy clean; BLOCKED on Swift metadata-accessor resolution; 2 paths forward (2026-05-03)¶
D46c was the c-developer implementation dispatch — D46c-fix the delta dispatch closing one of the two metadata-accessor resolution paths empirically. Both ran without apparatus regressions; both landed at a SECONDARY block that revealed an architectural fact about Tahoe 26.x's dyld shared cache.
D46c outcome (clean build/deploy, install bails)¶
D46c authored:
inject/iosmux_aua_edi_hook.h— public APIiosmux_aua_edi_hook_install(void).inject/iosmux_aua_edi_hook.m(~433 lines) — naked-asm outer wrapper preserves Swift cc R13 (self) + RAX (indirect-result)- callee-saved regs across CALL into trampoline; C body inspects
the Optional
tag via Swift VWT slot +0x30, captures.somebyte image on first observation, replays via VWTdestroy(slot+0x08) +initializeWithCopy(slot+0x10) when AUA-caller path produces.none. Per-thread recursion guard via__thread. AUA caller-discriminator computed from CDS image base + hardcoded FUN_100020700 range (CDS:0x100020700..0x1000218fc). inject/iosmux_inject.m— added#include+ finaliosmux_aua_edi_hook_install()call at end ofiosmux_inject_init.inject/Makefile— addediosmux_aua_edi_hook.mto source list.
Build chain on havoc: make clean + make succeeded; build sha
7b196be83ca56206357bff13a08c2678cfa602cb3aa10a92dfe5d22a24e80d0a
(first deploy) and 439421936acdba224305d7a62b80e43b48bd679976e5b48fca612c0dd3954df1
(after scoped-dlsym fallback). Codesign clean. Deploy via
ssh havoc-root cp to /Library/Developer/CoreDevice/iosmux_inject.dylib
clean. CDS respawn (PID 8086 first, 8198 second) clean.
Devicectl trigger ran end-to-end:
xcrun devicectl device info details --device E8A190DD-... exit 0
(WARNING Failed to acquire assertion still fires because hook
never armed).
Empirical block at install function:
D46c: thunk resolved at 0x10d5e0b10— thunk dlsym OK.D46c: dlsym(RTLD_DEFAULT, $s10CoreDevice16DeviceIdentifierOMa) returned NULL— metadata accessor NOT in exports trie.- D46c-fix added scoped
dlsym(dlopen("/Library/Developer/PrivateFrameworks/CoreDevice.framework/CoreDevice", RTLD_NOLOAD), ...): also returned NULL. D46c: install FAILED — dlsym($s10CoreDevice16DeviceIdentifierOMa) returned NULL.
Hook entry log D46c: hook installed at thunk 0x... NEVER fires
because install bails before trampoline patching. Trampoline never
patched. Thunk never hooked. AUA gate continues to trip.
CDS PID stable across both deploys. Zero new DiagnosticReports.
iPhone state stable at connected (no DDI). Apparatus restored to
D23 v2 baseline at end (52df2cc6...4803).
D46c-fix outcome (LC_SYMTAB walk also blank)¶
D46c-fix authored a Mach-O LC_SYMTAB walker as fallback in
iosmux_aua_edi_hook.m:
find_coredevice_image_index()— iterates_dyld_image_count()/_dyld_get_image_name(i), exact trailing-component match/CoreDevice(avoids "CoreDeviceUtilities").resolve_via_symtab(target)— locatesLC_SYMTABload command in the chosen image, maps__LINKEDITsegment viaLC_SEGMENT_64to convert file offsets to in-memory pointers, iteratesnlist_64entries (skippingN_STAB), matches symbol name against target.
Built clean. Deployed clean. Build sha
0f5482afd9dfa99e3abcee9075b7aefcfa67fc537188230fff08d1db5a2e52be.
Empirical result:
D46c: thunk resolved at 0x...
D46c: dlsym(RTLD_DEFAULT, $s10CoreDevice16DeviceIdentifierOMa) returned NULL — trying scoped dlsym
D46c: scoped dlsym also returned NULL — trying LC_SYMTAB walk
D46c: resolve_via_symtab — image[5] header=0x11146c000 slide=0x11146c000 linkedit_mem=0x111498000 nsyms=5281 stroff=4611680 symoff=4512448
D46c: resolve_via_symtab — '_$s10CoreDevice16DeviceIdentifierOMa' not found in 5281 symbols
D46c: install FAILED — symtab walk also failed for metadata accessor
5281 nlist entries iterated correctly. Zero matches for the target
symbol. Per-image LC_SYMTAB is also blank for this internal
Swift symbol.
Architectural finding — Tahoe 26.x dyld shared cache strips local symbols¶
CoreDevice on Tahoe 26.x lives in the dyld shared cache. The cache
builder strips per-image LC_SYMTAB local-symbol entries and
consolidates them into a separate cache-side symbol-info file
shipped with the cache binary. Both dlsym (exports trie) AND
direct nlist_64 walk per-image return blank for internal Swift
symbols (e.g., type metadata accessors _$s..OMa for non-public
types).
Captured in memory rule feedback_dyld_shared_cache_strips_symbols.
Affects any future symbol-resolution attempt against framework
internals — skip these two paths and go directly to Ghidra-hardcoded
offsets OR dynamic metadata recovery.
CDS PID stable through D46c-fix (PID 8563 alive post-trigger). Zero new DiagnosticReports. iPhone stable. Apparatus restored.
Forward paths (post-D46c-fix)¶
Two paths remain to provide the metadata accessor; existing hook code activates with one-line install-function patch once either lands.
Path #2 — Hardcoded metadata-accessor offset (Ghidra-derived).
Mirrors existing project pattern at inject/iosmux_aua_keepalive.m:55
(IOSMUX_AUA_CD_REMOVECACHED_OFFSET). Two dispatches:
- D46d (research-only,
general-purposeagent): Ghidra/llvm-objdump on the on-disk extracted CoreDevice binary (extracted viadyld_shared_cache_utilsince shared-cache strips don't apply to the on-disk artifact) → derive slice VA of_$s10CoreDevice16DeviceIdentifierOMa. - D46e (impl,
c-developeragent): replace install function's dlsym/symtab resolution withg_did_meta = ((MaFn)((uint8_t *)cd_base + OFFSET))(); build, deploy, validate (this time hook should arm).
Path #3 — Dynamic metadata recovery via Swift runtime.
Single c-developer dispatch authoring runtime metadata walker:
hook captures R13 (self) on first entry, walks *self →
metadata pointer → vtable → ... to recover
Optional<DeviceIdentifier>'s metadata indirectly.
Version-resilient (no hardcoded offsets); higher implementation
risk (Swift runtime ABI undocumented for non-public types).
50-100 lines of Swift-runtime-aware C.
Path #2 recommended (project precedent, lower risk, faster to empirical validation).
Apparatus integrity (D46c + D46c-fix non-destructive overall)¶
D23 v2 baseline preserved through both dispatches. Pre-deploy backup verified pre-deploy + post-restore. Zero CDS crashes. Zero new DiagnosticReports. iPhone connected throughout. Working tree clean post-distill (this commit).
Distillation status (D46c + D46c-fix)¶
Raw notes (notes/d46c-impl.md + notes/d46c-fix-impl.md,
combined ~600 lines) deleted at distillation time per
feedback_notes_are_temporary_buffer. Build/deploy/validation
artifacts under /tmp/iosmux-d46c-*.{txt,sh} and
/tmp/iosmux-d46c-fix-*.{txt,sh} (transient). Hook code authored
in inject sources is committed to the repo separately (commit
pending). Empirical findings + dyld-shared-cache learning + 2
forward paths preserved in this section + the Q-D66-15 D46c
Resolution log entry + aua-side-channel-mechanism.md
§"D46c + D46c-fix outcome".
D46d update: metadata-accessor offset RESOLVED via Ghidra D36 query — 0x00259910 for D46e c-developer patch (2026-05-03)¶
D46d was the path #2 research dispatch deriving the slice VA / file
offset of _$s10CoreDevice16DeviceIdentifierOMa (the Swift type
metadata accessor for CoreDevice.DeviceIdentifier) for hardcoding
into iosmux_aua_edi_hook.m. Approach A (Ghidra D36 project query)
succeeded in 4 verification passes; havoc shared-cache extraction
was NOT triggered.
Resolved address¶
Slice VA / image-base-relative offset = 0x00259910 in
CoreDevice.framework x86_64 slice (sha256
bea205e2c64622d144bcc7664ee104083d0e192aca206739cca345dc7c420495,
matches D21/D22 baseline cited in iosmux_aua_keepalive.m:53 —
same binary, no drift).
Mangling subtlety (load-bearing for future sessions)¶
The expanded form _$s10CoreDevice16DeviceIdentifierOMa D46c tried
via dlsym is literally absent from Ghidra's symbol table and
also from any nm output. The canonical mangling Apple's strip
pipeline preserves is _$s10CoreDevice0B10IdentifierOMa —
identifier-substitution-compressed (0B = back-reference to
identifier index 0). Both forms swift demangle to the same
demangled string type metadata accessor for CoreDevice.DeviceIdentifier.
This means D46c's failure had TWO causes simultaneously:
- Shared-cache local-symbol stripping (memory rule
feedback_dyld_shared_cache_strips_symbols— D46c-fix's nlist walk would have failed regardless). - Wrong mangled form in the
dlsymcall — even an unstripped binary wouldn't match the expanded form.
For any future Swift-runtime symbol resolution against framework
internals: search for the SUBSTITUTION-COMPRESSED form first, not
the length-prefixed expanded form. swift demangle on the
canonical form confirms semantic identity.
Verification chain (4-pass Ghidra)¶
- Query #1 (
iosmux_d46d_query.java) — exact-symbol miss for expanded form; broad scan surfaced$$type_metadata_for_CoreDevice.DeviceIdentifierat0x003ecf80,CoreDevice::DeviceIdentifierat0x0038da0c. - Query #2 (
iosmux_d46d_query2.java) — secondary mangled labels confirmed:0x003ecf80 = _$s10CoreDevice0B10IdentifierON(type metadata DATA),0x0038da0c = _$s10CoreDevice0B10IdentifierOMn(nominal type descriptor). Both demangle as expected. - Query #3 (
iosmux_d46d_xref.java) — xrefs to theMndescriptor at0x0038da0cinclude CODE ref from0x00259923(aLEA RSI, [0x38da0c]inside Ghidra-named functiontypeMetadataAccessorentry0x00259910). - Query #4 (
iosmux_d46d_disasm.java) — disasm of0x00259910confirmed textbook Swift singleton-metadata accessor pattern (cached fast path +swift_getSingletonMetadataslow path viaLEA RSI, [Mn]; CALL stub). Function is 33 bytes. Secondary label_$s10CoreDevice0B10IdentifierOMaattached at the entry point. - Query #5 (
iosmux_d46d_verify.java) — convention check: verified thatiosmux_aua_keepalive.m'sIOSMUX_AUA_CD_REMOVECACHED_OFFSET = 0xdbe0matches Ghidra's slice VA forremoveCachedXPCConnectionexactly. Convention established: slice VA in this project IS the offset to use at runtime via_dyld_get_image_header(CoreDevice) + offset.
D46e patch shape (ready)¶
// inject/iosmux_aua_edi_hook.m additions:
#define IOSMUX_AUA_CD_DID_METADATA_OFFSET ((uintptr_t)0x00259910)
static void *iosmux_resolve_did_metadata_accessor(void) {
uint32_t n = _dyld_image_count();
for (uint32_t i = 0; i < n; i++) {
const char *name = _dyld_get_image_name(i);
if (!name) continue;
size_t len = strlen(name);
const char *suffix = "/CoreDevice.framework/Versions/A/CoreDevice";
size_t slen = strlen(suffix);
if (len < slen) continue;
if (strcmp(name + len - slen, suffix) != 0) continue;
const struct mach_header *hdr = _dyld_get_image_header(i);
return (void *)((uintptr_t)hdr + IOSMUX_AUA_CD_DID_METADATA_OFFSET);
}
return NULL;
}
Replace the existing dlsym/symtab walk chain in the install
function with a single call to this resolver. The accessor's
signature is Metadata *(MetadataRequest) — RDI = request,
returns {Metadata*, MetadataState} in {RAX, RDX}. Caller-side,
cast to the appropriate Swift ABI shape and invoke; the returned
Metadata* then yields the VWT at *(metadata - 8) per existing
hook logic.
Apparatus integrity (D46d non-destructive)¶
Pure host-side static analysis on the Linux host. Zero apparatus
interaction. Zero ssh to havoc. Zero binary modifications. Read-only
Ghidra runs (-noanalysis -readOnly) — D36.gpr unchanged.
Distillation status (D46d)¶
Raw notes (notes/d46d-metadata-offset.md, 229 lines) deleted at
distillation per feedback_notes_are_temporary_buffer. Ghidra
script artifacts under /tmp/iosmux_d46d_*.java and logs at
/tmp/iosmux-d46d-*-out.log (transient). Verdict + offset + patch
shape preserved in this section + the Q-D66-15 D46d Resolution log
entry + aua-side-channel-mechanism.md
§"Path #2".
See also¶
aua-side-channel-mechanism.md— main architectural doc; current state and verdicts.aua-history-d36-d37.md— host-side static disasm era (D36 + D37-C/D/E/F/G) preceding D38.aua-history-d24-d35.md— earlier empirical localisation (D24 + D30 + D31 + D32 + D33 + D34/D35a).docs/plans/d66-research-questions.mdQ-D66-15 — Resolution log table.