Overview

Low
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in DevTools
DescriptionUse after free in DevTools
ComponentDevTools
Sub-componentInspector
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker523442920
Fix commit23294e43bec4 (v8/v8) +38/-4
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-13 (per vendor advisory)
Fix landed2026-08-10 (public git)
Disclosed2026-09-08
Patch gap29 days — fix public in source before the advisory

Background

`InspectedContext`
The V8 inspector object that owns the per-context inspector state, including the InjectedScript instance, and is reference-counted via std::shared_ptr.
`InjectedScript`
The object that wraps and serializes JavaScript values into DevTools protocol RemoteObjects; it is owned by (and lives no longer than) its InspectedContext.
`ConsoleHelper`
A short-lived helper built around a console call that resolves the session, injected script, and context needed to service CommandLineAPI functions such as inspect.
CommandLineAPI `inspect`
A console-only helper function that reveals an object in DevTools; internally it wraps the argument through InjectedScript, which can re-enter user JavaScript during serialization.

Root Cause Analysis

ConsoleHelper::injectedScript() obtained the InspectedContext via m_inspector->getContext(...) into a function-local std::shared_ptr<InspectedContext>, then returned the raw InjectedScript* owned by that context. Because the local shared_ptr was destroyed when the function returned, nothing in ConsoleHelper held a strong reference to the context for the remainder of the inspect operation. When the captured CommandLineAPI inspect function was invoked from ordinary page script — outside any active ContextScope that would otherwise pin the context — object serialization inside InjectedScript::wrapObjectMirror could re-enter JavaScript (e.g. a property getter) that synchronously destroyed the context, freeing the InjectedScript while it was still being used, producing a use-after-free.

The fix stores the shared_ptr in a new ConsoleHelper member m_inspectedContext, so the InspectedContext and its owned InjectedScript remain alive for the whole duration of inspectImpl execution.

Key insight
The single mistake was returning a raw pointer whose owner’s strong reference was dropped at function scope, leaving the InjectedScript unprotected against synchronous context destruction during serialization. Promoting the shared_ptr to the ConsoleHelper member m_inspectedContext keeps the owning InspectedContext pinned across the re-entrant serialization, closing the UAF window.

Attack Path

  1. Enable inspector and capture `inspect` With an inspector session and Runtime domain enabled, evaluate an expression with includeCommandLineAPI to save the CommandLineAPI inspect function to a page-reachable global (globalThis.savedInspect = inspect).
  2. Invoke from page scope Call savedInspect(obj) from ordinary page-level script execution, where no ContextScope holds the InspectedContext alive, so ConsoleHelper::injectedScript()’s dropped local shared_ptr is the only thing that had pinned it.
  3. Re-enter during serialization Pass an object whose serialization triggers a JavaScript getter (the PoC redefines Error.prototype.name with a getter) that runs during InjectedScript::wrapObjectMirror.
  4. Destroy the context synchronously Inside that getter, fire context destruction (inspector.fireContextDestroyed()), which frees the InspectedContext and its owned InjectedScript while the wrap call is still executing.
  5. Use the freed object Control returns into wrapObjectMirror operating on the now-freed InjectedScript, yielding the use-after-free; further escalation to a controlled read/write or code execution is not shown by the patch (inferred).

Impact Assessment

An attacker gains a use-after-free of the InjectedScript/InspectedContext in the V8 inspector, reachable whenever DevTools/an inspector client is attached with Runtime enabled and page script can capture and re-invoke the CommandLineAPI inspect function. The bug executes in the process hosting V8 inspection — the renderer process for Chrome DevTools (inferred) — and the preconditions are an active inspector session plus attacker-controlled page JavaScript. The advisory classifies this as a use-after-free in DevTools; any escalation to memory disclosure or code execution beyond the crash is (inferred) and not demonstrated by the patch, and no in-the-wild exploitation is stated.

Changed Functions

FunctionChangeNotes
get
test/inspector/runtime/regress-523442920.js
modified

Files Changed

  • test/inspector/runtime/regress-523442920-expected.txt
  • test/inspector/runtime/regress-523442920.js
  • src/inspector/v8-console.cc

Audit Directions

  • Raw pointer outliving its owning smart pointer
    Flag functions that fetch an owner via a local shared_ptr/scoped_refptr and return or expose a raw pointer/reference into it; the callee must hold a strong reference for as long as it uses the object.
  • Re-entrant serialization sinks
    Audit InjectedScript/mirror wrapping paths (wrapObjectMirror, wrapObject, RemoteObject generation) for points that invoke user JavaScript (getters, toString, proxies) which can synchronously destroy the context or session being used.
  • CommandLineAPI functions invoked outside a `ContextScope`
    Review all console/CommandLineAPI helpers that can be captured and called from page script, checking that each pins its InspectedContext rather than relying on a caller-established scope.
From 23294e43bec49915a9f3fb9f3b45acc559d1bd6e Mon Sep 17 00:00:00 2001
From: Yang Guo <[email protected]>
Date: Mon, 10 Aug 2026 05:11:17 +0000
Subject: [PATCH] [inspector] Pin InspectedContext in ConsoleHelper to prevent UAF

