High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionPrivilege escalation in the DOM: Networking component
ComponentNetworking
Bug ClassLogic Error
Tracker2051013
Fix commitfa4f02f2d552 (firefox) +50/-0
CISA KEVNot listed
CreditedYaqoub Aldurayhim
Disclosed2026-08-18

Background

`nsSimpleNestedURI`
a URI implementation that wraps an inner nsIURI inside an outer scheme, used for schemes such as view-source:.
`nsNestedAboutURI`
a nsSimpleNestedURI subclass representing about: URIs that carries an inner moz-safe-about: target URI.
`Deserialize`
the method that reconstructs a URI object from mozilla::ipc::URIParams received over IPC, typically from a less-privileged child process.
`NS_IsContentAccessibleAboutURI`
a helper that reports whether an about: URI is one that untrusted content is permitted to load.

Root Cause Analysis

When nsSimpleNestedURI::Deserialize (and nsNestedAboutURI::Deserialize) rebuilt a URI from IPC URIParams, it called DeserializeURI to construct mInnerURI and accepted whatever inner URI the sender supplied without checking it against the outer URI’s own spec. This violated the invariant that a nested URI’s inner URI must be exactly the URI implied by the outer URI’s scheme and path — for view-source: the inner spec must equal the outer path, and for about: the inner must be moz-safe-about: plus the outer path and the outer must be content-accessible. A compromised child process could therefore hand the parent an outer/inner pair that disagreed, so security decisions made on the outer spec would actually dereference an attacker-chosen inner URI.

The fix adds IsValidInnerURI, invoked at the end of Deserialize, which recomputes the expected inner spec from the deserialized outer URI and rejects the deserialization when the supplied inner URI does not match. Because the check is derived from the trusted outer fields rather than trusting the transmitted inner URI, a mismatched pair can no longer survive deserialization.

Key insight
The single mistake was trusting the IPC-supplied inner URI of a nested URI instead of re-deriving it from the outer URI’s own spec, so outer/inner could be desynchronized across the trust boundary. The fix closes this by validating on deserialization that the inner URI is exactly the one the outer spec implies, rejecting any mismatch.

Attack Path

  1. Compromise a child process An attacker gains code execution in a content/child process that can send URIParams to the more-privileged parent.
  2. Forge a nested URI The child serializes a view-source: or about: nested URI whose outer spec looks benign but whose inner URI points at a different, privileged target.
  3. Send across IPC The crafted URIParams is delivered to the parent, where Deserialize reconstructs the mismatched outer/inner pair without validation.
  4. Trigger a spec-based security decision Code that authorizes based on the outer scheme/path (e.g. content-accessibility of an about: page) permits the load, but the attacker-controlled inner URI is what actually gets dereferenced.
  5. Escalate privilege The mismatch lets content reach a resource or context it should not, achieving privilege escalation in the parent’s Networking/DOM handling.

Impact Assessment

An attacker who already controls a child process can construct a nested view-source: or about: URI whose inner target does not match its outer spec, causing the parent process to act on an attacker-chosen inner URI while authorizing against the innocuous outer one. This yields privilege escalation in the DOM/Networking path within the more-privileged (parent) process. The precondition is the ability to send crafted URIParams over IPC, i.e. a compromised or malicious child process.

Files Changed

  • netwerk/base/nsSimpleNestedURI.cpp
  • netwerk/base/nsSimpleNestedURI.h
  • netwerk/protocol/about/nsAboutProtocolHandler.cpp
  • netwerk/protocol/about/nsAboutProtocolHandler.h

Audit Directions

  • IPC deserialization trust
    Audit every Deserialize that reconstructs composite objects from URIParams or other IPC payloads and confirm each embedded component is re-validated against trusted fields rather than accepted as sent.
  • Nested/wrapper URI consistency
    For any URI type holding an inner URI, verify the inner URI is re-derived from or checked against the outer spec so the two cannot disagree after crossing a trust boundary.
  • Spec-versus-target security checks
    Look for authorization decisions (content-accessibility, scheme allowlists) made on an outer spec while a separately stored inner target is what is actually dereferenced.
