Skip to content

darwin: pass NULL envp to arm64 initializers#1114

Open
4ch12dy wants to merge 1 commit into
frida:mainfrom
4ch12dy:fix-darwin-arm64-init-envp-null
Open

darwin: pass NULL envp to arm64 initializers#1114
4ch12dy wants to merge 1 commit into
frida:mainfrom
4ch12dy:fix-darwin-arm64-init-envp-null

Conversation

@4ch12dy

@4ch12dy 4ch12dy commented May 27, 2026

Copy link
Copy Markdown

Summary

This changes the Darwin arm64 mapper to pass NULL for envp when invoking Mach-O initializers, while keeping the existing empty argv, apple, and result handling unchanged.

Details

The current arm64 mapper passes the same synthetic empty_strv pointer for both argv and envp. This means envp is non-NULL and points into mapper-owned runtime data.

Most initializers ignore this value, but initializer-triggered runtime/framework paths may propagate argument registers or reinterpret them across callback boundaries. In those cases, a non-NULL synthetic envp can be treated as an arbitrary pointer instead of an absent environment, which may cause crashes during early attach.

Passing NULL better represents the absence of an injected-process environment and avoids exposing mapper-owned storage through envp.

Testing

  • Built a local iOS arm64e server with this change.
  • Repeatedly attached to a local iOS arm64e test target that previously failed during early initializer execution.
  • Confirmed repeated attach success after the change, with no new crash reports for that target during the validation loop.

@oleavr

oleavr commented Jun 4, 2026

Copy link
Copy Markdown
Member

Thank you! Why only do this for arm64? And what is the exact issue here, is it that we're passing an empty envp, and there's code assuming there's at least one element before the terminator?

@4ch12dy

4ch12dy commented Jun 10, 2026

Copy link
Copy Markdown
Author

Thanks for taking a look!

I kept this arm64-only because that is where I have a reliable repro and validation: iOS arm64e, attaching to a target that previously crashed during early initializer execution. I have not verified the same failure mode on the x86 or
32-bit ARM Darwin mapper paths. I noticed those paths also pass empty_strv as envp, so if the intended mapper contract is “no environment”, I’m happy to update the PR to make those pass NULL as well for consistency.

The issue I observed does not look like code iterating an empty envp and assuming there is at least one element before the terminator.

The crash evidence points to the non-NULL pointer value itself leaking through the arm64 initializer ABI. The mapper was calling initializers with:

x0 = 0
x1 = empty_strv
x2 = empty_strv
x3 = apple_strv
x4 = 0

In the failing process, the initializer-triggered path entered an Objective-C/framework path. The crashing frame was objc_msgSend with selector setSymptomsStatus:, and the receiver was the mapper-owned empty_strv address inside
the Frida payload. In other words, the synthetic non-NULL envp pointer was later interpreted as an Objective-C object receiver. Since it is non-NULL but not an object, objc_msgSend crashed.

Passing NULL for x2/envp avoids exposing this mapper-owned address. With this change I rebuilt an iOS arm64e server and repeatedly attached to the previously failing target after restarting it; all attempts succeeded and no new
crash report was generated.

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.

2 participants