Add C++ test for Scheduler delegate UAF after JS-throw teardown (#56800)#56800
Open
fkgozali wants to merge 1 commit into
Open
Add C++ test for Scheduler delegate UAF after JS-throw teardown (#56800)#56800fkgozali wants to merge 1 commit into
fkgozali wants to merge 1 commit into
Conversation
|
@fkgozali has exported this pull request. If you are a Meta employee, you can view the originating Diff in D104777850. |
9e56873 to
27c7f91
Compare
fkgozali
added a commit
to fkgozali/react-native
that referenced
this pull request
May 12, 2026
…book#56800) Summary: Reproduces, in a standalone gtest, the use-after-free race between Scheduler teardown and pending rendering-update lambdas previously enqueued via runtimeScheduler->scheduleRenderingUpdate inside Scheduler::uiManagerDidFinishTransaction. The lambda captures the SchedulerDelegate by raw pointer; when the delegate is destroyed (as part of an instance teardown triggered by an uncaught fatal error) before the lambda runs, the dereference is a use-after-free unless the invalidation-token guard in Scheduler::setDelegate (enableSchedulerDelegateInvalidation) is enabled at queue time. The test: - Drives the *real* Scheduler::uiManagerDidFinishTransaction so the lambda is enqueued via the regular code path into a real RuntimeScheduler's pending-rendering-updates queue. - Initiates teardown via an uncaught JSI host-function throw routed through RuntimeScheduler's onTaskError callback (the test's analog of a host-side fatal handler), which drops the delegate. - Triggers the next event loop tick to drain the queue. Three test cases: 1. Sanity_LambdaRunsOnNextTickWhenDelegateAlive -- baseline: lambda runs and reaches the delegate when no teardown happens. 2. GuardEnabled_JSThrowInitiatedTeardownIsSafe -- with the guard ON, the pending lambda observes the invalidation token after teardown and returns without touching the freed delegate. Safe. 3. GuardDisabled_JSThrowInitiatedTeardownIsUAF -- with the guard OFF, the lambda dereferences the destroyed delegate. Caught by EXPECT_DEATH via a magic-sentinel ASSERT_EQ in the recording delegate, or by AddressSanitizer on the vptr load. Fantom is intentionally not used here: it shares the global runtime VM across tests, which would interfere with this test's contract that no further JS executes after a fatal-driven instance teardown. Changelog: [Internal] Reviewed By: javache Differential Revision: D104777850
27c7f91 to
8e4535e
Compare
…book#56800) Summary: Reproduces, in a standalone gtest, the use-after-free race between Scheduler teardown and pending rendering-update lambdas previously enqueued via runtimeScheduler->scheduleRenderingUpdate inside Scheduler::uiManagerDidFinishTransaction. The lambda captures the SchedulerDelegate by raw pointer; when the delegate is destroyed (as part of an instance teardown triggered by an uncaught fatal error) before the lambda runs, the dereference is a use-after-free unless the invalidation-token guard in Scheduler::setDelegate (enableSchedulerDelegateInvalidation) is enabled at queue time. The test: - Drives the *real* Scheduler::uiManagerDidFinishTransaction so the lambda is enqueued via the regular code path into a real RuntimeScheduler's pending-rendering-updates queue. - Initiates teardown via an uncaught JSI host-function throw routed through RuntimeScheduler's onTaskError callback (the test's analog of a host-side fatal handler), which drops the delegate. - Triggers the next event loop tick to drain the queue. Three test cases: 1. Sanity_LambdaRunsOnNextTickWhenDelegateAlive -- baseline: lambda runs and reaches the delegate when no teardown happens. 2. GuardEnabled_JSThrowInitiatedTeardownIsSafe -- with the guard ON, the pending lambda observes the invalidation token after teardown and returns without touching the freed delegate. Safe. 3. GuardDisabled_JSThrowInitiatedTeardownIsUAF -- with the guard OFF, the lambda dereferences the destroyed delegate. Caught by EXPECT_DEATH via a magic-sentinel ASSERT_EQ in the recording delegate, or by AddressSanitizer on the vptr load. Fantom is intentionally not used here: it shares the global runtime VM across tests, which would interfere with this test's contract that no further JS executes after a fatal-driven instance teardown. Changelog: [Internal] Reviewed By: javache Differential Revision: D104777850
8e4535e to
9e699f7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Reproduces, in a standalone gtest, the use-after-free race between Scheduler
teardown and pending rendering-update lambdas previously enqueued via
runtimeScheduler->scheduleRenderingUpdate inside
Scheduler::uiManagerDidFinishTransaction.
The lambda captures the SchedulerDelegate by raw pointer; when the delegate
is destroyed (as part of an instance teardown triggered by an uncaught fatal
error) before the lambda runs, the dereference is a use-after-free unless
the invalidation-token guard in Scheduler::setDelegate
(enableSchedulerDelegateInvalidation) is enabled at queue time.
The test:
is enqueued via the regular code path into a real RuntimeScheduler's
pending-rendering-updates queue.
RuntimeScheduler's onTaskError callback (the test's analog of a host-side
fatal handler), which drops the delegate.
Three test cases:
reaches the delegate when no teardown happens.
pending lambda observes the invalidation token after teardown and returns
without touching the freed delegate. Safe.
lambda dereferences the destroyed delegate. Caught by EXPECT_DEATH via a
magic-sentinel ASSERT_EQ in the recording delegate, or by AddressSanitizer
on the vptr load.
Fantom is intentionally not used here: it shares the global runtime VM across
tests, which would interfere with this test's contract that no further JS
executes after a fatal-driven instance teardown.
Changelog: [Internal]
Reviewed By: javache
Differential Revision: D104777850