Critical watch CVSS 7.5 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Passwords
DescriptionUse after free in Passwords
ComponentPasswords
Sub-componentUI>Browser>Passwords
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.
Tracker516957738
Fix commit2abda6c797ed (chromium/src) +61/-45
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-27 (per vendor advisory)
Fix landed2026-05-27 (public git)
Disclosed2026-06-02
Patch gap6 days — fix public in source before the advisory

Background

`DeviceAuthenticatorMac`
the macOS device-reauth object (chrome/browser/device_reauth/mac/device_authenticator_mac.mm) that Password Manager uses to verify the user before exposing stored credentials.
`AuthenticateUserWithNonBiometrics`
the non-biometric fallback (used when CanAuthenticateWithBiometrics() is false) that, per the inline comment, “runs a dialog with a nested run loop,” pumping the message loop while the calling frame is suspended.
Nested run loop re-entrancy
during that modal loop, unrelated tasks such as page/tab destruction can execute, freeing the authenticator and its callers before the dialog returns.
`base::WeakPtr` / `weak_ptr_factory_`
provides a pointer that becomes null when this is destroyed, used to confirm survival across the nested loop. This commit (#1636852) fixes bugs 516957738 and 516878683 together.

Root Cause Analysis

The non-biometric branch of AuthenticateWithMessage invoked authenticator_->AuthenticateUserWithNonBiometrics(...) synchronously, inline on the caller’s stack, and that call spins a modal dialog’s nested run loop. While the loop runs, the page or UI that triggered the reauth can be destroyed, tearing down DeviceAuthenticatorMac and the caller objects suspended above it on the stack. The pre-patch code guarded only its own this via weak_this before OnAuthenticationCompleted, but the nested loop still executed within the outer call stack, so re-entrant teardown produced a use-after-free in those callers.

The fix posts the dialog work to a separate task (AuthenticateWithNonBiometricsAsync, bound through weak_ptr_factory_.GetWeakPtr()) and returns immediately, so the nested run loop no longer runs inside the originating stack; the new method re-checks the weak pointer after the dialog before reporting completion. The unit tests move from base::MockCallback to base::test::TestFuture, confirming the result is now delivered asynchronously.

Key insight
The single mistake was pumping a modal nested run loop synchronously on the caller’s stack, letting re-entrant destruction free still-referenced caller frames; the fix defers the dialog to a posted, WeakPtr-guarded task so no nested loop runs inside the originating call.

Attack Path

  1. Trigger password reauth A Password Manager action on macOS without available biometrics reaches AuthenticateWithMessage and takes the !CanAuthenticateWithBiometrics() branch.
  2. Synchronous nested loop Pre-patch, AuthenticateUserWithNonBiometrics runs its modal dialog’s nested run loop inline on the caller’s stack.
  3. Teardown during the dialog The initiating page/UI is closed while the loop spins, freeing the authenticator’s caller objects that remain on the suspended stack.
  4. Dangling access on unwind When the dialog returns, execution unwinds through the freed caller frames, a use-after-free in the browser process (exact freed object and any further primitive inferred).

Impact Assessment

The vendor advisory rates this critical and describes a use-after-free in Passwords. It is reachable only on macOS via the non-biometric reauth fallback, requires user interaction to surface the modal dialog, and requires the initiating page to be closed during that dialog; the corruption is in the browser process and any escalation beyond the dangling access is (inferred). The advisory does not report in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
if
chrome/browser/device_reauth/mac/device_authenticator_mac.mm
modified
TEST_P
chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
modified

Files Changed

  • chrome/browser/device_reauth/mac/device_authenticator_mac.h
  • chrome/browser/device_reauth/mac/device_authenticator_mac.mm
  • chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm

Audit Directions

  • Synchronous nested run loops on caller stacks
    Audit any call documented to run a nested/modal run loop that is invoked inline; prefer posting it as a task to avoid re-entrant frees of live caller frames.
  • Self-only WeakPtr guards
    Flag spots where a weak_this check protects the immediate object but leaves objects higher on the stack exposed across the re-entrant wait.
  • Other reauth/modal entry points
    Review the Windows and other platform DeviceAuthenticator implementations and other Password Manager flows that surface OS dialogs for the same inline-nested-loop pattern.
From 2abda6c797ed977b2247d63484168e1d8ea5108f Mon Sep 17 00:00:00 2001
From: Viktor Semeniuk <[email protected]>
Date: Wed, 27 May 2026 04:41:46 -0700
Subject: [PATCH] Post AuthenticateUserWithNonBiometrics call

Fixed: 516957738, 516878683
Change-Id: I19a660927c166c8d0eee855ad5a82efad866e692
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7876622
Commit-Queue: Viktor Semeniuk <[email protected]>
Reviewed-by: Ioana Treib <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1636852}
---

