CVE-2026-12291
Overview
Background
- `nsHttpChannel`
- Gecko’s concrete HTTP channel implementation in the Networking: HTTP component that drives a request/response lifecycle and dispatches events to its listener.
- `nsIStreamListener` / `OnStopRequest`
- the listener callback interface whose
OnStopRequestmethod signals a channel that data delivery has finished. - `mListener`
- the
nsHttpChannelmember holding a reference to the stream listener that receivesOnStartRequest/OnDataAvailable/OnStopRequestnotifications. - `nsCOMPtr`
- a smart pointer that owns a strong (refcounted) reference to an XPCOM object, keeping it alive for the pointer’s scope.
Root Cause Analysis
In nsHttpChannel::ContinueOnStopRequest, the terminal notification was dispatched by dereferencing the raw member pointer directly as mListener->OnStopRequest(this, aStatus). Because OnStopRequest runs arbitrary listener code, that callback (or reentrancy it triggers, such as tearing down or reassigning the channel’s listener chain) can drop the last strong reference to the object that mListener points at, freeing it while the virtual call is still in progress. The invariant violated is that a callee must remain alive for the full duration of a call made through a pointer to it; here no local strong reference guarded the listener across the OnStopRequest invocation.
The fix copies mListener into a local nsCOMPtr<nsIStreamListener> listener before invoking the method, so the listener holds an owning reference on the stack that outlives the call regardless of what the callback does to mListener. This closes the use-after-free window without changing the notification semantics.
nsCOMPtr strong reference for the duration of the call.Attack Path
- Initiate a request
Attacker-controlled content starts an HTTP load handled by an
nsHttpChannelwhose listener chain includes code the attacker can influence. - Reach request completion
Drive the channel to the stop phase so
ContinueOnStopRequestfiresmListener->OnStopRequest. - Drop the listener during the callback
From within
OnStopRequest, cause the last strong reference to the target listener to be released (for example by tearing down or reassigningmListener), freeing it mid-call. - Use the freed object The in-flight virtual dispatch and any continued use operate on freed memory, yielding a use-after-free.
Impact Assessment
OnStopRequest callback.Files Changed
netwerk/protocol/http/nsHttpChannel.cpp
Audit Directions
- Raw member-pointer callbacksFlag any
mSomething->Method(...)whereMethodruns external or reentrant code and no localnsCOMPtr/strong reference guards the callee for the call’s duration. - Listener lifecycle around `OnStopRequest`/`OnStartRequest`Review channel and stream code paths where completion callbacks can reassign or release
mListener, ensuring a stack-owned reference wraps each invocation. - Reentrancy during teardownAudit stop/cancel/finalize paths that dispatch to observers or listeners, checking that objects are refcount-pinned before any callback that could free them.
Patch
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
index 6a0f61ad92c..337b24d2576 100644
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -10472,7 +10472,8 @@ nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet,
MOZ_ASSERT(!LoadOnStopRequestCalled(),
"We should not call OnStopRequest twice");
StoreOnStopRequestCalled(true);
- mListener->OnStopRequest(this, aStatus);
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ listener->OnStopRequest(this, aStatus);
}
StoreOnStopRequestCalled(true);