Medium watch CVSS 3.1 chrome Info Disclosure 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
3.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in BFCache
DescriptionInformation leak in BFCache
ComponentBFCache
Sub-componentBFCache
Bug ClassInfo Disclosure
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
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.
Tracker520121111
Fix commit67e1aad4cc16 (chromium/src) +74/-11
CWECWE-200 (Information exposure)
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-06-04 (per vendor advisory)
Fix landed2026-07-24 (public git)
Disclosed2026-08-25
Patch gap32 days — fix public in source before the advisory

Background

`BackForwardCacheMetrics`
A ref-counted browser-side object associated with a main-frame document and shared between all NavigationEntryImpls with the same main-document document_sequence_number_; among other things it carries the NotRestoredReasons tree for that document.
Document sequence number (DSN)
An identifier for a document within a history entry; in this code path the committing value arrives as params.document_sequence_number, i.e. it is supplied by the renderer in commit parameters.
`NotRestoredReasons` API
A web-exposed API that reports to a page why its back/forward navigation was not served from BFCache, including a per-frame reasons tree, so its contents must belong to the document it is reported to.
`SiteInstance`
The browser’s grouping of documents by site for process isolation; a same-document main-frame navigation never changes the SiteInstance.

Root Cause Analysis

BackForwardCacheMetrics::CreateOrReuseBackForwardCacheMetricsForNavigation(), called from the navigation-commit path in navigation_controller_impl.cc, decides whether a new navigation entry should share the previous entry’s metrics object. Before the patch, for a main-frame navigation it returned previous_entry_metrics whenever committing_document_sequence_number == previous_entry_metrics->document_sequence_number_, treating a DSN match alone as proof the navigation was same-document. Because that DSN comes from the renderer and, per the commit message, is not a reliable same-document signal, a main-frame commit in an unrelated cross-site SiteInstance could be bound to the previous entry’s metrics, including its NotRestoredReasons tree, which could later be reported through the NotRestoredReasons API to the wrong document.

The fix adds a SiteInstanceImpl* committing_main_frame_site_instance parameter (the caller passes rfh->GetSiteInstance() for main-frame navigations, nullptr otherwise) and tightens the main-frame reuse condition to also require previous_entry->site_instance() == committing_main_frame_site_instance. When the previous entry has no SiteInstance (e.g. after session restore) the old DSN-only behavior is deliberately retained, and subframe navigations still always reuse the metrics.

Key insight
A renderer-supplied document sequence number was trusted as the sole proof that a main-frame commit was same-document, letting one site’s BFCache metrics be attached to another site’s entry; the fix cross-checks it against a browser-known invariant, that same-document navigations keep the same SiteInstance.

Attack Path

  1. Victim document creates metrics A main-frame document on one site commits and its NavigationEntryImpl gets a BackForwardCacheMetrics object holding that document’s DSN and, later, its NotRestoredReasons data.
  2. Cross-site main-frame commit reports a matching DSN A subsequent main-frame navigation commits in a different SiteInstance but its commit params carry a document_sequence_number equal to the previous entry’s (as modelled by the regression test); doing this deliberately most plausibly requires a compromised renderer or an unusual DSN collision (inferred).
  3. Metrics object is reused Pre-patch, CreateOrReuseBackForwardCacheMetricsForNavigation() saw only the DSN match and returned previous_entry_metrics for the new entry.
  4. Reasons reported to the wrong document The previous document’s NotRestoredReasons tree is later surfaced via the NotRestoredReasons API for the unrelated cross-site entry, disclosing information about the other document.

Impact Assessment

