← All issues

[9] RealtimeIncoming{Audio,Video}Source removes sink after derived members destroyed

Severity: Medium | Component: WebCore WebRTC media pipeline | 02e76c6

Rated Medium because the diff fixes a race-window UAF where libwebrtc media-thread callbacks can dereference destroyed derived state; reaching the window from web content requires driving RTCPeerConnection teardown while a preventSourceFromEnding observer blocks the normal requestToEnd path.

Crashes occurred when WebRTC audio/video callbacks accessed destroyed member variables during object destruction. The compiler-generated destructors in derived classes (e.g. RealtimeIncomingAudioSourceCocoa) destroy derived members before calling the base class destructor, but the base class destructor's stop() call is what removes the audio/video track sink. The fix ensures derived destructors call stop() to remove sinks before any member destruction occurs.

Source/WebCore/platform/mediastream/RealtimeIncomingAudioSource.cpp

RealtimeIncomingAudioSource::~RealtimeIncomingAudioSource()
{
- stop();
+ // Subclasses must call stop() in their destructors to ensure the audio
+ // track sink is removed BEFORE derived members are destroyed. Otherwise,
+ // the OnData callback may access destroyed members on the audio thread.
+ ASSERT(!isProducingData());
m_audioTrack->UnregisterObserver(this);
}

Source/WebCore/platform/mediastream/cocoa/RealtimeIncomingAudioSourceCocoa.cpp

+RealtimeIncomingAudioSourceCocoa::~RealtimeIncomingAudioSourceCocoa()
+{
+ stop();
+}

Source/WebCore/platform/mediastream/cocoa/RealtimeIncomingVideoSourceCocoa.mm

+RealtimeIncomingVideoSourceCocoa::~RealtimeIncomingVideoSourceCocoa()
+{
+ stop();
+}

Explicit destructors are added to all four concrete subclasses (RealtimeIncomingAudioSourceCocoa, RealtimeIncomingVideoSourceCocoa, RealtimeIncomingAudioSourceLibWebRTC, RealtimeIncomingVideoSourceLibWebRTC). Each calls stop() to remove the WebRTC audio/video track sink BEFORE C++ proceeds to destroy any derived members. The base destructors no longer call stop(); they retain only m_audioTrack->UnregisterObserver(this) / m_videoTrack->UnregisterObserver(this) and now ASSERT(!isProducingData()) to document and verify the new contract.

Use-after-free across C++ destruction order: a base-class destructor unregistered a still-active multi-threaded callback after the derived members it accesses had already been destroyed.

When a remote peer sends audio or video via RTCPeerConnection, libwebrtc surfaces each remote track as a webrtc::AudioTrackInterface/webrtc::VideoTrackInterface. WebCore wraps each track in a RealtimeIncoming*Source that implements libwebrtc's sink interfaces (AudioTrackSinkInterface::OnData, VideoSinkInterface::OnFrame). The WebKit object registers itself as a sink via AddSink/AddOrUpdateSink from startProducingData() and must remove itself via RemoveSink from stopProducingData() before it dies. libwebrtc invokes OnData/OnFrame on its own media threads, not on the WebCore main thread. RemoveSink inside libwebrtc takes a sink_lock_ that serialises against the callback dispatch.

For a derived class D : B, ~D runs first, then D's member destructors, then ~B — so any operation that must complete while derived state is alive has to happen inside ~D. RTCPeerConnection::doClose() is the normal teardown path and calls requestToEnd() on its sources, but a RealtimeMediaSourceObserver whose preventSourceFromEnding() returns true can block requestToEnd(), so the source can reach destruction with isProducingData() still true.

When a RealtimeIncomingAudioSourceCocoa is destroyed, the compiler first runs the derived destructor, then destroys derived data members (m_audioBufferList, the pixel buffer pool), and only then invokes the base destructor. The base destructor was the one calling stop(), which removes the WebRTC sink. That means a media-thread OnData or OnFrame callback could already be running, or could be entered, while the WebRTC track still held a pointer to the partially-destroyed subclass; the callback then dereferences members like m_audioBufferList that the C++ runtime has already torn down.

🔒

Detailed C++ destruction-order and cross-thread-callback lifetime analysis, including how a normal teardown path can be subverted and what primitives the resulting race could yield

Subscribe to read more

🔒

Four reusable audit patterns identified for cross-thread callback lifetime bugs across the WebRTC media pipeline and related WebCore subsystems, with concrete grep targets

Subscribe to read more