Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Actor
DescriptionUse after free in Actor
ComponentActor
Bug ClassUAF
Tracker522092013
Fix commit4d43b652de34 (chromium/src) +34/-7
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-08

Changed Functions

FunctionChangeNotes
if
chrome/renderer/actor/click_tool.cc
modified
if
chrome/renderer/actor/script_tool.cc
modified
if
chrome/renderer/actor/tool_executor.cc
modified
if
chrome/renderer/actor/type_tool.cc
modified

Files Changed

  • chrome/renderer/actor/click_tool.cc
  • chrome/renderer/actor/script_tool.cc
  • chrome/renderer/actor/tool_executor.cc
  • chrome/renderer/actor/type_tool.cc
From 4d43b652de342e3a6cb91f8c1c4b66e78caee057 Mon Sep 17 00:00:00 2001
From: Johann Hofmann <[email protected]>
Date: Mon, 22 Jun 2026 14:08:23 -0700
Subject: [PATCH] Fix Use-After-Free in actor::ToolExecutor and ClickTool during Cancel

When cancelling a tool, the cancellation sequence can synchronously
dispatch events (like mouseup) which allow the page to detach the frame.
This destroys the RenderFrameImpl and associated objects, including
ToolExecutor and the active ClickTool, while they are still on the call
stack.

This CL adds WeakPtr checks after the cancellation calls to verify
if the objects are still alive before proceeding with unwinding/cleanup
code.

Bug: 522092013
TAG=agy
CONV=bcb8651f-d6f1-45e1-be85-def2fff71421

Change-Id: Ib46f14a44a381777ad24859c81f6053a18769b0a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7958383
Reviewed-by: David Bokan <[email protected]>
Auto-Submit: Johann Hofmann <[email protected]>
Commit-Queue: David Bokan <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1650573}
---

diff --git a/chrome/renderer/actor/click_tool.cc b/chrome/renderer/actor/click_tool.cc
index afc9a8b..de34f3f 100644
--- a/chrome/renderer/actor/click_tool.cc
+++ b/chrome/renderer/actor/click_tool.cc
@@ -110,8 +110,14 @@
 
 void ClickTool::Cancel() {
   if (click_dispatcher_) {
+    // click_dispatcher_->Cancel() synchronously dispatches DOM events that
+    // might destroy the owning frame and this tool. Use a weak pointer to
+    // detect if `this` is still valid.
+    base::WeakPtr<ClickTool> weak_this = weak_ptr_factory_.GetWeakPtr();
     click_dispatcher_->Cancel();
-    click_dispatcher_.reset();
+    if (weak_this) {
+      click_dispatcher_.reset();
+    }
   }
 }
 
diff --git a/chrome/renderer/actor/script_tool.cc b/chrome/renderer/actor/script_tool.cc
index a64c31a..8fd4322 100644
--- a/chrome/renderer/actor/script_tool.cc
+++ b/chrome/renderer/actor/script_tool.cc
@@ -112,8 +112,14 @@
   if (!execution_id_.has_value()) {
     return;
   }
+  // CancelScriptTool() synchronously dispatches DOM events that might destroy
+  // the owning frame and this tool. Use a weak pointer to detect if `this` is
+  // still valid.
+  base::WeakPtr<ScriptTool> weak_this = weak_ptr_factory_.GetWeakPtr();
   frame_->GetWebFrame()->GetDocument().CancelScriptTool(execution_id_.value());
-  execution_id_.reset();
+  if (weak_this) {
+    execution_id_.reset();
+  }
 }
 
 std::string ScriptTool::DebugString() const {
diff --git a/chrome/renderer/actor/tool_executor.cc b/chrome/renderer/actor/tool_executor.cc
index 9230e74..34f58e50 100644
--- a/chrome/renderer/actor/tool_executor.cc
+++ b/chrome/renderer/actor/tool_executor.cc
@@ -270,11 +270,17 @@
   // The browser and renderer should agree on the active tool.
   CHECK_EQ(tool_->task_id(), task_id);
 
+  // tool_->Cancel() synchronously dispatches DOM events that might destroy the
+  // owning frame and this executor. Use a weak pointer to detect if `this` is
+  // still valid.
+  base::WeakPtr<ToolExecutor> weak_this = weak_ptr_factory_.GetWeakPtr();
   tool_->Cancel();
 
-  // The result code doesn't matter as it will be ignored by the browser
-  // process.
-  ToolFinished(MakeResult(mojom::ActionResultCode::kInvokeCanceled));
+  if (weak_this) {
+    // The result code doesn't matter as it will be ignored by the browser
+    // process.
+    ToolFinished(MakeResult(mojom::ActionResultCode::kInvokeCanceled));
+  }
 }
 
 void ToolExecutor::ToolFinished(mojom::ActionResultPtr result) {
diff --git a/chrome/renderer/actor/type_tool.cc b/chrome/renderer/actor/type_tool.cc
index c8aeb1db..c715ea0 100644
--- a/chrome/renderer/actor/type_tool.cc
+++ b/chrome/renderer/actor/type_tool.cc
@@ -501,13 +501,22 @@
   // Clicking is completed before key dispatching, so there shouldn't be both.
   CHECK(!(click_dispatcher_ && key_dispatcher_));
 
+  // click_dispatcher_->Cancel() or key_dispatcher_->Cancel() synchronously
+  // dispatches DOM events that might destroy the owning frame and this tool.
+  // Use a weak pointer to detect if `this` is still valid.
+  base::WeakPtr<TypeTool> weak_this = weak_ptr_factory_.GetWeakPtr();
+
   if (click_dispatcher_) {
     click_dispatcher_->Cancel();
-    click_dispatcher_.reset();
+    if (weak_this) {
+      click_dispatcher_.reset();
+    }
   }
   if (key_dispatcher_) {
     key_dispatcher_->Cancel();
-    key_dispatcher_.reset();
+    if (weak_this) {
+      key_dispatcher_.reset();
+    }
   }
 }
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in actor::ToolExecutor during synchronous frame detachment

