CVE-2026-11657
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.cc |
modified |
Files Changed
chrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl.ccchrome/browser/ui/autofill/payments/mandatory_reauth_bubble_controller_impl_unittest.cc
Patch
From f930066e3f87cb488f05c07344dae0150857290d Mon Sep 17 00:00:00 2001
From: Vinny Persky <vinnypersky@google.com>
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 <smcgruer@chromium.org>
Commit-Queue: Vinny Persky <vinnypersky@google.com>
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);
Regression Test / PoC
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);
Original Bug Report
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.ccchrome/browser/ui/autofill/autofill_bubble_controller_base.ccchrome/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)
- Using macOS, ensure Touch ID and Apple Watch authentication are not configured for the user.
- In Chrome, ensure a credit card is saved and the Mandatory Reauth opt-in promo is eligible to be shown.
- Navigate to a site that opens a popup window via
window.open(). - In the popup, trigger the Mandatory Reauth opt-in bubble (e.g., via an autofill flow).
- Click the “Yes” button on the bubble.
- While the macOS system password prompt is visible, have the opener window call
popup.close()on the popup window handle. - Dismiss the macOS password prompt (accept or cancel).
- Observe if a crash occurs in the browser process due to a Use-After-Free in
OnBubbleClosed.
Recommended Fix
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.