Medium watch CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in DevTools
DescriptionUse after free in DevTools
ComponentDevTools
Sub-componentPlatform>DevTools
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.
Tracker384844003
Fix commitbc43bccd60f6 (chromium/src) +5/-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
CreditedSakana.S
Reported2024-12-18 · bounty $2000 (per vendor advisory)
Fix landed2025-01-15 (public git)
Disclosed2025-01-28
Patch gap13 days — fix public in source before the advisory

Background

Oilpan / Blink GC
Blink’s tracing garbage collector, which reclaims heap objects once no traced reference reaches them.
`GarbageCollected` / `GarbageCollectedMixin`
base classes that opt an object into Oilpan; GarbageCollectedMixin lets a non-leaf interface participate in tracing without being the object’s primary GC base.
`Member<T>`
a traced smart pointer that keeps its pointee alive and visible to the collector, unlike a raw T* which the GC neither sees nor keeps alive.
`DevToolsAgent::Client`
the abstract callback interface (implemented by GCed classes such as WebDevToolsAgentImpl and WorkerInspectorController) that DevToolsAgent calls back into to attach sessions and inspect the frame or worker.

Root Cause Analysis

DevToolsAgent is itself a GarbageCollected object, but it stored its callback interface as a bare Client* client_ raw pointer. Because that pointer was neither a Member<Client> nor traced in DevToolsAgent::Trace, Oilpan had no visibility into the reference: it could collect the concrete Client implementation (e.g. WebDevToolsAgentImpl or WorkerInspectorController) while DevToolsAgent was still alive and holding the now-dangling pointer, and any subsequent callback through client_ would be a use-after-free. The invariant violated is that every cross-reference between GCed objects must be a traced Member so the collector keeps the target alive (or clears the slot) rather than leaving a stale raw pointer.

The fix makes Client a GarbageCollectedMixin, changes the field to Member<Client> const client_, and adds visitor->Trace(client_) to DevToolsAgent::Trace, so the collector now sees the edge and keeps the client alive for as long as the agent references it.

Key insight
The core mistake was holding a raw pointer to a garbage-collected object across a lifetime boundary the GC could not observe; the fix promotes that pointer to a traced Member<Client> (and makes Client a GC mixin) so Oilpan keeps the referenced client alive instead of freeing it out from under DevToolsAgent.

Attack Path

  1. Establish a DevTools agent Attach a debugging front-end (or trigger auto-attach) so a DevToolsAgent is created holding a raw client_ pointer to its GCed implementation such as WebDevToolsAgentImpl.
  2. Drop the implementation's other references Navigate, detach the frame/worker, or otherwise release the strong references to the Client implementation while DevToolsAgent itself stays reachable.
  3. Force a GC Drive allocation or a detach-time collection so Oilpan reclaims the untraced Client object, leaving DevToolsAgent::client_ dangling.
  4. Re-enter through the agent Send a further DevTools message that causes DevToolsAgent to call back through client_, dereferencing freed memory.
  5. Exploit the UAF Reoccupy the freed slot with attacker-controlled data before the callback to steer the stale invocation toward controlled behavior.

Impact Assessment

An attacker who can reach DevTools plumbing gains a use-after-free on a heap object in the renderer process, which can be shaped into memory corruption and, combined with further primitives, renderer code execution. It occurs in the Blink renderer’s inspector layer and requires an active/attachable DevTools agent plus the ability to trigger the client’s collection (e.g. via navigation or worker/frame teardown) while the agent survives.

Changed Functions

FunctionChangeNotes
CORE_EXPORT
third_party/blink/renderer/core/inspector/devtools_agent.h
modified
Client
third_party/blink/renderer/core/inspector/devtools_agent.h
modified

Files Changed

  • third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
  • third_party/blink/renderer/core/inspector/devtools_agent.cc
  • third_party/blink/renderer/core/inspector/devtools_agent.h
  • third_party/blink/renderer/core/inspector/worker_inspector_controller.h

Audit Directions

  • Raw pointers to GCed objects
    Grep for bare T* fields inside GarbageCollected classes and confirm each is a Member/WeakMember (or clearly non-owning and cleared on teardown), since untraced raw pointers to Oilpan objects are the canonical source of these UAFs.
  • Missing Trace edges
    Cross-check every Member-like field against the class’s Trace method; a field that exists but is never handed to visitor->Trace behaves like an untraced raw pointer.
  • Interfaces referenced by GCed holders
    Audit abstract callback/observer interfaces (like Client) whose implementations are GCed but which are not themselves GarbageCollectedMixin, as they force holders into unsafe raw-pointer references.
