CVE-2026-7966
Overview
Files Changed
chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc
Patch
From d0efcafb889fec8a81c2fbc8abf305896c82a254 Mon Sep 17 00:00:00 2001 From: Minoru Chikamune <[email protected]> Date: Thu, 02 Apr 2026 00:52:40 -0700 Subject: [PATCH] [LCPP] Restrict LCPCriticalPathPredictorHost to the outermost main frame This CL adds a frame gate to LCPCriticalPathPredictorHost::Create to ensure that only the outermost main frame can bind the interface. Bug: 497341787 Change-Id: I1fd830f0b302810fb3afd00e61e25e4d9602084d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7724500 Reviewed-by: Yoshisato Yanagisawa <[email protected]> Commit-Queue: Minoru Chikamune <[email protected]> Cr-Commit-Position: refs/heads/main@{#1609011} --- diff --git a/chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc b/chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc index e204d3ea..d2edc6b 100644 --- a/chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc +++ b/chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc @@ -30,6 +30,11 @@ content::RenderFrameHost* render_frame_host, mojo::PendingReceiver<blink::mojom::LCPCriticalPathPredictorHost> receiver) { + // Only valid for the main frame. + if (render_frame_host->GetParentOrOuterDocument()) { + return; + } + // The object is bound to the lifetime of the |render_frame_host| and the mojo // connection. See DocumentService for details. new LCPCriticalPathPredictorHost(*render_frame_host, std::move(receiver));
Original Bug Report
Site Isolation bypass via missing frame gate in LCPCriticalPathPredictorHost
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A compromised renderer in a cross-origin subframe can bind the LCPCriticalPathPredictorHost Mojo interface to inject arbitrary URLs into the parent frame’s prefetch database. When the user later navigates to the parent site, the browser automatically prefetches these injected URLs with full credentials, including SameSite=Strict cookies. This results in a persistent, credentialed blind GET CSRF against the embedder’s origin.
Affected files:
chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.ccchrome/browser/page_load_metrics/observers/lcp_critical_path_predictor_page_load_metrics_observer.ccchrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_util.cc
Estimated timestamp from git blame: 2024-10-21
Summary
There is a potential Site Isolation bypass in the LCP Critical Path Predictor (LCPP) implementation. A compromised renderer hosting a cross-origin iframe can poison the LCPP prefetch database for the parent frame’s origin. By binding the LCPCriticalPathPredictorHost Mojo interface, which lacks a subframe check, an attacker can inject arbitrary same-site URLs. These URLs are then automatically prefetched with credentials (including SameSite=Strict cookies) when the user later navigates to the victim’s site.
Note: The following analysis is based on code review by an AI tooling agent. The steps below are a suggested/potential attack path, as we do not currently have the capability to run a live Proof of Concept.
Vulnerability Details
This issue arises from a combination of missing frame gating and incorrect page resolution during Mojo IPC handling:
- Missing Frame Gate:
LCPCriticalPathPredictorHost::Create()(chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc) fails to verify that the bindingRenderFrameHostis an outermost main frame. This contrasts with other predictors likeNavigationPredictor::Create(), which safely reject subframes usingGetParentOrOuterDocument(). - Page Resolution Escalation: When the compromised subframe renderer calls
NotifyFetchedSubresource, the browser retrieves thePageobject viarender_frame_host().GetPage(). Because aPagerepresents the entire document tree, calling this on a subframe returns the outermost main frame’sPage. - Unvalidated Data Append: The host then retrieves the
LcpCriticalPathPredictorPageLoadMetricsObserverattached to the main frame and callsAppendFetchedSubresourceUrl. This blindly records the attacker’s URL into the main frame’slcpp_data_inputs_without verifying the origin of the reporting frame. - Database Persistence: When the user navigates away or closes the tab,
FinalizeLCPpersists this data into the SQLite database, keying it to the main frame’s host. - Credentialed Execution: Upon a future navigation to the victim site (e.g., via the Omnibox, where
initiator_originis null), the browser reads the poisoned LCPP entry. It constructs a prefetch request (network::ResourceRequest) withsite_for_cookiesandrequest_initiatorset to the victim’s origin, resulting in a fully authenticated GET request that bypassesSameSiteand Fetch Metadata protections.
Suggested Attack Steps
- An attacker sets up a malicious page that is loaded as a cross-origin iframe on a benign site (
victim.com). - The attacker exploits a standard renderer bug (e.g., in V8) to gain code execution within their cross-origin subframe.
- The compromised renderer requests to bind
blink::mojom::LCPCriticalPathPredictorHost. - The attacker sends a
NotifyFetchedSubresourceIPC message containing a sensitive GET endpoint on the parent origin (e.g.,https://victim.com/sensitive_action?update=1). - The user eventually closes the tab or navigates away, saving the poisoned prediction to the browser’s local LCPP database under the
victim.comkey. - Days later, the user types
victim.cominto the Omnibox. - The browser attempts to optimize the load, retrieves the poisoned LCPP data, and issues a prefetch request to
https://victim.com/sensitive_action?update=1. Since the browser believes it is optimizing a first-party load, it attaches all first-party cookies (includingSameSite=Strict), resulting in a successful GET CSRF.
Suggested Fix
Add a subframe check in LCPCriticalPathPredictorHost::Create() to prevent cross-origin iframes from binding the interface:
void LCPCriticalPathPredictorHost::Create(
content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::LCPCriticalPathPredictorHost> receiver) {
// Only valid for the main frame.
if (render_frame_host->GetParentOrOuterDocument()) {
return;
}
// ... existing code ...
}
Additionally, AppendFetchedSubresourceUrl should optionally validate that the reported subresource’s origin makes sense in the context of the reporting frame, though blocking the interface entirely for subframes is the most robust fix.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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. And please feel free to reach out to me directly if you have concerns or feedback on the project.