The consequence is a cross-site information leak: an attacker-controlled document can observe BFCache NotRestoredReasons data that belongs to a different site’s document, which may reveal details of that document’s frames or of why it could not be cached (exact fields exposed are inferred, as the diff does not show the reporting code). The leak is in data the browser process hands back to web content, but reaching the mismatched-DSN condition likely depends on a compromised renderer or a rare DSN collision (inferred), and the existing kCheckDocumentSequenceNumber block is noted in the code as not enforceable on Android. The advisory states only “Information leak in BFCache” and reports no in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
NavigationEntryImpl
content/browser/back_forward_cache/back_forward_cache_metrics.h
modified
NavigationRequest
content/browser/back_forward_cache/back_forward_cache_metrics.h
modified
RenderFrameHostImpl
content/browser/back_forward_cache/back_forward_cache_metrics.h
modified
SiteInstanceImpl
content/browser/back_forward_cache/back_forward_cache_metrics.h
modified
BackForwardCacheMetrics
content/browser/back_forward_cache/back_forward_cache_metrics.h
modified
CONTENT_EXPORT
content/browser/back_forward_cache/back_forward_cache_metrics.h
modified
TestObserver
content/browser/back_forward_cache/back_forward_cache_metrics.h
modified
TEST_F
content/browser/back_forward_cache/back_forward_cache_metrics_unittest.cc
modified

Files Changed

  • content/browser/back_forward_cache/back_forward_cache_metrics_unittest.cc
  • content/browser/back_forward_cache/back_forward_cache_metrics.cc
  • content/browser/back_forward_cache/back_forward_cache_metrics.h
  • content/browser/renderer_host/navigation_controller_impl.cc

Audit Directions

  • Renderer-supplied identity in commit params
    Audit other uses of document_sequence_number, item_sequence_number and similar DidCommitProvisionalLoadParams fields where a match is used to share or attach browser-side state across entries; confirm each is cross-checked against browser-known facts (SiteInstance, origin, RenderFrameHost).
  • Web-exposed diagnostics carrying cross-document data
    Review APIs such as NotRestoredReasons and other navigation/performance timing reports for paths where state attached to a NavigationEntryImpl can be inherited by an entry of a different site.
  • Fallback branches that skip the new check
    Examine the null-SiteInstance (session restore) fallback and the Android path where kCheckDocumentSequenceNumber is not enforced, to see whether a DSN-only decision can still be reached by a cross-site commit.
From 67e1aad4cc16ee14b6ed7d6a2057e36695ca8556 Mon Sep 17 00:00:00 2001
From: Anna Sato <annasato@chromium.org>
Date: Thu, 23 Jul 2026 23:12:41 -0700
Subject: [PATCH] [BFCache] Gate metrics reuse on main-frame SiteInstance

CreateOrReuseBackForwardCacheMetricsForNavigation reused the previous
NavigationEntry's BackForwardCacheMetrics whenever the renderer-supplied
document sequence number matched, regardless of SiteInstance. The
document sequence number alone is not a reliable signal that two
main-frame commits belong to the same document, so the previous entry's
NotRestoredReasons tree could end up attached to an unrelated cross-site
entry and later be reported via the NotRestoredReasons API for the wrong
document.

Same-document main-frame navigations never change SiteInstance, so
require the committing main-frame SiteInstance to match the previous
entry's before treating a document sequence number match as
same-document. A null previous-entry SiteInstance (e.g. after session
restore) continues to rely on the sequence number alone, matching the
existing behaviour in SetSkippableForSameDocumentEntries.

Also adds a unit test covering both the cross-SiteInstance and
same-SiteInstance cases.

TAG=agy
CONV=23a358e0-79f8-4fd1-8ddc-2be156279091

