CVE-2026-87553
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
BindLambdaForTestingcontent/browser/preloading/prerender/prerender_host_registry_unittest.cc |
modified | |
TEST_Fcontent/browser/preloading/prerender/prerender_host_registry_unittest.cc |
modified | |
ifcontent/browser/renderer_host/render_frame_host_impl.cc |
modified |
Files Changed
content/browser/preloading/prerender/prerender_host_registry_unittest.cccontent/browser/renderer_host/render_frame_host_impl.cccontent/browser/renderer_host/render_frame_host_impl.hcontent/common/navigation_client.mojomcontent/renderer/render_frame_impl.cccontent/test/navigation_simulator_impl.cccontent/test/navigation_simulator_impl.h
Patch
From cbef3d0f82fdb884a10fe58f5cb75fcc3eef50fd Mon Sep 17 00:00:00 2001 From: Rakina Zata Amni <[email protected]> Date: Sun, 02 Aug 2026 20:56:10 -0700 Subject: [PATCH] Remove DidCommitParams has_potentially_trustworthy_unique_origin Instead of passing has_potentially_trustworthy_unique_origin from the renderer and clamping it in the browser, this CL removes the field from DidCommitProvisionalLoadParams entirely and computes it in the browser process inside RenderFrameHostImpl::SetLastCommittedOrigin. This is a security improvement as it prevents a compromised renderer from claiming that an opaque origin is potentially trustworthy. Bug: 496595299 TAG=agy CONV=16ca3df5-92fb-4b33-a9bf-29956e520fc5 Change-Id: I032fc6c526130cb431d9772693c398795142b6c9 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8181587 Reviewed-by: Alex Moshchuk <[email protected]> Reviewed-by: Sam McNally <[email protected]> Commit-Queue: Rakina Zata Amni <[email protected]> Cr-Commit-Position: refs/heads/main@{#1672472} --- diff --git a/content/browser/preloading/prerender/prerender_host_registry_unittest.cc b/content/browser/preloading/prerender/prerender_host_registry_unittest.cc index cdaf2fcd..cd144968 100644 --- a/content/browser/preloading/prerender/prerender_host_registry_unittest.cc +++ b/content/browser/preloading/prerender/prerender_host_registry_unittest.cc @@ -1796,19 +1796,6 @@ insecure_navigations); } -TEST_F(PrerenderHostRegistryTest, - HasPotentiallyTrustworthyUniqueOriginIsSetWhilePrerendering) { - SetupPrerenderAndCommit( - base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) { - navigation->set_has_potentially_trustworthy_unique_origin(true); - })); - EXPECT_TRUE(contents() - ->GetPrimaryMainFrame() - ->frame_tree_node() - ->current_replication_state() - .has_potentially_trustworthy_unique_origin); -} - // End replication state matching tests ------------ TEST_F(PrerenderHostRegistryTest, OneTaskToDeleteAllHosts) { diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index 59c3e37..48e7b97f 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc @@ -5301,8 +5301,7 @@ // The origin is only updated for cross-document navigations. if (!was_within_same_document || !features::IsEnforceSameDocumentOriginInvariantsEnabled()) { - SetLastCommittedOrigin(params.origin, - params.has_potentially_trustworthy_unique_origin); + SetLastCommittedOrigin(params.origin); } // If the navigation was a cross-document navigation and it's not the @@ -5441,14 +5440,13 @@ } } -void RenderFrameHostImpl::SetLastCommittedOrigin( - const url::Origin& origin, - bool is_potentially_trustworthy_unique_origin) { +void RenderFrameHostImpl::SetLastCommittedOrigin(const url::Origin& origin) { last_committed_origin_ = origin; - // TODO(https://crbug.com/40159049): Instead of passing - // `is_potentially_trustworthy_unique_origin`, maybe we can just check if the - // origin is opaque and use ``network::IsOriginPotentiallyTrustworthy()` on - // its precursor origin. + const url::SchemeHostPort& precursor = + origin.GetTupleOrPrecursorTupleIfOpaque(); + bool is_potentially_trustworthy_unique_origin = + origin.opaque() && precursor.IsValid() && + network::IsUrlPotentiallyTrustworthy(precursor.GetURL()); browsing_context_state()->SetCurrentOrigin( origin, is_potentially_trustworthy_unique_origin); } @@ -5459,13 +5457,7 @@ void RenderFrameHostImpl::SetLastCommittedOriginForTesting( const url::Origin& origin) { - // Default setting `is_potentially_trustworthy_unique_origin` to just whether - // the origin is opaque or not, since we don't really have a way to get the - // correct value from a random origin. Since this function is used mostly for - // unit tests that won't actually use this value (which is only used in the - // renderer), it should be good enough. - SetLastCommittedOrigin( - origin, /*is_potentially_trustworthy_unique_origin=*/origin.opaque()); + SetLastCommittedOrigin(origin); } const url::Origin& RenderFrameHostImpl::ComputeTopFrameOrigin( @@ -5824,19 +5816,7 @@ GetStoragePartition()->IncrementActiveDocumentCount( GetNetworkIsolationKey()); } - // The `is_potentially_trustworthy_unique_origin` bit should be inherited from - // the creator frame if it exists. Note that we do this even when the new - // frame is sandboxed, following `DocumentLoader::CaclculateOrigin()`. - // TODO(https://crbug.com/40159049): Once we can always trust - // `network::IsOriginPotentiallyTrustworthy()` instead of passing around - // `has_potentially_trustworthy_unique_origin`, remove this. - bool is_potentially_trustworthy_unique_origin = - creator_frame ? creator_frame->browsing_context_state() - ->current_replication_state() - .has_potentially_trustworthy_unique_origin - : false; - SetLastCommittedOrigin(new_frame_origin, - is_potentially_trustworthy_unique_origin); + SetLastCommittedOrigin(new_frame_origin); if (!creator_frame || !creator_frame->is_error_document_) { frame_tree_node()->set_last_successful_origin(new_frame_origin); } diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h index aa51bc7..2b56bf69 100644 --- a/content/browser/renderer_host/render_frame_host_impl.h +++ b/content/browser/renderer_host/render_frame_host_impl.h @@ -4010,8 +4010,7 @@ // Update this frame's last committed origin. This will also update the origin // and the "has_potentially_trustworthy_unique_origin" bit in the // FrameReplicationState. - void SetLastCommittedOrigin(const url::Origin& origin, - bool is_potentially_trustworthy_unique_origin); + void SetLastCommittedOrigin(const url::Origin& origin); // Stores a snapshot of the inherited base URL from the initiator's // FrameLoadRequest, if this document inherited one (e.g., about:srcdoc). diff --git a/content/common/navigation_client.mojom b/content/common/navigation_client.mojom index e038cee..f6becf5 100644 --- a/content/common/navigation_client.mojom +++ b/content/common/navigation_client.mojom @@ -159,10 +159,6 @@ // enforcing. array<uint32> insecure_navigations_set; - // True if the document for the load is a unique origin that should be - // considered potentially trustworthy. - bool has_potentially_trustworthy_unique_origin; - // Request ID generated by the renderer. int32 request_id; diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index 746d0107..2dda49f 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -5178,9 +5178,6 @@ params->insecure_request_policy = frame_->GetInsecureRequestPolicy(); params->insecure_navigations_set = frame_->GetInsecureRequestToUpgrade(); - params->has_potentially_trustworthy_unique_origin = - frame_origin.IsOpaque() && frame_origin.IsPotentiallyTrustworthy(); - // Set the URL to be displayed in the browser UI to the user. Note this might // be different than the URL actually used in the DocumentLoader (see comments // in GetLoadingUrl() and MaybeGetOverriddenURL()). This might not be the URL diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc index d3200ab..fa8b6b1 100644 --- a/content/test/navigation_simulator_impl.cc +++ b/content/test/navigation_simulator_impl.cc @@ -1723,8 +1723,6 @@ params->insecure_request_policy = insecure_request_policy_; params->insecure_navigations_set = insecure_navigations_set_; - params->has_potentially_trustworthy_unique_origin = - has_potentially_trustworthy_unique_origin_; params->commit_navigation_start = base::TimeTicks::Now(); params->commit_navigation_end = base::TimeTicks::Now(); diff --git a/content/test/navigation_simulator_impl.h b/content/test/navigation_simulator_impl.h index 7bf8fc1..90c8ee2 100644 --- a/content/test/navigation_simulator_impl.h +++ b/content/test/navigation_simulator_impl.h @@ -217,12 +217,6 @@ insecure_navigations_set_ = insecure_navigations_set; } - void set_has_potentially_trustworthy_unique_origin( - bool has_potentially_trustworthy_unique_origin) { - has_potentially_trustworthy_unique_origin_ = - has_potentially_trustworthy_unique_origin; - } - void set_supports_loading_mode_header(std::string value) { supports_loading_mode_header_ = value; } @@ -405,7 +399,6 @@ blink::mojom::InsecureRequestPolicy insecure_request_policy_ = blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone; std::vector<uint32_t> insecure_navigations_set_; - bool has_potentially_trustworthy_unique_origin_ = false; // Any DNS aliases, as read from CNAME records, for the request URL that // would be in the network::mojom::URLResponseHead. The alias chain order
Regression Test / PoC
diff --git a/content/browser/preloading/prerender/prerender_host_registry_unittest.cc b/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
index cdaf2fcd..cd144968 100644
--- a/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
+++ b/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
@@ -1796,19 +1796,6 @@
insecure_navigations);
}
-TEST_F(PrerenderHostRegistryTest,
- HasPotentiallyTrustworthyUniqueOriginIsSetWhilePrerendering) {
- SetupPrerenderAndCommit(
- base::BindLambdaForTesting([](NavigationSimulatorImpl* navigation) {
- navigation->set_has_potentially_trustworthy_unique_origin(true);
- }));
- EXPECT_TRUE(contents()
- ->GetPrimaryMainFrame()
- ->frame_tree_node()
- ->current_replication_state()
- .has_potentially_trustworthy_unique_origin);
-}
-
// End replication state matching tests ------------
TEST_F(PrerenderHostRegistryTest, OneTaskToDeleteAllHosts) {
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index d3200ab..fa8b6b1 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -1723,8 +1723,6 @@
params->insecure_request_policy = insecure_request_policy_;
params->insecure_navigations_set = insecure_navigations_set_;
- params->has_potentially_trustworthy_unique_origin =
- has_potentially_trustworthy_unique_origin_;
params->commit_navigation_start = base::TimeTicks::Now();
params->commit_navigation_end = base::TimeTicks::Now();
diff --git a/content/test/navigation_simulator_impl.h b/content/test/navigation_simulator_impl.h
index 7bf8fc1..90c8ee2 100644
--- a/content/test/navigation_simulator_impl.h
+++ b/content/test/navigation_simulator_impl.h
@@ -217,12 +217,6 @@
insecure_navigations_set_ = insecure_navigations_set;
}
- void set_has_potentially_trustworthy_unique_origin(
- bool has_potentially_trustworthy_unique_origin) {
- has_potentially_trustworthy_unique_origin_ =
- has_potentially_trustworthy_unique_origin;
- }
-
void set_supports_loading_mode_header(std::string value) {
supports_loading_mode_header_ = value;
}
@@ -405,7 +399,6 @@
blink::mojom::InsecureRequestPolicy insecure_request_policy_ =
blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone;
std::vector<uint32_t> insecure_navigations_set_;
- bool has_potentially_trustworthy_unique_origin_ = false;
// Any DNS aliases, as read from CNAME records, for the request URL that
// would be in the network::mojom::URLResponseHead. The alias chain order
Original Bug Report
Potential: Compromised renderer can bypass Secure Context ancestor checks via spoofed replication state
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A compromised renderer can spoof the has_potentially_trustworthy_unique_origin flag during a navigation commit. The browser process blindly trusts this flag, stores it, and broadcasts it to sibling renderers. This allows a cross-process child frame to erroneously treat an insecure ancestor as a Secure Context, bypassing W3C requirements for restricted APIs like Service Workers.
Affected files:
content/browser/renderer_host/render_frame_host_impl.cccontent/browser/renderer_host/browsing_context_state.ccthird_party/blink/renderer/core/frame/local_dom_window.ccthird_party/blink/renderer/core/frame/remote_frame.ccthird_party/blink/renderer/platform/weborigin/security_origin.cc
Estimated timestamp from git blame: 2025-07-10
Summary
A compromised renderer process can send a spoofed has_potentially_trustworthy_unique_origin boolean in the DidCommitProvisionalLoadParams IPC during a navigation commit. The browser process accepts this value without validation and broadcasts it to other renderer processes.
This flaw allows a cross-process descendant frame, which should be considered an insecure context due to an insecure ancestor, to incorrectly obtain isSecureContext=true. This erroneously grants the child frame access to sensitive APIs (e.g., Service Workers, WebAuthn) that are gated behind W3C Secure Contexts requirements (§3.1), completely bypassing the ancestor-chain security check.
Vulnerability Details
- Renderer Spoofing: When a navigation commits, the renderer sends a
DidCommitProvisionalLoadParamsIPC containing thehas_potentially_trustworthy_unique_originflag. A compromised renderer can maliciously set this totruefor an opaque origin (e.g., a sandboxed<iframe>). - Lack of Browser Validation: In
content/browser/renderer_host/render_frame_host_impl.cc, withinRenderFrameHostImpl::DidNavigate, the browser process extracts this flag directly from the IPC parameters and passes it toSetLastCommittedOrigin. Crucially,RenderFrameHostImpl::ValidateDidCommitParamsdoes not validate this specific flag.A TODO comment in// render_frame_host_impl.cc:5220 SetLastCommittedOrigin(params.origin, params.has_potentially_trustworthy_unique_origin);SetLastCommittedOriginexplicitly acknowledges that the browser should calculate this value itself usingnetwork::IsOriginPotentiallyTrustworthy()instead of trusting the renderer. - State Replication:
SetLastCommittedOrigincallsBrowsingContextState::SetCurrentOrigin, which stores the poisoned flag in the browser’sreplication_state_and immediately broadcasts it to allRenderFrameProxyHosts in other renderer processes. - Poisoning the RemoteFrame: In a clean renderer process (e.g., one hosting a cross-origin child frame),
RemoteFrame::SetReplicatedOriginreceives the broadcast. It copies the origin and applies the poisoned flag by callingsecurity_origin->SetOpaqueOriginIsPotentiallyTrustworthy(true). - Bypassing the Ancestor Check: When the clean child frame evaluates its secure context status,
LocalDOMWindow::HasInsecureContextInAncestorswalks up the frame tree. For the spoofed ancestor’sRemoteFrame, it callsSecurityOrigin::IsPotentiallyTrustworthy(). Because the origin is opaque, this method returns the poisonedis_opaque_origin_potentially_trustworthy_flag (true). - Erroneous API Access: The descendant frame’s
secure_context_mode_is set tokSecureContext. The clean renderer now erroneously exposes Secure-Context-gated APIs (like Service Workers) to the child frame, despite it having an insecure ancestor.
Furthermore, browser-side checks for API access often fail to re-verify the ancestor chain. For example, ServiceWorkerContainerHostForClient::Register only checks if the frame’s own origin is potentially trustworthy, relying on the renderer’s isSecureContext evaluation for the ancestor chain requirement.
Potential Reproduction Steps
(Note: These are suggested steps; a working PoC has not been executed yet.)
- Host a top-level page (
https://top.example) that embeds an attacker-controlled, sandboxed iframe:<iframe sandbox='allow-scripts' src='http://attacker.example/mid.html'>. mid.htmlembeds a cross-origin victim iframe:<iframe src='https://victim.example/child.html'>. Due to Site Isolation,child.htmlis in a different process.- Exploit a vulnerability in the renderer hosting
mid.htmlto achieve RCE. - During the navigation commit for
mid.html, use the RCE to modify theDidCommitProvisionalLoadParamsIPC, settinghas_potentially_trustworthy_unique_origin = true. - The browser receives the IPC, updates its state, and broadcasts the poisoned flag to the clean renderer process hosting
child.html. - When
child.htmlcommits, itsLocalDOMWindow::HasInsecureContextInAncestorscheck will evaluate the spoofedRemoteFrameas trustworthy. window.isSecureContextwill betrueinchild.html. Ifchild.htmlattempts to register a Service Worker (e.g., via an XSS or its own legitimate code), the registration will succeed, compromising the victim’s origin.
Suggested Fix
Remove the has_potentially_trustworthy_unique_origin field from mojom::DidCommitProvisionalLoadParams. The browser process should exclusively determine the trustworthiness of an origin during navigation commits.
As noted in the existing TODO in RenderFrameHostImpl::SetLastCommittedOrigin (crbug.com/1153336 or crbug.com/40159049), the browser should check if the origin is opaque and use network::IsOriginPotentiallyTrustworthy() on its precursor origin, rather than trusting the potentially compromised renderer process.
Evaluated with Chrome root at commit: 0eb4855bda702feaaa8b899336664f97e3df88b8
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. Please feel free to reach out to me if you have concerns or feedback.