diff --git a/chrome/browser/device_reauth/mac/device_authenticator_mac.h b/chrome/browser/device_reauth/mac/device_authenticator_mac.h
index 24db5a70..265939d 100644
--- a/chrome/browser/device_reauth/mac/device_authenticator_mac.h
+++ b/chrome/browser/device_reauth/mac/device_authenticator_mac.h
@@ -48,6 +48,9 @@
   // Called when the authentication completes with the result |success|.
   void OnAuthenticationCompleted(bool success);
 
+  // Triggers non-biometric authentication asynchronously.
+  void AuthenticateWithNonBiometricsAsync(const std::u16string& message);
+
   // Callback to be executed after the authentication completes.
   AuthenticateCallback callback_;
 
diff --git a/chrome/browser/device_reauth/mac/device_authenticator_mac.mm b/chrome/browser/device_reauth/mac/device_authenticator_mac.mm
index c9cb06d..c3da5198 100644
--- a/chrome/browser/device_reauth/mac/device_authenticator_mac.mm
+++ b/chrome/browser/device_reauth/mac/device_authenticator_mac.mm
@@ -8,6 +8,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
+#include "base/task/sequenced_task_runner.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/device_reauth/mac/authenticator_mac.h"
 #include "chrome/browser/password_manager/password_manager_util_mac.h"
@@ -81,20 +82,16 @@
     return;
   }
   callback_ = std::move(callback);
-  // Always use CanAuthenticateWithBiometrics() before invoking the biometrics
-  // API, and if it fails use password_manager_util_mac::AuthenticateUser()
-  // instead, until crbug.com/40236979 is fixed.
   if (!CanAuthenticateWithBiometrics()) {
-    // AuthenticateUserWithNonBiometrics runs a dialog with a nested run loop,
-    // so protect against this page disappearing within that nested run loop.
-    // https://crbug.com/508289938
-    auto weak_this = weak_ptr_factory_.GetWeakPtr();
-    bool success = authenticator_->AuthenticateUserWithNonBiometrics(
-        l10n_util::GetStringFUTF16(IDS_PASSWORDS_AUTHENTICATION_PROMPT_PREFIX,
-                                   message));
-    if (weak_this) {
-      weak_this->OnAuthenticationCompleted(success);
-    }
+    // AuthenticateUserWithNonBiometrics runs a dialog with a nested run loop.
+    // Post a task to run it asynchronously, so that it doesn't block the
+    // current call stack. This prevents UaF in the callers if the page is
+    // closed during the nested run loop. https://crbug.com/508289938
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &DeviceAuthenticatorMac::AuthenticateWithNonBiometricsAsync,
+            weak_ptr_factory_.GetWeakPtr(), message));
     return;
   }
 
