← Chrome — 2026-W38

e0dc8587bb9bc63e9ffbffe8931ae11c2b854d18  [M144-LTS] Prevent UAF in TargetHandler::SetAttachedTargetsOfType

severity medium class UAF CVSS 4.5 confidence 0.6 signal score 6.0 content/browser/devtools AI review CONFIRMED
Primitive: In-loop detachment synchronously destroys sibling DevToolsAgentHosts; fix collects strong refs first.
Triage note: Real UAF but reachable only via the DevTools protocol (a debugger client), not ordinary web content. Bug:553119925. Backport M150.

The bug at a glance

A genuine use-after-free, not mere hardening: SetAttachedTargetsOfType constructed strong references from bare pointer keys of a container that in-loop AutoDetach could mutate and whose sibling DevToolsAgentHost objects it could synchronously destroy. Dereferencing a freed key (scoped_refptr construction bumps a refcount on freed memory) is exploitable in principle. It is capped at medium / CVSS ~4.5 because the code path lives in content/browser/devtools and is driven by the DevTools Target domain (autoAttachRelated + hidden/OOPIF target churn). Reaching it requires DevTools protocol access - a debugging client, an extension with the debugger permission, or automation - rather than unprivileged web content, which sharply limits the remote attack surface. The [M144-LTS] cherry-pick (from main 76d750b5) shows it was judged worth backporting to the LTS branch, consistent with a real but access-gated browser-process UAF.

DevTools ‘auto-attach related’ bookkeeping. When targets are re-computed (e.g. a page with an auto-attached OOPIF that also owns hidden targets navigates cross-site), SetAttachedTargetsOfType detaches the targets no longer present. Detaching one target can synchronously dispose its child sessions and tear down owned resources - including sibling DevToolsAgentHost instances that are still listed as later entries in the map being iterated. The old loop iterated a shallow copy whose keys were bare DevToolsAgentHost* pointers, so a cascade could free an object that a subsequent iteration then turned into a scoped_refptr.

Root cause

The pre-fix code did auto old_sessions = auto_attached_sessions_; (a copy of the flat_set/map) and looped over it, per entry doing scoped_refptr<DevToolsAgentHost> host(entry.first); then, if the host matched the type/source and was absent from new_hosts, calling AutoDetach(source, host.get()). Two lifetime problems: (1) the copy’s keys are raw pointers - copying the container does NOT take strong references to the hosts, so old_sessions does not keep siblings alive. (2) AutoDetach on one host runs synchronous teardown (child session disposal, hidden-target destruction) that can drop the last reference to a different host which appears later in old_sessions. When the loop reaches that later entry, scoped_refptr<DevToolsAgentHost> host(entry.first) constructs a strong ref from a dangling pointer - a refcount increment on freed memory, i.e. UAF; the session value object (entry.second, dereferenced for auto_attacher_id_) can likewise be freed. The second loop’s base::Contains(old_sessions, host.get()) reads the same possibly-stale copy. The fix restructures into two phases: first iterate the live auto_attached_sessions_ and, for each qualifying entry, push a strong scoped_refptr into hosts_to_detach while every entry is still alive and valid; then iterate hosts_to_detach (which now owns strong refs, so targets survive the detach cascade) calling AutoDetach. Because AutoDetach re-validates each host against the live map, any host already torn down by an earlier cascade is safely skipped. The membership checks are switched from base::Contains over the stale copy to .contains() on the live map.

Key code

target_handler.cc: collect strong refs from the live map before detaching

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)) {
    hosts_to_detach.push_back(std::move(host));
  }
}
// hosts stay alive across the detach cascade; AutoDetach re-validates each.
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);
  }
}

Patch walkthrough

  • content/browser/devtools/protocol/target_handler.cc — SetAttachedTargetsOfType is rewritten. The auto old_sessions = auto_attached_sessions_; copy and the in-loop AutoDetach are removed. Phase 1 iterates the live auto_attached_sessions_ by const ref and collects scoped_refptr strong references for every host that matches the type, matches the auto_attacher source id, and is not in new_hosts - guaranteeing all entries are still alive at the moment their pointers are promoted to strong refs. Phase 2 iterates the collected strong refs and calls AutoDetach, so even if detaching one host synchronously destroys a sibling, the sibling is kept alive by hosts_to_detach and AutoDetach’s own re-validation against the live map skips it cleanly. The membership tests are also moved from base::Contains(old_sessions,…) to new_hosts.contains(…) / auto_attached_sessions_.contains(…) so they consult live containers.
  • third_party/blink/web_tests/http/tests/inspector-protocol/target/auto-attach-related-hidden-targets-detach.js — New web test that reproduces the cascade: it auto-attaches related targets for a page, creates an OOPIF, then creates 20 hidden targets under the iframe session and auto-attaches each, and finally navigates the page cross-site. That navigation detaches the iframe and disposes its many hidden targets in one synchronous cascade - exactly the sibling-destruction scenario. The test asserts the iframe session is detached (Runtime.evaluate errors) and that navigation completed without a crash.
  • third_party/blink/web_tests/.../auto-attach-related-hidden-targets-detach-expected.txt — Expectations for the new test: logs ‘iframe session detached’ and ‘SUCCESS: Navigated and detached without crash’. This is the regression oracle - pre-fix the navigation cascade would crash the browser process.