diff --git a/netwerk/base/nsSimpleNestedURI.cpp b/netwerk/base/nsSimpleNestedURI.cpp
index 5274499227d..e255c885642 100644
--- a/netwerk/base/nsSimpleNestedURI.cpp
+++ b/netwerk/base/nsSimpleNestedURI.cpp
@@ -132,9 +132,31 @@ bool nsSimpleNestedURI::Deserialize(const mozilla::ipc::URIParams& aParams) {
   if (!nsSimpleURI::Deserialize(params.simpleParams())) return false;
 
   mInnerURI = DeserializeURI(params.innerURI());
+  if (!mInnerURI || !IsValidInnerURI(mInnerURI)) {
+    return false;
+  }
+
   return true;
 }
 
+bool nsSimpleNestedURI::IsValidInnerURI(nsIURI* aInnerURI) {
+  if (!Scheme().EqualsLiteral("view-source")) {
+    return false;
+  }
+
+  nsAutoCString innerSpec;
+  if (NS_FAILED(aInnerURI->GetAsciiSpec(innerSpec))) {
+    return false;
+  }
+
+  nsAutoCString pathQueryRef;
+  if (NS_FAILED(GetPathQueryRef(pathQueryRef))) {
+    return false;
+  }
+
+  return innerSpec == pathQueryRef;
+}
+
 // nsINestedURI
 
 NS_IMETHODIMP
diff --git a/netwerk/base/nsSimpleNestedURI.h b/netwerk/base/nsSimpleNestedURI.h
index 719eb30d1b8..aa3a5a3e42f 100644
--- a/netwerk/base/nsSimpleNestedURI.h
+++ b/netwerk/base/nsSimpleNestedURI.h
@@ -51,6 +51,10 @@ class nsSimpleNestedURI : public nsSimpleURI, public nsINestedURI {
   nsresult SetQuery(const nsACString& aQuery) override;
   nsresult SetRef(const nsACString& aRef) override;
   bool Deserialize(const mozilla::ipc::URIParams&);
+
+  // Returns true if aInnerURI is the inner URI our own spec implies.
+  virtual bool IsValidInnerURI(nsIURI* aInnerURI);
+
   nsresult ReadPrivate(nsIObjectInputStream* stream);
 
  public:
diff --git a/netwerk/protocol/about/nsAboutProtocolHandler.cpp b/netwerk/protocol/about/nsAboutProtocolHandler.cpp
index b9ff235f357..82822d0fc67 100644
--- a/netwerk/protocol/about/nsAboutProtocolHandler.cpp
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.cpp
@@ -352,6 +352,29 @@ bool nsNestedAboutURI::Deserialize(const mozilla::ipc::URIParams& aParams) {
   return true;
 }
 
+bool nsNestedAboutURI::IsValidInnerURI(nsIURI* aInnerURI) {
+  if (!Scheme().EqualsLiteral("about")) {
+    return false;
+  }
+
+  if (!NS_IsContentAccessibleAboutURI(this)) {
+    return false;
+  }
+
+  nsAutoCString expectedSpec;
+  if (NS_FAILED(GetPathQueryRef(expectedSpec))) {
+    return false;
+  }
+  expectedSpec.InsertLiteral("moz-safe-about:", 0);
+
+  nsAutoCString innerSpec;
+  if (NS_FAILED(aInnerURI->GetAsciiSpec(innerSpec))) {
+    return false;
+  }
+
+  return innerSpec == expectedSpec;
+}
+
 // nsSimpleURI
 /* virtual */ already_AddRefed<nsSimpleURI> nsNestedAboutURI::StartClone() {
   NS_ENSURE_TRUE(mInnerURI, nullptr);
diff --git a/netwerk/protocol/about/nsAboutProtocolHandler.h b/netwerk/protocol/about/nsAboutProtocolHandler.h
index f5af1509b6f..65c42837bf0 100644
--- a/netwerk/protocol/about/nsAboutProtocolHandler.h
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.h
@@ -76,6 +76,7 @@ class nsNestedAboutURI final : public nsSimpleNestedURI {
  protected:
   nsCOMPtr<nsIURI> mBaseURI;
   bool Deserialize(const mozilla::ipc::URIParams&);
+  bool IsValidInnerURI(nsIURI* aInnerURI) override;
   nsresult ReadPrivate(nsIObjectInputStream* stream);
 
  public:
Loading diff…