@@ -105,6 +102,17 @@
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
+void DeviceAuthenticatorMac::AuthenticateWithNonBiometricsAsync(
+    const std::u16string& message) {
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
+  bool success = authenticator_->AuthenticateUserWithNonBiometrics(
+      l10n_util::GetStringFUTF16(IDS_PASSWORDS_AUTHENTICATION_PROMPT_PREFIX,
+                                 message));
+  if (weak_this) {
+    weak_this->OnAuthenticationCompleted(success);
+  }
+}
+
 void DeviceAuthenticatorMac::OnAuthenticationCompleted(bool success) {
   touch_id_auth_context_ = nullptr;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm b/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
index bd67286..36112da7 100644
--- a/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
+++ b/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
@@ -8,8 +8,8 @@
 #include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
+#include "base/test/test_future.h"
 #include "base/time/time.h"
 #include "chrome/browser/device_reauth/chrome_device_authenticator_factory.h"
 #include "chrome/browser/device_reauth/mac/authenticator_mac.h"
@@ -23,8 +23,6 @@
 
 namespace {
 
-using MockAuthResultCallback =
-    base::MockCallback<DeviceAuthenticatorMac::AuthenticateCallback>;
 using device_reauth::ReauthResult;
 
 constexpr base::TimeDelta kAuthValidityPeriod = base::Seconds(60);
@@ -104,8 +102,6 @@
     return &touch_id_test_environment_;
   }
 
-  MockAuthResultCallback& result_callback() { return result_callback_; }
-
   base::HistogramTester& histogram_tester() { return histogram_tester_; }
 
  private:
@@ -119,22 +115,20 @@
       .metadata_secret = "TestMetadataSecret"};
   device::fido::mac::ScopedTouchIdTestEnvironment touch_id_test_environment_{
       config_};
-  MockAuthResultCallback result_callback_;
   base::HistogramTester histogram_tester_;
 
   // This is owned by the authenticator.
   raw_ptr<MockSystemAuthenticator> system_authenticator_ = nullptr;
 };
 
-// If time that passed since the last successful authentication is smaller than
-// kAuthValidityPeriod, no reauthentication is needed.
 TEST_P(DeviceAuthenticatorMacTest, NoReauthenticationIfLessThan60Seconds) {
   SimulateReauthSuccess();
-  EXPECT_CALL(result_callback(), Run(/*success=*/true));
+  base::test::TestFuture<bool> future_1;
 
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_1.GetCallback());
+  EXPECT_TRUE(future_1.Get());
 
   // Since the delay is smaller than kAuthValidityPeriod there shouldn't be
   // another prompt, so the auth should be reported as successful. If there is a
@@ -142,21 +136,21 @@
   // since there is no prompt expected.
   task_environment().FastForwardBy(kAuthValidityPeriod / 2);
 
-  EXPECT_CALL(result_callback(), Run(/*success=*/true));
+  base::test::TestFuture<bool> future_2;
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_2.GetCallback());
+  EXPECT_TRUE(future_2.Get());
 }
 
-// If the time since the last reauthentication is greater than
-// kAuthValidityPeriod or the authentication failed, reauthentication is needed.
 TEST_P(DeviceAuthenticatorMacTest, ReauthenticationIfMoreThan60Seconds) {
   SimulateReauthSuccess();
-  EXPECT_CALL(result_callback(), Run(/*success=*/true));
+  base::test::TestFuture<bool> future_1;
 
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_1.GetCallback());
+  EXPECT_TRUE(future_1.Get());
 
   // Make the reauth prompt auth fail.
   SimulateReauthFailure();
@@ -165,36 +159,36 @@
   // authentication.
   task_environment().FastForwardBy(kAuthValidityPeriod * 2);
 
-  EXPECT_CALL(result_callback(), Run(/*success=*/false));
+  base::test::TestFuture<bool> future_2;
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_2.GetCallback());
+  EXPECT_FALSE(future_2.Get());
 }
 
-// If previous authentication failed kAuthValidityPeriod isn't started and
-// reauthentication will be needed.
 TEST_P(DeviceAuthenticatorMacTest, ReauthenticationIfPreviousFailed) {
   SimulateReauthFailure();
 
   // First authentication fails, no last_good_auth_timestamp_ should be
   // recorded, which fill force reauthentication.
-  EXPECT_CALL(result_callback(), Run(/*success=*/false));
+  base::test::TestFuture<bool> future_1;
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_1.GetCallback());
+  EXPECT_FALSE(future_1.Get());
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm b/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
index bd67286..36112da7 100644
--- a/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
+++ b/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
@@ -8,8 +8,8 @@
 #include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/metrics/histogram_tester.h"