Flapjack, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A Use-After-Free (UAF) vulnerability exists in the renderer process when cancelling an active Glic Actor tool. The cancellation sequence synchronously dispatches a DOM mouseup event, allowing a malicious page to detach the frame and destroy the active tool and its executor while they are still on the call stack. Upon return from the event dispatch, the unwinding code accesses the freed objects.

Affected files:

  • chrome/renderer/actor/click_tool.cc
  • chrome/renderer/actor/tool_executor.cc

Estimated timestamp from git blame: 2025-12-10

Summary

A Use-After-Free (UAF) vulnerability has been identified in the actor::ClickTool and actor::ToolExecutor classes in the renderer process. The vulnerability occurs because the tool cancellation process (ToolExecutor::CancelTool) synchronously dispatches a mouseup event to the Blink engine via ClickDispatcher::DoMouseUpImpl. This synchronous dispatch allows an attacker-controlled JavaScript event listener to detach the frame, which synchronously destroys the RenderFrameImpl and its associated objects, including the ToolExecutor and the active ClickTool. When the synchronous event dispatch returns, the methods on the call stack continue execution on the freed memory.

Technical Details

To trigger this vulnerability, an attacker must win a race condition to ensure that the CancelTool IPC is processed before the 5ms delayed task in ClickDispatcher::DoMouseDown executes. This can be achieved reliably by stalling the renderer main thread.

Suggested steps an attacker might follow:

  1. Create a page with an iframe and a target element inside it.
  2. Add mousedown and mouseup event listeners to the target element.
  3. Trigger the Actor feature to click the element. This causes ToolExecutor::ExecuteTool to invoke ClickTool::Execute, which creates a ClickDispatcher that synchronously fires the mousedown event.
  4. In the mousedown JavaScript listener, execute a busy-wait loop for 35 seconds to block the renderer main thread.
  5. While the thread is blocked, the browser process’s 30-second kGlicActorPageToolTimeout expires, causing it to send a CancelTool Mojo IPC to the renderer.
  6. When the 35-second busy-wait completes, the main thread resumes. The delayed 5ms DoMouseUp task is not yet runnable, so the task scheduler processes the pending CancelTool IPC.
  7. ChromeRenderFrameObserver::CancelTool calls tool_executor_->CancelTool(), which chains to ClickTool::Cancel() and then ClickDispatcher::Cancel().
  8. Since the 5ms timer hasn’t fired, the mouse is still “down”. ClickDispatcher::Cancel() calls DoMouseUpImpl(), which synchronously dispatches the mouseup event via widget->HandleInputEvent().
  9. The attacker’s mouseup listener is executed. The listener calls window.frameElement.remove(), detaching the iframe.
  10. Blink synchronously processes the detachment, calling RenderFrameImpl::FrameDetached. This destroys the RenderFrameImpl, which notifies ChromeRenderFrameObserver::OnDestruct, destroying the observer. This implicitly destroys the ToolExecutor, ClickTool, and ClickDispatcher.
  11. The attacker uses heap grooming in the mouseup listener to overwrite the freed ToolExecutor chunk.
  12. Execution unwinds back to ClickTool::Cancel() at chrome/renderer/actor/click_tool.cc:114, which calls click_dispatcher_.reset() on the freed object.
  13. Execution unwinds to ToolExecutor::CancelTool(), which calls ToolFinished() (chrome/renderer/actor/tool_executor.cc:277).
  14. Inside ToolFinished() (chrome/renderer/actor/tool_executor.cc:280), the code accesses multiple fields on the freed this pointer and executes std::move(completion_callback_).Run(), leading to potential Remote Code Execution (RCE) in the renderer process.

While ClickDispatcher::DoMouseUpImpl correctly checks !weak_this after returning from HandleInputEvent, its callers (ClickTool and ToolExecutor) do not verify if they were destroyed during the synchronous call.

(Note: These are suggested steps based on static analysis; our tooling does not yet execute code to provide a working PoC.)

Proposed Fix

The objects on the call stack must check for their own destruction after any synchronous call that can execute script. ClickTool::Cancel should use a base::WeakPtr to check its existence before accessing its members. Similarly, ToolExecutor::CancelTool must verify the ToolExecutor is still alive before calling ToolFinished or accessing this.

Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker