← All issues

[5] RemoteMediaPlayerProxy rejects duplicate CreateAudioSourceProvider

The GPU audio handler assumed a renderer would only ever ask once.

Severity: High | Component: WebKit GPU Process media IPC | 0181991

Rated High because a compromised WebContent process can send a duplicate CreateAudioSourceProvider to overwrite live callback closures without tapStorage->lock while a MediaToolbox thread is invoking them, freeing a RemoteAudioSourceProviderProxy mid-use in the more-privileged GPU process; escalation to a stronger primitive requires reclaiming the freed slot during the audio-thread window, which the diff does not confirm, so the reliably-reachable outcome is a GPU-process crash across the sandbox boundary.

A well-behaved WebContent process sends CreateAudioSourceProvider at most once per RemoteMediaPlayerProxy. A second message reaches AudioSourceProviderAVFObjC's setConfigureAudioStorageCallback / setAudioCallback, which overwrite the callbacks without taking tapStorage->lock while a MediaToolbox thread may be invoking them under that lock, freeing the captured RemoteAudioSourceProviderProxy mid-use. The fix rejects the duplicate message with a MESSAGE_CHECK.

Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.cpp

+#define MESSAGE_CHECK(assertion, message) MESSAGE_CHECK_WITH_MESSAGE_BASE(assertion, m_webProcessConnection.get(), message)
...
void RemoteMediaPlayerProxy::createAudioSourceProvider()
{
#if ENABLE(WEB_AUDIO) && PLATFORM(COCOA)
+ MESSAGE_CHECK(!m_remoteAudioSourceProvider, "RemoteAudioSourceProvider already created.");
+
RefPtr player = m_player;
if (!player)
return;
...
+#undef MESSAGE_CHECK

LayoutTests/ipc/create-audio-source-provider-twice.html

+ for (let i = 0; i < 5; i++) {
+ CoreIPC.GPU.RemoteMediaPlayerProxy.SetShouldEnableAudioSourceProvider(playerId, { shouldEnable: true });
+ CoreIPC.GPU.RemoteMediaPlayerProxy.CreateAudioSourceProvider(playerId, {});
+ await new Promise(resolve => setTimeout(resolve, 20));
+ }

A MESSAGE_CHECK(!m_remoteAudioSourceProvider, "RemoteAudioSourceProvider already created.") is added at the top of RemoteMediaPlayerProxy::createAudioSourceProvider(), rejecting the IPC message and tearing down the misbehaving connection (via MESSAGE_CHECK_WITH_MESSAGE_BASE against m_webProcessConnection) if a provider was already created for this player. A #define/#undef MESSAGE_CHECK pair binds the macro to this file's web-process connection. The rest is a regression test that drives CreateAudioSourceProvider twice through the IPC Testing API.

Missing idempotency guard on an IPC handler lets a duplicate message overwrite a live, cross-thread-referenced callback object without synchronization, freeing it mid-use.

The GPU process hosts RemoteMediaPlayerProxy objects, one per media element, receiving IPC messages such as CreateAudioSourceProvider and SetShouldEnableAudioSourceProvider from WebContent. AudioSourceProviderAVFObjC is the Cocoa implementation that bridges an AVFoundation media stream into Web Audio; it installs callbacks (setConfigureAudioStorageCallback, setAudioCallback) later invoked from a MediaToolbox real-time audio thread (the "tap" callback) to pull audio samples. tapStorage->lock serializes access to that tap state between the main thread and the audio thread. MESSAGE_CHECK/MESSAGE_CHECK_WITH_MESSAGE_BASE is WebKit's IPC-hardening macro: when the assertion fails, it treats the message as malicious and tears down the offending connection. m_remoteAudioSourceProvider is the member holding the created provider, used here as the has-been-created sentinel. IPCTestingAPIEnabled exposes CoreIPC to test JavaScript so a test can send raw IPC directly, bypassing the WebContent bindings that would only send the message once.

This is a use-after-free driven by a duplicate-message / missing-idempotency-guard combined with a cross-thread lifetime and locking gap. Before the fix, createAudioSourceProvider() assumed a well-behaved WebContent process would send CreateAudioSourceProvider at most once per proxy but did not enforce that. On a second message it re-runs the provider-setup path, reaching AudioSourceProviderAVFObjC::setConfigureAudioStorageCallback / setAudioCallback and overwriting the previously installed callback closures. Per the commit message, that overwrite happens without holding tapStorage->lock, while a MediaToolbox audio thread may concurrently be invoking those same callbacks under that lock. The overwrite releases the previously captured RemoteAudioSourceProviderProxy, dropping its last reference, while the audio thread still holds and dereferences it.

The exploit direction, modeled by the test: send CreateAudioSourceProvider for an active player, let the MediaToolbox tap thread begin invoking the installed callbacks under tapStorage->lock, then send a second CreateAudioSourceProvider. The second message overwrites the callbacks without the lock, dropping the last reference to the captured proxy while the audio thread still dereferences it. If an attacker could reclaim the freed RemoteAudioSourceProviderProxy allocation from the racing thread's window under controlled heap conditions, the in-flight callback dereference could operate on attacker-shaped memory, which could yield a type-confusion or control-flow primitive in the GPU process. At minimum it is a reliable GPU-process crash reachable from WebContent.

This vulnerability weakens the cross-process trust boundary between the WebContent sandbox and the GPU process. The security model assumes GPU-process IPC handlers are robust against malformed or out-of-sequence messages from a potentially compromised WebContent process; this handler instead trusted the sender to call it at most once. Because the vulnerable code runs in the more-privileged GPU process and triggering it only requires the ability to send IPC as WebContent, it functions as a WebContent-to-GPU sandbox-crossing bug.

The fix chooses the cheap, robust option, rejecting the duplicate outright rather than making re-installation thread-safe. Any IPC entry point that installs or replaces callbacks captured by another thread is a candidate for the same pattern.

Note: The overwrite mechanism, the absence of tapStorage->lock during that overwrite, and the mid-dereference free on the audio thread are attributed to the commit message and cannot be confirmed from the diff, which shows only the added guard and the test. The idempotency gap the guard closes is directly visible.