-#include "base/test/mock_callback.h"
 #include "base/test/task_environment.h"
+#include "base/test/test_future.h"
 #include "base/time/time.h"
 #include "chrome/browser/device_reauth/chrome_device_authenticator_factory.h"
 #include "chrome/browser/device_reauth/mac/authenticator_mac.h"
@@ -23,8 +23,6 @@
 
 namespace {
 
-using MockAuthResultCallback =
-    base::MockCallback<DeviceAuthenticatorMac::AuthenticateCallback>;
 using device_reauth::ReauthResult;
 
 constexpr base::TimeDelta kAuthValidityPeriod = base::Seconds(60);
@@ -104,8 +102,6 @@
     return &touch_id_test_environment_;
   }
 
-  MockAuthResultCallback& result_callback() { return result_callback_; }
-
   base::HistogramTester& histogram_tester() { return histogram_tester_; }
 
  private:
@@ -119,22 +115,20 @@
       .metadata_secret = "TestMetadataSecret"};
   device::fido::mac::ScopedTouchIdTestEnvironment touch_id_test_environment_{
       config_};
-  MockAuthResultCallback result_callback_;
   base::HistogramTester histogram_tester_;
 
   // This is owned by the authenticator.
   raw_ptr<MockSystemAuthenticator> system_authenticator_ = nullptr;
 };
 
-// If time that passed since the last successful authentication is smaller than
-// kAuthValidityPeriod, no reauthentication is needed.
 TEST_P(DeviceAuthenticatorMacTest, NoReauthenticationIfLessThan60Seconds) {
   SimulateReauthSuccess();
-  EXPECT_CALL(result_callback(), Run(/*success=*/true));
+  base::test::TestFuture<bool> future_1;
 
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_1.GetCallback());
+  EXPECT_TRUE(future_1.Get());
 
   // Since the delay is smaller than kAuthValidityPeriod there shouldn't be
   // another prompt, so the auth should be reported as successful. If there is a
@@ -142,21 +136,21 @@
   // since there is no prompt expected.
   task_environment().FastForwardBy(kAuthValidityPeriod / 2);
 
-  EXPECT_CALL(result_callback(), Run(/*success=*/true));
+  base::test::TestFuture<bool> future_2;
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_2.GetCallback());
+  EXPECT_TRUE(future_2.Get());
 }
 
-// If the time since the last reauthentication is greater than
-// kAuthValidityPeriod or the authentication failed, reauthentication is needed.
 TEST_P(DeviceAuthenticatorMacTest, ReauthenticationIfMoreThan60Seconds) {
   SimulateReauthSuccess();
-  EXPECT_CALL(result_callback(), Run(/*success=*/true));
+  base::test::TestFuture<bool> future_1;
 
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_1.GetCallback());
+  EXPECT_TRUE(future_1.Get());
 
   // Make the reauth prompt auth fail.
   SimulateReauthFailure();
@@ -165,36 +159,36 @@
   // authentication.
   task_environment().FastForwardBy(kAuthValidityPeriod * 2);
 
-  EXPECT_CALL(result_callback(), Run(/*success=*/false));
+  base::test::TestFuture<bool> future_2;
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_2.GetCallback());
+  EXPECT_FALSE(future_2.Get());
 }
 
-// If previous authentication failed kAuthValidityPeriod isn't started and
-// reauthentication will be needed.
 TEST_P(DeviceAuthenticatorMacTest, ReauthenticationIfPreviousFailed) {
   SimulateReauthFailure();
 
   // First authentication fails, no last_good_auth_timestamp_ should be
   // recorded, which fill force reauthentication.
-  EXPECT_CALL(result_callback(), Run(/*success=*/false));
+  base::test::TestFuture<bool> future_1;
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_1.GetCallback());
+  EXPECT_FALSE(future_1.Get());
 
   // Although it passed less than kAuthValidityPeriod no valid authentication
   // should be recorded as reauth will fail.
   SimulateReauthFailure();
   task_environment().FastForwardBy(kAuthValidityPeriod / 2);
 
