CVE-2025-0762
Overview
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;
GarbageCollectedMixinlets 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
WebDevToolsAgentImplandWorkerInspectorController) thatDevToolsAgentcalls 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.
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
- Establish a DevTools agent
Attach a debugging front-end (or trigger auto-attach) so a
DevToolsAgentis created holding a rawclient_pointer to its GCed implementation such asWebDevToolsAgentImpl. - Drop the implementation's other references
Navigate, detach the frame/worker, or otherwise release the strong references to the
Clientimplementation whileDevToolsAgentitself stays reachable. - Force a GC
Drive allocation or a detach-time collection so Oilpan reclaims the untraced
Clientobject, leavingDevToolsAgent::client_dangling. - Re-enter through the agent
Send a further DevTools message that causes
DevToolsAgentto call back throughclient_, dereferencing freed memory. - Exploit the UAF Reoccupy the freed slot with attacker-controlled data before the callback to steer the stale invocation toward controlled behavior.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
CORE_EXPORTthird_party/blink/renderer/core/inspector/devtools_agent.h |
modified | |
Clientthird_party/blink/renderer/core/inspector/devtools_agent.h |
modified |
Files Changed
third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.hthird_party/blink/renderer/core/inspector/devtools_agent.ccthird_party/blink/renderer/core/inspector/devtools_agent.hthird_party/blink/renderer/core/inspector/worker_inspector_controller.h
Audit Directions
- Raw pointers to GCed objectsGrep for bare
T*fields insideGarbageCollectedclasses and confirm each is aMember/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 edgesCross-check every
Member-like field against the class’sTracemethod; a field that exists but is never handed tovisitor->Tracebehaves like an untraced raw pointer. - Interfaces referenced by GCed holdersAudit abstract callback/observer interfaces (like
Client) whose implementations are GCed but which are not themselvesGarbageCollectedMixin, as they force holders into unsafe raw-pointer references.
Patch
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(); }
Original Bug Report
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
- put manifest.json/background.js into the extension_path
- 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