Medium watch ✔︎ verified CVSS 7.5 chrome Race 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactRace in DevTools
DescriptionRace in DevTools
ComponentDevTools
Sub-componentInspector
Bug ClassRace
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable — factors set by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIConfirmed
Reviewer notes: none; all cited lines (devtools_session.cc:173-174,190-201,193,227,274; .h:77,174; v8-inspector-session-impl.cc:395) match. Detach-inside-nested-loop is correctly marked inferred; UAF supported only by commit messages and reporter, not a shown crash frame.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker40071155
Fix commit531984a74b5f (chromium/src) +4/-2
Related commits73b918cfcdb3 prerequisite API · v8/v8
CWECWE-362 (Race condition)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedAnonymous
Reported2023-09-01 · bounty $1000 (per vendor advisory)
Fix landed2025-12-08 (public git)
Disclosed2026-02-10
Patch gap64 days — fix public in source before the advisory

Background

`V8InspectorSession`
the V8-side object for one DevTools protocol session; it owns the V8 domain agents and the m_dispatcher that routes Runtime/Debugger/Profiler/… commands.
`DevToolsSession`
the Blink garbage-collected object that bridges a browser DevTools connection to Blink agents and to its v8_session_.
Nested message loop on pause
while JS is paused in the debugger, V8 asks the embedder to run a nested loop (V8InspectorClient::runMessageLoopOnPause, per the V8 header comment in the diff), so other tasks can run while the protocol call that caused the pause is still on the stack.
`connectShared`
a new V8Inspector API that returns a std::shared_ptr<V8InspectorSession>, which lets V8 keep the session alive while it is in use.

Root Cause Analysis

Re-analyzed on 2026-10-03 against the actual fix commit 531984a74b5f. An earlier analysis was withdrawn because it had been written against a commit that was not the security fix (73b918cfcdb3).
Before the fix, DevToolsSession::ConnectToV8 got the session from inspector->connect(...) and stored it in std::unique_ptr<v8_inspector::V8InspectorSession> v8_session_ (devtools_session.cc:193, devtools_session.h:174), so Blink was the only owner. DevToolsSession::Detach, which is also the host_remote_ disconnect handler (devtools_session.cc:173-174), destroyed the session immediately with v8_session_.reset() (devtools_session.cc:227). Protocol commands reach V8 through v8_session_->dispatchProtocolMessage(...) (devtools_session.cc:274), which ends in m_dispatcher.Dispatch(dispatchable).Run() (v8-inspector-session-impl.cc:395). The V8 commit message says “blink is allowed to tear down V8 sessions on the nested run-loop, while actual agent/session functions remain on the stack”, so a Detach that runs inside a nested pause loop frees the V8InspectorSessionImpl under the outer dispatch frames. This breaks the invariant that a session must not be destroyed while one of its own methods is on the stack; the bug comes from task ordering (hence the advisory’s “Race” label) and shows up as a use-after-free. Prerequisite V8 commit 73b918cf adds connectShared, which calls session->setWeakThis(session), and puts KeepSessionAliveScope keepAlive(*this); at the top of dispatchProtocolMessage to hold a strong reference for the whole dispatch. Primary Blink commit 531984a7 switches ConnectToV8 to connectShared and v8_session_ to std::shared_ptr. Without that switch m_weakThis is never set and the scope protects nothing; with it, reset() only drops Blink’s reference. The same commit adds v8_session_->stop(); before the reset, because (per the reland message) the destructor used to resume a paused session, and once destruction is deferred the nested loop must be ended explicitly so the stack can unwind.
Key insight
Blink exclusively owned a V8 session that could be destroyed from a nested loop while its own dispatchProtocolMessage was still on the stack. The fix changes ownership to shared, makes each dispatch hold a strong reference, and stops the session explicitly on detach so the deferred teardown still completes.

Attack Path

  1. Attach a DevTools session A DevTools client attaches to a renderer, and ConnectToV8 creates the V8 session (devtools_session.cc:190-201).
  2. Enter a nested pause loop from a dispatched command A V8-domain command goes through dispatchProtocolMessage (devtools_session.cc:274, v8-inspector-session-impl.cc:395) and leads to a debugger pause that runs a nested message loop (inferred from the V8 commit message).
  3. Detach while nested The session is detached, for example when the host pipe disconnects and Detach runs (devtools_session.cc:173-174). v8_session_.reset() then frees the session (devtools_session.cc:227). That the disconnect is processed inside the nested loop is (inferred).
  4. Unwind into freed state When the nested loop returns, the outer dispatch frames continue using the freed V8InspectorSessionImpl and its dispatcher/agents. The reporter attributes the crash to DomainDispatcherImpl (reporter’s claim; that frame is not in the material).

