← Chrome — 2026-W31

021769f1006aad305fac2a3ad96c3a607e5d3051  [M144-LTS] InspectorIndexedDBAgent must not use v8_session_ after disposal

severity low class UAF CVSS 5.4 signal score 5.09 third_party/blink/renderer/modules/ AI review CONFIRMED
Structural signals: null_guard (+2.59), area:third_party/blink/renderer/modules/ (+2.5)

The bug at a glance

A genuine use-after-free of the DanglingUntriaged raw_ptr v8_session_ in a renderer-side DevTools agent, confirmed by a test that repros 100% under ASAN; severity is capped at medium because reaching it requires an attached DevTools/Inspector IndexedDB session rather than plain web content.

An attacker (or malicious automation/extension) driving the DevTools IndexedDB protocol can start an async requestData() read and then navigate the inspected frame so the agent is disposed before the callback fires, leaving v8_session_ dangling when the callback dereferences it.

Root cause

InspectorIndexedDBAgent holds v8_session_ as a raw_ptr<…,DanglingUntriaged>. Before this fix, Dispose() did not null the pointer and async callbacks (e.g. OpenCursorCallback::Invoke) and lifecycle hooks (DidCommitLoadForLocalFrame, disable) called agent_->v8_session()->releaseObjectGroup()/used the session unconditionally. If the agent is disposed while a requestData() callback is still pending during navigation, the V8InspectorSession has been torn down and the dangling v8_session_ is used, producing a UAF. The fix nulls v8_session_ in Dispose(), routes session use through a ReleaseObjectGroup() helper that null-checks, and adds an explicit null check in the callback that fails the request with kSessionDetachedDuringOperation.

Key code

Dispose() nulls v8_session_ and callbacks now null-check before use

void InspectorIndexedDBAgent::Dispose() {
  ReleaseObjectGroup();
  v8_session_ = nullptr;
  InspectorBaseAgent<protocol::IndexedDB::Metainfo>::Dispose();
}
...
v8_inspector::V8InspectorSession* v8_session = agent->v8_session();
if (!v8_session) {
  request_callback_->sendFailure(
      protocol::Response::ServerError(kSessionDetachedDuringOperation));
  return;
}

Patch walkthrough

  • third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.cc — Adds Dispose() which calls ReleaseObjectGroup() then sets v8_session_ = nullptr; introduces ReleaseObjectGroup() guarded by a null check; and inserts a null check on the session inside the async callback before dereferencing, closing the window where a disposed agent’s dangling session pointer could be used.
  • third_party/blink/renderer/modules/indexeddb/inspector_indexed_db_agent.h — Declares the Dispose() override and private ReleaseObjectGroup() helper and documents that v8_session_ is null after disposal, making the lifecycle contract explicit for the DanglingUntriaged raw_ptr.
  • third_party/blink/web_tests/http/tests/inspector-protocol/storage/indexed-db-request-data-race-with-navigation-large-db.js — Regression test that issues IndexedDB.requestData() against a large store and immediately navigates, racing teardown against the pending callback to exercise the UAF window.

Background

InspectorIndexedDBAgent — Blink renderer agent implementing the DevTools IndexedDB protocol domain for an inspected frame.

V8InspectorSession — V8 inspector object owning inspector-created JS wrappers/object groups; destroyed when the DevTools session detaches.

raw_ptr DanglingUntriaged — A MiraclePtr raw_ptr annotation marking a pointer known to possibly dangle, signaling an unresolved lifetime hazard.

Oilpan Member vs raw_ptr — v8_session_ is a non-traced raw_ptr, so it is not kept alive or cleared automatically when the session dies.

Vulnerability window

  1. Reachability — A DevTools client enables the IndexedDB domain and issues an async requestData() on a frame with a sizeable store.
  2. Trigger — The client navigates the inspected frame so the agent is disposed and the V8InspectorSession torn down while the requestData() callback is still pending.
  3. Primitive — The pending callback dereferences the dangling v8_session_ raw_ptr, a use-after-free (inferred that the freed session memory may be reclaimed/controlled).
  4. Escalation — Controlling the reclaimed V8InspectorSession allocation could turn the UAF into type confusion or control-flow hijack (inferred).

Proof of concept

Conceptually: with the DevTools IndexedDB domain enabled, create a large object store, call IndexedDB.requestData() and immediately navigate the inspected frame; the agent disposes while the read callback is in flight, so the callback touches the freed session. The committed regression test demonstrates the race shape without weaponization.

Exploitation

  1. Setup — Attach a DevTools/Inspector session and enable the IndexedDB domain on a target frame (inferred to require inspector access).
  2. Race — Launch a long-running requestData() and navigate to force agent disposal mid-callback.
  3. Reclaim — Spray allocations to reoccupy the freed V8InspectorSession before the dangling use (inferred).
  4. Exploit — Leverage the controlled dereference for info leak or corruption (inferred).

Detection & hunting

For defenders and detection engineers:

  • grep for raw_ptr members used in async DevTools callbacks without null checks after Dispose() — Audit Inspector*Agent classes where a session/pointer member is used in Event/callback Invoke paths but not cleared in Dispose().
  • ASAN heap-use-after-free in InspectorIndexedDBAgent / V8InspectorSession during navigation — Run inspector-protocol web tests under ASAN and watch for UAF stacks referencing releaseObjectGroup/requestData callbacks.

Audit directions

  • Other InspectorBaseAgent subclasses holding raw_ptr — Check each for a Dispose() that nulls the session and for null checks in all async callbacks, mirroring the InspectorDOMAgent pattern this CL follows.
  • DanglingUntriaged raw_ptr members in blink/renderer/modules — Treat each as an unresolved lifetime hazard and verify clearing on owner teardown plus guarded use.

Before / after

Loading diff…