Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Mobile
DescriptionUse after free in Mobile
ComponentMobile
Bug ClassUAF
Tracker504069514
Fix commitc51f6f1236c6 (chromium/src) +12/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Changed Functions

FunctionChangeNotes
if
ios/chrome/browser/intelligence/explain_with_gemini/coordinator/explain_with_gemini_mediator.mm
modified

Files Changed

  • ios/chrome/browser/intelligence/explain_with_gemini/coordinator/explain_with_gemini_mediator.mm
From c51f6f1236c6575397abbd63f120fc608db09b48 Mon Sep 17 00:00:00 2001
From: Yasaman Sedaghat <yasamans@google.com>
Date: Mon, 20 Apr 2026 14:18:52 -0700
Subject: [PATCH] [ios] Fix a potential Use-After-Free (UAF) vulnerability in ExplainWithGeminiMediator on iOS.

We were capturing a raw web::WebState* by value within an asynchronous
block. If the WebState is destroyed before the block executes, we will
have a dangling pointer. With this change, we capture the
base::WeakPtr<web::WebState> and check its validity inside the block
before use.

Fixed: 504069514
Change-Id: Idbe23a2d2724cfdaebe1a7bcd85822fa7e27c245
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7778590
Reviewed-by: Adam Arcaro <adamta@google.com>
Commit-Queue: Yasaman Sedaghat <yasamans@google.com>
Cr-Commit-Position: refs/heads/main@{#1617767}
---

diff --git a/ios/chrome/browser/intelligence/explain_with_gemini/coordinator/explain_with_gemini_mediator.mm b/ios/chrome/browser/intelligence/explain_with_gemini/coordinator/explain_with_gemini_mediator.mm
index 79741e6..9f27e6a1 100644
--- a/ios/chrome/browser/intelligence/explain_with_gemini/coordinator/explain_with_gemini_mediator.mm
+++ b/ios/chrome/browser/intelligence/explain_with_gemini/coordinator/explain_with_gemini_mediator.mm
@@ -104,9 +104,11 @@
       WebSelectionTabHelper::FromWebState(webState);
   __weak __typeof(self) weakSelf = self;
   tabHelper->GetSelectedText(base::BindOnce(^(WebSelectionResponse* response) {
-    if (weakSelf && response.valid && response.selectedText.length) {
+    web::WebState* capturedWebState = weakWebState.get();
+    if (weakSelf && capturedWebState && response.valid &&
+        response.selectedText.length) {
       [weakSelf triggerExplainWithGeminiForText:response.selectedText
-                                       webState:webState];
+                                       webState:capturedWebState];
     }
   }));
 }
@@ -128,10 +130,11 @@
 
   __weak __typeof(self) weakSelf = self;
   tabHelper->GetSelectedText(base::BindOnce(^(WebSelectionResponse* response) {
-    if (weakSelf) {
+    web::WebState* capturedWebState = weakWebState.get();
+    if (weakSelf && capturedWebState) {
       [weakSelf addItemWithResponse:response
                          completion:completion
-                           webState:webState];
+                           webState:capturedWebState];
       return;
     }
     completion(@[]);
@@ -157,8 +160,12 @@
   }
 
   __weak __typeof(self) weakSelf = self;
+  base::WeakPtr<web::WebState> weakWebState = webState->GetWeakPtr();
   UIAction* action = [self actionWithHandler:^(UIAction* a) {
-    [weakSelf triggerExplainWithGeminiForText:text webState:webState];
+    web::WebState* capturedWebState = weakWebState.get();
+    if (weakSelf && capturedWebState) {
+      [weakSelf triggerExplainWithGeminiForText:text webState:capturedWebState];
+    }
   }];
   RecordGeminiEntryPointAvailable(gemini::EntryPoint::EditMenu);
   completion(@[ action ]);
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Use-After-Free in ExplainWithGeminiMediator via async block capture

Project Fortify, 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 without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.

Overview: A potential Use-After-Free (UAF) exists in ExplainWithGeminiMediator due to capturing a raw web::WebState* by value within an asynchronous Objective-C block. If the WebState is destroyed (e.g., via window.close()) before the block executes, the dangling pointer is dereferenced during a virtual method call, leading to potential remote code execution in the browser process.

Affected files:

  • ios/chrome/browser/intelligence/explain_with_gemini/coordinator/explain_with_gemini_mediator.mm
  • ios/chrome/browser/web_selection/model/web_selection_tab_helper.mm

Estimated timestamp from git blame: 2026-02-18

Vulnerability Details

A potential Use-After-Free (UAF) vulnerability exists in ExplainWithGeminiMediator on iOS. The issue arises when the mediator extracts a raw web::WebState* from a base::WeakPtr<web::WebState> and captures this raw pointer by value inside asynchronous Objective-C blocks.

In ExplainWithGeminiMediator.mm, methods like addItemForWebState:withCompletion: and fetchSelectionForWebState: pass a completion block to WebSelectionTabHelper::GetSelectedText. This block is executed asynchronously via base::SequencedTaskRunner::GetCurrentDefault()->PostTask after receiving a text selection payload via IPC from the renderer.

While WebSelectionTabHelper implements WebStateDestroyed to clear callbacks when a tab is closed, it only does so if the callback hasn’t already been posted to the task runner (if (!final_callback_) { return; }).

If a WebState is closed after the valid text selection response has been received and posted to the task queue, but before that queued task runs, the WebStateImpl is destroyed. When the block eventually executes, it invokes canPerformExplainWithGeminiInWebState:, which dereferences the dangling pointer by calling the virtual method webState->GetBrowserState().

Crucially, MiraclePtr (BackupRefPtr) does not protect raw C++ pointers captured by value inside Objective-C blocks or C++ lambdas, as the automated rewriter skips these compiler-generated fields. Therefore, the pointer remains completely unprotected.

Potential Attack Scenario

These are suggested steps an attacker could follow to trigger this vulnerability:

  1. A user visits a malicious website and long-presses to trigger the iOS edit menu.
  2. The mediator requests the selected text by evaluating JavaScript in the renderer.
  3. The malicious page overrides Selection.prototype.toString or intercepts the message event to return a valid text selection payload.
  4. Simultaneously, the malicious page schedules a window.close() call (e.g., via setTimeout) to execute immediately after returning the payload.
  5. The browser process receives the selection payload, and WebSelectionTabHelper posts the Objective-C block to the main thread’s task runner.
  6. The browser process then receives the close request, destroying the WebStateImpl. WebStateDestroyed is called but fails to cancel the already-posted task.
  7. The main thread executes the posted block, which attempts a virtual call on the destroyed WebState object, resulting in a UAF in the browser process.

Suggested Fix

Do not capture the raw web::WebState* inside the Objective-C blocks. Instead, capture the base::WeakPtr<web::WebState> and check its validity inside the block before use.

  base::WeakPtr<web::WebState> weakWebState = webState->GetWeakPtr();
  __weak __typeof(self) weakSelf = self;
  tabHelper->GetSelectedText(base::BindOnce(^(WebSelectionResponse* response) {
    web::WebState* capturedWebState = weakWebState.get();
    if (weakSelf && capturedWebState) {
      [weakSelf addItemWithResponse:response
                         completion:completion
                           webState:capturedWebState];
    } else {
      completion(@[]);
    }
  }));

This pattern should be applied to all blocks capturing the WebState in ExplainWithGeminiMediator.mm.

Evaluated with Chrome root at commit: 7353d249d9cacf9c7218e1d7b8a39cf39c72d646


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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