Interpreter/JIT apps (.NET MAUI, Mono) do not run on research guests
Measured 2026-10-06 on a research guest (iPhone17,3, iOS 26.0 / 23A341,
kernel xnu-12377.42.6~55 RELEASE_ARM64_VRESEARCH1, roothide bootstrap present,
Launchpad/bundle 2.2.5). Subject: a stock .NET MAUI app built with the
Mono interpreter — the main binary exports interp_to_native_trampoline,
native_to_interp_trampoline, mono_jit_call and carries " interp" strings.
No application-specific identifiers, paths, or data are retained in this note.
Symptom
The app installs (via installd or apps.install) and spawns — it becomes
frontmost and gets a pid — then is SIGKILLed in under a second on the first
JIT page it executes:
Exception Type: EXC_BAD_ACCESS (SIGKILL)
Exception Subtype: KERN_PROTECTION_FAILURE
Termination: namespace CODESIGNING, code 2, indicator "Invalid Page"
codeSigningFlags: 0x2200000D (CS_VALID|CS_GET_TASK_ALLOW|CS_INSTALLER|CS_DYLD_PLATFORM|CS_SIGNED; no CS_DEBUGGED)The faulting frame is inside the app's own Mono runtime. This reads like a signing bug but is not one — it is the kernel refusing to execute a dynamically generated (unsigned) page.
Root cause (measured in-process)
A diagnostic dylib was injected into the live app via the existing
SystemHook → <jbroot>/usr/lib/TweakLoader.dylib load path (SystemHook
dlopens that exact path for every app process, so dropping a name-gated stub
there runs code inside the target). It exercised the JIT primitives directly:
| Probe (inside the app) | Result |
|---|---|
mmap(RW X, MAP_ANON) plain RWX | mmap succeeds — the kernel W^X relaxation works |
| execute an instruction written into that RWX page | SIGKILL — TXM blocks exec of unsigned pages |
mmap(RW X, MAP_ANON|MAP_JIT) | mmap succeeds |
pthread_jit_write_protect_supported_np() | returns 1 |
pthread_jit_write_protect_np (dlsym, incl. explicit libsystem_pthread handle) | NULL — not exported |
pthread_jit_write_with_callback_np (dlsym) | NULL — not exported |
linking pthread_jit_write_protect_np directly | dylib fails to load (undefined symbol) |
Two independent walls, either of which is fatal to Mono's interpreter:
- Plain RWX execution is TXM-blocked. The W^X hook (see
Research/KernelCustomFirmwarePatches/26.5_jb_hook_fixes.md) lets you map RWX memory, but TXM still refuses to execute a page whose contents are not covered by a valid signature →CODESIGNING "Invalid Page". - The sanctioned
MAP_JITpath is unusable from userland.MAP_JITmapping works and..._supported_np()claims support, but the write-protect toggle functions are not exported bylibsystem_pthreadon this PCC userland, so aMAP_JITregion can never be flipped writable to populate it. Mono has no way to emit code into a MAP_JIT region.
Net: there is no userland path to runtime JIT for a third-party process on this guest.
Why it presents as ".NET MAUI"
MAUI / .NET-iOS apps routinely ship with the Mono interpreter (all Debug
builds, and Release builds with UseInterpreter=true). The interpreter emits
native trampolines at runtime → needs to execute freshly written code → needs
JIT. Full-AOT builds (interpreter disabled) need no JIT and install/run
normally here. The same failure class applies to any JIT-dependent app
(emulators, Flutter debug, heavy JSC use).
Ruled out (all tested, not assumed)
- Entitlements (ad-hoc re-sign):
get-task-allow,dynamic-codesigning, andcom.apple.developer.cs.allow-jit+...web-browser-engine.webcontent.allow-jitalone → AMFI at exec: "allow-jit disallowed without …webcontent".allow-jit+webcontent→ installd refuses ("only allowed on app extensions …com.apple.web-browser-engine.content"). Installing viaapps.install(which bypasses installd) lets it spawn, but TXM still kills it at runtime;codeSigningFlagsunchanged.dynamic-codesigning→ installs + spawns, no runtime JIT grant → same kill.
csops(CS_OPS_SET_STATUS)to setCS_DEBUGGEDon self → returns success but the kernel filters the bit (flags unchanged).ptrace(PT_TRACE_ME)→EPERM.- debugserver /
idevicedebug/ frida-spawn (which would setCS_DEBUGGED) → all need the Developer Disk Image; on iOS 17+ the personalized DDI personalizes but fails to mount on the CFW guest (Failed to mount … at /System/Developer). frida is not installed on the guest (open :27042 earlier was only aniproxylocal listener).
Open questions
- Is running JIT/interpreter apps in scope for research guests? If not, worth
documenting as a known limitation — the
CODESIGNING "Invalid Page"symptom is easily misread as a signing bug. - If in scope, the guest needs either:
- the pthread JIT write-protect API restored/exposed
(
pthread_jit_write_protect_np/..._with_callback_np) soMAP_JITis usable, or - a kernel patch letting TXM permit unsigned-page execution for a
get-task-allowprocess (e.g. honoringCS_DEBUGGED, which currently cannot be set from userland here).
- the pthread JIT write-protect API restored/exposed
(
- A kernel-call bridge exists on the guest (
kcall10,sysent[439]), so a userland JIT-enable helper is conceivable — but it needs the exact kernel primitive that satisfies TXM on this A18/TXM part, and whetherCS_DEBUGGEDalone suffices under TXM here is unverified (the kernel'scheck_jit_entitlementpath, ret0xA0, gates oncom.apple.developer.cs.allow-jit+...web-browser-engine.webcontent).
Potential Workaround
Build/test a full-AOT (interpreter-disabled) .NET MAUI build — no JIT,
installs and runs normally via ideviceinstaller / apps.install.
Activity
0No activity yet.