CVE-2024-9399
Overview
Background
- WebTransport
- a network protocol, exposed to web content, that lets a site open a session for bidirectional streams and unreliable
datagrammessages over HTTP/3. - `WebTransportSessionProxy`
- the networking-layer object that proxies WebTransport events (such as received datagrams) between the socket thread and the main-thread listener that consumes them.
- `mPendingEvents`
- a queue of deferred callback closures on
WebTransportSessionProxythat holds events until the session is fully ready to deliver them. - `mStopRequestCalled`
- a boolean flag on
WebTransportSessionProxyindicating that request startup has completed and it is safe to dispatch events directly rather than queue them.
Root Cause Analysis
Incoming datagrams enter through WebTransportSessionProxy::OnDatagramReceivedInternal, which is responsible for marshaling the data onto the correct thread before WebTransportSessionProxy::NotifyDatagramReceived finally delivers it to mListener. In the vulnerable code, the “not yet ready” guard that checks !mStopRequestCalled and defers work onto mPendingEvents lived inside NotifyDatagramReceived, which runs only after the thread-dispatch and state validation performed by OnDatagramReceivedInternal. This violated the invariant that datagram processing must not touch session state, mTarget, or mListener before startup has completed, so a datagram arriving during the early, not-yet-active window was routed through the dispatch and state logic prematurely and could crash the process.
The fix moves the !mStopRequestCalled deferral check to the very top of OnDatagramReceivedInternal, so an early datagram is captured into mPendingEvents and replayed later, and it deletes the now-redundant guard from NotifyDatagramReceived. Because the deferral now happens before any thread dispatch or state inspection, the unsafe early code path is never reached.
mStopRequestCalled readiness guard too late in the pipeline — inside NotifyDatagramReceived rather than at the entry point OnDatagramReceivedInternal — so early datagrams bypassed deferral; the fix relocates that guard to the entry point so premature events are queued before any state-dependent processing runs.Attack Path
- Establish a session
A malicious website configures and initiates a WebTransport session against the victim browser, causing a
WebTransportSessionProxyto be created. - Race the readiness window
The site crafts the session so that datagrams arrive during the interval before
mStopRequestCalledis set, i.e. before the proxy is fully active. - Enter the unguarded path
The early datagram is handed to
OnDatagramReceivedInternal, which (in the vulnerable code) performs thread dispatch and reaches state logic without first checking readiness. - Trigger the fault
Processing the datagram against a not-yet-active
mState,mTarget, ormListenerleads to an invalid operation that crashes the networking process, producing a denial of service.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifnetwerk/protocol/webtransport/WebTransportSessionProxy.cpp |
modified |
Files Changed
netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
Audit Directions
- Guard placement in event pipelinesVerify that readiness or lifecycle guards sit at the true entry point of each event, not after thread dispatch or state validation that assumes the object is already active.
- Deferral consistency across entry pointsAudit other
WebTransportSessionProxyand networking proxy methods that enqueue ontomPendingEventsto ensure every path capable of arriving beforemStopRequestCalleddefers work uniformly. - Pre-activation race windowsLook for handlers that touch
mState,mTarget, ormListenerwhere remote-controlled input can arrive before initialization completes, since such windows invite denial-of-service crashes.
Patch
diff --git a/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
index dd01cc6ff8d..e0a80c25ba1 100644
--- a/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
+++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
@@ -1078,15 +1078,6 @@ void WebTransportSessionProxy::NotifyDatagramReceived(
MutexAutoLock lock(mMutex);
MOZ_ASSERT(mTarget->IsOnCurrentThread());
- if (!mStopRequestCalled) {
- CopyableTArray<uint8_t> copied(aData);
- mPendingEvents.AppendElement(
- [self = RefPtr{this}, data = std::move(copied)]() mutable {
- self->NotifyDatagramReceived(std::move(data));
- });
- return;
- }
-
if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
return;
}
@@ -1102,6 +1093,15 @@ NS_IMETHODIMP WebTransportSessionProxy::OnDatagramReceivedInternal(
{
MutexAutoLock lock(mMutex);
+ if (!mStopRequestCalled) {
+ CopyableTArray<uint8_t> copied(aData);
+ mPendingEvents.AppendElement(
+ [self = RefPtr{this}, data = std::move(copied)]() mutable {
+ self->OnDatagramReceivedInternal(std::move(data));
+ });
+ return NS_OK;
+ }
+
if (!mTarget->IsOnCurrentThread()) {
return mTarget->Dispatch(NS_NewRunnableFunction(
"WebTransportSessionProxy::OnDatagramReceived",