High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionInformation disclosure in the Networking: JAR component
ComponentNetworking
Bug ClassLogic Error
Tracker2061526
Fix commit3432782e7713 (firefox) +68/-10
CISA KEVNot listed
CreditedMozilla
Disclosed2026-09-29

Changed Functions

FunctionChangeNotes
Mutator
netwerk/protocol/res/SubstitutingJARURI.h
modified
if
netwerk/protocol/res/SubstitutingProtocolHandler.cpp
modified

Files Changed

  • modules/libjar/nsJARURI.cpp
  • netwerk/protocol/res/SubstitutingJARURI.h
  • netwerk/protocol/res/SubstitutingProtocolHandler.cpp
diff --git a/modules/libjar/nsJARURI.cpp b/modules/libjar/nsJARURI.cpp
index 65a70e2c558..b14bb34dd55 100644
--- a/modules/libjar/nsJARURI.cpp
+++ b/modules/libjar/nsJARURI.cpp
@@ -685,6 +685,23 @@ nsJARURI::GetInnermostURI(nsIURI** uri) {
   return NS_ImplGetInnermostURI(this, uri);
 }
 
+// Make sure the deserialized IPC URL is valid.
+static bool AreJARPartsCanonical(nsIURI* aJARFile, nsIURL* aJAREntry) {
+  nsAutoCString entrySpec;
+  if (NS_FAILED(aJAREntry->GetSpec(entrySpec))) {
+    return false;
+  }
+
+  // Parsing a jar: spec splits it at the last "!/", so an entry can neither
+  // carry a delimiter of its own nor use a scheme other than the bogus one.
+  if (!StringBeginsWith(entrySpec, NS_BOGUS_ENTRY_SCHEME) ||
+      entrySpec.Find(NS_JAR_DELIMITER) != kNotFound) {
+    return false;
+  }
+
+  return true;
+}
+
 void nsJARURI::Serialize(URIParams& aParams) {
   JARURIParams params;
 
@@ -721,6 +738,11 @@ bool nsJARURI::Deserialize(const URIParams& aParams) {
     return false;
   }
 
+  if (!AreJARPartsCanonical(file, entryURL)) {
+    NS_WARNING("Received structurally invalid jar URI parts!");
+    return false;
+  }
+
   mJARFile.swap(file);
   mJAREntry.swap(entryURL);
   mCharsetHint = params.charset();
diff --git a/netwerk/protocol/res/SubstitutingJARURI.h b/netwerk/protocol/res/SubstitutingJARURI.h
index 0e2e89f7a24..690b10364f1 100644
--- a/netwerk/protocol/res/SubstitutingJARURI.h
+++ b/netwerk/protocol/res/SubstitutingJARURI.h
@@ -200,6 +200,9 @@ class SubstitutingJARURI : public nsIJARURI,
   bool Deserialize(const mozilla::ipc::URIParams& aParams);
   nsresult ReadPrivate(nsIObjectInputStream* aStream);
 
+  // Builds the URI aSource resolves to
+  static nsresult ResolveSource(nsIURI* aSource, SubstitutingJARURI** aResult);
+
  public:
   class Mutator final : public nsIURIMutator,
                         public BaseURIMutator<SubstitutingJARURI>,
diff --git a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
index 12bcacd12a7..8aa1a50f0c6 100644
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -172,6 +172,33 @@ size_t SubstitutingJARURI::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
   return 0;
 };
 
+/* static */
+nsresult SubstitutingJARURI::ResolveSource(nsIURI* aSource,
+                                           SubstitutingJARURI** aResult) {
+  if (!aSource) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsAutoCString spec;
+  nsresult rv = aSource->GetSpec(spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURI> resolved;
+  rv = NS_NewURI(getter_AddRefs(resolved), spec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Only a substitution carrying the RESOLVE_JAR_URI flag resolves to a
+  // SubstitutingJARURI, and ResolveJARURI has checked that it maps to a local
+  // jar file.
+  RefPtr<SubstitutingJARURI> uri;
+  rv =
+      resolved->QueryInterface(kSubstitutingJARURIImplCID, getter_AddRefs(uri));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  uri.forget(aResult);
+  return NS_OK;
+}
+
 // SubstitutingJARURI::nsISerializable
 
 NS_IMETHODIMP
@@ -185,16 +212,21 @@ SubstitutingJARURI::Read(nsIObjectInputStream* aStream) {
   rv = aStream->ReadObject(true, getter_AddRefs(source));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mSource = do_QueryInterface(source, &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
+  // Read the resolved URI too so the stream stays in sync, but don't use it.
   nsCOMPtr<nsISupports> resolved;
   rv = aStream->ReadObject(true, getter_AddRefs(resolved));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mResolved = do_QueryInterface(resolved, &rv);
+  nsCOMPtr<nsIURI> sourceURI = do_QueryInterface(source, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  RefPtr<SubstitutingJARURI> uri;
+  rv = ResolveSource(sourceURI, getter_AddRefs(uri));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mSource = uri->mSource;
+  mResolved = uri->mResolved;
+
   return NS_OK;
 }
 
@@ -267,15 +299,16 @@ bool SubstitutingJARURI::Deserialize(const mozilla::ipc::URIParams& aParams) {
   const SubstitutingJARURIParams& jarUriParams =
       aParams.get_SubstitutingJARURIParams();
 
+  // The resolved URI in the params is deliberately ignored, see ResolveSource.
   nsCOMPtr<nsIURI> source = DeserializeURI(jarUriParams.source());
-  nsresult rv;
-  mSource = do_QueryInterface(source, &rv);
-  if (NS_FAILED(rv)) {
+  RefPtr<SubstitutingJARURI> uri;
+  if (NS_FAILED(ResolveSource(source, getter_AddRefs(uri)))) {
     return false;
   }
-  nsCOMPtr<nsIURI> jarUri = DeserializeURI(jarUriParams.resolved());
-  mResolved = do_QueryInterface(jarUri, &rv);
-  return NS_SUCCEEDED(rv);
+
+  mSource = uri->mSource;
+  mResolved = uri->mResolved;
+  return true;
 }
 
 nsresult SubstitutingJARURI::ReadPrivate(nsIObjectInputStream* aStream) {
Loading diff…