Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMemory safety bug fixed in Firefox 152
ComponentDOM
Bug ClassLogic Error
Tracker2039707
Fix commit4e5cf3aeaceb (firefox) +65/-25
CISA KEVNot listed
CreditedCarl Pearson
Disclosed2026-06-16

Background

HTTPS-Only mode
a Firefox feature whose TestHTTPAnswerRunnable fires a background probe over plain HTTP to decide whether a silently-upgraded HTTPS load can safely fall back.
HTTPS RR / SVCB lookup
a DNS query (FetchHTTPSSVC for nsIDNSHTTPSSVCRecord) that resolves a host’s HTTPS resource record, implying a local DNS resolution of the origin host name.
`ProxyDNSStrategy`
an enum (PROXY_DNS_STRATEGY_ORIGIN vs PROXY_DNS_STRATEGY_PROXY) indicating whether the client resolves the origin locally or delegates name resolution to a proxy.
`do_QueryInterface`
an XPCOM cast that returns a null nsCOMPtr when the target object does not implement the requested interface, requiring a null check before use.

Root Cause Analysis

In TestHTTPAnswerRunnable::Run the HTTPS-Only probe unconditionally issued an HTTPS RR (FetchHTTPSSVC) lookup, which performs a local DNS resolution of the origin host name. When the channel is configured to use a proxy that itself performs name resolution (SOCKS-with-remote-DNS / transparent-proxy-resolves-host), the invariant that the origin host name never be resolved locally is violated, leaking the host to the local resolver despite the proxy. Compounding this, the code dereferenced internalChannel (obtained via do_QueryInterface) in internalChannel->GetCaps(&caps) without first checking it for null, a potential null-pointer dereference.

The fix exposes the channel’s proxy DNS strategy through the new nsIHttpChannelInternal::GetProxyDNSStrategy accessor, and skips FetchHTTPSSVC whenever the strategy is PROXY_DNS_STRATEGY_PROXY. It also adds an explicit internalChannel && guard so the pointer is verified before dereference. Together these ensure the probe respects proxy DNS delegation and no longer touches a null channel pointer.

Key insight
The single mistake was that the HTTPS-Only background probe performed a local origin DNS lookup without consulting the channel’s proxy DNS strategy (and without null-checking the queried interface); the fix threads the ProxyDNSStrategy up to the DOM layer and gates the lookup so it is skipped when the proxy owns name resolution.

Attack Path

  1. Configure proxy DNS delegation The user (or environment) sets a proxy, such as SOCKS with remote DNS, so the origin host name is meant to be resolved only by the proxy.
  2. Trigger HTTPS-Only probe Navigating to an upgraded resource causes HTTPS-Only mode to schedule TestHTTPAnswerRunnable::Run for the background HTTP fallback test.
  3. Unconditional HTTPS RR lookup The runnable calls FetchHTTPSSVC regardless of the proxy configuration, issuing a local DNS query for the origin host name.
  4. Host name leaks locally The local resolver observes the origin host name, defeating the proxy’s DNS privacy guarantee.

Impact Assessment

An observer of the client’s local DNS traffic learns the origin host names of proxied HTTPS-Only navigations, undermining the proxy’s confidentiality even though traffic was intended to be resolved remotely. The leak occurs in the content/network path within the browser process handling HTTPS-Only probes; the precondition is a proxy configured to perform its own name resolution while HTTPS-Only mode is active. The unchecked internalChannel dereference additionally risked a null-pointer crash in the same path.

Changed Functions

FunctionChangeNotes
if
dom/security/nsHTTPSOnlyUtils.cpp
modified
GetIsTRRServiceChannel
netwerk/protocol/http/HttpBaseChannel.cpp
modified
if
netwerk/protocol/http/nsHttp.cpp
modified
nsICacheEntry
netwerk/protocol/http/nsHttp.h
modified
if
netwerk/protocol/http/nsHttpChannel.cpp
modified
ComputeProxyDNSStrategy
netwerk/protocol/http/nsHttpChannel.cpp
modified

Files Changed

  • dom/security/nsHTTPSOnlyUtils.cpp
  • netwerk/protocol/http/HttpBaseChannel.cpp
  • netwerk/protocol/http/HttpBaseChannel.h
  • netwerk/protocol/http/nsHttp.cpp
  • netwerk/protocol/http/nsHttp.h
  • netwerk/protocol/http/nsHttpChannel.cpp
  • netwerk/protocol/http/nsHttpChannel.h
  • netwerk/protocol/http/nsHttpConnectionMgr.cpp
  • netwerk/protocol/http/nsIHttpChannelInternal.idl

