[CoreIPC] [GPUP] off-main-thread ~RemoteAudioMediaStreamTrackRendererInternalUnitManagerUnit mutates unlocked AudioSession interruption-observer WeakHashSet
LayoutTests/fast/mediastream/delete-audio-unit.html
Source/WebCore/platform/mediastream/cocoa/AudioMediaStreamTrackRendererUnit.cpp
WebKit's GPU process hosts RemoteAudioMediaStreamTrackRendererInternalUnitManager, which manages audio rendering units on behalf of the WebProcess. Each unit runs an audio rendering callback on a real-time rendering thread while lifecycle operations (construction, destruction, observer registration) are expected to occur only on the main thread. AudioSession tracks interruption observers in a WeakHashSet — a non-thread-safe structure. This commit fixes a thread-safety race where deleting a running audio unit via IPC could cause its destructor to execute on the rendering thread, triggering an unguarded mutation of that WeakHashSet. The IPC-triggered deletion path did not guarantee which thread would drop the last reference, so if the rendering thread was active the destructor (and its AudioSession::removeObserver() call) could fire there, racing concurrent main-thread access to the same set. The fix ensures the unit is stopped before being released, preventing the rendering thread from holding the last reference; a new internals API deleteAudioUnit() reproduces the race in tests.
BEFORE (racy):
WebProcess GPUProcess main thread Rendering thread
│ │ │
├──IPC: deleteUnit────►│ │ ← unit still running
│ remove unit from map │
│ (main drops its ref) │
│ unit dtor fires here
│ ──► AudioSession::removeObserver()
│ [unlocked WeakHashSet write]
AFTER (fixed):
WebProcess GPUProcess main thread Rendering thread
│ │ │
├──IPC: deleteUnit────►│ │
│ unit->stop() │
│ (rendering thread releases ref) │
│ remove unit from map │
│ (last ref drops on main thread) │
│ unit dtor fires on main thread │
│ ──► AudioSession::removeObserver() [safe]│
Significance
This is a web-reachable race condition in the GPU process: an attacker-controlled MediaStream can trigger the vulnerable code path via IPC timing, potentially corrupting the AudioSession observer map from the wrong thread.
Audit directions
The repair relies on ref-counting discipline: stopping the unit must cause the rendering thread to release its reference before the IPC-side release fires. Investigate whether stop() is guaranteed synchronous with respect to the rendering thread's ref-drop, or whether a window remains. The same ownership pattern applies to other observer registrations and IPC-triggered lifecycle changes in the audio stack — auditing AudioSession observer registration/deregistration callsites for similar off-thread destructor paths is valuable. The new deleteAudioUnit() internals hook also makes this race more reliably reproducible for fuzzing.