CVE-2026-7965
Overview
Files Changed
content/browser/devtools/devtools_agent_host_impl.cccontent/browser/renderer_host/render_frame_host_impl.cc
Patch
From a77ddcdbea95bd88b0534a0d33fcc7f2e30ab4a2 Mon Sep 17 00:00:00 2001 From: Danil Somsikov <[email protected]> Date: Thu, 02 Apr 2026 05:00:50 -0700 Subject: [PATCH] Fix DevTools Target Confusion vulnerability via Fenced Frames A compromised renderer can hijack DevTools sessions for cross-origin pages by supplying a stolen 'devtools_frame_token' during fenced frame creation. Previously, the browser improperly scoped token uniqueness validation to the current tab and silently overwrote the process-global DevTools target map on collision. This CL mitigates the vulnerability by: 1. Updating the validation in to check for global uniqueness across all DevTools instances using `DevToolsAgentHost::GetForId()`. If a collision is detected, the browser will now treat it as a Bad Message and terminate the malicious renderer process. 2. Hardening the map insertion logic in `DevToolsAgentHostImpl::NotifyCreated` by upgrading a `DCHECK` to a `CHECK`. This ensures that even if a collision somehow bypasses initial validation, the browser will crash safely in production rather than allowing a silent target hijack. Bug: 497255035 Change-Id: I9857e1378ab289be626e71aa12944ef8c9e9cb2f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7716666 Auto-Submit: Danil Somsikov <[email protected]> Reviewed-by: Andrey Kosyakov <[email protected]> Reviewed-by: Arthur Sonzogni <[email protected]> Commit-Queue: Danil Somsikov <[email protected]> Cr-Commit-Position: refs/heads/main@{#1609111} --- diff --git a/content/browser/devtools/devtools_agent_host_impl.cc b/content/browser/devtools/devtools_agent_host_impl.cc index e47209e..d7c727ed 100644 --- a/content/browser/devtools/devtools_agent_host_impl.cc +++ b/content/browser/devtools/devtools_agent_host_impl.cc @@ -556,7 +556,7 @@ } void DevToolsAgentHostImpl::NotifyCreated() { - DCHECK(!GetDevtoolsInstances().contains(id_)); + CHECK(!GetDevtoolsInstances().contains(id_)); GetDevtoolsInstances()[id_] = this; for (auto& observer : GetDevtoolsObservers()) observer.DevToolsAgentHostCreated(this); diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index 3f5964a6..cb6aa69 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc @@ -200,6 +200,7 @@ #include "content/public/browser/content_browser_client.h" #include "content/public/browser/context_menu_params.h" #include "content/public/browser/cookie_access_details.h" +#include "content/public/browser/devtools_agent_host.h" #include "content/public/browser/disallow_activation_reason.h" #include "content/public/browser/document_ref.h" #include "content/public/browser/document_service_internal.h" @@ -10586,17 +10587,12 @@ return; } - // Ensure the devtools frame token doesn't exist in the FrameTree for - // this tab. - for (FrameTreeNode* node : - GetOutermostMainFrame()->frame_tree()->NodesIncludingInnerTreeNodes()) { - if (node->current_frame_host()->devtools_frame_token() == - devtools_frame_token) { - bad_message::ReceivedBadMessage( - GetProcess(), - bad_message::RFHI_CREATE_FENCED_FRAME_BAD_DEVTOOLS_FRAME_TOKEN); - return; - } + // Ensure the devtools frame token doesn't exist globally. + if (DevToolsAgentHost::GetForId(devtools_frame_token.ToString())) { + bad_message::ReceivedBadMessage( + GetProcess(), + bad_message::RFHI_CREATE_FENCED_FRAME_BAD_DEVTOOLS_FRAME_TOKEN); + return; } // Inactive pages cannot create fenced frames. If the page is in the BFCache,
Original Bug Report
DevTools Target Confusion via Fenced Frame Token Collision
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A compromised renderer can hijack DevTools sessions for cross-origin pages by supplying a stolen devtools_frame_token during fenced frame creation. The browser improperly scopes token uniqueness validation to the current tab and silently overwrites the process-global DevTools target map on collision, allowing an attacker to intercept Chrome DevTools Protocol (CDP) commands.
Affected files:
content/browser/devtools/devtools_agent_host_impl.cccontent/browser/devtools/render_frame_devtools_agent_host.cccontent/browser/renderer_host/render_frame_host_impl.cccontent/browser/renderer_host/render_frame_proxy_host.cc
Estimated timestamp from git blame: 2026-01-06
Overview
A logic error exists in DevToolsAgentHostImpl where process-global DevTools target IDs can be overwritten due to insufficient validation during Fenced Frame creation. A compromised renderer can exploit this to hijack DevTools automation sessions (e.g., Puppeteer) connecting to cross-origin targets, leading to cross-origin data theft.
Vulnerability Details
The issue stems from a combination of three factors:
- Cross-WebContents Token Leak: When a renderer opens a cross-origin popup, the browser sends a
PageBroadcast.CreateRemoteMainFrameMojo IPC to the opener renderer so it can build a remote frame proxy. This IPC leaks the victim frame’sdevtools_frame_tokento the potentially compromised opener renderer. - Insufficient Validation Scope: When a renderer creates a Fenced Frame, it supplies a
devtools_frame_tokenvia theLocalFrameHost.CreateFencedFrameMojo IPC.RenderFrameHostImpl::CreateFencedFrameattempts to ensure this token is unique to prevent collisions. However, it only checks nodes within the current tab’sFrameTree(GetOutermostMainFrame()->frame_tree()->NodesIncludingInnerTreeNodes()). It fails to check for collisions across differentWebContents. - Silent Map Overwrite: When the DevTools agent host is created for the new fenced frame,
DevToolsAgentHostImpl::NotifyCreatedregisters it in a process-global map:Becausevoid DevToolsAgentHostImpl::NotifyCreated() { DCHECK(!GetDevtoolsInstances().contains(id_)); GetDevtoolsInstances()[id_] = this; // ... }DCHECKis compiled out in Release builds, the global map entry for the victim’s token is silently overwritten with a pointer to the attacker’s new fenced frame agent host.
Potential Attack Steps
Note: These are theoretical steps as our tooling does not yet have the ability to run code to produce a working proof-of-concept.
- An attacker gains code execution in a renderer process (e.g., via a V8 bug).
- The compromised renderer opens a cross-origin window to a sensitive site (e.g.,
window.open('https://victim.com')). - The browser sends a
CreateRemoteMainFrameIPC to the attacker’s renderer. The attacker intercepts this IPC and extracts the victim’sdevtools_frame_token. - The compromised renderer crafts a
LocalFrameHost.CreateFencedFrameMojo IPC to create a fenced frame in its own document, passing the stolen victim’sdevtools_frame_tokeninstead of a randomly generated one. - The browser’s validation passes because the victim is in a different
WebContents. - The browser creates a
RenderFrameDevToolsAgentHostfor the attacker’s fenced frame, which silently overwrites the victim’s entry in the globalGetDevtoolsInstances()map. - An automated DevTools client (like Puppeteer or a Chrome Extension) attempts to attach to the victim’s target ID.
DevToolsAgentHostImpl::GetForIdreturns the attacker’s fenced frame. The DevTools client unknowingly sends CDP commands (e.g.,Runtime.evaluateto inject scripts orInput.dispatchKeyEventto type passwords) into the attacker’s frame, allowing the attacker to steal credentials or hijack the session.
Suggested Fix
- Harden Map Insertion: In
DevToolsAgentHostImpl::NotifyCreated(), upgrade theDCHECKto aCHECK, or safely handle the collision without overwriting existing valid instances. - Enforce Global Uniqueness: Update the validation in
RenderFrameHostImpl::CreateFencedFrameto check the globalDevToolsAgentHost::GetForId()(or a truly global registry) to ensure the token doesn’t collide with any existing target across allWebContents. - Browser-Side Token Generation: Consider having the browser unconditionally generate the
devtools_frame_tokenfor Fenced Frames rather than trusting the token provided by the renderer via IPC.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.