Bug: 520121111
Fixed: 520121111
Change-Id: I67b37359f65dbbf156bad242ec9f2ba10435ba66
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8137640
Commit-Queue: Anna Sato <annasato@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1667662}
---
diff --git a/content/browser/back_forward_cache/back_forward_cache_metrics.cc b/content/browser/back_forward_cache/back_forward_cache_metrics.cc
index 3283359..8c76e1bb 100644
--- a/content/browser/back_forward_cache/back_forward_cache_metrics.cc
+++ b/content/browser/back_forward_cache/back_forward_cache_metrics.cc
@@ -95,7 +95,8 @@
 BackForwardCacheMetrics::CreateOrReuseBackForwardCacheMetricsForNavigation(
     NavigationEntryImpl* previous_entry,
     bool is_main_frame_navigation,
-    int64_t committing_document_sequence_number) {
+    int64_t committing_document_sequence_number,
+    SiteInstanceImpl* committing_main_frame_site_instance) {
   // TODO(https://crbug.com/445585641): Make this enforceable on Android.
 #if !BUILDFLAG(IS_ANDROID)
   if (base::FeatureList::IsEnabled(kCheckDocumentSequenceNumber)) {
@@ -123,10 +124,18 @@
   }
 
   // Reuse `previous_entry_metrics` on subframe navigations and same-document
-  // navigations.
+  // main-frame navigations. The document sequence number used to detect the
+  // latter is supplied by the renderer; since same-document navigations never
+  // change SiteInstance, also require the committing SiteInstance to match
+  // `previous_entry`'s before treating the navigation as same-document.
+  // `previous_entry`'s SiteInstance may be null after session restore, in
+  // which case the document sequence number alone is trusted.
   if (!is_main_frame_navigation ||
-      committing_document_sequence_number ==
-          previous_entry_metrics->document_sequence_number_) {
+      (committing_document_sequence_number ==
+           previous_entry_metrics->document_sequence_number_ &&
+       (!previous_entry->site_instance() ||
+        previous_entry->site_instance() ==
+            committing_main_frame_site_instance))) {
     return previous_entry_metrics;
   }
 
diff --git a/content/browser/back_forward_cache/back_forward_cache_metrics.h b/content/browser/back_forward_cache/back_forward_cache_metrics.h
index 00f9887..6c5706a 100644
--- a/content/browser/back_forward_cache/back_forward_cache_metrics.h
+++ b/content/browser/back_forward_cache/back_forward_cache_metrics.h
@@ -42,13 +42,14 @@
 class NavigationEntryImpl;
 class NavigationRequest;
 class RenderFrameHostImpl;
+class SiteInstanceImpl;
 struct BackForwardCacheCanStoreDocumentResultWithTree;
 
 // Helper class for recording metrics around history navigations.
 // Associated with a main frame document and shared between all
 // NavigationEntries with the same document_sequence_number for the main
 // document.
-class BackForwardCacheMetrics
+class CONTENT_EXPORT BackForwardCacheMetrics
     : public base::RefCounted<BackForwardCacheMetrics> {
  public:
   using NotRestoredReason = BackForwardCache::NotRestoredReason;
@@ -99,13 +100,17 @@
   // navigation is a subframe navigation or if it's same-document with
   // `previous_entry`'s document.
   //
-  // |document_sequence_number| is the sequence number of the document
-  // associated with the navigating frame.
+  // `committing_document_sequence_number` is the sequence number of the
+  // document associated with the navigating frame.
+  // `committing_main_frame_site_instance` is the SiteInstance the main frame
+  // is committing in, and is only used when `is_main_frame_navigation` is
+  // true.
   static scoped_refptr<BackForwardCacheMetrics>
   CreateOrReuseBackForwardCacheMetricsForNavigation(
       NavigationEntryImpl* previous_entry,
       bool is_main_frame_navigation,
-      int64_t committing_document_sequence_number);
+      int64_t committing_document_sequence_number,
+      SiteInstanceImpl* committing_main_frame_site_instance);
 
   explicit BackForwardCacheMetrics(int64_t document_sequence_number);
 
@@ -188,11 +193,11 @@
 
   // Exported for testing.
   // The DisabledReason's source and id combined to give a unique uint64.
-  CONTENT_EXPORT static uint64_t MetricValue(BackForwardCache::DisabledReason);
+  static uint64_t MetricValue(BackForwardCache::DisabledReason);
 
   // Injects a clock for mocking time.
   // Should be called only from the UI thread.
-  CONTENT_EXPORT static void OverrideTimeForTesting(base::TickClock* clock);
+  static void OverrideTimeForTesting(base::TickClock* clock);
 
   class TestObserver {
    public:
diff --git a/content/browser/renderer_host/navigation_controller_impl.cc b/content/browser/renderer_host/navigation_controller_impl.cc
index f6fd7bb..b02d351c 100644
--- a/content/browser/renderer_host/navigation_controller_impl.cc
+++ b/content/browser/renderer_host/navigation_controller_impl.cc
@@ -1916,7 +1916,8 @@
     back_forward_cache_metrics = BackForwardCacheMetrics::
         CreateOrReuseBackForwardCacheMetricsForNavigation(
             GetLastCommittedEntry(), is_main_frame_navigation,
-            params.document_sequence_number);
+            params.document_sequence_number,
+            is_main_frame_navigation ? rfh->GetSiteInstance() : nullptr);
   }
 
   // Notify the last active entry that we have navigated away.
