Fix use-after-free when an Image is destroyed mid-download - #16345
Open
gmacmaster wants to merge 1 commit into
Open
Fix use-after-free when an Image is destroyed mid-download#16345gmacmaster wants to merge 1 commit into
gmacmaster wants to merge 1 commit into
Conversation
Contributor
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
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.
Description
Type of Change
Why
ImageResponseObserverCoordinator(in react-native core) copies its observer list under a lock, releases the lock, and then dereferences the raw observer pointers:WindowsImageManagernotifies that coordinator from the download thread and from theIAsyncOperationcompletion handler. Observers, however, are added and removed on the UI thread (ImageComponentView::setStateAndResubscribeImageResponseObserver), and the UI thread is also where the owningImageComponentView— and with it thestd::shared_ptr<WindowsImageResponseObserver>— is destroyed.So if a view is torn down while its download is still streaming, the observer can be freed between the snapshot and the dereference. The first thing
WindowsImageResponseObserver::didReceiveProgresstouches is itswinrt::weak_refmember, copied into the dispatcher lambda, so the crash surfaces as anEXCEPTION_ACCESS_VIOLATION_READinsidewinrt::com_ptr<T>::add_ref:The
weak_refdoesn't help here — it guards theImageComponentView, but the object that was freed is the observer that holds it.This is the Fabric recurrence of a lifetime bug that was fixed in the Paper
ReactImageback in #2820 and #2822. The scenario described there is the same one that reproduces today:The Fabric completion handler has always notified the coordinator off the UI thread, but the window was narrow because it fired once per request. #14493 (
[Fabric] Implement onProgress for Image) added aprogressCallbackthat callsnativeImageResponseProgressfrom the download loop on every 16 KB chunk, which widened that window enormously and is what makes this reachable in practice. #13440 is where the observer gained itswinrt::weak_ref, which resolved an unrelated reference cycle but does not protect against the observer itself being freed.We hit this in production on RNW 0.84 — a screen of remote thumbnails, navigated away from mid-load.
Resolves
N/A
What
All coordinator notifications are now marshalled onto the UI thread, so notification and subscription are serialized on the same thread and the coordinator can no longer iterate a snapshot while another thread frees an entry. Changes are confined to
requestImageinvnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp:progressCallbackcomputes the progress fraction on the download thread and posts only thenativeImageResponseProgresscall.Completedhandler keepsResolveImage()on the completion thread, so WIC decoding stays off the UI thread, and posts only thenativeImageResponseComplete/nativeImageResponseFailedcall via two smallpostComplete/postFailurehelpers.This incidentally moves
didReceiveFailureonto the UI thread as well. It previously resolved the weak ref and touchedImageComponentViewstate directly on the calling thread, which was a separate off-thread access to view state.Note for reviewers: the inner
UIDispatcher().Postcalls inImageComponentView::didReceiveProgressanddidReceiveImageare now a redundant second hop. They're harmless and FIFO ordering through the serial dispatcher is preserved, so I left them out of this change to keep the diff to a single file. Happy to fold the cleanup in if you'd prefer.Screenshots
N/A — crash fix with no visual change.
Testing
No automated tests added. The failure is a data race on object lifetime, so a deterministic unit test would require injecting a teardown between the coordinator's snapshot and its dereference, which isn't reachable from RNW without changing core.
Changelog
Yes.
Fixed a crash that could occur when an
Imagewas destroyed while its download was still in flight, such as navigating away from a screen before its images finished loading.Microsoft Reviewers: Open in CodeFlow