CVE-2025-6432
Overview
Background
- Speculative connect
- An optimization that opens a network/DNS connection early for a URL the user is likely to visit.
nsIOService::SpeculativeConnectInternaldrives it. - `nsILoadContext` / notification callbacks
- A channel’s notification callbacks expose the load context (origin attributes, container/userContextId, private-browsing state, proxy context). Proxy and DNS resolution consult this to apply the right per-container proxy.
- Multi-Account Containers + SOCKS proxy
- A container can be configured with a SOCKS proxy, including proxying DNS. Resolution must run through the container’s proxy context to avoid leaking the hostname via direct DNS.
Root Cause Analysis
RemoteWebNavigation issued the pre-navigation speculative connection via Services.io.speculativeConnect(uri, principal, null, false) — passing null for the notification callbacks. Without callbacks, the resulting channel had no nsILoadContext, so the speculative connect (and its DNS resolution) ran without the browser’s container/proxy context. For a container configured with a SOCKS proxy (with DNS meant to go through the proxy), the speculative DNS lookup could therefore be performed directly rather than via the SOCKS proxy — most observably “when the domain name was invalid or the SOCKS proxy was not responding,” where the stack fell back to a direct, context-less resolution. The result is a DNS request that leaks the target hostname outside the proxy the container intended to use.
The patch supplies the missing context. RemoteWebNavigation now defines a NotificationCallbacks object implementing nsIInterfaceRequestor whose getInterface(nsILoadContext) returns this._browser.browsingContext, and passes new NotificationCallbacks(this._browser) to speculativeConnect instead of null. Correspondingly, nsIOService::SpeculativeConnectInternal now propagates these callbacks to the channel: if (aCallbacks) { rv = channel->SetNotificationCallbacks(aCallbacks); NS_ENSURE_SUCCESS(rv, rv); }. With the load context attached, proxy/DNS resolution for the speculative connect runs under the correct container/proxy context, so the DNS request no longer bypasses the SOCKS proxy. The advisory (CWE-200, information exposure) and the diff agree: the leak stemmed from a context-less speculative connection, and the fix wires the container’s load context through.
null notification callbacks, so it had no nsILoadContext and resolved DNS outside the container’s SOCKS-proxy context; the fix passes a callbacks object exposing the browser’s browsingContext and makes nsIOService set it on the channel, keeping speculative DNS inside the proxy.Attack Path
- Precondition User has Multi-Account Containers enabled with a container configured to use a SOCKS proxy (ideally proxying DNS). (not attacker-controlled)
- Navigate
The user navigates (or hovers/initiates) to a URL in that container, triggering
RemoteWebNavigation’s speculative connect. - Context-less connect
The speculative connect is created with
nullcallbacks, so DNS resolution runs without the container’s proxy/load context. - Direct DNS Particularly when the hostname is invalid or the SOCKS proxy is unresponsive, the DNS lookup goes out directly instead of via the proxy.
- Primitive A passive network observer (or the configured upstream DNS) learns the hostname the user intended to visit, defeating the proxy’s privacy guarantee (confidentiality info-leak, CVSS low).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifnetwerk/base/nsIOService.cpp |
modified | |
getInterfacetoolkit/components/remotebrowserutils/RemoteWebNavigation.sys.mjs |
modified | |
constructortoolkit/components/remotebrowserutils/RemoteWebNavigation.sys.mjs |
modified |
Files Changed
netwerk/base/nsIOService.cpptoolkit/components/remotebrowserutils/RemoteWebNavigation.sys.mjs
Audit Directions
- Other `speculativeConnect(... null ...)` callersSearch for speculative/preconnect/prefetch calls that pass
nullcallbacks and thus lose container/proxy context. - Context propagation in preconnect/DNS prefetchAudit
nsIOServicepreconnect and DNS-prefetch paths to confirm load context flows to proxy resolution in all of them. - Proxy-failure fallbackReview DNS/proxy fallback behavior when a SOCKS proxy is unreachable or the hostname is invalid, for other places a direct lookup can escape the proxy.
- Container origin attributes on early connectionsCheck that userContextId/private-browsing attributes reach all speculative loads, not just full navigations.
Patch
diff --git a/netwerk/base/nsIOService.cpp b/netwerk/base/nsIOService.cpp
index e957628acc2..ab96edf9e16 100644
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -2190,6 +2190,10 @@ nsresult nsIOService::SpeculativeConnectInternal(
loadFlags |= nsIRequest::LOAD_ANONYMOUS;
channel->SetLoadFlags(loadFlags);
}
+ if (aCallbacks) {
+ rv = channel->SetNotificationCallbacks(aCallbacks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
nsCOMPtr<nsICancelable> cancelable;
RefPtr<IOServiceProxyCallback> callback = new IOServiceProxyCallback(
diff --git a/toolkit/components/remotebrowserutils/RemoteWebNavigation.sys.mjs b/toolkit/components/remotebrowserutils/RemoteWebNavigation.sys.mjs
index 0745607f549..bef43e68d4c 100644
--- a/toolkit/components/remotebrowserutils/RemoteWebNavigation.sys.mjs
+++ b/toolkit/components/remotebrowserutils/RemoteWebNavigation.sys.mjs
@@ -8,6 +8,18 @@ ChromeUtils.defineESModuleGetters(lazy, {
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});
+function NotificationCallbacks(browser) {
+ this._browser = browser;
+}
+NotificationCallbacks.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
+ getInterface(iid) {
+ if (iid.equals(Ci.nsILoadContext)) {
+ return this._browser.browsingContext;
+ }
+ },
+};
+
// This object implements the JS parts of nsIWebNavigation.
export class RemoteWebNavigation {
constructor(aBrowser) {
@@ -109,7 +121,12 @@ export class RemoteWebNavigation {
attrs
);
}
- Services.io.speculativeConnect(uri, principal, null, false);
+ Services.io.speculativeConnect(
+ uri,
+ principal,
+ new NotificationCallbacks(this._browser),
+ false
+ );
}
} catch (ex) {
// Can't setup speculative connection for this uri for some