diff --git a/content/browser/back_forward_cache/back_forward_cache_metrics_unittest.cc b/content/browser/back_forward_cache/back_forward_cache_metrics_unittest.cc
index 2d2cef5f..620d544 100644
--- a/content/browser/back_forward_cache/back_forward_cache_metrics_unittest.cc
+++ b/content/browser/back_forward_cache/back_forward_cache_metrics_unittest.cc
@@ -2,12 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "content/browser/back_forward_cache/back_forward_cache_metrics.h"
+
 #include "base/sanitizer_buildflags.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/browser/back_forward_cache/back_forward_cache_impl.h"
+#include "content/browser/renderer_host/navigation_controller_impl.h"
+#include "content/browser/renderer_host/navigation_entry_impl.h"
+#include "content/browser/site_instance_impl.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -388,6 +393,49 @@
       BackForwardCacheMetrics::HistoryNavigationOutcome::kNotRestored, 1);
 }
 
+TEST_F(BackForwardCacheMetricsTest,
+       MetricsNotReusedForCrossSiteInstanceMainFrameNavigation) {
+  const GURL url1("http://a.com/");
+  const GURL url2("http://b.com/");
+
+  NavigationControllerImpl& controller =
+      static_cast<NavigationControllerImpl&>(contents()->GetController());
+
+  // Navigate to `url1` and capture the entry's metrics, SiteInstance and
+  // document sequence number.
+  NavigationSimulator::NavigateAndCommitFromDocument(url1, main_test_rfh());
+  NavigationEntryImpl* entry1 = controller.GetEntryAtIndex(0);
+  BackForwardCacheMetrics* metrics1 = entry1->back_forward_cache_metrics();
+  SiteInstanceImpl* site_instance1 = entry1->site_instance();
+  int64_t dsn1 = entry1->root_node()->frame_entry->document_sequence_number();
+  ASSERT_TRUE(metrics1);
+  ASSERT_TRUE(site_instance1);
+
+  // Navigate cross-site to `url2`, which commits in a different SiteInstance.
+  NavigationSimulator::NavigateAndCommitFromDocument(url2, main_test_rfh());
+  NavigationEntryImpl* entry2 = controller.GetEntryAtIndex(1);
+  SiteInstanceImpl* site_instance2 = entry2->site_instance();
+  ASSERT_TRUE(site_instance2);
+  ASSERT_NE(site_instance1, site_instance2);
+  ASSERT_NE(entry2->back_forward_cache_metrics(), metrics1);
+
+  // A main-frame navigation that reports `entry1`'s document sequence number
+  // but commits in `entry2`'s SiteInstance must not reuse `entry1`'s metrics,
+  // since the document sequence number is renderer-supplied and a
+  // same-document navigation never changes SiteInstance.
+  scoped_refptr<BackForwardCacheMetrics> result = BackForwardCacheMetrics::
+      CreateOrReuseBackForwardCacheMetricsForNavigation(
+          entry1, /*is_main_frame_navigation=*/true, dsn1, site_instance2);
+  EXPECT_NE(result.get(), metrics1);
+
+  // A main-frame navigation reporting `entry1`'s document sequence number and
+  // committing in `entry1`'s SiteInstance should still reuse the metrics.
+  result = BackForwardCacheMetrics::
+      CreateOrReuseBackForwardCacheMetricsForNavigation(
+          entry1, /*is_main_frame_navigation=*/true, dsn1, site_instance1);
+  EXPECT_EQ(result.get(), metrics1);
+}
+
 TEST_F(BackForwardCacheMetricsTest, PageWithFormsMetricsNotStore) {
   DisableBackForwardCacheForTesting(contents(),
                                     BackForwardCache::TEST_REQUIRES_NO_CACHING);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/back_forward_cache/back_forward_cache_metrics_unittest.cc b/content/browser/back_forward_cache/back_forward_cache_metrics_unittest.cc
index 2d2cef5f..620d544 100644
--- a/content/browser/back_forward_cache/back_forward_cache_metrics_unittest.cc
+++ b/content/browser/back_forward_cache/back_forward_cache_metrics_unittest.cc
@@ -2,12 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "content/browser/back_forward_cache/back_forward_cache_metrics.h"
+
 #include "base/sanitizer_buildflags.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/browser/back_forward_cache/back_forward_cache_impl.h"
+#include "content/browser/renderer_host/navigation_controller_impl.h"
+#include "content/browser/renderer_host/navigation_entry_impl.h"
+#include "content/browser/site_instance_impl.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -388,6 +393,49 @@
       BackForwardCacheMetrics::HistoryNavigationOutcome::kNotRestored, 1);
 }
 
