← WebKit — 2026-W35

d40f4efab0e0cee61e29c8540abff5285bf5275d  [WebCore] Use-after-free in ImageLoader::dispatchPendingLoadEvent / dispatchPendingErrorEvent

severity low class UAF confidence 0.4 signal score 8.3 Source/WebCore/loader
Primitive: Element not kept alive across dispatchLoadEvent/dispatchErrorEvent reentrancy in ImageLoader
Triage note: dispatchPendingLoadEvent/ErrorEvent now take Ref protectedElement = element() before accessing document() and dispatching, where dispatchLoadEvent() and the error Event can run author script that destroys the element; previously only the error dispatch used protect(element()). This is a defensive protect()-style hardening with no commit message or identified crash, so it is not exploitable-grade.
Structural signals: msg_strong (+3.0), msg_medium (+2.0), area:Source/WebCore/loader (+2.0), surgical (+1.3)

Commit message

[WebCore] Use-after-free in ImageLoader::dispatchPendingLoadEvent / dispatchPendingErrorEvent
rdar://177909775

Reviewed by Alan Baradlay.

dispatchPendingLoadEvent() and dispatchPendingErrorEvent() dispatch author script and then
touch `this` again via updatedHasPendingEvent(). The only thing keeping the element alive
across the dispatch is a full-expression-scoped Ref plus the m_protectedElement member, and
the 0s m_derefElementTimer can clear that member. A load handler can re-arm the timer by
removing the <img> from its <picture> (selectImageSource(RelevantMutation::Yes) calls
updatedHasPendingEvent()), then spin a nested run loop via showModalDialog(). The timer
fires and drops m_protectedElement while the dispatch is still on the stack. When the
dispatch returns the temporary Ref destructs as the last reference, ~HTMLImageElement frees
the loader via its unique_ptr<HTMLImageLoader>, and the trailing updatedHasPendingEvent()
runs on freed `this`.

Hold a stack Ref to the element across the dispatch and the trailing updatedHasPendingEvent()
in both functions. The loader is owned by the element, so keeping the element alive keeps
this ImageLoader alive.

Test: fast/images/image-load-event-in-modal-dialog-crash.html

* LayoutTests/fast/images/image-load-event-in-modal-dialog-crash-expected.txt: Added.
* LayoutTests/fast/images/image-load-event-in-modal-dialog-crash.html: Added.
* LayoutTests/fast/images/resources/self-closing-modal-dialog.html: Added.
* Source/WebCore/loader/ImageLoader.cpp:
(WebCore::ImageLoader::dispatchPendingLoadEvent):
(WebCore::ImageLoader::dispatchPendingErrorEvent):

Originally-landed-as: 305413.1064@safari-7624.5-branch (fe774071a22a). rdar://185367866
Canonical link: https://commits.webkit.org/319690@main

Before / after

Loading diff…