Skip to content

Stop alert handlers reading the action name from freed memory - #142

Open
cortfritz wants to merge 1 commit into
GenericJam:masterfrom
cortfritz:fix/ios-alert-action-use-after-free
Open

Stop alert handlers reading the action name from freed memory#142
cortfritz wants to merge 1 commit into
GenericJam:masterfrom
cortfritz:fix/ios-alert-action-use-after-free

Conversation

@cortfritz

Copy link
Copy Markdown

Both alert NIFs build their button handlers from a dangling pointer:

NSString *action = btn[@"action"] ?: @"dismiss";
constchar *act_c = [action UTF8String];
[ac addAction:[UIAlertAction actionWithTitle:label style:as
handler:^(UIAlertAction *_) {
mob_deliver_alert_action(act_c);
}]];

The block captures a const char *, so it retains nothing. action belongs to buttons, a local the enclosing dispatch_async block drops as soon as it returns — and -UTF8String's buffer is autorelease-scoped besides. Both are gone long before the user taps.

mob_deliver_alert_action then passes that memory to enif_make_atom, so the screen receives an {:alert, <whatever was there>} matching no clause, or the NIF call fails outright if the bytes are not valid UTF-8.

Change

Capture the NSString — ARC retains it for the life of the handler — and convert inside the block, where the pointer only has to survive the enif_make_atom call. Applied to nif_alert_show and nif_action_sheet_show.

This is the kind of thing that hides in testing, because freed bytes usually still spell the old string until the allocator reuses them, so it is added to AGENTS.md as pre-empt rule 15.

Verification

Rung 1.

  • xcrun clang-format --dry-run -Werror ios/mob_nif.m — clean.
  • clang -fsyntax-only against the iOS 26.5 SDK — no diagnostics introduced.
  • Not exercised at runtime: on iOS 26 the dialog does not present in the first place, which is the companion PR. That one has to land before this path can be reached on a device at all.

Independent of the companion PR as a diff — different hunks, no conflict — but only observable once alerts present.

https://claude.ai/code/session_01FGHFA67yQndR78WYAU9DM7

Both alert NIFs build their button handlers like this:
NSString *action = btn[@"action"] ?: @"dismiss";
const char *act_c = [action UTF8String];
[ac addAction:[UIAlertAction actionWithTitle:label style:as
handler:^(UIAlertAction *_) {
mob_deliver_alert_action(act_c);
}]];
The block captures a C pointer, so it retains nothing. `action` is owned
by `buttons`, a local the enclosing dispatch block drops on return, and
-UTF8String's buffer is autorelease-scoped on top of that. Both are gone
well before the user taps. mob_deliver_alert_action then hands whatever
occupies that memory to enif_make_atom, so the screen gets an
{:alert, <garbage>} that matches no clause — or a crash, if the bytes are
not valid UTF-8.
Capture the NSString instead and convert inside the handler, where the
pointer only has to survive the enif_make_atom call.
This is hard to see in testing: freed bytes usually still spell the old
string, so it works until the allocator reuses them. AGENTS.md gets it as
pre-empt rule 15.
Sign up for freeto 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

@cortfritz