Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in iOS
DescriptionUse after free in iOS
ComponentChromium
Bug ClassUAF
Tracker504195132
Fix commitaf56b540ea49 (chromium/src) +8/-10
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
for
ios/chrome/browser/passwords/bottom_sheet/coordinator/credential_suggestion_bottom_sheet_mediator.mm
modified

Files Changed

  • ios/chrome/browser/passwords/bottom_sheet/coordinator/credential_suggestion_bottom_sheet_mediator.mm
From af56b540ea49750a59b626ef92190659e73a1781 Mon Sep 17 00:00:00 2001
From: Rafał Godlewski <rgod@google.com>
Date: Mon, 27 Apr 2026 07:02:49 -0700
Subject: [PATCH] [iOS] Copy PasswordForm objects in bottom sheet mediator

Bug: 504195132
Change-Id: I2689c3f0be42714d92010039888b3674c1c4ed48
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7792817
Reviewed-by: Tommy Martino <tmartino@chromium.org>
Commit-Queue: Rafał Godlewski <rgod@google.com>
Cr-Commit-Position: refs/heads/main@{#1621057}
---

diff --git a/ios/chrome/browser/passwords/bottom_sheet/coordinator/credential_suggestion_bottom_sheet_mediator.mm b/ios/chrome/browser/passwords/bottom_sheet/coordinator/credential_suggestion_bottom_sheet_mediator.mm
index 36682cd..1692145 100644
--- a/ios/chrome/browser/passwords/bottom_sheet/coordinator/credential_suggestion_bottom_sheet_mediator.mm
+++ b/ios/chrome/browser/passwords/bottom_sheet/coordinator/credential_suggestion_bottom_sheet_mediator.mm
@@ -194,7 +194,7 @@
 
   // Vector of forms that have been received via the password sharing feature
   // and the user has not been notified about them yet.
-  std::vector<const password_manager::PasswordForm*> _sharedUnnotifiedForms;
+  std::vector<password_manager::PasswordForm> _sharedUnnotifiedForms;
 
   // Profile images of password senders if any of the passwords were received
   // via the password sharing feature. Empty otherwise.
@@ -496,7 +496,7 @@
     if (form.type ==
             password_manager::PasswordForm::Type::kReceivedViaSharing &&
         !form.sharing_notification_displayed) {
-      _sharedUnnotifiedForms.push_back(&form);
+      _sharedUnnotifiedForms.push_back(form);
       __weak __typeof__(self) weakSelf = self;
       image_fetcher::ImageFetcherParams params(NO_TRAFFIC_ANNOTATION_YET,
                                                kImageFetcherUmaClient);
@@ -527,14 +527,12 @@
     return;
   }
 
-  for (const password_manager::PasswordForm* form : _sharedUnnotifiedForms) {
-    // Make a non-const copy so we can modify it.
-    password_manager::PasswordForm updatedForm = *form;
-    updatedForm.sharing_notification_displayed = true;
-    if (form->IsUsingAccountStore()) {
-      _accountPasswordStore->UpdateLogin(std::move(updatedForm));
+  for (password_manager::PasswordForm& form : _sharedUnnotifiedForms) {
+    form.sharing_notification_displayed = true;
+    if (form.IsUsingAccountStore()) {
+      _accountPasswordStore->UpdateLogin(std::move(form));
     } else {
-      _profilePasswordStore->UpdateLogin(std::move(updatedForm));
+      _profilePasswordStore->UpdateLogin(std::move(form));
     }
   }
   _sharedUnnotifiedForms.clear();
@@ -554,7 +552,7 @@
   if (_sharedUnnotifiedForms.size() == 1) {
     return base::SysUTF16ToNSString(l10n_util::GetStringFUTF16(
         IDS_IOS_PASSWORD_SHARING_NOTIFICATION_SINGLE_PASSWORD_SUBTITLE,
-        _sharedUnnotifiedForms[0]->sender_name,
+        _sharedUnnotifiedForms[0].sender_name,
         base::SysNSStringToUTF16(domain)));
   } else {
     return base::SysUTF16ToNSString(l10n_util::GetStringFUTF16(
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential UAF in iOS CredentialSuggestionBottomSheetMediator across navigation

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 without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.

Overview: A potential Use-After-Free exists in the iOS browser process within CredentialSuggestionBottomSheetMediator. The mediator stores raw pointers to PasswordForm objects that are freed during cross-document navigation, and subsequently dereferences them if the user interacts with the persistent bottom sheet.

Affected files:

  • ios/chrome/browser/passwords/bottom_sheet/coordinator/credential_suggestion_bottom_sheet_mediator.mm
  • components/password_manager/ios/shared_password_controller.mm
  • ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.mm
  • ios/chrome/browser/passwords/bottom_sheet/coordinator/credential_suggestion_bottom_sheet_mediator_base.mm
  • ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm

Estimated timestamp from git blame: 2024-02-29

Summary

A potential use-after-free (UAF) vulnerability exists in the browser process of Chrome for iOS. The CredentialSuggestionBottomSheetMediator incorrectly maintains raw pointers to PasswordForm objects. These objects are destroyed during same-tab cross-document navigations, but the bottom sheet UI is not automatically dismissed. If a user interacts with the bottom sheet after a navigation has occurred, the application dereferences these dangling pointers, leading to a UAF and a potential arbitrary memory read.

Technical Details

When the password bottom sheet is triggered on iOS for a page containing credentials received via Google Password Sharing (where sharing_notification_displayed is false), CredentialSuggestionBottomSheetMediator::fetchCredentialsForForm:webState: is called.

This method retrieves suggestions from PasswordManager::GetBestMatches, which returns a base::span<const password_manager::PasswordForm> pointing to the internal std::vector<PasswordForm> best_matches_ inside FormFetcherImpl. The mediator then stores raw pointers to the shared credentials in its std::vector<const password_manager::PasswordForm*> _sharedUnnotifiedForms member variable.

When a cross-document navigation occurs in the same tab, SharedPasswordController::webState:didFinishNavigation: detects the navigation and calls PasswordManager::DidNavigateMainFrame. This resets the password cache (password_form_cache_.Clear()), which destroys all PasswordFormManager and FormFetcherImpl objects associated with the previous page. The best_matches_ vector is destructed, freeing the memory.

The vulnerability exists because the credential bottom sheet is not dismissed upon same-tab navigation. The mediator only dismisses the sheet on tab switches (via WebStateListObserving).

When the victim subsequently interacts with the persistent sheet (e.g., swiping it away or selecting ‘Use Keyboard’), the mediator calls -markSharedPasswordNotificationsDisplayed. This method iterates through _sharedUnnotifiedForms and performs a copy-construction:

password_manager::PasswordForm updatedForm = *form;

Since form is a dangling pointer, this results in a UAF read. The PasswordForm struct contains numerous complex members like std::u16string and std::string. The copy constructor will attempt to read string sizes and pointers from the freed memory, leading to an arbitrary memory read primitive. The corrupted object is then passed to PasswordStoreInterface::UpdateLogin.

Note: MiraclePtr (BackupRefPtr) does not protect against this issue because the dangling pointers are bare const T* elements within a std::vector, not raw_ptr<>.

Potential Reproduction Steps

Please note: These are theoretical steps as we do not have a working Proof of Concept.

  1. An attacker (who must be a member of the victim’s Google Family Group) shares a password for https://attacker.example with the victim.
  2. The victim opens https://attacker.example in iOS Chrome. The page contains a login form and a script designed to trigger navigation after a short delay (e.g., setTimeout(() => location.href = '/next', 2000)).
  3. The victim taps a password field, triggering the password bottom sheet. The mediator populates _sharedUnnotifiedForms with raw pointers to the shared credential.
  4. The page’s timer fires and a cross-document navigation commits. This destroys the PasswordFormManager and frees the PasswordForm objects in memory. The bottom sheet remains visible on screen.
  5. The victim interacts with the bottom sheet (e.g., dismisses it by swiping down).
  6. The mediator attempts to mark the notification as displayed, dereferencing the dangling pointer in the browser process.

Suggested Fix

To fix this issue, _sharedUnnotifiedForms should not store raw pointers to memory owned by the Password Manager cache. Instead, it should store independent copies of the PasswordForm objects, or store a unique identifier (like the form’s primary key) that can be used to safely look up or update the form in the Password Store later. Additionally, it is highly recommended to update the coordinator or mediator to dismiss the bottom sheet when a same-tab cross-document navigation occurs, by implementing web::WebStateObserver.

Evaluated with Chrome root at commit: 7353d249d9cacf9c7218e1d7b8a39cf39c72d646


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