High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionPrivilege escalation due to incorrect boundary conditions in the Networking component
ComponentNetworking
Bug ClassLogic Error
Tracker2054644
Fix commitb61c80e501f1 (firefox) +75/-4
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
if
netwerk/protocol/http/HttpBaseChannel.cpp
modified
if
netwerk/protocol/http/HttpChannelParent.cpp
modified

Files Changed

  • netwerk/protocol/http/CacheEntryWriteHandleParent.h
  • netwerk/protocol/http/HttpBaseChannel.cpp
  • netwerk/protocol/http/HttpBaseChannel.h
  • netwerk/protocol/http/HttpChannelParent.cpp
  • netwerk/protocol/http/HttpChannelParent.h
  • netwerk/protocol/http/nsHttpChannel.cpp
diff --git a/netwerk/protocol/http/CacheEntryWriteHandleParent.h b/netwerk/protocol/http/CacheEntryWriteHandleParent.h
index e97149e410b..cee26c82fe6 100644
--- a/netwerk/protocol/http/CacheEntryWriteHandleParent.h
+++ b/netwerk/protocol/http/CacheEntryWriteHandleParent.h
@@ -9,6 +9,7 @@
 #include "nsCOMPtr.h"
 #include "nsICacheEntry.h"
 #include "nsICacheInfoChannel.h"
+#include "nsString.h"
 
 namespace mozilla {
 namespace net {
@@ -22,12 +23,16 @@ class CacheEntryWriteHandleParent final : public nsICacheEntryWriteHandle,
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSICACHEENTRYWRITEHANDLE
-  explicit CacheEntryWriteHandleParent(nsICacheEntry* aCacheEntry);
+  CacheEntryWriteHandleParent(nsICacheEntry* aCacheEntry,
+                              const nsACString& aBindingOrigin);
 
  private:
   virtual ~CacheEntryWriteHandleParent() = default;
 
   nsCOMPtr<nsICacheEntry> mCacheEntry;
+  // Origin of the principal that requested this write handle, used to bind
+  // any alt-data written through it to that principal.
+  nsCString mBindingOrigin;
 };
 
 }  // namespace net
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
index b2d86d03eaf..cf05679d4d8 100644
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -4483,6 +4483,22 @@ bool HttpBaseChannel::IsNavigation() {
   return LoadForceMainDocumentChannel() || (mLoadFlags & LOAD_DOCUMENT_URI);
 }
 
+nsresult HttpBaseChannel::GetAltDataBindingOrigin(nsACString& aOrigin) {
+  aOrigin.Truncate();
+  if (!mLoadInfo) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIPrincipal> principal = mLoadInfo->GetLoadingPrincipal();
+  if (!principal) {
+    principal = mLoadInfo->TriggeringPrincipal();
+  }
+  if (!principal) {
+    return NS_ERROR_FAILURE;
+  }
+  return principal->GetOrigin(aOrigin);
+}
+
 bool HttpBaseChannel::BypassServiceWorker() const {
   return mLoadFlags & LOAD_BYPASS_SERVICE_WORKER;
 }
diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h
index a5ea09db752..4d3dfea93f4 100644
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -504,6 +504,10 @@ class HttpBaseChannel : public nsHashPropertyBag,
 
   bool IsDeliveringAltData() const { return LoadDeliveringAltData(); }
 
+  // Computes the origin used to bind alternative (bytecode) cache data to the
+  // principal that produced it.
+  nsresult GetAltDataBindingOrigin(nsACString& aOrigin);
+
   static void PropagateReferenceIfNeeded(nsIURI* aURI,
                                          nsCOMPtr<nsIURI>& aRedirectURI);
 
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp
index 21eea2440e5..88f1dedcaec 100644
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -1275,6 +1275,11 @@ HttpChannelParent::OnStartRequest(nsIRequest* aRequest) {
     mCacheEntry = do_QueryInterface(cacheEntry);
     args.cacheEntryAvailable() = static_cast<bool>(mCacheEntry);
 
+    // Capture the load's principal origin now, while the channel is alive, to
+    // bind any alt-data written later (after the channel may have been
+    // cleared).
+    chan->GetAltDataBindingOrigin(mAltDataBindingOrigin);
+
     httpChannelImpl->GetCacheKey(&args.cacheKey());
     httpChannelImpl->GetAlternativeDataType(args.altDataType());
   }
