Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Lens
DescriptionUse after free in Lens
ComponentLens
Sub-componentLensOverlay
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.
Tracker401029609
Fix commit2690d69b56b7 (chromium/src) +12/-2
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
CreditedSungKwon Lee of Enki Whitehat
Reported2025-03-05 (per vendor advisory)
Fix landed2025-03-07 (public git)
Disclosed2025-03-19
Patch gap12 days — fix public in source before the advisory

Background

`LensPermissionBubbleController`
the browser-side object that shows the Lens Overlay permission dialog and handles its accept/cancel callbacks.
`OnPermissionDialogAccept`
the callback invoked when the user presses the accept button in the permission bubble; it enables Lens prefs and tears down the dialog widget.
`CloseUISync`
a LensOverlayController teardown routine that synchronously destroys Lens UI, including resetting the LensPermissionBubbleController.
`base::WeakPtr`
a non-owning pointer that becomes null once its referent is destroyed, allowing a callback to detect that this has been freed.

Root Cause Analysis

OnPermissionDialogAccept calls pref_service_->SetBoolean(prefs::kLensSharingPageScreenshotEnabled, true) and then continues to touch member state (pref_service_, dialog_widget_) on this. Setting that preference synchronously fires a chain of pref-change observers that can reach LensOverlayController, and if the controller then fails to capture a page screenshot it calls CloseUISync(), which resets and destroys the owning LensPermissionBubbleController. Because SetBoolean re-enters and can free this before returning, the subsequent statements in OnPermissionDialogAccept execute on a destroyed object. Specifically, the second SetBoolean(prefs::kLensSharingPageContentEnabled, true) and the dialog_widget_ = nullptr assignment dereference freed memory, producing a use-after-free.

The fix captures a base::WeakPtr to the controller before the first SetBoolean and guards each post-SetBoolean access with a .get() liveness check.

Key insight
A callback that mutates shared pref state can synchronously trigger the destruction of the very object it is running on, so any member access after the mutating call is a use-after-free unless the object’s continued existence is re-validated.

Attack Path

  1. Trigger the permission flow Induce the Lens Overlay to present its permission bubble, instantiating a LensPermissionBubbleController.
  2. Accept the dialog Cause OnPermissionDialogAccept to run, invoking SetBoolean on prefs::kLensSharingPageScreenshotEnabled.
  3. Force re-entrant teardown Arrange conditions where LensOverlayController cannot capture a page screenshot, so the pref-change callbacks reach CloseUISync() and free the controller.
  4. Dereference freed object Control returns into OnPermissionDialogAccept, which touches pref_service_/dialog_widget_ on the now-freed this, exercising the UAF.

Impact Assessment

This is a critical use-after-free in the browser process reachable through the Lens Overlay permission UI. An attacker who can shape the freed allocation may achieve memory corruption and potentially code execution in the browser process, which runs with high privilege. The bug requires a race between accepting the dialog and a failed screenshot-driven teardown, but the reentrancy path is deterministic enough to be a serious exploitation target.

Files Changed

  • chrome/browser/ui/lens/lens_permission_bubble_controller.cc

Audit Directions

  • Re-entrant pref mutation
    Audit callbacks that call PrefService::SetBoolean/SetValue and then continue to access this; pref observers can synchronously destroy the caller.
  • Post-callback member access
    Flag any method that invokes an external callback or observer-notifying call and afterward dereferences members without re-checking object liveness via base::WeakPtr.
  • Synchronous UI teardown paths
    Trace CloseUISync-style synchronous destructors to find all callers whose stack frames may still reference the destroyed object after they return.
From 2690d69b56b764bfb1997cfb0715ac22b5976648 Mon Sep 17 00:00:00 2001
From: Nihar Majmudar <[email protected]>
Date: Fri, 07 Mar 2025 10:53:29 -0800
Subject: [PATCH] [LensOverlay] Check if controller exists in OnPermissionDialogAccept.

There's a chance LensPermissionBubbleController gets destroyed before
the OnPermissionDialogAccepted callback finishes. Check if the
controller still exists at this point.

This risk occurs after SetBoolean() is called on the pref service
since that then invokes a set of callbacks that could eventually lead
to CloseUISync() being called (more thorough RCA in bug).

Change-Id: I3d1842c3531fad474200a27ef054a30b753ccd61
Bug: 401029609
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6333695
Reviewed-by: Duncan Mercer <[email protected]>
Commit-Queue: Nihar Majmudar <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1429619}
---

diff --git a/chrome/browser/ui/lens/lens_permission_bubble_controller.cc b/chrome/browser/ui/lens/lens_permission_bubble_controller.cc
index 314f74f..73c8a3cb 100644
--- a/chrome/browser/ui/lens/lens_permission_bubble_controller.cc
+++ b/chrome/browser/ui/lens/lens_permission_bubble_controller.cc
@@ -164,11 +164,21 @@
 void LensPermissionBubbleController::OnPermissionDialogAccept() {
   RecordPermissionUserAction(LensPermissionUserAction::kAcceptButtonPressed,
                              invocation_source_);
+  base::WeakPtr<LensPermissionBubbleController>
+      lens_permission_bubble_controller = weak_ptr_factory_.GetWeakPtr();
   pref_service_->SetBoolean(prefs::kLensSharingPageScreenshotEnabled, true);
-  if (lens::features::IsLensOverlayContextualSearchboxEnabled()) {
+  // TODO(crbug.com/401029609): Rethink permission bubble lifetime.
+  // Must check WeakPtr in case CloseUISync() is called in
+  // LensOverlayController. This happens if the LensOverlayController cannot
+  // successfully take a screenshot of the page. If CloseUISync() is called,
+  // LensPermissionBubbleController gets reset before the following.
+  if (lens_permission_bubble_controller.get() &&
+      lens::features::IsLensOverlayContextualSearchboxEnabled()) {
     pref_service_->SetBoolean(prefs::kLensSharingPageContentEnabled, true);
   }
-  dialog_widget_ = nullptr;
+  if (lens_permission_bubble_controller.get()) {
+    dialog_widget_ = nullptr;
+  }
 }
 
 void LensPermissionBubbleController::OnPermissionDialogCancel() {
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.