CVE-2026-87619
Overview
Background
- Prefetch
- Chromium’s speculative loading mechanism that fetches a resource before navigation, implemented in
content::PrefetchService. - StoragePartition
- the isolated storage container (cookies, cache, service workers) that a
BrowserContextassigns to a given origin or embedder context. - Default storage partition
- the
StoragePartitionreturned byBrowserContext::GetDefaultStoragePartition(), used for ordinary top-level browsing. - Renderer initiator info
- the
GetRendererInitiatorInfo()record on a prefetch request that ties the request back to theRenderFrameHostthat initiated it.
Root Cause Analysis
The eligibility check in content/browser/preloading/prefetch/prefetch_service.cc only compared default_storage_partition against GetStoragePartitionForUrl(params.url), verifying that the target URL mapped to the default partition. It never verified that the initiating frame itself lived in the default partition, so a prefetch begun from a frame hosted in a non-default StoragePartition was still treated as eligible and serviced through the default partition. This violated the invariant that prefetch’s cookie and service-worker checks must run against the same partition as the initiator, producing an observable cross-partition discrepancy.
The fix computes initiator_storage_partition from renderer_info->GetRenderFrameHost()->GetStoragePartition() and adds it as a rejection condition, so any prefetch whose initiator is not in the default partition is finished as PreloadingEligibility::kNonDefaultStoragePartition.
initiator_storage_partition before allowing prefetch to proceed.Attack Path
- Host a frame in a non-default partition
An embedder scenario places a frame (with an initiating
RenderFrameHost) inside a non-defaultStoragePartition. - Trigger a prefetch That frame initiates a prefetch whose target URL resolves to the default storage partition.
- Bypass the partition guard The old check passes because only the target URL’s partition matched the default, so eligibility is granted.
- Observe the discrepancy Cookie and service-worker state from the default partition influence the prefetch behavior, exposing an observable difference across the partition boundary.
Impact Assessment
StoragePartition. Consistent with the metadata, the impact is a low-severity information/observability leak rather than memory corruption.Files Changed
content/browser/preloading/prefetch/prefetch_service.cc
Audit Directions
- Initiator-vs-target partition parityWherever prefetch, prerender, or speculative loading makes an eligibility decision, confirm the initiator’s
StoragePartitionis validated, not just the target URL’s. - Optional initiator infoAudit uses of
GetRendererInitiatorInfo()andGetRenderFrameHost()for null cases, ensuring a missing initiator defaults safely to the default partition as the fix does. - Cross-partition observabilityReview other browser-process checks that gate on cookies or service workers to ensure they run against a single consistent partition, avoiding observable discrepancies across partition boundaries.
Patch
From 9a5cd91885172d12f35c0f826c5f9c729973231b Mon Sep 17 00:00:00 2001
From: Test User <chikamune@google.com>
Date: Thu, 25 Jun 2026 22:27:43 -0700
Subject: [PATCH] Bugfix: This CL fixes a bug reported in crbug.com/497491557
Analysis Document:
https://docs.google.com/document/d/16iYyG4o-2tAOFEJ1mIkzk6_EQmxMbjM9sX40sLNBUiE/edit?resourcekey=0-JEFuIGKb8N7MgyRLpV9TtA&tab=t.0#heading=h.25s5vbi8dmhl
Bug: 497491557
Change-Id: I788105707bec3c840395a00fc1817e84f219fe5f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7901928
Commit-Queue: Minoru Chikamune <chikamune@chromium.org>
Reviewed-by: Kouhei Ueno <kouhei@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1652905}
---
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index 4f9956a..0aaf4203 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -947,9 +947,17 @@
// check for service workers and existing cookies.
StoragePartition* default_storage_partition =
browser_context_->GetDefaultStoragePartition();
- if (default_storage_partition !=
- browser_context_->GetStoragePartitionForUrl(params.url,
- /*can_create=*/false)) {
+ StoragePartition* initiator_storage_partition = default_storage_partition;
+ if (auto* renderer_info = params.request().GetRendererInitiatorInfo()) {
+ if (auto* rfh = renderer_info->GetRenderFrameHost()) {
+ initiator_storage_partition = rfh->GetStoragePartition();
+ }
+ }
+
+ if (initiator_storage_partition != default_storage_partition ||
+ default_storage_partition !=
+ browser_context_->GetStoragePartitionForUrl(params.url,
+ /*can_create=*/false)) {
std::move(params).Finish(
PreloadingEligibility::kNonDefaultStoragePartition);
return;