Impact Assessment

The result is a use-after-free in the renderer process’s V8 inspector. The reporter calls it a “renderer UAF”, and the advisory rates it medium, not exploited in the wild. A DevTools/debugger session must be attached and then detached while paused. How far an untrusted party can control this sequence, and whether it leads to code execution, is (inferred) and not shown in the material.

Files Changed

  • PRESUBMIT.py
  • third_party/blink/renderer/core/inspector/devtools_session.cc
  • third_party/blink/renderer/core/inspector/devtools_session.h

Audit Directions

  • Other nested-loop entry points
    The V8 commit message says that client events and interrupts can also enter agent code and start a nested loop without passing through dispatchProtocolMessage. Check those paths for a missing keep-alive.
  • Raw pointers to shared-owned sessions
    DevToolsSession::V8Session() still returns v8_session_.get() (devtools_session.h:77). Check whether any caller caches that raw pointer across a point where the session can be detached.
  • Owner reset during re-entrancy
    Look for other unique_ptr::reset() calls in teardown handlers such as Mojo disconnect handlers that can run in a nested run loop while a method of the owned object is still on the stack.
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 9147703..01abdfd 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -748,6 +748,7 @@
             # Needed for interop with third-party library.
             r'^third_party/blink/renderer/core/typed_arrays/array_buffer/' +
             r'array_buffer_contents\.(cc|h)',
+            r'^third_party/blink/renderer/core/inspector/devtools_session\.h',
             r'^third_party/blink/renderer/core/typed_arrays/dom_array_buffer\.cc',
             '^third_party/blink/renderer/bindings/core/v8/' +
             'v8_wasm_response_extensions.cc',
diff --git a/third_party/blink/renderer/core/inspector/devtools_session.cc b/third_party/blink/renderer/core/inspector/devtools_session.cc
index 2a92b6d..2455ee368 100644
--- a/third_party/blink/renderer/core/inspector/devtools_session.cc
+++ b/third_party/blink/renderer/core/inspector/devtools_session.cc
@@ -190,7 +190,7 @@
 void DevToolsSession::ConnectToV8(v8_inspector::V8Inspector* inspector,
                                   int context_group_id) {
   const auto& cbor = v8_session_state_cbor_.Get();
-  v8_session_ = inspector->connect(
+  v8_session_ = inspector->connectShared(
       context_group_id, this,
       v8_inspector::StringView(cbor.data(), cbor.size()),
       client_is_trusted_ ? v8_inspector::V8Inspector::kFullyTrusted
@@ -224,6 +224,7 @@
   for (wtf_size_t i = agents_.size(); i > 0; i--)
     agents_[i - 1]->Dispose();
   agents_.clear();
+  v8_session_->stop();
   v8_session_.reset();
   agent_->client_->DebuggerTaskFinished();
 }
diff --git a/third_party/blink/renderer/core/inspector/devtools_session.h b/third_party/blink/renderer/core/inspector/devtools_session.h
index 4ee2af4e..133b4ff 100644
--- a/third_party/blink/renderer/core/inspector/devtools_session.h
+++ b/third_party/blink/renderer/core/inspector/devtools_session.h
@@ -171,7 +171,7 @@
   HeapMojoAssociatedRemote<mojom::blink::DevToolsSessionHost> host_remote_{
       nullptr};
   IOSession* io_session_;
-  std::unique_ptr<v8_inspector::V8InspectorSession> v8_session_;
+  std::shared_ptr<v8_inspector::V8InspectorSession> v8_session_;
   std::unique_ptr<protocol::UberDispatcher> inspector_backend_dispatcher_;
   InspectorSessionState session_state_;
   HeapVector<Member<InspectorAgent>> agents_;
Loading diff…

Original Bug Report

reported by he...@gmail.com

UAF in v8_inspector DomainDispatcherImpl

Steps to reproduce the problem:
will attach details soon.

Problem Description:
renderer UAF

Additional Comments:

**Chrome version: ** 118.0.5979.0 **Channel: ** Dev

OS: Linux

View on issue tracker