CVE-2026-11219
Overview
Files Changed
content/browser/renderer_host/navigator.cccontent/browser/renderer_host/render_frame_host_impl.cc
Patch
From 96f02574c13dad362717a59f1c654dda219aba08 Mon Sep 17 00:00:00 2001
From: Rakina Zata Amni <rakina@chromium.org>
Date: Mon, 04 May 2026 11:54:33 -0700
Subject: [PATCH] Check lifecycle state on renderer-initiated navigation IPCs
We should disallow navigations from inactive RFHs consistently across
all navigation-related IPCs. We already disallow navigations in this
way in RFHI::BeginNavigation(), and this CL just extends the same
check to other renderer-initiated navigation entrypoints.
Bug: 480074849
Change-Id: Ifa3133dc2453c6782b1f99308398dd7fb44f50dd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7805947
Commit-Queue: Rakina Zata Amni <rakina@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1624850}
---
diff --git a/content/browser/renderer_host/navigator.cc b/content/browser/renderer_host/navigator.cc
index 082682a4176..8cec2674 100644
--- a/content/browser/renderer_host/navigator.cc
+++ b/content/browser/renderer_host/navigator.cc
@@ -38,6 +38,7 @@
#include "content/common/navigation_params_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/disallow_activation_reason.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/invalidate_type.h"
#include "content/public/browser/navigation_controller.h"
@@ -1228,6 +1229,18 @@
return;
}
+ // Only active and prerendered documents are allowed to start navigation in
+ // their frame.
+ if (render_frame_host->lifecycle_state() !=
+ RenderFrameHostImpl::LifecycleStateImpl::kPrerendering) {
+ // If this is reached in case the RenderFrameHost is in BackForwardCache
+ // evict the document from BackForwardCache.
+ if (render_frame_host->IsInactiveAndDisallowActivation(
+ DisallowActivationReasonId::kBeginNavigation)) {
+ return;
+ }
+ }
+
controller_.NavigateFromFrameProxy(
render_frame_host, url, initiator_frame_token, initiator_process_id,
initiator_origin, initiator_base_url, is_renderer_initiated,
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 3bb0b14..9cc0d35 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -8758,6 +8758,17 @@
return;
}
+ // Only active and prerendered documents are allowed to start navigation in
+ // their frame.
+ if (lifecycle_state() != LifecycleStateImpl::kPrerendering) {
+ // If this is reached in case the RenderFrameHost is in BackForwardCache
+ // evict the document from BackForwardCache.
+ if (IsInactiveAndDisallowActivation(
+ DisallowActivationReasonId::kBeginNavigation)) {
+ return;
+ }
+ }
+
// All frames are allowed to navigate the global history.
if (delegate_->IsAllowedToGoToEntryAtOffset(offset)) {
frame_tree_->controller().GoToOffsetFromRenderer(
@@ -8781,6 +8792,18 @@
frame_tree_->root()->navigation_request(), has_user_gesture)) {
return;
}
+
+ // Only active and prerendered documents are allowed to start navigation in
+ // their frame.
+ if (lifecycle_state() != LifecycleStateImpl::kPrerendering) {
+ // If this is reached in case the RenderFrameHost is in BackForwardCache
+ // evict the document from BackForwardCache.
+ if (IsInactiveAndDisallowActivation(
+ DisallowActivationReasonId::kBeginNavigation)) {
+ return;
+ }
+ }
+
frame_tree_->controller().NavigateToNavigationApiKey(this, task_id, key,
actual_navigation_start);
}
Original Bug Report
Previous page executes JS after navigation, allowing history.back() tab hijack
Steps to reproduce the problem
Steps to Reproduce
(Optional) Start the Node.js webhook server for logging.
Serve the provided PoCs (PoC-redir.html and PoC-listener.html) using any HTTP server (not file://).
Open PoC-listener.html in Chrome Stable.
Trigger a cross-origin navigation using any of the following methods:
Click the provided link
Type a new URL in the address bar and press Enter
Drag the link to the tab bar
Wait ~2 seconds after navigation completes.
✅ Expected Behavior
After a cross-origin navigation fully commits:
The previous document should be frozen (BFCache) or destroyed.
No JavaScript should execute.
History APIs such as history.back() or history.go() should not be callable.
The old document must not regain control of the tab.
❌ Actual Behavior
After navigation completes:
The previous document continues executing JavaScript for approximately 2–3 seconds.
The page is able to call history.back() and regain control of the tab without user interaction.
Chrome DevTools reports the page being restored from BFCache, but the page is still able to execute code before freeze occurs.
Observed console message:
Navigation to PoC-listener.html was restored from back/forward cache
Despite this, the previous document executes code and triggers automatic navigation back to the attacker-controlled page.
📄 PoC Behavior Details PoC-redir.html
Uses two setTimeout(1ms) calls to trigger navigation and execute code before unload.
Allows the previous page to retain access to APIs such as localStorage after navigation has committed.
PoC-listener.html
Uses the pagehide event to execute code after navigation begins.
Demonstrates that the issue is not limited to programmatic navigation.
Works with:
Link clicks
Address bar navigation
Drag-and-drop navigation
This method is more realistic for real-world abuse.Additional Observations
requestAnimationFrame fires once after navigation.
Network requests initiated by the old document appear as permanently pending in the new page’s Network tab.
Request origin remains attributed to the original document.
Behavior is reproducible in:
Incognito mode
Fresh Chrome installation
rel=noopener does not mitigate this issue because it occurs in the same browsing context.
⚠ Security Impact
This allows a navigated-away document to:
Regain control of the active tab without user interaction.
Manipulate browser history using history.go(n) and history.back().
Perform phishing-style tab hijacking by forcing navigation back to attacker-controlled content.
This breaks the expected navigation lifecycle security guarantees.
Problem Description
After a cross-origin navigation fully commits, the previous document is expected to be frozen (BFCache) or destroyed and must no longer be able to execute JavaScript or invoke browser APIs such as History. However, the previous page continues executing JavaScript for approximately 2–3 seconds after navigation has completed.
During this post-navigation window, the old document is able to call history.back() and regain control of the active tab without any user interaction. This behavior occurs even though Chrome reports the page as being restored from the back/forward cache. This indicates that there is a lifecycle race window where the document remains partially active before being frozen.
This issue is reproducible using both timer-based execution (PoC-redir.html) and event-based execution using the pagehide event (PoC-listener.html). The listener-based PoC demonstrates that the behavior is not limited to programmatic navigation and can also be triggered using normal user navigation methods such as clicking links, typing URLs in the address bar, or dragging links to the tab bar.
Additional observations show that requestAnimationFrame fires once after navigation, and network requests initiated by the old document appear as permanently pending in the new page’s Network tab while retaining the original origin attribution.
The issue is reproducible on a fresh Chrome installation and in Incognito mode without any extensions. The behavior does not rely on window.open or opener relationships and occurs in the same browsing context, making rel=noopener ineffective as a mitigation.
This behavior breaks the expected navigation lifecycle security guarantees. A navigated-away document should not retain the ability to execute code or manipulate browser history. The current behavior enables phishing-style tab hijacking attacks by allowing an attacker-controlled page to force navigation back to itself after the user has already navigated to a trusted cross-origin site.
Summary
Previous page executes JS after navigation, allowing history.back() tab hijack
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \