Skip to content

feat(helper): bundle the interposer in the .app#64

Merged
0xnirapod merged 1 commit into
mainfrom
bundle-interposer
Jun 12, 2026
Merged

feat(helper): bundle the interposer in the .app#64
0xnirapod merged 1 commit into
mainfrom
bundle-interposer

Conversation

@0xnirapod

Copy link
Copy Markdown
Member

An installed SimEnclave couldn't inject. The helper found the interposer only by walking up from its own path to a dev-tree build-sim/, which doesn't exist next to /Applications, so arm() silently no-oped, the simulator was never armed, and apps ran uninjected. A permanent-key SecKeyCreateRandomKey then hit the bare simulator and failed -34018.

The fix

The helper bundles the interposer in Contents/Resources and resolves it from its own bundle (dev-tree fallback kept). The .app is self-contained, so the installer just drops it in /Applications and arming works out of the box.

Re-scoping the fence (the part worth reviewing)

The old fence forbade the interposer inside any .app, including the helper's own. That rule conflated the helper (the tool that injects) with a consuming app (the thing that must never ship it). It is not what keeps the interposer out of production. Three things do, none of which needs the dylib absent from the helper:

  1. The interposer is a simulator-slice binary. dyld on a real device refuses to load it (wrong Mach-O platform), so it cannot run on hardware wherever it sits.
  2. A consuming app wires DYLD_INSERT_LIBRARIES only in a debug scheme; a release build references nothing. iOS library validation blocks the injection on a device anyway.
  3. The variable stays in a reviewed allowlist.

So the fence now guards that boundary honestly:

  • fence-check.sh --helper <app> asserts the bundled interposer is present and is simulator-slice (via vtool), and that the helper does not inject into itself.
  • fence-check.sh --bundle <app> still rejects an interposer or the variable in a consuming app.
  • fence-selftest.sh covers both (no-interposer fails, non-sim-slice fails, real sim-slice passes; consuming-app cases unchanged).
  • Rules 1-3 over tracked files are unchanged.

The guarantee gets stronger and more honest: instead of "the dylib is hidden," it's "the only binary the tool ships can run nowhere but the Simulator."

Verified

Built the .app: interposer bundled, vtool platform IOSSIMULATOR, --helper passes, --bundle correctly fails on it, static fence passes, codesign --deep --strict ok, helper compiles. Installed it, launched the helper, and it armed the booted sim pointing at its own bundled dylib; the relaunched example app injected and the helper logged served LIST_KEYS app=dev.simenclave.SecureEnclaveExample, so the -34018 path is gone.

Custody gate: PASS (dev tool, the developer's own Mac SEP, no user keys or funds).

The helper now carries the interposer in Contents/Resources and resolves it from its own
bundle, so an installed SimEnclave arms the simulator out of the box. Before, the helper
only found the dylib by walking up to a dev-tree build-sim/, so the /Applications build
never armed and apps ran uninjected (SecKeyCreateRandomKey hit the bare simulator: -34018).

Re-scope the fence to the boundary that matters. The interposer is a simulator-slice binary
(dyld on a device refuses it), a consuming app injects it debug-scheme-only, and the
variable stays in the allowlist; those are the real guarantees, and none needs the dylib
absent from the helper. fence-check.sh gains --helper (the bundled payload must be
simulator-slice and the helper must not inject into itself); --bundle still rejects an
interposer in a consuming app. fence-selftest covers both. Docs updated to match.
@0xnirapod
0xnirapod merged commit 87130b4 into main Jun 12, 2026
3 checks passed
@0xnirapod
0xnirapod deleted the bundle-interposer branch June 12, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant