Chrome · DevTools
CVE-2026-17898
UAF in DevTools
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ForEachDOMListenerthird_party/blink/renderer/core/inspector/inspector_dom_agent.cc |
modified | |
forthird_party/blink/renderer/core/inspector/inspector_dom_agent.cc |
modified |
Files Changed
third_party/blink/renderer/core/inspector/inspector_dom_agent.ccthird_party/blink/renderer/core/inspector/inspector_dom_agent.hthird_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding-expected.txtthird_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding.js
Patch
From 61355c93e7c7fb3b617d5bf3951b96195ad28643 Mon Sep 17 00:00:00 2001
From: Dmitry Gozman <dgozman@chromium.org>
Date: Sun, 28 Jun 2026 14:15:30 -0700
Subject: [PATCH] [CDP]: fix re-entrancy while notifying dom listeners
Bug: 506193577
Change-Id: Ied4b76214facff3bbb73c64b4686e8b6e5b0ef90
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8005056
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Commit-Queue: Dmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1653810}
---
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
index b8ad8e910..58b9a0b3 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
@@ -507,18 +507,33 @@
}
void InspectorDOMAgent::NotifyDidAddDocument(Document* document) {
- for (DOMListener* listener : dom_listeners_)
+ ForEachDOMListener([document](const Member<DOMListener>& listener) {
listener->DidAddDocument(document);
+ });
}
void InspectorDOMAgent::NotifyWillRemoveDOMNode(Node* node) {
- for (DOMListener* listener : dom_listeners_)
+ ForEachDOMListener([node](const Member<DOMListener>& listener) {
listener->WillRemoveDOMNode(node);
+ });
}
void InspectorDOMAgent::NotifyDidModifyDOMAttr(Element* element) {
- for (DOMListener* listener : dom_listeners_)
+ ForEachDOMListener([element](const Member<DOMListener>& listener) {
listener->DidModifyDOMAttr(element);
+ });
+}
+
+void InspectorDOMAgent::ForEachDOMListener(
+ base::FunctionRef<void(const Member<DOMListener>&)> callback) {
+ // Notifying listeners may pause in debugger and thus cause all kinds of
+ // side effects, including adding/removing listeners, so make a copy.
+ HeapHashSet<Member<DOMListener>> copy(dom_listeners_);
+ for (const Member<DOMListener>& listener : copy) {
+ if (dom_listeners_.Contains(listener)) {
+ callback(listener);
+ }
+ }
}
void InspectorDOMAgent::SetDocument(Document* doc) {
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.h b/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
index 6b993d3f..af543c9 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
@@ -369,6 +369,7 @@
void NotifyDidAddDocument(Document*);
void NotifyWillRemoveDOMNode(Node*);
void NotifyDidModifyDOMAttr(Element*);
+ void ForEachDOMListener(base::FunctionRef<void(const Member<DOMListener>&)>);
// Node-related methods.
using NodeToIdMap = GCedHeapHashMap<Member<Node>, int>;
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding-expected.txt b/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding-expected.txt
new file mode 100644
index 0000000..bd984f2
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding-expected.txt
@@ -0,0 +1,6 @@
+Tests that disabling domains while paused on node removal DOM breakpoint works.
+removing the node
+paused in debugger, disabling domains
+resuming
+success
+
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding.js b/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding.js
new file mode 100644
index 0000000..b9cc0ee
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding.js
@@ -0,0 +1,27 @@
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+ var {page, session, dp} = await testRunner.startHTML(
+ '<style>#v{color:red}</style><div id=v>target</div><script>window.removeIt=()=>v.remove()</script>',
+ 'Tests that disabling domains while paused on node removal DOM breakpoint works.');
+
+ await dp.Runtime.enable();
+ await dp.Debugger.enable();
+ await dp.DOM.enable();
+ await dp.CSS.enable();
+
+ const { result: doc } = await dp.DOM.getDocument({ depth: -1, pierce: true });
+ const { result: node } = await dp.DOM.querySelector({ nodeId: doc.root.nodeId, selector: '#v' });
+ await dp.DOMDebugger.setDOMBreakpoint({ nodeId: node.nodeId, type: 'node-removed' });
+
+ testRunner.log('removing the node');
+ const evaluated = dp.Runtime.evaluate({ expression: 'window.removeIt()' });
+ await dp.Debugger.oncePaused();
+ testRunner.log('paused in debugger, disabling domains');
+ await dp.CSS.disable();
+ await dp.DOM.disable();
+ testRunner.log('resuming');
+ await dp.Debugger.resume();
+ await evaluated;
+ testRunner.log('success');
+
+ testRunner.completeTest();
+})
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding-expected.txt b/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding-expected.txt
new file mode 100644
index 0000000..bd984f2
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding-expected.txt
@@ -0,0 +1,6 @@
+Tests that disabling domains while paused on node removal DOM breakpoint works.
+removing the node
+paused in debugger, disabling domains
+resuming
+success
+
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding.js b/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding.js
new file mode 100644
index 0000000..b9cc0ee
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-disable-while-unbinding.js
@@ -0,0 +1,27 @@
+(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
+ var {page, session, dp} = await testRunner.startHTML(
+ '<style>#v{color:red}</style><div id=v>target</div><script>window.removeIt=()=>v.remove()</script>',
+ 'Tests that disabling domains while paused on node removal DOM breakpoint works.');
+
+ await dp.Runtime.enable();
+ await dp.Debugger.enable();
+ await dp.DOM.enable();
+ await dp.CSS.enable();
+
+ const { result: doc } = await dp.DOM.getDocument({ depth: -1, pierce: true });
+ const { result: node } = await dp.DOM.querySelector({ nodeId: doc.root.nodeId, selector: '#v' });
+ await dp.DOMDebugger.setDOMBreakpoint({ nodeId: node.nodeId, type: 'node-removed' });
+
+ testRunner.log('removing the node');
+ const evaluated = dp.Runtime.evaluate({ expression: 'window.removeIt()' });
+ await dp.Debugger.oncePaused();
+ testRunner.log('paused in debugger, disabling domains');
+ await dp.CSS.disable();
+ await dp.DOM.disable();
+ testRunner.log('resuming');
+ await dp.Debugger.resume();
+ await evaluated;
+ testRunner.log('success');
+
+ 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