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

Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Browser
DescriptionUse after free in Browser
ComponentBrowser
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.
Tracker508289938
Fix commit7a43db7d3d42 (chromium/src) +9/-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
CreditedGoogle
Reported2026-04-30 (per vendor advisory)
Fix landed2026-05-04 (public git)
Disclosed2026-05-27
Patch gap23 days — fix public in source before the advisory

Background

`DeviceAuthenticatorMac`
the macOS implementation of Chrome’s device re-authentication (e.g. for Password Manager), which can prompt the user to confirm their identity.
Nested run loop
a secondary message loop spun up while the calling stack is suspended (here, by a modal authentication dialog), during which arbitrary other events — including object destruction — can be processed.
`AuthenticateUserWithNonBiometrics`
the fallback path invoked when biometrics are unavailable; it shows a dialog and blocks on a nested run loop until the user responds.
`base::WeakPtr` / `weak_ptr_factory_`
a mechanism to hold a reference that automatically becomes null if the referent object is destroyed, so callers can test validity before dereferencing.

Root Cause Analysis

When CanAuthenticateWithBiometrics() returns false, the enclosing authentication method (its name is not shown in the @@ -85,9 hunk context) called authenticator_->AuthenticateUserWithNonBiometrics(...) and passed its return value straight into OnAuthenticationCompleted(...). The problem is that AuthenticateUserWithNonBiometrics runs a dialog with a nested run loop, so while it blocks, other events can be processed and the DeviceAuthenticatorMac instance (described in the comment as “this page”) can be torn down. On return from the nested loop, the original code immediately invoked OnAuthenticationCompleted on the now-freed this, a use-after-free.

The fix captures weak_ptr_factory_.GetWeakPtr() into weak_this before entering the nested run loop, stores the result in a local success, and only calls weak_this->OnAuthenticationCompleted(success) if weak_this is still valid afterward.

The restored invariant is that no method is dispatched on this across a nested-run-loop boundary without first checking the object still exists.

Key insight
The core mistake was calling a member function on this across a nested-run-loop boundary without re-validating that the object survived the reentrant loop; the fix gates the post-dialog call behind a base::WeakPtr so a destroyed DeviceAuthenticatorMac is detected instead of dereferenced.

Attack Path

  1. Trigger non-biometric re-auth On macOS, reach a flow that invokes DeviceAuthenticatorMac on a device where CanAuthenticateWithBiometrics() is false, causing the fallback AuthenticateUserWithNonBiometrics dialog and its nested run loop.
  2. Destroy the authenticator during the nested loop While the modal dialog’s nested run loop is pumping events, cause the owning UI/page to go away (e.g. navigate or close the surface that owns the DeviceAuthenticatorMac), which frees the instance.
  3. Return into freed `this` When the dialog completes, control returns to the original frame and the pre-patch code calls OnAuthenticationCompleted on the freed object, dereferencing freed memory (the use-after-free).
  4. Browser-process corruption (inferred) The use-after-free is in the browser process; any further exploitation primitive beyond the crash is not shown by the diff and is (inferred).

Impact Assessment

An attacker (or an unlucky timing interaction) that destroys the authenticating surface while the non-biometric authentication dialog is open can cause a use-after-free in the browser process on macOS; the vendor advisory rates this critical and classifies it as a use-after-free in Browser. Preconditions include the macOS platform, the non-biometric fallback path (biometrics unavailable), and the object being torn down during the dialog’s nested run loop. The diff demonstrates the dangling-this dereference only; escalation beyond a crash is (inferred), and the advisory does not assert in-the-wild exploitation.

Changed Functions

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

Files Changed

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

Audit Directions

  • Member calls across nested run loops
    Audit any code that shows a modal dialog or otherwise spins a nested run loop and then touches this or instance members on return; each such boundary needs a WeakPtr guard or equivalent liveness check.
  • Synchronous-looking blocking APIs
    Flag APIs whose names suggest a simple synchronous result (AuthenticateUser*, RunModal*, ShowDialog*) but internally pump events, since callers often assume the stack is inert while they block.
  • macOS/Cocoa reentrancy
    Review AppKit-driven flows (device_reauth, printing, file pickers, permission prompts) where OS-level modal UI can reenter Chromium and outlive or destroy the originating object.
From 7a43db7d3d42a665df15826decddcd7109028d10 Mon Sep 17 00:00:00 2001
From: Avi Drissman <[email protected]>
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 <[email protected]>
Reviewed-by: Ioana Treib <[email protected]>
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;
   }
 
Loading diff…

Original Bug Report

reported by [email protected]

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.mm
  • chrome/browser/device_reauth/mac/authenticator_mac.mm
  • chrome/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;
  }
  1. Evaluation Order: Under C++17 evaluation rules for member function calls (E1.E2(E3)), the postfix-expression (the this pointer for OnAuthenticationCompleted) is evaluated and cached in a register or on the stack before the argument expression (authenticator_->...) is evaluated.
  2. Blocking Call & Nested Loop: AuthenticateUserWithNonBiometrics eventually invokes AuthorizationCopyRights via password_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 nested CFRunLoop. Because Chromium’s MessagePumpCFRunLoop is registered to kCFRunLoopCommonModes, it continues to pump tasks and Mojo IPCs from renderers while the dialog is visible.
  3. Object Destruction: If the renderer sends an IPC that destroys the WebContents (e.g., a timer firing window.close()), the destruction is processed synchronously during the nested loop. This destroys the ContentPasswordManagerDriverFactory, the ContentPasswordManagerDriver, the PasswordAutofillManager, and ultimately the DeviceAuthenticatorMac instance. The backing memory is freed.
  4. Use-After-Free: When the user dismisses the system dialog, AuthorizationCopyRights returns. Execution resumes in AuthenticateWithMessage, which uses the previously cached (and now dangling) this pointer to call OnAuthenticationCompleted.

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.

  1. On a macOS device where Touch ID is disabled or unavailable, an attacker hosts a malicious page with a password field.
  2. The attacker’s JavaScript sets a setTimeout to call window.close() after a brief delay.
  3. The user interacts with the password field and selects a saved credential, triggering an Autofill re-authentication request to the browser process.
  4. The browser invokes DeviceAuthenticatorMac::AuthenticateWithMessage, bringing up the blocking OS password dialog.
  5. While the dialog is visible, the setTimeout fires, sending the window.close() IPC to the browser.
  6. The browser processes the IPC in the nested CFRunLoop, destroying the frame and freeing the DeviceAuthenticatorMac object.
  7. 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.
  8. The user clicks “Cancel” on the OS password prompt.
  9. 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.

View on issue tracker