-  EXPECT_CALL(result_callback(), Run(/*success=*/false));
+  base::test::TestFuture<bool> future_2;
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      future_2.GetCallback());
+  EXPECT_FALSE(future_2.Get());
 }
 
-// If pending authentication can be canceled.
 TEST_P(DeviceAuthenticatorMacTest, CancelPendingAuthentication) {
   // Non-biometric reauth is modal, and hence cannot be requested twice.
   if (!is_biometric_available()) {
@@ -203,14 +197,14 @@
   touch_id_environment()->SimulateTouchIdPromptSuccess();
   touch_id_environment()->DoNotResolveNextPrompt();
 
+  base::test::TestFuture<bool> future;
   authenticator()->AuthenticateWithMessage(
-      /*message=*/u"Chrome is trying to show passwords.",
-      result_callback().Get());
+      /*message=*/u"Chrome is trying to show passwords.", future.GetCallback());
 
   // Authentication should fail as it will take 10 seconds to authenticate, and
   // there will be a cancellation in the meantime.
-  EXPECT_CALL(result_callback(), Run(/*success=*/false));
   authenticator()->Cancel();
+  EXPECT_FALSE(future.Get());
 }
 
 TEST_P(DeviceAuthenticatorMacTest, BiometricAuthenticationAvailability) {
@@ -240,8 +234,10 @@
 TEST_P(DeviceAuthenticatorMacTest, RecordSuccessAuthHistogram) {
   SimulateReauthSuccess();
 
+  base::test::TestFuture<bool> future;
   authenticator()->AuthenticateWithMessage(
-      /*message=*/u"Chrome is trying to show passwords.", base::DoNothing());
+      /*message=*/u"Chrome is trying to show passwords.", future.GetCallback());
+  EXPECT_TRUE(future.Get());
 
   histogram_tester().ExpectUniqueSample(kHistogramName, ReauthResult::kSuccess,
                                         1);
@@ -250,10 +246,17 @@
 TEST_P(DeviceAuthenticatorMacTest, RecordSkippedAuthHistogram) {
   SimulateReauthSuccess();
 
+  base::test::TestFuture<bool> future_1;
   authenticator()->AuthenticateWithMessage(
-      /*message=*/u"Chrome is trying to show passwords.", base::DoNothing());
+      /*message=*/u"Chrome is trying to show passwords.",
+      future_1.GetCallback());
+  EXPECT_TRUE(future_1.Get());
+
+  base::test::TestFuture<bool> future_2;
   authenticator()->AuthenticateWithMessage(
-      /*message=*/u"Chrome is trying to show passwords.", base::DoNothing());
+      /*message=*/u"Chrome is trying to show passwords.",
+      future_2.GetCallback());
+  EXPECT_TRUE(future_2.Get());
 
   histogram_tester().ExpectBucketCount(kHistogramName, ReauthResult::kSuccess,
                                        1);
@@ -264,8 +267,10 @@
 TEST_P(DeviceAuthenticatorMacTest, RecordFailAuthHistogram) {
   SimulateReauthFailure();
 
+  base::test::TestFuture<bool> future;
   authenticator()->AuthenticateWithMessage(
-      /*message=*/u"Chrome is trying to show passwords.", base::DoNothing());
+      /*message=*/u"Chrome is trying to show passwords.", future.GetCallback());
+  EXPECT_FALSE(future.Get());
 
   histogram_tester().ExpectUniqueSample(kHistogramName, ReauthResult::kFailure,
                                         1);
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in PasswordAutofillManager::DidAcceptSuggestion 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) vulnerability exists in PasswordAutofillManager on macOS during fallback device authentication. If the containing frame is destroyed while the synchronous macOS password modal dialog is active, the manager is deleted but its member variables are subsequently dereferenced upon dialog dismissal. This could potentially allow arbitrary code execution inside the unsandboxed browser process.

Affected files:

  • components/password_manager/core/browser/password_autofill_manager.cc

