Chrome · DevTools
CVE-2026-85042
UAF in DevTools
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
forcontent/browser/devtools/protocol/target_handler.cc |
modified | |
forthird_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach.js |
modified | |
ifthird_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach.js |
modified |
Files Changed
content/browser/devtools/protocol/target_handler.ccthird_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach-expected.txtthird_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach.js
Patch
From 76d750b581336a8426fc135270395b01b3af8e97 Mon Sep 17 00:00:00 2001
From: Maksim Sadym <sadym@chromium.org>
Date: Mon, 31 Aug 2026 13:09:41 -0700
Subject: [PATCH] Prevent UAF in TargetHandler::SetAttachedTargetsOfType
Collect strong DevToolsAgentHost references prior to detachment to prevent
use-after-free when in-loop detachment synchronously destroys sibling
agent hosts.
Bug: 553119925
Change-Id: I406b611dedd30fea7be7ffee1252e35493107480
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8301599
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Commit-Queue: Maksim Sadym <sadym@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1689188}
---
diff --git a/content/browser/devtools/protocol/target_handler.cc b/content/browser/devtools/protocol/target_handler.cc
index 053e06a..7e3c947d 100644
--- a/content/browser/devtools/protocol/target_handler.cc
+++ b/content/browser/devtools/protocol/target_handler.cc
@@ -970,18 +970,30 @@
const base::flat_set<scoped_refptr<DevToolsAgentHost>>& new_hosts,
const std::string& type) {
DCHECK(!type.empty());
- auto old_sessions = auto_attached_sessions_;
- for (auto& entry : old_sessions) {
+ // Detaching a target (e.g. a subframe) can synchronously dispose child
+ // sessions and tear down owned resources (e.g. hidden targets), which may
+ // destroy sibling DevToolsAgentHost instances and mutate
+ // `auto_attached_sessions_`.
+ // To avoid use-after-free on bare pointer keys or session objects, first
+ // collect strong references to the hosts that need to be detached while all
+ // entries are still alive and valid.
+ std::vector<scoped_refptr<DevToolsAgentHost>> hosts_to_detach;
+ for (const auto& entry : auto_attached_sessions_) {
scoped_refptr<DevToolsAgentHost> host(entry.first);
if (host->GetType() == type &&
entry.second->auto_attacher_id_ ==
reinterpret_cast<uintptr_t>(source) &&
!new_hosts.contains(host)) {
- AutoDetach(source, host.get());
+ hosts_to_detach.push_back(std::move(host));
}
}
- for (auto& host : new_hosts) {
- if (!old_sessions.contains(host.get())) {
+ // AutoDetach re-validates each host against the live `auto_attached_sessions_`
+ // map, safely skipping any targets already detached by earlier cascades.
+ for (const auto& host : hosts_to_detach) {
+ AutoDetach(source, host.get());
+ }
+ for (const auto& host : new_hosts) {
+ if (!auto_attached_sessions_.contains(host.get())) {
AutoAttach(source, host.get(), false);
}
}
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach-expected.txt
new file mode 100644
index 0000000..35bd945
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach-expected.txt
@@ -0,0 +1,4 @@
+Tests that navigating a page with an auto-attached iframe and hidden targets detaches cleanly without crashing.
+iframe session detached
+SUCCESS: Navigated and detached without crash
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach.js b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach.js
new file mode 100644
index 0000000..fd3cfec
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach.js
@@ -0,0 +1,56 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+ testRunner.log('Tests that navigating a page with an auto-attached iframe and hidden targets detaches cleanly without crashing.');
+ const bp = testRunner.browserP();
+
+ const page = await testRunner.createPage();
+ const pageSession = await page.createSession();
+
+ // Auto-attach related targets for the page.
+ await bp.Target.autoAttachRelated({
+ targetId: page.targetId(),
+ waitForDebuggerOnStart: false,
+ });
+
+ // Create an OOPIF.
+ const [attached] = await Promise.all([
+ bp.Target.onceAttachedToTarget(),
+ pageSession.evaluate(() => {
+ const iframe = document.createElement('iframe');
+ iframe.src = 'http://devtools.oopif.test:8080/inspector-protocol/resources/iframe.html';
+ document.body.appendChild(iframe);
+ }),
+ ]);
+ const iframeSession = testRunner.createSessionFor(attached.params.sessionId);
+
+ // Create hidden targets under the iframe session and auto-attach them.
+ const kHiddenTargetCount = 20;
+ for (let i = 0; i < kHiddenTargetCount; ++i) {
+ const {result: hiddenTarget} =
+ await iframeSession.protocol.Target.createTarget({
+ url: 'about:blank',
+ hidden: true,
+ });
+ await bp.Target.autoAttachRelated({
+ targetId: hiddenTarget.targetId,
+ waitForDebuggerOnStart: false,
+ });
+ }
+
+ // Cross-site navigation detaches the iframe and disposes its hidden targets.
+ await page.navigate('http://devtools.oopif-b.test:8080/inspector-protocol/resources/iframe.html');
+
+ // Assert that iframeSession was cleanly detached.
+ const evalResult = await iframeSession.protocol.Runtime.evaluate({expression: '1 + 1'});
+ if (evalResult.error) {
+ testRunner.log('iframe session detached');
+ } else {
+ testRunner.log('ERROR: iframe session should be detached but it was not');
+ }
+
+ testRunner.log('SUCCESS: Navigated and detached without crash');
+ testRunner.completeTest();
+});
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach-expected.txt
new file mode 100644
index 0000000..35bd945
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach-expected.txt
@@ -0,0 +1,4 @@
+Tests that navigating a page with an auto-attached iframe and hidden targets detaches cleanly without crashing.
+iframe session detached
+SUCCESS: Navigated and detached without crash
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach.js b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach.js
new file mode 100644
index 0000000..fd3cfec
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach.js
@@ -0,0 +1,56 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+ testRunner.log('Tests that navigating a page with an auto-attached iframe and hidden targets detaches cleanly without crashing.');
+ const bp = testRunner.browserP();
+
+ const page = await testRunner.createPage();
+ const pageSession = await page.createSession();
+
+ // Auto-attach related targets for the page.
+ await bp.Target.autoAttachRelated({
+ targetId: page.targetId(),
+ waitForDebuggerOnStart: false,
+ });
+
+ // Create an OOPIF.
+ const [attached] = await Promise.all([
+ bp.Target.onceAttachedToTarget(),
+ pageSession.evaluate(() => {
+ const iframe = document.createElement('iframe');
+ iframe.src = 'http://devtools.oopif.test:8080/inspector-protocol/resources/iframe.html';
+ document.body.appendChild(iframe);
+ }),
+ ]);
+ const iframeSession = testRunner.createSessionFor(attached.params.sessionId);
+
+ // Create hidden targets under the iframe session and auto-attach them.
+ const kHiddenTargetCount = 20;
+ for (let i = 0; i < kHiddenTargetCount; ++i) {
+ const {result: hiddenTarget} =
+ await iframeSession.protocol.Target.createTarget({
+ url: 'about:blank',
+ hidden: true,
+ });
+ await bp.Target.autoAttachRelated({
+ targetId: hiddenTarget.targetId,
+ waitForDebuggerOnStart: false,
+ });
+ }
+
+ // Cross-site navigation detaches the iframe and disposes its hidden targets.
+ await page.navigate('http://devtools.oopif-b.test:8080/inspector-protocol/resources/iframe.html');
+
+ // Assert that iframeSession was cleanly detached.
+ const evalResult = await iframeSession.protocol.Runtime.evaluate({expression: '1 + 1'});
+ if (evalResult.error) {
+ testRunner.log('iframe session detached');
+ } else {
+ testRunner.log('ERROR: iframe session should be detached but it was not');
+ }
+
+ testRunner.log('SUCCESS: Navigated and detached without crash');
+ testRunner.completeTest();
+});
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page