CVE-2026-113265
Overview
Fix not yet public
Original Bug Report
Cross-origin FrameNavigationEntry deletion via BFCached subframe Detach
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 vulnerability allows a compromised renderer to delete arbitrary FrameNavigationEntry subtrees belonging to a cross-origin foreground page. By invoking LocalFrameHost::Detach on a BFCached subframe, the browser-side handler resolves the active tab’s primary NavigationController via a stale FrameTree reference. An attacker can match and delete the victim’s subframe history state using an attacker-controlled unique frame name.
Affected files:
content/browser/renderer_host/render_frame_host_impl.cccontent/browser/renderer_host/frame_tree_node.cccontent/browser/renderer_host/navigation_entry_impl.cccontent/browser/renderer_host/render_frame_host_impl_interface_binders.cc
Estimated timestamp from git blame: 2025-10-01
Root Cause Analysis
In RenderFrameHostImpl::Detach() (the browser-side handler for the blink::mojom::LocalFrameHost Mojo interface), there is no release-build validation to check if the frame’s lifecycle state is LifecycleStateImpl::kInBackForwardCache ([content/browser/renderer_host/render_frame_host_impl.cc:6013]).
When Detach() is called on a BFCached subframe, the lifecycle state is updated to LifecycleStateImpl::kReadyToBeDeleted, and it initiates subtree pending deletion via StartPendingDeletionOnSubtree(PendingDeletionReason::kFrameDetach) ([content/browser/renderer_host/render_frame_host_impl.cc:6076]).
This eventually runs CleanupLastCommittedNavigationEntry() -> FrameTreeNode::MaybeRemoveFromLastCommittedEntry(). Inside MaybeRemoveFromLastCommittedEntry(), the browser resolves the current NavigationController via:
NavigationEntryImpl* nav_entry = navigator().controller().GetLastCommittedEntry();
Where navigator() refers to frame_tree().navigator() ([content/browser/renderer_host/frame_tree_node.cc:320]).
Crucially, when a page enters the Back-Forward Cache (BFCache), its main RenderFrameHostImpl is stored in a StoredPage structure, but the subframe FrameTreeNodes’ frame_tree_ reference (raw_ref<FrameTree>) is never modified or reassigned. It continues to point to the live primary FrameTree of the WebContents.
If the tab has navigated to a new cross-origin page (e.g., https://victim.com), the primary FrameTree’s active navigation controller will return the victim’s active NavigationEntryImpl. The browser then calls:
nav_entry->RemoveEntryForFrame(this, /*only_if_different_position=*/false);
This triggers a Breadth-First Search (BFS) over the victim’s navigation tree nodes, calling MatchesFrame() to locate a match ([content/browser/renderer_host/navigation_entry_impl.cc:1145]). Since the attacker can pre-configure the BFCached subframe’s replication state unique_name to collide with the victim’s target subframe name (e.g. default position-based names such as <!--framePath //<!--frame0-->-->), MatchesFrame() returns true and the victim’s FrameNavigationEntry (and its entire descendant subtree) is unconditionally deleted from the parent node ([content/browser/renderer_host/navigation_entry_impl.cc:266]).
This represents a potential cross-origin browser-state integrity violation where a compromised renderer can mutate another origin’s browser-process navigation state.
Suggested Exploitation Steps (Potential/Theoretical)
Note: Our tooling does not have the ability to execute code; the following are theoretical steps to trigger the potential vulnerability:
- A compromised renderer hosting
https://evil.cominvokesmojom::FrameHost::CreateChildFramewhile active, specifyingis_created_by_script = trueand aframe_unique_namethat matches a predictable, position-based name on a target victim site (e.g.,<!--framePath //<!--frame0-->-->). - The user or script navigates the top-level tab to
https://victim.com. Theevil.compage is placed into the Back-Forward Cache (kInBackForwardCache). - The victim page loads and populates its own subframes, adding a target subframe with the matching unique name to its active
NavigationEntryImpl. - The compromised
evil.comrenderer process (ignoring the cache freeze) sends aLocalFrameHost::Detach()IPC message to its BFCached subframe. - The browser dispatches the
Detach()call, transitioning the subframe’s state tokReadyToBeDeletedand executingMaybeRemoveFromLastCommittedEntry()viaStartPendingDeletionOnSubtree. - The browser incorrectly resolves the primary
FrameTree’s navigation entry (which is nowvictim.com’s navigation entry), finds the name match, and permanently erases the victim’s subframeFrameNavigationEntrysubtree.
Suggested Fix
To prevent this, RenderFrameHostImpl::Detach() should check if the frame is currently in the Back-Forward Cache and reject the call or terminate the calling renderer (e.g., via ReceivedBadMessage) if an unexpected detach IPC is received from a cached frame:
if (lifecycle_state() == LifecycleStateImpl::kInBackForwardCache) {
bad_message::ReceivedBadMessage(GetProcess(), bad_message::RFH_DETACH_WHILE_BFCACHED);
return;
}
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.