CVE-2026-74935
Overview
Background
- `nsSimpleNestedURI`
- a URI implementation that wraps an inner
nsIURIinside an outer scheme, used for schemes such asview-source:. - `nsNestedAboutURI`
- a
nsSimpleNestedURIsubclass representingabout:URIs that carries an innermoz-safe-about:target URI. - `Deserialize`
- the method that reconstructs a URI object from
mozilla::ipc::URIParamsreceived 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.
Attack Path
- Compromise a child process
An attacker gains code execution in a content/child process that can send
URIParamsto the more-privileged parent. - Forge a nested URI
The child serializes a
view-source:orabout:nested URI whose outer spec looks benign but whose inner URI points at a different, privileged target. - Send across IPC
The crafted
URIParamsis delivered to the parent, whereDeserializereconstructs the mismatched outer/inner pair without validation. - 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. - 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
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.cppnetwerk/base/nsSimpleNestedURI.hnetwerk/protocol/about/nsAboutProtocolHandler.cppnetwerk/protocol/about/nsAboutProtocolHandler.h
Audit Directions
- IPC deserialization trustAudit every
Deserializethat reconstructs composite objects fromURIParamsor other IPC payloads and confirm each embedded component is re-validated against trusted fields rather than accepted as sent. - Nested/wrapper URI consistencyFor 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 checksLook for authorization decisions (content-accessibility, scheme allowlists) made on an outer spec while a separately stored inner target is what is actually dereferenced.
Patch
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: