← WebKit — 2026-W36

5b052db563f413b813c64976d70f3d557903d4da  Heap UAF in RemoteImageDecoderAVFManager::m_remoteImageDecoders

severity medium class Race confidence 0.55 signal score 10.5 Source/WebCore/platform
Primitive: RemoteImageDecoderAVF manager map accessed off main run loop
Triage note: RC_REMOVE_SEC_ACCEL_3-gated change adds assertIsMainRunLoop and WTF_GUARDED_BY_CAPABILITY(mainRunLoop) on m_remoteImageDecoders plus a protectedDecoder capture to destroy on the creation thread. Predominantly thread-assertion/annotation instrumentation with one lifetime capture, so not graded exploitable.
Structural signals: smartptr_member_added (+3.0), uaf_lifetime (+2.2), msg_strong (+3.0), msg_soft (+1.0), surgical (+1.3)

Commit message

Heap UAF in RemoteImageDecoderAVFManager::m_remoteImageDecoders
rdar://186141126
https://bugs.webkit.org/show_bug.cgi?id=323146

Reviewed by Jean-Yves Avenard.

ImageFrameWorkQueue::start() captures a Ref to the ImageDecoder in the lambda it
dispatches to the "org.webkit.ImageDecoder" WorkQueue. When the decoding loop
exits, the lambda is destroyed on that WorkQueue thread, and the loop-exit bounce
to the main thread only carried protectedThis, protectedWorkQueue and
protectedSource, not protectedDecoder. So if the main thread had already dropped
BitmapImageSource::m_decoder, the decoder's last Ref was released on the decoding
thread and ~ImageDecoder ran there.

That is not safe. RemoteImageDecoderAVF::~RemoteImageDecoderAVF() calls
RemoteImageDecoderAVFManager::deleteRemoteImageDecoder(), which mutates the
process global m_remoteImageDecoders registry. That registry is not synchronized
and it holds WeakPtr<RemoteImageDecoderAVF>, which is not thread safe, while the
main thread keeps adding to and looking up in the same registry from
createImageDecoder() and from the EncodedDataStatusChanged message handler. The
result is heap corruption of the hash table, since removing an entry can shrink
and reallocate the bucket array underneath the main thread.

Include protectedDecoder in the existing "Ensure destruction happens on creation
thread" bounce, so that the last reference held by the decoding thread is always
released on the main thread. ImageFrameWorkQueue::start() is the only place in
the tree that holds a Ref to an ImageDecoder off the main thread:
BitmapImageSource::m_decoder, the only other owner, is main thread only.

Document the main run loop requirement on the registry with
WTF_GUARDED_BY_CAPABILITY(mainRunLoop) and assert it in the three functions that
touch it, so a future off-main caller is caught at compile time or in debug
builds instead of corrupting memory.

Test: sar3/fast/images/image-decoder-destroyed-on-decoding-thread.html

* LayoutTests/sar3/fast/images/image-decoder-destroyed-on-decoding-thread-expected.txt: Added.
* LayoutTests/sar3/fast/images/image-decoder-destroyed-on-decoding-thread.html: Added.
* Source/WebCore/platform/graphics/ImageFrameWorkQueue.cpp:
(WebCore::ImageFrameWorkQueue::start):
* Source/WebKit/WebProcess/GPU/media/RemoteImageDecoderAVFManager.cpp:
(WebKit::RemoteImageDecoderAVFManager::createImageDecoder):
(WebKit::RemoteImageDecoderAVFManager::deleteRemoteImageDecoder):
(WebKit::RemoteImageDecoderAVFManager::encodedDataStatusChanged):
* Source/WebKit/WebProcess/GPU/media/RemoteImageDecoderAVFManager.h:

Identifier: 316606.615@safari-7625-branch

Before / after

Loading diff…