ConsoleHelper::injectedScript() previously dropped its local
shared_ptr<InspectedContext> before returning the raw InjectedScript*
pointer. If the CommandLineAPI inspect function was captured and invoked
from a page script outside an active ContextScope, synchronous context
destruction during object serialization caused a use-after-free in
InjectedScript::wrapObjectMirror.

Keep a std::shared_ptr<InspectedContext> in ConsoleHelper so the
context remains pinned during inspectImpl execution.

TAG=agy
CONV=812b1007-2667-4bf3-8c0b-7669dff6da64

Fixed: 523442920
Change-Id: I96dcd0c3c04b02d3ab3a92a0a3dbb6a0876b25e8
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8225404
Commit-Queue: Simon Zünd <[email protected]>
Auto-Submit: Yang Guo <[email protected]>
Reviewed-by: Simon Zünd <[email protected]>
Cr-Commit-Position: refs/heads/main@{#109151}
---
diff --git a/src/inspector/v8-console.cc b/src/inspector/v8-console.cc
index 0fa7caf..0ad21a5 100644
--- a/src/inspector/v8-console.cc
+++ b/src/inspector/v8-console.cc
@@ -67,10 +67,9 @@
   int groupId() const { return m_inspector->contextGroupId(contextId()); }
 
   InjectedScript* injectedScript(int sessionId) {
-    std::shared_ptr<InspectedContext> context =
-        m_inspector->getContext(groupId(), contextId());
-    if (!context) return nullptr;
-    return context->getInjectedScript(sessionId);
+    m_inspectedContext = m_inspector->getContext(groupId(), contextId());
+    if (!m_inspectedContext) return nullptr;
+    return m_inspectedContext->getInjectedScript(sessionId);
   }
 
   V8InspectorSessionImpl* session(int sessionId) {
@@ -210,6 +209,7 @@
   const v8::debug::ConsoleCallArguments& m_info;
   const v8::debug::ConsoleContext& m_consoleContext;
   V8InspectorImpl* m_inspector;
+  std::shared_ptr<InspectedContext> m_inspectedContext;
 };
 
 void createBoundFunctionProperty(
diff --git a/test/inspector/runtime/regress-523442920-expected.txt b/test/inspector/runtime/regress-523442920-expected.txt
new file mode 100644
index 0000000..ac306d2
--- /dev/null
+++ b/test/inspector/runtime/regress-523442920-expected.txt
@@ -0,0 +1,2 @@
+Tests that destroying context during inspect does not cause UAF (regress-523442920).
+Success (no crash).
diff --git a/test/inspector/runtime/regress-523442920.js b/test/inspector/runtime/regress-523442920.js
new file mode 100644
index 0000000..d3442cd
--- /dev/null
+++ b/test/inspector/runtime/regress-523442920.js
@@ -0,0 +1,32 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+let {session, contextGroup, Protocol} =
+  InspectorTest.start('Tests that destroying context during inspect does not cause UAF (regress-523442920).');
+
+(async function test() {
+  await Protocol.Runtime.enable();
+
+  // Capture CommandLineAPI inspect function.
+  await Protocol.Runtime.evaluate({
+    expression: 'globalThis.savedInspect = inspect;',
+    includeCommandLineAPI: true,
+  });
+
+  // Call savedInspect from page-level execution where no ContextScope is active.
+  contextGroup.addScript(`
+    let e = new Error();
+    delete e.name;
+    Object.defineProperty(Object.getPrototypeOf(e), 'name', {
+      get() {
+        inspector.fireContextDestroyed();
+        return '';
+      }
+    });
+    savedInspect(e);
+  `);
+
+  InspectorTest.log('Success (no crash).');
+  InspectorTest.completeTest();
+})();
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/inspector/runtime/regress-523442920-expected.txt b/test/inspector/runtime/regress-523442920-expected.txt
new file mode 100644
index 0000000..ac306d2
--- /dev/null
+++ b/test/inspector/runtime/regress-523442920-expected.txt
@@ -0,0 +1,2 @@
+Tests that destroying context during inspect does not cause UAF (regress-523442920).
+Success (no crash).
diff --git a/test/inspector/runtime/regress-523442920.js b/test/inspector/runtime/regress-523442920.js
new file mode 100644
index 0000000..d3442cd
--- /dev/null
+++ b/test/inspector/runtime/regress-523442920.js
@@ -0,0 +1,32 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+let {session, contextGroup, Protocol} =
+  InspectorTest.start('Tests that destroying context during inspect does not cause UAF (regress-523442920).');
+
+(async function test() {
+  await Protocol.Runtime.enable();
+
+  // Capture CommandLineAPI inspect function.
+  await Protocol.Runtime.evaluate({
+    expression: 'globalThis.savedInspect = inspect;',
+    includeCommandLineAPI: true,
+  });
+
+  // Call savedInspect from page-level execution where no ContextScope is active.
+  contextGroup.addScript(`
+    let e = new Error();
+    delete e.name;
+    Object.defineProperty(Object.getPrototypeOf(e), 'name', {
+      get() {
+        inspector.fireContextDestroyed();
+        return '';
+      }
+    });
+    savedInspect(e);
+  `);
+
+  InspectorTest.log('Success (no crash).');
+  InspectorTest.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.