CVE-2026-13029
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/webauth/authenticator_common_impl.cc |
modified |
Files Changed
content/browser/webauth/authenticator_common_impl.cc
Patch
From 43731ada00e8a3a9bcd8fc8eafe91d0940914cab Mon Sep 17 00:00:00 2001 From: Ken Buchanan <[email protected]> Date: Wed, 10 Jun 2026 15:25:48 -0700 Subject: [PATCH] [WebAuthn] Guard for AuthenticatorCommonImpl destruction during cleanup There might be rare cases where the WebContents can be destroyed synchronously during clearing of a WebAuthn request's state, as a consequence of it dismissing UI. This change adds a guard to the `Cleanup()` method that ensures continued liveness of the `AuthenticatorCommonImpl` during request cleanup. Fixed: 521495992 Change-Id: I2f93b31b37e007302a7d9fb7ca3e5b8c76fb2b3b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7921707 Commit-Queue: Ken Buchanan <[email protected]> Reviewed-by: Martin Kreichgauer <[email protected]> Cr-Commit-Position: refs/heads/main@{#1644920} --- diff --git a/content/browser/webauth/authenticator_common_impl.cc b/content/browser/webauth/authenticator_common_impl.cc index 58d2c46..2521be0 100644 --- a/content/browser/webauth/authenticator_common_impl.cc +++ b/content/browser/webauth/authenticator_common_impl.cc @@ -3222,7 +3222,15 @@ void AuthenticatorCommonImpl::Cleanup() { CHECK(!req_state_ || req_state_->request_key.value() == next_request_key_); + // `req_state_.reset()` destroys the embedder request delegate which can + // synchronously close UI which (via activation observers) may destroy the + // hosting WebContents and therefore `this`. See https://crbug.com/521495992. + base::WeakPtr<AuthenticatorCommonImpl> weak_this = weak_factory_.GetWeakPtr(); req_state_.reset(); + if (!weak_this) { + return; + } + next_request_key_++; CHECK(next_request_key_); // crash on overflow. Only 2^64 WebAuthn requests // per instance of this object are supported.
Original Bug Report
Potential UAF in AuthenticatorCommonImpl::Cleanup during synchronous widget destruction
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: AuthenticatorCommonImpl::Cleanup() synchronously resets the request state, which triggers WebAuthn modal dialog widget destruction. Under specific conditions, such as the page being hosted in an extension popup, widget closure synchronously destroys the hosting WebContents and the AuthenticatorCommonImpl instance itself. This leads to a potential Use-After-Free (UAF) write and read when Cleanup() subsequently increments and checks next_request_key_.
Affected files:
content/browser/webauth/authenticator_common_impl.cccontent/browser/webauth/authenticator_common_impl.h
Estimated timestamp from git blame: 2024-08-06
Location
content/browser/webauth/authenticator_common_impl.cc:3223-3229content/browser/webauth/authenticator_common_impl.h
Root Cause Analysis
AuthenticatorCommonImpl::Cleanup() performs req_state_.reset() to tear down request-specific state. However, resetting this state can synchronously close UI dialogs/widgets. Under certain environments (like an extension popup), closing the UI shifts focus/activation, triggering a chain that synchronously destroys the hosting WebContents, the Mojo DocumentService AuthenticatorImpl, and AuthenticatorCommonImpl itself.
Since Cleanup() accesses this immediately after the reset without any liveness guards, this leads to a potential browser-process Use-After-Free (UAF) write (next_request_key_++) and read (CHECK(next_request_key_)):
// content/browser/webauth/authenticator_common_impl.cc:3223-3229
void AuthenticatorCommonImpl::Cleanup() {
CHECK(!req_state_ || req_state_->request_key.value() == next_request_key_);
req_state_.reset(); // <--- Synchronous widget teardown and self-destruction
next_request_key_++; // <--- UAF Write to next_request_key_ on freed heap
CHECK(next_request_key_); // <--- UAF Read from freed heap
}
next_request_key_ is a plain uint64_t member (defined in authenticator_common_impl.h:384), meaning it is not protected by MiraclePtr.
Potential Destruction Sequence
req_state_.reset()deletes theRequestStatestructure.- Deleting
RequestStatedestroys its memberstd::unique_ptr<AuthenticatorRequestClientDelegate> request_delegate. - In Chrome, this invokes
~ChromeAuthenticatorRequestDelegate(), which callsdialog_model_->OnRequestComplete()(defined inchrome/browser/webauthn/chrome_authenticator_request_delegate.cc:290). - Observers are notified, and
AuthenticatorRequestDialogController::OnRequestComplete()transitions the current step toStep::kClosed. AuthenticatorRequestDialogModel::SetStep()sees thatkClosedhas no dialog UI type, and executesview_controller_.reset()(defined inauthenticator_request_dialog_model.cc:167).- The view controller’s default destructor destroys
std::unique_ptr<views::Widget> widget_, invokingviews::Widget::~Widget()(defined inui/views/widget/widget.cc:295). - Since the widget has
CLIENT_OWNS_WIDGETownership, the destructor removes the dialog from theWebContentsModalDialogManagerviaWillClose()and callsnative_widget_->Close(). - The native widget closure shifts native window activation back to the browser window.
- This activation change is observed by the parent tree, triggering
ExtensionPopup::OnWidgetTreeActivated()on the extension popup (defined inchrome/browser/ui/views/extensions/extension_popup.cc:152). - Since the WebAuthn dialog was already removed from the modal manager, the popup checks
web_modal::WebContentsHasActiveWebModal()which returns false, leading to the popup closing itself viaCloseDeferredIfNecessary(). - Popup closure synchronously destroys its
WebContentsand the frame-bound MojoDocumentServiceAuthenticatorImplcontaining theAuthenticatorCommonImplinstance, freeing its heap memory. - The call stack unwinds back to
Cleanup(), which attempts to executenext_request_key_++on the freedthisobject.
Suggested / Potential Reproduction Steps
(Note: These are potential steps based on code analysis; our tooling agent does not yet have the capability to execute code or verify the exact platform-specific activation timing.)
- Load an extension popup page that initiates a WebAuthn registration or assertion request (via
navigator.credentials.create/navigator.credentials.get). - While the WebAuthn modal dialog is active, complete or abort the request (e.g., via user cancellation or programmatic abort).
- Observe if the destruction of the widget shifts focus and synchronously tears down the
WebContentscontainer and theAuthenticatorCommonImplinstance before theCleanup()call stack unwinds.
Suggested Fix
Guard the post-reset instructions inside AuthenticatorCommonImpl::Cleanup() with a WeakPtr liveness check, similarly to how identical patterns were resolved in the sibling Digital Credentials implementation:
void AuthenticatorCommonImpl::Cleanup() {
CHECK(!req_state_ || req_state_->request_key.value() == next_request_key_);
base::WeakPtr<AuthenticatorCommonImpl> weak_this = weak_factory_.GetWeakPtr();
req_state_.reset();
if (!weak_this) {
return;
}
next_request_key_++;
CHECK(next_request_key_);
}
Evaluated with Chrome root at commit: 3947e01999a53d4e2382e39736cb79d79c7dffcf
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.