CVE-2026-12442
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
switchchrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc |
modified | |
ifchrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc |
modified |
Files Changed
chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc
Patch
From 9ba399777917241b7109962cf1eab7d70e64d3c7 Mon Sep 17 00:00:00 2001
From: Mohamed Amir Yosef <mamir@chromium.org>
Date: Wed, 10 Jun 2026 07:13:24 -0700
Subject: [PATCH] [TouchToFill] Fix UAF in TouchToFillController during destruction
During the destruction of `TouchToFillController` on Android, its member
variables are destroyed in the reverse order of their declaration:
1. `weak_ptr_factory_`
2. `visibility_controller_`
3. `ttf_delegate_`
When `ttf_delegate_` is destroyed, its destructor invokes
`authenticator_->Cancel()`. On Android, this synchronously runs a
failure callback that re-enters
`TouchToFillController::ActionCompleted`. Because `ActionCompleted` was
bound using `base::Unretained(this)`, it executes and accesses
`visibility_controller_` (which was already destroyed in step 2). This
triggers a Use-After-Destruction and a subsequent Use-After-Free
vulnerability.
This CL prevents the UAF by replacing `base::Unretained(this)` with
`weak_ptr_factory_.GetWeakPtr()` when binding the `ActionCompleted`
callback. Since `weak_ptr_factory_` is destroyed first, any re-entrant
callbacks fired during the destruction of `ttf_delegate_` or other
members are safely invalidated and ignored. Explanatory comments were
also added to prevent regressions.
Fixed: 521950423
Change-Id: I5a03851dc47d9cbcd38bc5f2d4f6b411c101b88d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7914725
Commit-Queue: Mohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: Ioana Treib <ioanap@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1644623}
---
diff --git a/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc b/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc
index af3aa93..183b8ca 100644
--- a/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc
+++ b/chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc
@@ -206,10 +206,11 @@
password_manager::metrics_util::LogFillSuggestionGroupedMatchAccepted(
/*grouped_match_accepted=*/false);
}
- // Unretained is safe here because TouchToFillController owns the delegate.
+ // A WeakPtr is necessary because the delegate may trigger this callback
+ // during or after the destruction of this controller.
ttf_delegate_->OnCredentialSelected(
credential, base::BindOnce(&TouchToFillController::ActionCompleted,
- base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()));
}
void TouchToFillController::OnAcknowledgementBeforeFillingReceived(
@@ -222,8 +223,8 @@
switch (dismiss_reason) {
case AcknowledgeGroupedCredentialSheetBridge::DismissReason::kAccept:
- // Unretained is safe here because TouchToFillController owns the
- // delegate.
+ // A WeakPtr is necessary because the delegate may trigger this callback
+ // during or after the destruction of this controller.
ttf_delegate_->OnCredentialSelected(
credential, base::BindOnce(&TouchToFillController::ActionCompleted,
weak_ptr_factory_.GetWeakPtr()));
@@ -241,24 +242,28 @@
void TouchToFillController::OnPasskeyCredentialSelected(
const PasskeyCredential& credential) {
view_.reset();
- // Unretained is safe here because TouchToFillController owns the delegate.
+ // A WeakPtr is necessary because the delegate may trigger this callback
+ // during or after the destruction of this controller.
ttf_delegate_->OnPasskeyCredentialSelected(
credential, base::BindOnce(&TouchToFillController::ActionCompleted,
- base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()));
}
void TouchToFillController::OnManagePasswordsSelected(bool passkeys_shown) {
view_.reset();
- // Unretained is safe here because TouchToFillController owns the delegate.
+ // A WeakPtr is necessary because the delegate may trigger this callback
+ // during or after the destruction of this controller.
ttf_delegate_->OnManagePasswordsSelected(
passkeys_shown, base::BindOnce(&TouchToFillController::ActionCompleted,
- base::Unretained(this)));
+ weak_ptr_factory_.GetWeakPtr()));
}
void TouchToFillController::OnHybridSignInSelected() {
view_.reset();
+ // A WeakPtr is necessary because the delegate may trigger this callback
+ // during or after the destruction of this controller.
ttf_delegate_->OnHybridSignInSelected(base::BindOnce(
- &TouchToFillController::ActionCompleted, base::Unretained(this)));
+ &TouchToFillController::ActionCompleted, weak_ptr_factory_.GetWeakPtr()));
}
void TouchToFillController::OnShowCredManSelected() {
@@ -271,9 +276,10 @@
if (!ttf_delegate_) {
return;
}
- // Unretained is safe here because TouchToFillController owns the delegate.
+ // A WeakPtr is necessary because the delegate may trigger this callback
+ // during or after the destruction of this controller.
ttf_delegate_->OnCredManDismissed(base::BindOnce(
- &TouchToFillController::ActionCompleted, base::Unretained(this)));
+ &TouchToFillController::ActionCompleted, weak_ptr_factory_.GetWeakPtr()));
}
void TouchToFillController::OnDismiss() {
@@ -284,9 +290,10 @@
// PasswordSuggestionBottomSheetV2 is launched
return;
}
- // Unretained is safe here because TouchToFillController owns the delegate.
+ // A WeakPtr is necessary because the delegate may trigger this callback
+ // during or after the destruction of this controller.
ttf_delegate_->OnDismiss(base::BindOnce(
- &TouchToFillController::ActionCompleted, base::Unretained(this)));
+ &TouchToFillController::ActionCompleted, weak_ptr_factory_.GetWeakPtr()));
}
Profile* TouchToFillController::GetProfile() {
Original Bug Report
Potential UAF and Use-After-Destruction in TouchToFillController on Android during Teardown
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 re-entrancy issue during the destruction of TouchToFillController on Android can trigger a Use-After-Destruction of its visibility controller weak pointer. This occurs because the owned delegate synchronously fires its cancellation callback during its own destruction, re-entering the parent controller. This can potentially lead to a Use-After-Free (UAF) and a controlled virtual function call in the unsandboxed browser process.
Affected files:
chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.hchrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.ccchrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.cc
Estimated timestamp from git blame: 2023-07-12
Description
There is a potential re-entrancy vulnerability in the Android implementation of Touch-to-Fill (TTF). During the destruction of TouchToFillController, its member variables are destroyed in the reverse order of their declaration. Because the controller delegate (ttf_delegate_) is declared before the visibility controller weak pointer (visibility_controller_), the visibility controller is destroyed first, while the delegate is destroyed last.
When ttf_delegate_ is destroyed, its destructor ~TouchToFillControllerAutofillDelegate is invoked, which calls authenticator_->Cancel(). On Android, DeviceAuthenticatorAndroid::Cancel() synchronously executes any pending re-authentication callback with a failure status. This callback propagates back and synchronously runs TouchToFillController::ActionCompleted via a base::Unretained binding.
Re-entering ActionCompleted on a partially destructed TouchToFillController leads to a Use-After-Destruction of the visibility_controller_ member. Specifically, evaluating if (visibility_controller_) dereferences a freed WeakReference::Flag on the heap, and a subsequent call to SetShown() results in an indirect virtual call on an already freed KeyboardReplacingSurfaceVisibilityControllerImpl object inside the browser process.
Potential Attack Scenario
Note: These are suggested analytical steps as our tooling does not currently have the capability to run or verify proof-of-concept exploit code.
- An attacker prompts the user to focus a password field on an attacker-controlled page, triggering the Touch-to-Fill bottom sheet UI.
- The user selects a credential, which triggers a biometric authentication prompt via
DeviceAuthenticatorAndroid. - While the biometric prompt is visible, the attacker’s page programmatically closes the window (e.g., via
window.close()), initiating aWebContentsteardown. - The teardown triggers the destruction of
ChromePasswordManagerClientandTouchToFillController. - During member destruction of
TouchToFillController,visibility_controller_is destructed first, freeing its underlying tracking flag. Next,ttf_delegate_is destroyed. - The delegate’s destructor calls
Cancel(), synchronously invoking the failure callback which re-entersTouchToFillController::ActionCompleted. ActionCompletedperforms a Use-After-Destruction check onvisibility_controller_and attempts to callSetShown()on the freed visibility controller object.
Affected Files and Code References
chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.h:141-165(Member declaration order ensuresvisibility_controller_is destroyed beforettf_delegate_)chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc:317-322(ActionCompletedaccessesvisibility_controller_after its destruction)chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_autofill_delegate.cc:101-107(Destructor synchronously cancels the authenticator)chrome/browser/device_reauth/android/device_authenticator_android.cc:136-147(Cancel()runs the callback synchronously)
Suggested Fix
To resolve this issue, avoid binding the ActionCompleted callback with base::Unretained(this). Instead, use a weak pointer from the controller’s weak_ptr_factory_. Since weak_ptr_factory_ is destroyed first during the member destruction phase of TouchToFillController, any re-entrant execution of the callback during the destruction of the delegate will be safely ignored as the weak pointer will already be invalidated.
// chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.cc:209-212
ttf_delegate_->OnCredentialSelected(
credential, base::BindOnce(&TouchToFillController::ActionCompleted,
weak_ptr_factory_.GetWeakPtr()));
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.