Background

DevTools auto-attach / autoAttachRelated — A DevTools Target-domain feature that automatically creates debugging sessions for related targets (subframes/OOPIFs, workers, hidden targets). TargetHandler tracks these in auto_attached_sessions_ and must detach sessions whose targets vanish. It is reachable only by a client speaking the DevTools protocol.

DevToolsAgentHost lifetime — Ref-counted (scoped_refptr) objects representing debuggable targets. A container keyed by raw DevToolsAgentHost* does not extend their lifetime; only a held scoped_refptr does. Detaching a target can drop the last ref to that host and to related hidden targets.

Hidden targets / OOPIF cascade — Hidden targets and out-of-process iframes can be owned transitively. Tearing down a parent frame session disposes child sessions and destroys owned hidden targets synchronously, which is how a single AutoDetach can free several sibling hosts at once.

Vulnerability window

  1. Introduction — SetAttachedTargetsOfType iterated a raw-pointer-keyed copy and detached in-loop, assuming no entry it would visit could be freed by detaching a prior one.
  2. Trigger discovered (bug 553119925) — Navigating a page whose auto-attached OOPIF owns hidden targets causes a synchronous multi-host teardown, invalidating later entries mid-loop -> UAF.
  3. Fix on main (76d750b5, main@{#1689188}) — Gyuyoung Kim split the operation into collect-strong-refs then detach, with AutoDetach re-validation.
  4. Backport (2026-09-14) — Cherry-picked to the M144-LTS branch (7559@{#5309}) with owners override, indicating it was deemed important enough for the long-term-support line.

Proof of concept

The diff ships a crash-reproduction web test driven through the DevTools inspector protocol, not a memory-corruption exploit. It deterministically builds the sibling-destruction cascade (page + OOPIF + 20 hidden targets, then cross-site navigation) and checks the browser survives. Reproduced verbatim below as the honest repro. Turning this into an actual UAF exploit would require heap grooming of freed DevToolsAgentHost objects in the browser process from a DevTools client - not attempted and not present in the diff.

// auto-attach-related-hidden-targets-detach.js (from the diff)
await bp.Target.autoAttachRelated({targetId: page.targetId(), waitForDebuggerOnStart: false});
// create an OOPIF, attach to it, then create hidden targets under it:
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 in one cascade:
await page.navigate('http://devtools.oopif-b.test:8080/inspector-protocol/resources/iframe.html');
// Pre-fix: browser crash. Post-fix: 'SUCCESS: Navigated and detached without crash'.

Exploitation

  1. Access — Attacker needs to speak the DevTools protocol against the target: a malicious/compromised DevTools front-end, an extension holding the ‘debugger’ permission, remote-debugging automation, or a chromedriver-style harness. Not reachable by plain web pages.
  2. Setup — Enable autoAttachRelated on a page, spawn an OOPIF and multiple hidden child targets so that several sibling DevToolsAgentHosts coexist in auto_attached_sessions_.
  3. Trigger — Cause a cross-site navigation (or equivalent) that detaches the OOPIF; the synchronous cascade frees sibling hosts while SetAttachedTargetsOfType is mid-iteration, yielding a scoped_refptr construction / session deref on freed memory.
  4. Escalation (theoretical) — A browser-process UAF could be steered toward controlled-write/refcount corruption with heap grooming, but the required protocol access already implies substantial privilege; realistic impact is browser-process crash/DoS with a low-probability corruption tail.

Detection & hunting

For defenders and detection engineers:

  • Browser-process crashes in TargetHandler::SetAttachedTargetsOfType / AutoDetach —
  • DevTools protocol usage —
  • ASAN/LSAN fleet —

Audit directions

  • Iterate-then-mutate over ref-counted containers —
  • Synchronous disposal cascades —
  • auto_attacher_id_ / session deref —

Before / after

Loading diff…