+TEST_F(BackForwardCacheMetricsTest,
+       MetricsNotReusedForCrossSiteInstanceMainFrameNavigation) {
+  const GURL url1("http://a.com/");
+  const GURL url2("http://b.com/");
+
+  NavigationControllerImpl& controller =
+      static_cast<NavigationControllerImpl&>(contents()->GetController());
+
+  // Navigate to `url1` and capture the entry's metrics, SiteInstance and
+  // document sequence number.
+  NavigationSimulator::NavigateAndCommitFromDocument(url1, main_test_rfh());
+  NavigationEntryImpl* entry1 = controller.GetEntryAtIndex(0);
+  BackForwardCacheMetrics* metrics1 = entry1->back_forward_cache_metrics();
+  SiteInstanceImpl* site_instance1 = entry1->site_instance();
+  int64_t dsn1 = entry1->root_node()->frame_entry->document_sequence_number();
+  ASSERT_TRUE(metrics1);
+  ASSERT_TRUE(site_instance1);
+
+  // Navigate cross-site to `url2`, which commits in a different SiteInstance.
+  NavigationSimulator::NavigateAndCommitFromDocument(url2, main_test_rfh());
+  NavigationEntryImpl* entry2 = controller.GetEntryAtIndex(1);
+  SiteInstanceImpl* site_instance2 = entry2->site_instance();
+  ASSERT_TRUE(site_instance2);
+  ASSERT_NE(site_instance1, site_instance2);
+  ASSERT_NE(entry2->back_forward_cache_metrics(), metrics1);
+
+  // A main-frame navigation that reports `entry1`'s document sequence number
+  // but commits in `entry2`'s SiteInstance must not reuse `entry1`'s metrics,
+  // since the document sequence number is renderer-supplied and a
+  // same-document navigation never changes SiteInstance.
+  scoped_refptr<BackForwardCacheMetrics> result = BackForwardCacheMetrics::
+      CreateOrReuseBackForwardCacheMetricsForNavigation(
+          entry1, /*is_main_frame_navigation=*/true, dsn1, site_instance2);
+  EXPECT_NE(result.get(), metrics1);
+
+  // A main-frame navigation reporting `entry1`'s document sequence number and
+  // committing in `entry1`'s SiteInstance should still reuse the metrics.
+  result = BackForwardCacheMetrics::
+      CreateOrReuseBackForwardCacheMetricsForNavigation(
+          entry1, /*is_main_frame_navigation=*/true, dsn1, site_instance1);
+  EXPECT_EQ(result.get(), metrics1);
+}
+
 TEST_F(BackForwardCacheMetricsTest, PageWithFormsMetricsNotStore) {
   DisableBackForwardCacheForTesting(contents(),
                                     BackForwardCache::TEST_REQUIRES_NO_CACHING);
Loading diff…

Original Bug Report

reported by vm...@google.com

Cross-origin BackForwardCacheMetrics metadata leakage via forged document sequence number

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential logic flaw in Chromium’s Back/Forward Cache metrics allows a compromised renderer to bypass Site Isolation and leak cross-origin NotRestoredReasons. By forging a document sequence number (DSN) during navigation, an attacker’s renderer can potentially reuse a victim’s metrics object containing sensitive frame-tree information. This could expose URLs, iframe properties, and script source locations of the previously visited victim site to the attacker.

Affected files:

  • content/browser/back_forward_cache/back_forward_cache_metrics.cc
  • content/browser/renderer_host/navigation_controller_impl.cc
  • content/browser/renderer_host/navigation_request.cc

Estimated timestamp from git blame: 2019-04-02

Description

A potential vulnerability exists in the Back/Forward Cache metrics subsystem where a compromised renderer can bypass Site Isolation to access sensitive, cross-origin frame-tree metadata of a previously visited page.

In content/browser/back_forward_cache/back_forward_cache_metrics.cc:126-130, the function CreateOrReuseBackForwardCacheMetricsForNavigation is responsible for choosing whether to associate a new navigation with the previous entry’s BackForwardCacheMetrics object or create a new one. This decision is made solely based on the equality of a renderer-supplied document_sequence_number (DSN):

// content/browser/back_forward_cache/back_forward_cache_metrics.cc:126-130
if (!is_main_frame_navigation ||
    committing_document_sequence_number ==
        previous_entry_metrics->document_sequence_number_) {
  return previous_entry_metrics;
}

Because the committing renderer controls the document_sequence_number parameter inside the DidCommitProvisionalLoad Mojo IPC, a compromised renderer could forge this sequence number. There is currently no SiteInstance or origin check in this metrics reuse path. While a sibling DSN consumer in content/browser/renderer_host/navigation_controller_impl.cc:5211-5214 was previously hardened with a SiteInstance validation to prevent forgery, this metrics reuse path remains unprotected.

Potential Attack Scenario

(Note: These are potential, suggested steps; our tooling does not currently run active exploits.)

  1. Victim Commit: The user loads https://victim.com (which delivers Cache-Control: no-store to make it BFCache-ineligible). The browser associates a BackForwardCacheMetrics object ($M_v$) with its navigation entry, setting the tracked DSN to $V$.
  2. Navigation and Unload: The user navigates to the attacker’s page https://attacker.com. During the unload of the victim page, UnloadOldFrame() runs first. Because the page is ineligible for BFCache, SetNotRestoredReasons() is called, populating $M_v$ with the victim’s complete frame-tree details (including subframe URLs, names, attributes, and JavaScript locations).
  3. DSN Forgery: The compromised attacker.com renderer process estimates the victim’s DSN ($V$) by bounding the victim process’s startup time. Upon commit, the attacker’s renderer transmits a forged document_sequence_number equal to $V$ inside the DidCommitProvisionalLoad IPC parameters.
  4. Metrics Hijacking: Because the forged DSN matches the last committed entry’s metrics DSN, CreateOrReuseBackForwardCacheMetricsForNavigation returns the victim’s metrics object ($M_v$) with no validation. This binds the victim’s populated metrics object to the attacker’s NavigationEntry ($E_a$).
  5. State Pinning: The attacker renderer performs a same-document navigation (such as a history.pushState()), forging the DSN $V$ again. This creates a new entry $E_{a2}$ that shares $M_v$ and shifts the active nav_entry_id to $E_{a2}$.
  6. Intermediate Navigation: The attacker navigates the tab to a BFCache-eligible page https://attacker.com/third with a normal DSN, putting the primary attacker page into the BFCache (keyed under $E_{a2}$).
  7. BFCache-Bypass Restoration: The attacker’s script calls history.go(-2) to target $E_a$. The BFCache lookup misses because the cached page is keyed under $E_{a2}$’s ID. This initiates a full cross-document history restoration navigation to $E_a$.
  8. Data Leaked to Attacker: In content/browser/renderer_host/navigation_request.cc:2282-2294, during the restoration navigation, the browser extracts the web-exposed reasons from the hijacked metrics object ($M_v$) and writes them to commit_params_->not_restored_reasons which are then committed directly to the attacker’s renderer. The attacker reads the victim’s URL tree from performance.getEntriesByType('navigation')[0].notRestoredReasons.

Suggested Fix

To prevent this cross-origin metrics reuse, enforce a SiteInstance validation gate inside CreateOrReuseBackForwardCacheMetricsForNavigation (or its browser-process callers). For example, ensure that the metrics object is only reused if the previous_entry’s SiteInstance matches the SiteInstance associated with the committing navigation request.

// Suggested verification logic:
if (previous_entry->site_instance() && 
    previous_entry->site_instance() != committing_site_instance) {
  // Do not reuse metrics cross-origin
}

Evaluated with Chrome root at commit: 57b021e1fdae94a215627d29aeb1ccf2eb5b3e91


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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