← WebKit — 2026-W37

d147fe73cf325652651f542a234f60897daf0480  Hold AudioHardwareListener client weakly and stop caching listener proxies across clients

severity medium class UAF confidence 0.62 signal score 18 Source/WebCore/platform patch-now
Primitive: Use-after-free: AudioHardwareListener held its Client by raw reference and called into it after the client was destroyed
Triage note: AudioHardwareListener::Client becomes AbstractRefCountedAndCanMakeWeakPtr and m_client changes from Client& to WeakPtr<Client>, with every callback (audioHardwareDidBecomeActive/Inactive/outputDeviceChanged across Mac/Remote proxies) guarded by RefPtr client = this->client(). RemoteAudioHardwareListenerProxy is made RefCounted and the proxy held via ThreadSafeWeakPtr. This closes a lifetime/UAF where the listener outlived or raced its client across the GPU/media boundary.
Structural signals: smartptr_member_added (+6.47), null_guard (+1.95), uaf_lifetime (+3.57), msg_strong (+3.0), msg_medium (+2.0), msg_soft (+1.0)

Commit message

Hold AudioHardwareListener client weakly and stop caching listener proxies across clients
rdar://177436036

Reviewed by Jean-Yves Avenard.

AudioHardwareListener stored its Client as a raw reference, while
RemoteMediaSessionManagerProxy::ensureAudioHardwareListenerProxy()
cached the first listener it created and reused it (with the original
client) for every subsequent caller, in addition to holding it
strongly via m_audioHardwareListenerProxy. Because each
RemoteMediaSessionManagerProxy also overwrites the process-global
AudioHardwareListener factory with a lambda capturing Ref{*this}, two
WebPageProxy instances could end up with B's cached listener bound to
A; closing A's page then made B's RemoteAudioOutputDeviceChanged
dispatch a virtual call on a freed proxy (UI-process heap-use-after-
free). Within a single page, removing the last media session cleared
MediaSessionManagerCocoa::m_audioHardwareListener but left the
strongly-held m_audioHardwareListenerProxy, so the same IPC message
null-dereferenced m_audioHardwareListener in audioOutputDeviceChanged.

Make AudioHardwareListener::Client an AbstractRefCountedAndCanMakeWeakPtr
(matching NowPlayingManagerClient), store m_client as a WeakPtr, and
upgrade to a protecting RefPtr before dispatching in every listener
subclass. In RemoteMediaSessionManagerProxy, capture *this weakly in
the creation lambda (also removing a leak of the last-constructed
proxy), always create a fresh listener per call, only stash a
ThreadSafeWeakPtr to it when the client is *this*, and dispatch IPC
through that weak pointer so the listener's lifetime is governed
solely by MediaSessionManagerCocoa::m_audioHardwareListener. Also
guard MediaSessionManagerCocoa::audioOutputDeviceChanged() against a
null m_audioHardwareListener for defense in depth, and make
RemoteAudioHardwareListenerProxy ref-counted to satisfy the new Client
contract.

Tests: ipc/remote-media-session-manager-audio-hardware-listener-crash.html
       ipc/remote-media-session-manager-audio-hardware-listener-uaf.html

* LayoutTests/ipc/remote-media-session-manager-audio-hardware-listener-crash-expected.txt: Added.
* LayoutTests/ipc/remote-media-session-manager-audio-hardware-listener-crash.html: Added.
* LayoutTests/ipc/remote-media-session-manager-audio-hardware-listener-uaf-expected.txt: Added.
* LayoutTests/ipc/remote-media-session-manager-audio-hardware-listener-uaf.html: Added.
* Source/WebCore/platform/audio/AudioHardwareListener.h:
(WebCore::AudioHardwareListener::client const):
* Source/WebCore/platform/audio/cocoa/MediaSessionManagerCocoa.mm:
(WebCore::MediaSessionManagerCocoa::audioOutputDeviceChanged):
* Source/WebCore/platform/audio/mac/AudioHardwareListenerMac.cpp:
(WebCore::AudioHardwareListenerMac::processIsRunningChanged):
(WebCore::AudioHardwareListenerMac::outputDeviceChanged):
* Source/WebKit/GPUProcess/GPUConnectionToWebProcess.cpp:
(WebKit::GPUConnectionToWebProcess::createAudioHardwareListener):
* Source/WebKit/GPUProcess/GPUConnectionToWebProcess.h:
* Source/WebKit/GPUProcess/media/RemoteAudioHardwareListenerProxy.h:
* Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.cpp:
(WebKit::RemoteMediaSessionManagerProxy::RemoteMediaSessionManagerProxy):
(WebKit::RemoteMediaSessionManagerProxy::remoteAudioHardwareDidBecomeActive):
(WebKit::RemoteMediaSessionManagerProxy::remoteAudioHardwareDidBecomeInactive):
(WebKit::RemoteMediaSessionManagerProxy::remoteAudioOutputDeviceChanged):
(WebKit::RemoteMediaSessionManagerProxy::ensureAudioHardwareListenerProxy):
* Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.h:
* Source/WebKit/WebProcess/GPU/media/RemoteAudioHardwareListener.cpp:
(WebKit::RemoteAudioHardwareListener::audioHardwareDidBecomeActive):
(WebKit::RemoteAudioHardwareListener::audioHardwareDidBecomeInactive):
(WebKit::RemoteAudioHardwareListener::audioOutputDeviceChanged):

Originally-landed-as: [email protected] (ddf732bdf8b3). rdar://185368109
Canonical link: https://commits.webkit.org/320953@main

Before / after

Loading diff…