Estimated timestamp from git blame: 2022-08-26

Description

A potential Use-After-Free (UAF) vulnerability exists in PasswordAutofillManager::DidAcceptSuggestion on macOS. This issue stems from a synchronous nested run loop run during non-biometric fallback authentication.

While mitigations exist in DeviceAuthenticatorMac using a weak pointer guard to protect its own lifetime, the calling frame PasswordAutofillManager::DidAcceptSuggestion lacks lifetime tracking after the synchronous authentication flow returns, leading to a potential UAF on the this pointer.

Root Cause Analysis

  1. In PasswordAutofillManager::DidAcceptSuggestion (components/password_manager/core/browser/password_autofill_manager.cc line 288), accepting a standard password suggestion invokes OnPasswordCredentialSuggestionAccepted (line 427).
  2. If device re-authentication is required but biometric authentication (Touch ID) is currently unavailable (e.g., due to clamshell mode or biometric lockout), DeviceAuthenticatorMac::AuthenticateWithMessage (chrome/browser/device_reauth/mac/device_authenticator_mac.mm line 71) falls back to non-biometric authentication via AuthenticateUserWithNonBiometrics (line 92).
  3. This eventually calls password_manager_util_mac::AuthenticateUser (chrome/browser/password_manager/password_manager_util_mac.mm line 73) which invokes base::mac::GetAuthorizationRightsWithPrompt (line 88).
  4. This displays a system modal dialog (AuthorizationCopyRights with kAuthorizationFlagInteractionAllowed) that runs a synchronous nested CFRunLoop on the main UI thread to keep the application responsive while the prompt is active.
  5. During this nested run loop, the attacker-controlled page can trigger a navigation or close the frame, which causes RenderFrameDeleted to fire.
  6. ContentPasswordManagerDriverFactory::RenderFrameDeleted (components/password_manager/content/browser/content_password_manager_driver_factory.cc line 116) erases the driver from frame_driver_map_, immediately destroying the ContentPasswordManagerDriver and its inline PasswordAutofillManager member (components/password_manager/content/browser/content_password_manager_driver.h line 215).
  7. When the user dismisses the OS modal dialog, the nested run loop finishes and execution unwinds back to PasswordAutofillManager::DidAcceptSuggestion (line 433).
  8. The code immediately dereferences member fields of this (such as password_client_ and password_manager_driver_) to determine if it should enter a loading state. Since the PasswordAutofillManager instance has been destroyed, this results in a Use-After-Free.

MiraclePtr (BackupRefPtr) Status

Because PasswordAutofillManager is allocated inline within ContentPasswordManagerDriver and there are no external raw_ptr<PasswordAutofillManager> pointers keeping its refcount active at this point in the call stack, the memory is immediately deallocated back to PartitionAlloc and is reclaimable, potentially bypassing MiraclePtr protections.

Potential Steps to Trigger the Vulnerability

Note: These steps are theoretical/suggested based on static analysis; our tooling does not currently have the capability to run code or execute a proof of concept.

  1. Run Google Chrome on macOS with a locked-out or unavailable Touch ID configuration (such as clamshell mode) and a device screen lock configured.
  2. Navigate to an attacker-controlled page containing a password form and trigger the autofill suggestions dropdown.
  3. Accept a password suggestion to trigger the OS authentication prompt.
  4. While the modal authentication prompt is displayed, have the page automatically close the containing frame or perform a cross-origin navigation.
  5. Dismiss the authentication prompt. This returns control to DidAcceptSuggestion, which should result in a browser process crash or unexpected memory access.

Suggested Remediation

Add a base::WeakPtr guard in PasswordAutofillManager::DidAcceptSuggestion to track the lifetime of this across the reauthentication flow. For example:

base::WeakPtr<PasswordAutofillManager> weak_this = weak_ptr_factory_.GetWeakPtr();

// ... Call to OnPasswordCredentialSuggestionAccepted ...

if (!weak_this) {
  return;
}

// Safely resume post-authentication logic using weak_this

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


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