Skip to content

fix(android): use-after-free in RAF choreographer frame callback#143

Merged
triniwiz merged 1 commit into
masterfrom
fix/ownership-allocation
Jul 5, 2026
Merged

fix(android): use-after-free in RAF choreographer frame callback#143
triniwiz merged 1 commit into
masterfrom
fix/ownership-allocation

Conversation

@NathanWalker

Copy link
Copy Markdown
Contributor

AChoreographer_postFrameCallback cannot be cancelled, but clear_callback() freed the Box handed to the choreographer while a frame callback was still queued. The next vsync then fired with a dangling pointer, corrupting whatever had reused the allocation (often the V8 heap), crashing either inside libcanvasnative.so during dispatchVsync or later in unrelated V8 code (HandleScope::Extend, StubCache::Set). Every canvas teardown queued the UAF; framework HMR reboots made it reliably fatal.

A stop()/start() restart could also spawn a duplicate callback chain whose stale sibling would free the new chain's allocation via lock.data.take().

Make each posted frame callback the sole owner of its allocation: on every fire it either re-posts the same FrameChain box or drops it, and no other code path frees it. A generation counter retires stale chains after a restart, and clear_callback() now only detaches the JS-side closure, which still guarantees the callback target is never invoked after release.

AChoreographer_postFrameCallback cannot be cancelled, but clear_callback()
freed the Box handed to the choreographer while a frame callback was still
queued. The next vsync then fired with a dangling pointer, corrupting
whatever had reused the allocation (often the V8 heap), crashing either
inside libcanvasnative.so during dispatchVsync or later in unrelated V8
code (HandleScope::Extend, StubCache::Set). Every canvas teardown queued
the UAF; framework HMR reboots made it reliably fatal.

A stop()/start() restart could also spawn a duplicate callback chain whose
stale sibling would free the new chain's allocation via lock.data.take().

Make each posted frame callback the sole owner of its allocation: on every
fire it either re-posts the same FrameChain box or drops it, and no other
code path frees it. A generation counter retires stale chains after a
restart, and clear_callback() now only detaches the JS-side closure, which
still guarantees the callback target is never invoked after release.
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0522ff5d-0f16-4a8b-b5f2-dd33acc39fa8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ownership-allocation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@triniwiz triniwiz merged commit ae9dfb2 into master Jul 5, 2026
8 of 9 checks passed
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