Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Payments
DescriptionUse after free in Payments
ComponentPayments
Bug ClassUAF
Tracker513465272
Fix commitf930066e3f87 (chromium/src) +35/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-08

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.cc
modified

Files Changed

  • chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.cc
  • chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl_unittest.cc
From f930066e3f87cb488f05c07344dae0150857290d Mon Sep 17 00:00:00 2001
From: Vinny Persky <[email protected]>
Date: Wed, 27 May 2026 11:03:54 -0700
Subject: [PATCH] Fix potential macOS UAF in re-auth

There is a potential UAF in the re-auth flow for MacOS in the opt-in
bubble acceptance, that this CL fixes.

Fixed: 513465272
Change-Id: Ib18132e3c1139932ef83ba8044a0ebc86aa9c945
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7876118
Reviewed-by: Stephen McGruer <[email protected]>
Commit-Queue: Vinny Persky <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1637093}
---

diff --git a/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.cc b/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.cc
index 17844c0e..5bdc942b 100644
--- a/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.cc
+++ b/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.cc
@@ -188,6 +188,13 @@
   view_android_.reset();
 #endif
 
+  // On macOS without biometrics, the accept/cancel callbacks have the potential
+  // to destroy WebContents in a nested runloop due to OS implementations. A
+  // weak pointer must be kept to then later be checked.
+  // TODO(crbug.com/517126769): Make callbacks below asynchronous so that a weak
+  // pointer isn't needed.
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
+
   if (current_bubble_type_ == MandatoryReauthBubbleType::kOptIn) {
     if (!bubble_hide_initiated_by_bubble_manager_) {
       LogBubbleCloseOptInMetrics(closed_reason);
@@ -226,6 +233,12 @@
     current_bubble_type_ = MandatoryReauthBubbleType::kInactive;
   }
 
+  if (!weak_this) {
+    // `this` was freed inside the callback (e.g. WebContents destroyed during
+    // a nested run loop on macOS). Do not touch members.
+    return;
+  }
+
   UpdatePageActionIcon();
 }
 
diff --git a/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl_unittest.cc b/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl_unittest.cc
index ec34c650..ad9fa3a 100644
--- a/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl_unittest.cc
+++ b/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/test/mock_callback.h"
 #include "base/test/with_feature_override.h"
 #include "chrome/browser/ui/autofill/autofill_bubble_base.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "components/autofill/core/browser/metrics/payments/mandatory_reauth_metrics.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
@@ -138,6 +139,27 @@
       1);
 }
 
+// Regression test: on macOS without biometrics, it's possible for the
+// accept/cancel callbacks to destroy web contents when a user accepts the
+// re-auth bubble, which would cause a use-after-free. This test ensures this
+// case is handled.
+TEST_P(MandatoryReauthBubbleControllerImplTestWithFeatureOverride,
+       OnBubbleClosedSurvivesWebContentsDestructionInAcceptCallback) {
+  // The accept callback destroys the WebContents that owns `ctrl` (via
+  // WebContentsUserData).
+  base::OnceClosure destroy_web_contents = base::BindOnce(
+      [](Browser* browser) {
+        browser->tab_strip_model()->DetachAndDeleteWebContentsAt(0);
+      },
+      browser());
+
+  controller()->SetupAndShowBubble(std::move(destroy_web_contents),
+                                   base::DoNothing(), base::DoNothing());
+
+  // Simulate the user clicking "Yes" on the opt-in bubble.
+  controller()->OnBubbleClosed(PaymentsUiClosedReason::kAccepted);
+}
+
 INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(
     MandatoryReauthBubbleControllerImplTestWithFeatureOverride);
 
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl_unittest.cc b/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl_unittest.cc
index ec34c650..ad9fa3a 100644
--- a/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl_unittest.cc
+++ b/chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/test/mock_callback.h"
 #include "base/test/with_feature_override.h"
 #include "chrome/browser/ui/autofill/autofill_bubble_base.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "components/autofill/core/browser/metrics/payments/mandatory_reauth_metrics.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
@@ -138,6 +139,27 @@
       1);
 }
 