@@ -2012,9 +2017,26 @@ NS_INTERFACE_MAP_BEGIN(CacheEntryWriteHandleParent)
   NS_INTERFACE_MAP_ENTRY(nsICacheEntryWriteHandle)
 NS_INTERFACE_MAP_END
 
+// Records, atomically with the alt-data write, the origin of the principal
+// that produced the alt-data.
+static void RecordAltDataPrincipal(nsICacheEntry* aCacheEntry,
+                                   const nsACString& aOrigin) {
+  if (!aCacheEntry) {
+    return;
+  }
+  if (aOrigin.IsEmpty()) {
+    // Fail closed: without a known principal, clear any binding so that reads
+    // reject this alt-data.
+    aCacheEntry->SetMetaDataElement("alt-data-principal", nullptr);
+  } else {
+    aCacheEntry->SetMetaDataElement("alt-data-principal",
+                                    PromiseFlatCString(aOrigin).get());
+  }
+}
+
 CacheEntryWriteHandleParent::CacheEntryWriteHandleParent(
-    nsICacheEntry* aCacheEntry)
-    : mCacheEntry(aCacheEntry) {}
+    nsICacheEntry* aCacheEntry, const nsACString& aBindingOrigin)
+    : mCacheEntry(aCacheEntry), mBindingOrigin(aBindingOrigin) {}
 
 NS_IMETHODIMP
 CacheEntryWriteHandleParent::OpenAlternativeOutputStream(
@@ -2028,12 +2050,13 @@ CacheEntryWriteHandleParent::OpenAlternativeOutputStream(
       mCacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
   if (NS_SUCCEEDED(rv)) {
     mCacheEntry->SetMetaDataElement("alt-data-from-child", "1");
+    RecordAltDataPrincipal(mCacheEntry, mBindingOrigin);
   }
   return rv;
 }
 
 CacheEntryWriteHandleParent* HttpChannelParent::AllocCacheEntryWriteHandle() {
-  return new CacheEntryWriteHandleParent(mCacheEntry);
+  return new CacheEntryWriteHandleParent(mCacheEntry, mAltDataBindingOrigin);
 }
 
 nsresult HttpChannelParent::OpenAlternativeOutputStream(
@@ -2048,6 +2071,7 @@ nsresult HttpChannelParent::OpenAlternativeOutputStream(
       mCacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
   if (NS_SUCCEEDED(rv)) {
     mCacheEntry->SetMetaDataElement("alt-data-from-child", "1");
+    RecordAltDataPrincipal(mCacheEntry, mAltDataBindingOrigin);
   }
   return rv;
 }
diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h
index f48f3e9294f..a01de32e8f1 100644
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -266,6 +266,10 @@ class HttpChannelParent final : public nsIInterfaceRequestor,
 
   RefPtr<HttpBaseChannel> mChannel;
   nsCOMPtr<nsICacheEntry> mCacheEntry;
+  // Origin of the load's principal, captured while the channel is still alive
+  // (mChannel is cleared on document channel cleanup, before alt-data is
+  // written). Used to bind child-written alt-data to the producing principal.
+  nsCString mAltDataBindingOrigin;
 
   nsCOMPtr<nsIChannel> mRedirectChannel;
   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
index 63df4c9dfa2..6165e474561 100644
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -5946,6 +5946,24 @@ nsresult nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry,
   nsAutoCString contentType;
   mCachedResponseHead->ContentType(contentType);
 
+  // Alt-data written by a content process (bytecode cache) may only be
+  // consumed by a load whose principal matches the one that produced it.
+  if (altDataFromChild && !altDataType.IsEmpty()) {
+    nsAutoCString storedOrigin;
+    cacheEntry->GetMetaDataElement("alt-data-principal",
+                                   getter_Copies(storedOrigin));
+    nsAutoCString currentOrigin;
+    GetAltDataBindingOrigin(currentOrigin);
+    if (storedOrigin.IsEmpty() || currentOrigin.IsEmpty() ||
+        !storedOrigin.Equals(currentOrigin)) {
+      LOG(
+          ("Rejecting child-written alt-data due to principal mismatch "
+           "[channel=%p, stored='%s', current='%s']",
+           this, storedOrigin.get(), currentOrigin.get()));
+      altDataType.Truncate();
+    }
+  }
+
   bool foundAltData = false;
   bool deliverAltData = true;
   if (!LoadDisableAltDataCache() && !altDataType.IsEmpty() &&
Loading diff…