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 <[email protected]> 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 <[email protected]> Reviewed-by: Daniel Cheng <[email protected]> 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