+// Regression test: on macOS without biometrics, it's possible for the
+// accept/cancel callbacks to destroy web contents when a user accepts the
+// re-auth bubble, which would cause a use-after-free. This test ensures this
+// case is handled.
+TEST_P(MandatoryReauthBubbleControllerImplTestWithFeatureOverride,
+       OnBubbleClosedSurvivesWebContentsDestructionInAcceptCallback) {
+  // The accept callback destroys the WebContents that owns `ctrl` (via
+  // WebContentsUserData).
+  base::OnceClosure destroy_web_contents = base::BindOnce(
+      [](Browser* browser) {
+        browser->tab_strip_model()->DetachAndDeleteWebContentsAt(0);
+      },
+      browser());
+
+  controller()->SetupAndShowBubble(std::move(destroy_web_contents),
+                                   base::DoNothing(), base::DoNothing());
+
+  // Simulate the user clicking "Yes" on the opt-in bubble.
+  controller()->OnBubbleClosed(PaymentsUiClosedReason::kAccepted);
+}
+
 INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(
     MandatoryReauthBubbleControllerImplTestWithFeatureOverride);
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in MandatoryReauthBubbleControllerImpl::OnBubbleClosed on macOS

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential Use-After-Free (UAF) exists in the browser process when handling the Mandatory Reauth opt-in bubble on macOS. A nested run loop triggered during system authentication allows the controller to be destroyed, leading to virtual function calls on a freed object when authentication completes.

Affected files:

  • chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.cc
  • chrome/browser/ui/autofill/autofill_bubble_controller_base.cc
  • chrome/browser/device_reauth/mac/device_authenticator_mac.mm

Estimated timestamp from git blame: 2023-05-23

Summary

A potential Use-After-Free (UAF) vulnerability has been identified in MandatoryReauthBubbleControllerImpl::OnBubbleClosed within the browser process. On macOS, accepting the mandatory reauth opt-in bubble can trigger a synchronous system authentication flow. This flow invokes AuthorizationCopyRights, which spins a nested run loop. If the associated WebContents and its controller are destroyed during this nested loop (e.g., via a window.close() call from an opener page), the code will proceed to access the freed controller once the authentication dialog is dismissed.

Vulnerability Details

In chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.cc, the OnBubbleClosed method handles the closure of the opt-in bubble. When a user accepts the prompt, the following logic is executed:

// chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.cc:205
std::move(accept_mandatory_reauth_callback_).Run();
// ...
UpdatePageActionIcon(); // line 229

On macOS, if biometrics (Touch ID) are not configured, this callback leads to DeviceAuthenticatorMac::AuthenticateWithMessage, which invokes authenticator_->AuthenticateUserWithNonBiometrics. This call ultimately triggers AuthorizationCopyRights in base/mac/authorization_util.mm, displaying a system password prompt.

AuthorizationCopyRights spins a nested run loop on the UI thread to keep the interface responsive. While this nested loop is active, the browser can process other tasks, including window destruction requests. If the bubble was shown in a popup window, an attacker-controlled opener page can call popup.close(). This results in the destruction of the popup’s WebContents and all associated WebContentsUserData, including the MandatoryReauthBubbleControllerImpl object.

When the user interacts with the system password prompt, the nested loop exits and the stack unwinds back to OnBubbleClosed. The code then attempts to call UpdatePageActionIcon() at line 229. At this point, the this pointer is dangling. Because UpdatePageActionIcon is a virtual method defined in AutofillBubbleControllerBase, this results in a virtual call on a freed object, potentially leading to arbitrary code execution in the unsandboxed browser process.

Potential Impact

This issue could allow for a sandbox escape and arbitrary code execution in the context of the browser process. While it requires the user to interact with the Mandatory Reauth bubble, it can be triggered without an initial renderer compromise.

Suggested Reproduction Steps (Potential)

  1. Using macOS, ensure Touch ID and Apple Watch authentication are not configured for the user.
  2. In Chrome, ensure a credit card is saved and the Mandatory Reauth opt-in promo is eligible to be shown.
  3. Navigate to a site that opens a popup window via window.open().
  4. In the popup, trigger the Mandatory Reauth opt-in bubble (e.g., via an autofill flow).
  5. Click the “Yes” button on the bubble.
  6. While the macOS system password prompt is visible, have the opener window call popup.close() on the popup window handle.
  7. Dismiss the macOS password prompt (accept or cancel).
  8. Observe if a crash occurs in the browser process due to a Use-After-Free in OnBubbleClosed.

The controller should check its own lifetime using a base::WeakPtr after returning from the potentially synchronous authentication callback. If the controller has been destroyed, it should return early from OnBubbleClosed before attempting to call UpdatePageActionIcon().

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


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