CVE-2026-9884
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/device_reauth/mac/device_authenticator_mac.mm |
modified |
Files Changed
chrome/browser/device_reauth/mac/device_authenticator_mac.mm
Patch
From 7a43db7d3d42a665df15826decddcd7109028d10 Mon Sep 17 00:00:00 2001
From: Avi Drissman <avi@chromium.org>
Date: Mon, 04 May 2026 07:50:53 -0700
Subject: [PATCH] Guard against nested run loop issues in DeviceAuthenticatorMac
DeviceAuthenticatorMac can fall back to showing a dialog which uses a
nested run loop. For that case, use weak pointers appropriately to avoid
any UaF issues.
Fixed: 508289938
Link: https://chromium-review.googlesource.com/id/I1e8c2c85ea4d1d4ab245bccfcc910fa26a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7807746
Commit-Queue: Avi Drissman <avi@chromium.org>
Reviewed-by: Ioana Treib <ioanap@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1624662}
---
diff --git a/chrome/browser/device_reauth/mac/device_authenticator_mac.mm b/chrome/browser/device_reauth/mac/device_authenticator_mac.mm
index aa2414df..c9cb06d 100644
--- a/chrome/browser/device_reauth/mac/device_authenticator_mac.mm
+++ b/chrome/browser/device_reauth/mac/device_authenticator_mac.mm
@@ -85,9 +85,16 @@
// API, and if it fails use password_manager_util_mac::AuthenticateUser()
// instead, until crbug.com/40236979 is fixed.
if (!CanAuthenticateWithBiometrics()) {
- OnAuthenticationCompleted(authenticator_->AuthenticateUserWithNonBiometrics(
+ // 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)));
+ message));
+ if (weak_this) {
+ weak_this->OnAuthenticationCompleted(success);
+ }
return;
}
Original Bug Report
Potential Use-After-Free in DeviceAuthenticatorMac due to nested run loop
Flapjack, 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 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 DeviceAuthenticatorMac when falling back to synchronous system password authentication on macOS. The blocking OS call spins a nested event loop, allowing IPCs to destroy the authenticator instance while its this pointer is already cached on the stack. When the call returns, execution continues using the dangling pointer, potentially leading to arbitrary code execution in the browser process.
Affected files:
chrome/browser/device_reauth/mac/device_authenticator_mac.mmchrome/browser/device_reauth/mac/authenticator_mac.mmchrome/browser/password_manager/password_manager_util_mac.mm
Estimated timestamp from git blame: 2023-03-03
Summary
A potential Use-After-Free (UAF) vulnerability exists in DeviceAuthenticatorMac::AuthenticateWithMessage on macOS. When biometric authentication is unavailable, the code falls back to a synchronous OS password prompt. This system call spins a nested macOS event loop, allowing Chromium’s main thread to continue processing IPC messages. If a malicious page triggers frame destruction (e.g., via window.close()) while the prompt is active, the DeviceAuthenticatorMac instance is freed. Upon the prompt’s dismissal, the browser resumes execution using a cached, dangling this pointer, which can be leveraged for Arbitrary Code Execution (ACE) in the browser process.
Technical Details
In chrome/browser/device_reauth/mac/device_authenticator_mac.mm, the AuthenticateWithMessage function handles device re-authentication. If Touch ID is unavailable, it performs a synchronous fallback:
if (!CanAuthenticateWithBiometrics()) {
OnAuthenticationCompleted(authenticator_->AuthenticateUserWithNonBiometrics(
l10n_util::GetStringFUTF16(IDS_PASSWORDS_AUTHENTICATION_PROMPT_PREFIX,
message)));
return;
}
- Evaluation Order: Under C++17 evaluation rules for member function calls (
E1.E2(E3)), the postfix-expression (thethispointer forOnAuthenticationCompleted) is evaluated and cached in a register or on the stack before the argument expression (authenticator_->...) is evaluated. - Blocking Call & Nested Loop:
AuthenticateUserWithNonBiometricseventually invokesAuthorizationCopyRightsviapassword_manager_util_mac::AuthenticateUser. This is a blocking macOS system API that displays a modal password dialog. To keep the application responsive, it spins a nestedCFRunLoop. Because Chromium’sMessagePumpCFRunLoopis registered tokCFRunLoopCommonModes, it continues to pump tasks and Mojo IPCs from renderers while the dialog is visible. - Object Destruction: If the renderer sends an IPC that destroys the
WebContents(e.g., a timer firingwindow.close()), the destruction is processed synchronously during the nested loop. This destroys theContentPasswordManagerDriverFactory, theContentPasswordManagerDriver, thePasswordAutofillManager, and ultimately theDeviceAuthenticatorMacinstance. The backing memory is freed. - Use-After-Free: When the user dismisses the system dialog,
AuthorizationCopyRightsreturns. Execution resumes inAuthenticateWithMessage, which uses the previously cached (and now dangling)thispointer to callOnAuthenticationCompleted.
Inside OnAuthenticationCompleted, a write occurs (touch_id_auth_context_ = nullptr;), followed by a read and execution of a base::OnceCallback (std::move(callback_).Run(success);). Because the implicit this pointer is held on the stack during the call, it is not protected by MiraclePtr (BackupRefPtr). An attacker who reclaims the freed memory via heap spraying can forge the callback_ member, leading to Arbitrary Code Execution in the browser process.
Potential Reproduction Steps
Note: Our tooling agent cannot execute code, so these are suggested steps based on static analysis.
- On a macOS device where Touch ID is disabled or unavailable, an attacker hosts a malicious page with a password field.
- The attacker’s JavaScript sets a
setTimeoutto callwindow.close()after a brief delay. - The user interacts with the password field and selects a saved credential, triggering an Autofill re-authentication request to the browser process.
- The browser invokes
DeviceAuthenticatorMac::AuthenticateWithMessage, bringing up the blocking OS password dialog. - While the dialog is visible, the
setTimeoutfires, sending thewindow.close()IPC to the browser. - The browser processes the IPC in the nested
CFRunLoop, destroying the frame and freeing theDeviceAuthenticatorMacobject. - The attacker’s JavaScript (e.g., from another open window or a Web Worker) performs heap spraying to reclaim the freed memory with a forged
base::OnceCallback. - The user clicks “Cancel” on the OS password prompt.
- The system call returns, and the browser executes the hijacked callback, achieving code execution.
Suggested Fix
Do not call OnAuthenticationCompleted directly using the implicit this pointer inline with the synchronous blocking call. Instead, evaluate the result first, and use a base::WeakPtr to safely check if the object survived the nested run loop before proceeding.
if (!CanAuthenticateWithBiometrics()) {
base::WeakPtr<DeviceAuthenticatorMac> 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);
}
return;
}
Evaluated with Chrome root at commit: cc901875d53bf4e4fe0e01f02843871da4106e70
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.