Audit Directions

  • Proxy-oblivious DNS lookups
    Audit every FetchHTTPSSVC / DNS-prefetch / HTTPS RR call site to confirm it consults ProxyDNSStrategy and skips local resolution under PROXY_DNS_STRATEGY_PROXY.
  • Unchecked `do_QueryInterface` results
    Flag any dereference of an nsCOMPtr from do_QueryInterface (e.g. internalChannel->GetCaps) that is not guarded by a null check.
  • Privacy-sensitive probes
    Review background/side-channel network probes such as HTTPS-Only fallback tests for host-name or metadata leakage that bypasses configured proxy or TRR routing.
diff --git a/dom/security/nsHTTPSOnlyUtils.cpp b/dom/security/nsHTTPSOnlyUtils.cpp
index 5c0fe170e7f..a8776d807a0 100644
--- a/dom/security/nsHTTPSOnlyUtils.cpp
+++ b/dom/security/nsHTTPSOnlyUtils.cpp
@@ -1152,8 +1152,16 @@ TestHTTPAnswerRunnable::Run() {
         new nsDNSPrefetch(mURI, originAttributes, origChannel->GetTRRMode());
     nsCOMPtr<nsIHttpChannelInternal> internalChannel =
         do_QueryInterface(origChannel);
+    // If the channel will be proxied and the proxy is responsible for DNS
+    // resolution, skip the HTTPS RR lookup to avoid leaking the host name
+    nsIHttpChannelInternal::ProxyDNSStrategy dnsStrategy =
+        nsIHttpChannelInternal::PROXY_DNS_STRATEGY_ORIGIN;
+    if (internalChannel) {
+      (void)internalChannel->GetProxyDNSStrategy(&dnsStrategy);
+    }
     uint32_t caps;
-    if (NS_SUCCEEDED(internalChannel->GetCaps(&caps))) {
+    if (dnsStrategy != nsIHttpChannelInternal::PROXY_DNS_STRATEGY_PROXY &&
+        internalChannel && NS_SUCCEEDED(internalChannel->GetCaps(&caps))) {
       (void)resolver->FetchHTTPSSVC(
           caps & NS_HTTP_REFRESH_DNS, false,
           [self = RefPtr{this}](nsIDNSHTTPSSVCRecord* aRecord) {
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
index 29fd6bd1af2..6b25f26ba53 100644
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -4075,6 +4075,14 @@ HttpBaseChannel::SetBypassProxy(bool aBypassProxy) {
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HttpBaseChannel::GetProxyDNSStrategy(
+    nsIHttpChannelInternal::ProxyDNSStrategy* aStrategy) {
+  NS_ENSURE_ARG_POINTER(aStrategy);
+  *aStrategy = nsIHttpChannelInternal::PROXY_DNS_STRATEGY_ORIGIN;
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 HttpBaseChannel::GetIsTRRServiceChannel(bool* aIsTRRServiceChannel) {
   NS_ENSURE_ARG_POINTER(aIsTRRServiceChannel);
diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h
index 57d06397bf7..641955664ca 100644
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -274,6 +274,8 @@ class HttpBaseChannel : public nsHashPropertyBag,
   NS_IMETHOD GetBypassProxy(bool* aBypassProxy) override;
   NS_IMETHOD SetBypassProxy(bool aBypassProxy) override;
   bool BypassProxy();
+  NS_IMETHOD GetProxyDNSStrategy(
+      nsIHttpChannelInternal::ProxyDNSStrategy* aStrategy) override;
 
   NS_IMETHOD GetIsTRRServiceChannel(bool* aTRR) override;
   NS_IMETHOD SetIsTRRServiceChannel(bool aTRR) override;
diff --git a/netwerk/protocol/http/nsHttp.cpp b/netwerk/protocol/http/nsHttp.cpp
index 2b295ebcfec..689ae111b9a 100644
--- a/netwerk/protocol/http/nsHttp.cpp
+++ b/netwerk/protocol/http/nsHttp.cpp
@@ -1164,18 +1164,19 @@ nsLiteralCString HttpVersionToTelemetryLabel(HttpVersion version) {
   return "unknown"_ns;
 }
 
-ProxyDNSStrategy GetProxyDNSStrategyHelper(const char* aType, uint32_t aFlag) {
+nsIHttpChannelInternal::ProxyDNSStrategy GetProxyDNSStrategyHelper(
+    const char* aType, uint32_t aFlag) {
   if (!aType) {
-    return ProxyDNSStrategy::ORIGIN;
+    return nsIHttpChannelInternal::PROXY_DNS_STRATEGY_ORIGIN;
   }
 
   if (!(aFlag & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST)) {
     if (aType == kProxyType_SOCKS) {
-      return ProxyDNSStrategy::ORIGIN;
+      return nsIHttpChannelInternal::PROXY_DNS_STRATEGY_ORIGIN;
     }
   }
 
-  return ProxyDNSStrategy::PROXY;
+  return nsIHttpChannelInternal::PROXY_DNS_STRATEGY_PROXY;
 }
 
 }  // namespace net
diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h
index 3ceedc566bc..3db762e768e 100644
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -15,6 +15,7 @@
 
 #include "mozilla/UniquePtr.h"
 #include "NSSErrorsService.h"
+#include "nsIHttpChannelInternal.h"
 
 class nsICacheEntry;
 
@@ -526,15 +527,8 @@ void DisallowHTTPSRR(uint32_t& aCaps);
 
 nsLiteralCString HttpVersionToTelemetryLabel(HttpVersion version);
 
-enum class ProxyDNSStrategy : uint8_t {
-  // To resolve the origin of the end server we are connecting
-  // to.
-  ORIGIN = 1 << 0,
-  // To resolve the host name of the proxy.
-  PROXY = 1 << 1
-};
-
-ProxyDNSStrategy GetProxyDNSStrategyHelper(const char* aType, uint32_t aFlag);
+nsIHttpChannelInternal::ProxyDNSStrategy GetProxyDNSStrategyHelper(
+    const char* aType, uint32_t aFlag);
 
 }  // namespace net
 }  // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
index 5e9cf58a4e9..92cd5988765 100644
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1095,8 +1095,8 @@ nsresult nsHttpChannel::MaybeUseHTTPSRRForUpgrade(bool aShouldUpgrade,
       return true;
     }
 
-    auto dnsStrategy = GetProxyDNSStrategy();
-    if (dnsStrategy != ProxyDNSStrategy::ORIGIN) {
+    auto dnsStrategy = ComputeProxyDNSStrategy();
+    if (dnsStrategy != nsIHttpChannelInternal::PROXY_DNS_STRATEGY_ORIGIN) {
       return true;
     }
 
@@ -7799,18 +7799,27 @@ nsHttpChannel::GetOrCreateChannelClassifier() {
   return classifier.forget();
 }
 
-ProxyDNSStrategy nsHttpChannel::GetProxyDNSStrategy() {
+nsIHttpChannelInternal::ProxyDNSStrategy
+nsHttpChannel::ComputeProxyDNSStrategy() {
   // When network_dns_force_use_https_rr is true, return DNS_PREFETCH_ORIGIN.
   // This ensures that we always perform HTTPS RR query.
   nsCOMPtr<nsProxyInfo> proxyInfo(static_cast<nsProxyInfo*>(mProxyInfo.get()));
   if (!proxyInfo || StaticPrefs::network_dns_force_use_https_rr()) {
-    return ProxyDNSStrategy::ORIGIN;
+    return nsIHttpChannelInternal::PROXY_DNS_STRATEGY_ORIGIN;
   }
 
   // If the proxy is not to perform name resolution itself.
   return GetProxyDNSStrategyHelper(proxyInfo->Type(), proxyInfo->Flags());
 }
 
+NS_IMETHODIMP
+nsHttpChannel::GetProxyDNSStrategy(
+    nsIHttpChannelInternal::ProxyDNSStrategy* aStrategy) {
+  NS_ENSURE_ARG_POINTER(aStrategy);
+  *aStrategy = ComputeProxyDNSStrategy();
+  return NS_OK;
+}
+
 // BeginConnect() SHOULD NOT call AsyncAbort(). AsyncAbort will be called by
 // functions that called BeginConnect if needed. Only
 // MaybeResolveProxyAndBeginConnect and OnProxyAvailable ever call
@@ -8019,13 +8028,13 @@ nsresult nsHttpChannel::BeginConnect() {
   }
 
   bool trrEnabled = false;
-  auto dnsStrategy = GetProxyDNSStrategy();
+  auto dnsStrategy = ComputeProxyDNSStrategy();
   bool httpsRRAllowed =
       !LoadBeConservative() && !(mCaps & NS_HTTP_BE_CONSERVATIVE) &&
       !(mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
         mLoadInfo->GetExternalContentPolicyType() !=
             ExtContentPolicy::TYPE_DOCUMENT) &&
-      dnsStrategy == ProxyDNSStrategy::ORIGIN &&
+      dnsStrategy == nsIHttpChannelInternal::PROXY_DNS_STRATEGY_ORIGIN &&
       !mConnectionInfo->UsingConnect() && canUseHTTPSRRonNetwork(trrEnabled) &&
       StaticPrefs::network_dns_use_https_rr_as_altsvc();
   if (!httpsRRAllowed) {
@@ -8225,7 +8234,7 @@ void nsHttpChannel::MaybeStartDNSPrefetch() {
     return;
   }
 
-  auto dnsStrategy = GetProxyDNSStrategy();
+  auto dnsStrategy = ComputeProxyDNSStrategy();
 
   LOG(
       ("nsHttpChannel::MaybeStartDNSPrefetch [this=%p, strategy=%u] "
@@ -8233,7 +8242,7 @@ void nsHttpChannel::MaybeStartDNSPrefetch() {
        this, static_cast<uint32_t>(dnsStrategy),
        mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
 
-  if (dnsStrategy == ProxyDNSStrategy::ORIGIN) {
+  if (dnsStrategy == nsIHttpChannelInternal::PROXY_DNS_STRATEGY_ORIGIN) {
     OriginAttributes originAttributes;
     StoragePrincipalHelper::GetOriginAttributesForNetworkState(
         this, originAttributes);
diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h
index 5d94f675aa7..a95779cc524 100644
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -308,8 +308,13 @@ class nsHttpChannel final : public HttpBaseChannel,
 
   // Based on the proxy configuration determine the strategy for resolving the
   // end server host name.
-  ProxyDNSStrategy GetProxyDNSStrategy();
+  nsIHttpChannelInternal::ProxyDNSStrategy ComputeProxyDNSStrategy();
 
+ public:
+  NS_IMETHOD GetProxyDNSStrategy(
+      nsIHttpChannelInternal::ProxyDNSStrategy* aStrategy) override;
+
+ private:
   // Add Sec-Fetch-Storage-Access headers based on cookie partitioning
   void AddStorageAccessHeadersToRequest();
   bool DispatchRelease();
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
index 07c31a8c733..f2706a42cd3 100644
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -3673,12 +3673,13 @@ void nsHttpConnectionMgr::DoSpeculativeConnectionInternal(
     return;
   }
 
-  ProxyDNSStrategy strategy = GetProxyDNSStrategyHelper(
+  nsIHttpChannelInternal::ProxyDNSStrategy strategy = GetProxyDNSStrategyHelper(
       aEnt->mConnInfo->ProxyType(), aEnt->mConnInfo->ProxyFlag());
   // Speculative connections can be triggered by non-Necko consumers,
   // so add an extra check to ensure HTTPS RR isn't fetched when a proxy is
   // used.
-  if (aFetchHTTPSRR && strategy == ProxyDNSStrategy::ORIGIN &&
+  if (aFetchHTTPSRR &&
+      strategy == nsIHttpChannelInternal::PROXY_DNS_STRATEGY_ORIGIN &&
       NS_SUCCEEDED(aTrans->FetchHTTPSRR())) {
     // nsHttpConnectionMgr::DoSpeculativeConnection will be called again
     // when HTTPS RR is available.
diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl
index dd42bbaa190..8d7d452b85f 100644
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -275,6 +275,18 @@ interface nsIHttpChannelInternal : nsISupports
      */
     [must_use] attribute boolean bypassProxy;
 
+    /**
+     * Based on the proxy configuration, indicates how the end server host
+     * name will be resolved. ORIGIN (1) means the client resolves the host
+     * locally; PROXY (2) means the proxy resolves the host (so no DNS query
+     * for the origin host should be issued by the client).
+     */
+    cenum ProxyDNSStrategy : 8 {
+      PROXY_DNS_STRATEGY_ORIGIN = 1,
+      PROXY_DNS_STRATEGY_PROXY = 2,
+    };
+    [must_use, noscript] readonly attribute nsIHttpChannelInternal_ProxyDNSStrategy proxyDNSStrategy;
+
     /**
      * True if channel is used by the internal trusted recursive resolver
      * This flag places data for the request in a cache segment specific to TRR
Loading diff…