CVE-2026-17760
Overview
Files Changed
chrome/common/chrome_features.cc
Patch
From 96eb3bf4e95bcca753660a2c4de9427305f7165c Mon Sep 17 00:00:00 2001
From: Kouhei Ueno <kouhei@chromium.org>
Date: Sun, 28 Jun 2026 19:17:05 -0700
Subject: [PATCH] Disable NSP to preconnect fallback
Fixed: 506473189
Change-Id: I15e4c6aa45e781b63a1d47ba012be447d3e393cb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8017042
Reviewed-by: Hiroki Nakagawa <nhiroki@chromium.org>
Auto-Submit: Kouhei Ueno <kouhei@chromium.org>
Commit-Queue: Hiroki Nakagawa <nhiroki@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1653833}
---
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 37d284bb..4afb40b 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -1365,7 +1365,7 @@
#endif
// Allows Chrome to do preconnect when prerender fails.
-BASE_FEATURE(kPrerenderFallbackToPreconnect, base::FEATURE_ENABLED_BY_DEFAULT);
+BASE_FEATURE(kPrerenderFallbackToPreconnect, base::FEATURE_DISABLED_BY_DEFAULT);
#if BUILDFLAG(IS_CHROMEOS)
// If enabled, use managed per-printer print job options set via
Original Bug Report
Cross-origin history leak via No-State Prefetch fallback to LoadingPredictor
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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A renderer-initiated No-State Prefetch can trigger a preconnect fallback path that inadvertently drops the initiator origin. This fallback queries the user’s browsing history to perform predictive actions, such as warming up ServiceWorkers and initiating preconnects. A malicious site can potentially observe these actions via global side channels to infer the user’s cross-origin browsing history.
Affected files:
chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_manager_delegate.cccomponents/no_state_prefetch/browser/no_state_prefetch_manager.ccchrome/browser/predictors/loading_predictor.cccomponents/no_state_prefetch/browser/no_state_prefetch_link_manager.ccchrome/browser/predictors/resource_prefetch_predictor.cc
Estimated timestamp from git blame: 2024-06-10
Summary
When a <link rel="prerender"> request fails under certain conditions (such as being a duplicate request or running on a low-end device), the No-State Prefetch (NSP) manager falls back to a preconnect path. During this fallback, the initiator_origin is dropped, and the request is incorrectly passed to the LoadingPredictor as a high-confidence, omnibox-initiated hint (OMNIBOX_PRERENDER_FALLBACK).
Because the LoadingPredictor treats this as a first-party navigation prediction, it queries the local user history to initiate preconnects to historical subresources and attempts to warm up ServiceWorker processes for the target site. An attacker can potentially measure these actions through side channels to leak the user’s cross-origin browsing history.
Root Cause Analysis
In components/no_state_prefetch/browser/no_state_prefetch_manager.cc, the SkipNoStatePrefetchContentsAndMaybePreconnect function is called when an NSP request is aborted for specific reasons (e.g., FINAL_STATUS_DUPLICATE, FINAL_STATUS_LOW_END_DEVICE). This triggers MaybePreconnect, which drops the initiator_origin entirely:
void NoStatePrefetchManager::MaybePreconnect(Origin origin,
const GURL& url_arg) const {
delegate_->MaybePreconnect(url_arg); // initiator_origin is lost
}
The implementation in ChromeNoStatePrefetchManagerDelegate::MaybePreconnect then calls LoadingPredictor::PrepareForPageLoad with a std::nullopt initiator and an OMNIBOX_PRERENDER_FALLBACK hint.
Inside LoadingPredictor::PrepareForPageLoad, the null initiator and high-confidence hint cause the browser to:
- Warm up ServiceWorkers:
MaybeWarmUpServiceWorkercreates a first-partyStorageKeyfor the target URL and spawns a worker process if the user previously registered one. - Preconnect to Historical Subresources: It calls
resource_prefetch_predictor_->PredictPreconnectOrigins, querying the local SQLite database for origins the user historically loaded when visiting the target site, and initiates network preconnects to them.
Potential Exploitation Steps
Note: These are suggested steps based on static code analysis; a working proof-of-concept has not yet been executed by our tooling.
- An attacker navigates the victim to their malicious site (
https://attacker.com). - The attacker’s JavaScript injects
<link rel="prerender" href="https://victim.com">to start an NSP request. - The attacker waits approximately 501ms. This bypasses the global 500ms rate limit (
DoesRateLimitAllowPrefetch). - The attacker injects a second identical
<link rel="prerender" href="https://victim.com">. - The second request passes the rate limit but hits the
FINAL_STATUS_DUPLICATEcheck inStartPrefetchingWithPreconnectFallbackbecause the first request is still active or recently finished. - This triggers the
MaybePreconnectfallback path, dropping theinitiator_originand invoking theLoadingPredictor. - The attacker observes the resulting side effects. For example, they can measure main-thread jank (via
requestAnimationFrame) caused by the ServiceWorker process spawn, or they can use socket pool exhaustion to count how many historical subresource preconnects the browser initiated.
(Alternatively, simply triggering a single prerender on a low-end device instantly hits FINAL_STATUS_LOW_END_DEVICE and triggers the same fallback path without needing duplicate requests.)
Suggested Fix
The initiator_origin must be preserved through the preconnect fallback chain.
- Update
NoStatePrefetchManager::MaybePreconnectanddelegate_->MaybePreconnectto accept thestd::optional<url::Origin>& initiator_origin. - Pass this
initiator_originintoLoadingPredictor::PrepareForPageLoad. - Ensure that
LoadingPredictoreither correctly isolates/partitions ServiceWorker warmups and history-based predictions based on theinitiator_origin, or explicitly drops/ignores renderer-initiated (kLinkRelPrerender) hints when performing history-sensitive operations.
Evaluated with Chrome root at commit: a1e33f5848218e21d4a16ae2c1bc94e815c30c7f
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. And please feel free to reach out to me directly if you have concerns or feedback on the project.