Medium watch CVSS 3.1 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
3.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in SiteIsolation
DescriptionInsufficient validation of untrusted input in SiteIsolation
ComponentSiteIsolation
Sub-componentLCPP
Bug ClassInput Validation
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker497341787
Fix commitd0efcafb889f (chromium/src) +5/-0
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-29 (per vendor advisory)
Fix landed2026-04-02 (public git)
Disclosed2026-05-05
Patch gap33 days — fix public in source before the advisory

Background

LCP Critical Path Predictor (LCPP)
a Chrome performance subsystem that predicts the Largest Contentful Paint element; its renderer side talks to the browser via the blink::mojom::LCPCriticalPathPredictorHost Mojo interface.
Interface binding (`Create`)
the browser-side factory that, given a RenderFrameHost and a PendingReceiver, instantiates and binds the host object to that frame.
`GetParentOrOuterDocument`
a RenderFrameHost accessor returning the parent frame or outer document; it is non-null for subframes and for frames embedded in an outer document (e.g. fenced frames), and null only for the outermost main frame.
Outermost main frame
the top-level document of a tab, the only frame for which GetParentOrOuterDocument() is null.

Root Cause Analysis

The LCPP host-binding factory (Create in lcp_critical_path_predictor_host.cc) unconditionally constructed a LCPCriticalPathPredictorHost for whatever render_frame_host requested the interface, with no check that the frame was the top-level document. Because any frame — subframes, and embedded documents such as fenced frames — could bind the interface, a non-main frame (potentially cross-origin) could obtain an LCPP host it was not supposed to have, which is an insufficient-validation / site-isolation issue. The invariant that only the outermost main frame may use LCPP was not enforced at the binding boundary.

The fix adds an early frame gate at the top of Create: if render_frame_host->GetParentOrOuterDocument() is non-null (i.e. the frame is not the outermost main frame), the function returns before constructing the object, leaving the receiver unbound. Placing the guard before the new LCPCriticalPathPredictorHost(...) ensures the object is never created for disallowed frames.

Key insight
The core mistake was binding a main-frame-only Mojo interface for any requesting frame; the fix gates Create on GetParentOrOuterDocument() being null so only the outermost main frame can bind it.

Attack Path

  1. Run script in a non-main frame Load content in a subframe or an embedded document such as a fenced frame (potentially cross-origin to the top-level page).
  2. Request the LCPP interface From that frame, trigger binding of blink::mojom::LCPCriticalPathPredictorHost, reaching the browser-side Create factory.
  3. Obtain an unauthorized host (pre-fix) Because Create did no frame-type check, it constructed and bound an LCPCriticalPathPredictorHost for the non-main frame, giving it access to an interface reserved for the outermost main frame (inferred: cross-frame influence over LCPP state/hints).

Impact Assessment

Before the fix, a non-outermost frame (including a cross-origin embedded/fenced frame) could bind and use the LCPP host interface, breaking the site-isolation expectation that this interface is restricted to the top-level document (the concrete downstream effect beyond unauthorized access is not demonstrated by the diff). This operates at the renderer-to-browser Mojo boundary and requires only the ability to load content in such a frame. The advisory rates it medium and classifies it as “Insufficient validation of untrusted input in SiteIsolation”; no in-the-wild exploitation is stated.

Files Changed

  • chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc

Audit Directions

  • Unguarded Mojo factory binding
    Audit Create/BindReceiver factories for interfaces intended for the main frame to ensure they check GetParentOrOuterDocument()/IsOutermostMainFrame() before constructing the host.
  • Fenced-frame / embedded-document reach
    For any per-document interface, verify that outer-document embeddings (fenced frames, portals) cannot bind interfaces meant only for a top-level document.
  • DocumentService without frame-type checks
    Review DocumentService-derived hosts whose lifetime is tied to a RenderFrameHost but which assume main-frame-only usage without enforcing it at bind time.
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));
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • chrome/browser/page_load_metrics/observers/lcp_critical_path_predictor_page_load_metrics_observer.cc
  • chrome/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:

  1. Missing Frame Gate: LCPCriticalPathPredictorHost::Create() (chrome/browser/predictors/lcp_critical_path_predictor/lcp_critical_path_predictor_host.cc) fails to verify that the binding RenderFrameHost is an outermost main frame. This contrasts with other predictors like NavigationPredictor::Create(), which safely reject subframes using GetParentOrOuterDocument().
  2. Page Resolution Escalation: When the compromised subframe renderer calls NotifyFetchedSubresource, the browser retrieves the Page object via render_frame_host().GetPage(). Because a Page represents the entire document tree, calling this on a subframe returns the outermost main frame’s Page.
  3. Unvalidated Data Append: The host then retrieves the LcpCriticalPathPredictorPageLoadMetricsObserver attached to the main frame and calls AppendFetchedSubresourceUrl. This blindly records the attacker’s URL into the main frame’s lcpp_data_inputs_ without verifying the origin of the reporting frame.
  4. Database Persistence: When the user navigates away or closes the tab, FinalizeLCP persists this data into the SQLite database, keying it to the main frame’s host.
  5. Credentialed Execution: Upon a future navigation to the victim site (e.g., via the Omnibox, where initiator_origin is null), the browser reads the poisoned LCPP entry. It constructs a prefetch request (network::ResourceRequest) with site_for_cookies and request_initiator set to the victim’s origin, resulting in a fully authenticated GET request that bypasses SameSite and Fetch Metadata protections.

Suggested Attack Steps

  1. An attacker sets up a malicious page that is loaded as a cross-origin iframe on a benign site (victim.com).
  2. The attacker exploits a standard renderer bug (e.g., in V8) to gain code execution within their cross-origin subframe.
  3. The compromised renderer requests to bind blink::mojom::LCPCriticalPathPredictorHost.
  4. The attacker sends a NotifyFetchedSubresource IPC message containing a sensitive GET endpoint on the parent origin (e.g., https://victim.com/sensitive_action?update=1).
  5. The user eventually closes the tab or navigates away, saving the poisoned prediction to the browser’s local LCPP database under the victim.com key.
  6. Days later, the user types victim.com into the Omnibox.
  7. 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 (including SameSite=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.

View on issue tracker