From bc43bccd60f6d79d329ed70231547d75ac57bd4b Mon Sep 17 00:00:00 2001
From: Andrey Kosyakov <caseq@chromium.org>
Date: Wed, 15 Jan 2025 10:17:13 -0800
Subject: [PATCH] Make DevToolsAgent::Client a GCMixin, since the implementation is GCed

We used to keep a raw pointer to Client in DevToolsAgent, which does
not play well when the implementation is getting collected.

Bug: 384844003
Change-Id: Id34886635955133f0be746a9e6e910f9e6891dbf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6175790
Commit-Queue: Andrey Kosyakov <caseq@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1406819}
---

diff --git a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
index 73d5b643..e7c60ea 100644
--- a/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
+++ b/third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h
@@ -66,7 +66,7 @@
 
   WebDevToolsAgentImpl(WebLocalFrameImpl*);
   ~WebDevToolsAgentImpl() override;
-  virtual void Trace(Visitor*) const;
+  void Trace(Visitor*) const override;
   DevToolsAgent* GetDevToolsAgent() const { return agent_.Get(); }
 
   void WillBeDestroyed();
diff --git a/third_party/blink/renderer/core/inspector/devtools_agent.cc b/third_party/blink/renderer/core/inspector/devtools_agent.cc
index 27aa9471..c57c5fc 100644
--- a/third_party/blink/renderer/core/inspector/devtools_agent.cc
+++ b/third_party/blink/renderer/core/inspector/devtools_agent.cc
@@ -189,6 +189,7 @@
 DevToolsAgent::~DevToolsAgent() = default;
 
 void DevToolsAgent::Trace(Visitor* visitor) const {
+  visitor->Trace(client_);
   visitor->Trace(associated_receiver_);
   visitor->Trace(host_remote_);
   visitor->Trace(associated_host_remote_);
diff --git a/third_party/blink/renderer/core/inspector/devtools_agent.h b/third_party/blink/renderer/core/inspector/devtools_agent.h
index f44494a..acf99dd 100644
--- a/third_party/blink/renderer/core/inspector/devtools_agent.h
+++ b/third_party/blink/renderer/core/inspector/devtools_agent.h
@@ -40,7 +40,7 @@
 class CORE_EXPORT DevToolsAgent : public GarbageCollected<DevToolsAgent>,
                                   public mojom::blink::DevToolsAgent {
  public:
-  class Client {
+  class Client : public GarbageCollectedMixin {
    public:
     virtual ~Client() = default;
     virtual void AttachSession(DevToolsSession*, bool restore) = 0;
@@ -139,7 +139,7 @@
   void ReportChildTargetsImpl(bool report,
                               bool wait_for_debugger,
                               base::OnceClosure callback);
-  Client* client_;
+  Member<Client> const client_;
   // DevToolsAgent is not tied to ExecutionContext
   HeapMojoAssociatedReceiver<mojom::blink::DevToolsAgent, DevToolsAgent>
       associated_receiver_{this, nullptr};
diff --git a/third_party/blink/renderer/core/inspector/worker_inspector_controller.h b/third_party/blink/renderer/core/inspector/worker_inspector_controller.h
index 400ad43..50c7d78 100644
--- a/third_party/blink/renderer/core/inspector/worker_inspector_controller.h
+++ b/third_party/blink/renderer/core/inspector/worker_inspector_controller.h
@@ -72,7 +72,7 @@
   WorkerInspectorController& operator=(const WorkerInspectorController&) =
       delete;
   ~WorkerInspectorController() override;
-  void Trace(Visitor*) const;
+  void Trace(Visitor*) const override;
 
   CoreProbeSink* GetProbeSink() const { return probe_sink_.Get(); }
   DevToolsAgent* GetDevToolsAgent() const { return agent_.Get(); }
Loading diff…

Original Bug Report

reported by as...@gmail.com

use-after-poison in blink::DevToolsSession::DispatchProtocolCommandImpl

VULNERABILITY DETAILS UAP in blink::DevToolsSession::DispatchProtocolCommandImpl

VERSION Chrome Version: 133.0.6889.0(Developer Build) Operating System: Ubuntu 24.04

REPRODUCTION CASE

  1. put manifest.json/background.js into the extension_path
  2. run the command: ./chrome –user-data-dir=./noexist –no-sandbox –load-extension=“extension_path”

FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION Type of crash: renderer Crash State: see asan.log file

CREDIT INFORMATION Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: Sakana.S

View on issue tracker