← WebKit — 2026-W39

5a3139cdf3e05dfcbeedffaff64b5a01d2a29485  Cross-thread UAF of GPUProcessConnection in WebMediaStrategy::canDecodeExtendedType via worker MSE changeType probing

severity medium class Race confidence 0.7 signal score 10.47 Source/WebKit/WebProcess patch-now
Primitive: Data race / UAF on WebProcess::m_gpuProcessConnection RefPtr read off the main thread while main thread resets it
Triage note: RC_REMOVE_SEC_ACCEL_3-bracketed fix adds m_gpuProcessConnectionLock guarding m_gpuProcessConnection and existingGPUProcessConnectionOffMainThread(), and keeps the closed connection alive until lock release; WebMediaStrategy::canDecodeExtendedType read the RefPtr off the main thread concurrently with gpuProcessConnectionClosed() nulling it, a cross-thread race on the connection pointer.
Structural signals: smartptr_member_added (+6.47), msg_strong (+3.0), msg_soft (+1.0)

Commit message

Cross-thread UAF of GPUProcessConnection in WebMediaStrategy::canDecodeExtendedType via worker MSE changeType probing
https://bugs.webkit.org/show_bug.cgi?id=325112
rdar://187952722

Reviewed by Youenn Fablet.

WebProcess::m_gpuProcessConnection is written only on the main run loop, but
WebMediaStrategy::canDecodeExtendedType() runs on whichever thread is probing
codec support. With MediaSource-in-workers that is a worker thread, and it is
reachable from ordinary web content at shipping defaults:
SourceBuffer.changeType() with a codec string that has not been seen before
misses MIMETypeCache, so the whole support probe
(SourceBufferPrivateAVFObjC::canSwitchToType ->
SourceBufferParserAVFObjC::isContentTypeSupported ->
AVStreamDataParserMIMETypeCache::canDecodeExtendedType) re-runs synchronously
on the worker and ends up in WebMediaStrategy::canDecodeExtendedType().

existingGPUProcessConnection() was an inline, unsynchronized raw load of that
member, so the worker did a raw load followed by a separate ref(). Meanwhile
GPUProcessConnection::didClose() on the main thread calls
WebProcess::gpuProcessConnectionClosed(), which nulls the member, and the
client cascade hands the last Ref to a WorkQueue lambda, so the connection is
freed off the main thread. A worker caught between its load and its ref()
increments a refcount inside freed memory and then reads the IPC connection
pointer out of the freed block.

Locking that single accessor and returning an owning RefPtr would fix the
race, but it charges a lock and a reference to every caller. Almost all of
them run on the main run loop, where the member cannot change under them and
there is no race; only the worker path needs protection. The common, safe
path should not pay for the one unsafe path.

Instead keep the fast accessor and add a slow, safe one:

  - existingGPUProcessConnection() still returns a raw pointer with no lock,
    but is now annotated WTF_REQUIRES_CAPABILITY(mainRunLoop). Every call that
    is not proven to be on the main run loop is a build error rather than a
    latent crash.

  - existingGPUProcessConnectionOffMainThread() is added for the worker path.
    It takes a new lock and returns an owning RefPtr, so the caller observes
    either null or a connection it already holis no
    window between the load and the reference.

  - The two main-thread writes also take the lock so a locked reader never
    observes a torn value. gpuProcessConnectionClosed() moves the old value
    into a local, releases the lock, and only the
    connection is never destroyed while the lo

The member is annotated WTF_GUARDED_BY_CAPABILITY(mainRunLoop), so every
main-thread access is verified at build time; only
existingGPUProcessConnectionOffMainThread() is exempt, because it reads under
the lock.

Test: media/media-source/worker/media-source-worker-changetype-gpu-process-crash.html

* LayoutTests/media/media-source/worker/media-gpu-process-crash-expected.txt: Added.
* LayoutTests/media/media-source/worker/media-gpu-process-crash.html: Added.
* Source/WebKit/WebProcess/GPU/GPUProcessConne
(WebKit::GPUProcessConnection::didBecomeUnresponsive): Assert the main run
loop before the main-thread accessor.
(WebKit::GPUProcessConnection::didClose): Ditto.
* Source/WebKit/WebProcess/GPU/media/WebMediaStrategy.cpp:
(WebKit::WebMediaStrategy::canDecodeExtendedType): Take the connection through
the off-main-thread accessor.
(WebKit::NowPlayingInfoForGPUManager::clearNowPlayingInfoPrivate): Assert the
main run loop.
* Source/WebKit/WebProcess/WebPage/WebPage.cpp
(WebKit::WebPage::~WebPage): Assert the main run loop before the accessor.
(WebKit::WebPage::updatePreferences): Ditto.
(WebKit::WebPage::configureLoggingChannel): Ditto.
* Source/WebKit/WebProcess/WebPage/Cocoa/WebPa
(WebKit::WebPage::setMediaPlaybackEnvironment): Ditto.
(WebKit::WebPage::setDisplayCaptureEnvironment): Ditto.
* Source/WebKit/WebProcess/WebProcess.cpp:
(WebKit::WebProcess::createWebPage): Assert the main run loop.
(WebKit::WebProcess::ensureGPUProcessConnection): Assign the member under the
lock.
(WebKit::WebProcess::existingGPUProcessConnectionOffMainThread): Added; take
the reference under the lock.
(WebKit::WebProcess::gpuProcessConnectionClosed): Clear the member under the
lock, and destroy the connection after the lock is released.
(WebKit::WebProcess::gpuProcessConnectionDidBecomeUnresponsive): Assert the
main run loop.
* Source/WebKit/WebProcess/WebProcess.h:

Identifier: 316606.786@safari-7625-branch

Before / after

Loading diff…