Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in Autofill
DescriptionInsufficient policy enforcement in Autofill
ComponentAutofill
Bug ClassLogic Error
Tracker498396238
Fix commitfc0dd2220ced (chromium/src) +17/-10
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Changed Functions

FunctionChangeNotes
if
components/autofill/core/browser/form_import/addresses/address_profile_save_manager.cc
modified

Files Changed

  • components/autofill/core/browser/form_import/addresses/address_profile_save_manager.cc
From fc0dd2220ced570378f79d20c808e691c611e981 Mon Sep 17 00:00:00 2001
From: Florian Leimgruber <fleimgruber@google.com>
Date: Thu, 02 Apr 2026 08:01:50 -0700
Subject: [PATCH] Fix DCHECK() violation in MultiStepImportMerger

See crbug.com/498396238 for a detailed description of the issue.
Essentially:
- TimestampedSameOriginQueue::Push() DCHECKs() that the origin of newly
  added items matches the current origin of the queue.
- Push() is called from two places:
  - ProcessMultiStepImport(): The DCHECK() is guaranteed, because the
    function calls RemoveOutdatedItems().
  - AddMultiStepComplementCandidate(): Not guaranteed. This function is
    called asynchronously after the user accepts a save prompt, with the
    origin of the form that the profile was imported from. If the user
    has since navigated away and submitted a different, incomplete form
    on the new site, the DCHECK() will fail.

To fix it, this CL only calls Push() when the origin still matches. This
means that if the user has since navigated away, the imported profile is
not pushed into the queue and won't support multi-step complement. This
seems more reasonable than clearing the candidates that exist on the new
origin.

Fixed: 498396238
Change-Id: If180c7d4ca2dc2833156ce156be9515dfb41bacc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7720882
Commit-Queue: Florian Leimgruber <fleimgruber@google.com>
Reviewed-by: Jihad Hanna <jihadghanna@google.com>
Cr-Commit-Position: refs/heads/main@{#1609213}
---

diff --git a/components/autofill/core/browser/form_import/addresses/address_profile_save_manager.cc b/components/autofill/core/browser/form_import/addresses/address_profile_save_manager.cc
index 2387c70..533b4646 100644
--- a/components/autofill/core/browser/form_import/addresses/address_profile_save_manager.cc
+++ b/components/autofill/core/browser/form_import/addresses/address_profile_save_manager.cc
@@ -23,12 +23,21 @@
 // with additional optional information.
 // This function adds the imported profile as a candidate. This is only done
 // after the user decision to incorporate manual edits.
-void AddMultiStepComplementCandidate(FormDataImporter* form_data_importer,
-                                     const AutofillProfile& profile,
-                                     const url::Origin& origin) {
+void MaybeAddMultiStepComplementCandidate(FormDataImporter* form_data_importer,
+                                          const AutofillProfile& profile,
+                                          const url::Origin& origin) {
   if (!form_data_importer) {
     return;
   }
+  MultiStepImportMerger& import_merger =
+      form_data_importer->GetAddressFormDataImporter()
+          .multi_step_import_merger();
+  // Avoid adding profiles that don't match the currently tracked origin. It is
+  // possible that the user has navigated away since the import prompt was shown
+  // and submitted an (incomplete) address on the new origin in the meantime.
+  if (import_merger.origin().has_value() && import_merger.origin() != origin) {
+    return;
+  }
   // Metrics depending on `import_process.import_metadata()` are collected
   // for the `confirmed_import_candidate`. E.g. whether the removal of an
   // invalid phone number made the import possible. Just like regular updates,
@@ -36,10 +45,8 @@
   // The `import_metadata` is thus initialized to a neutral element.
   ProfileImportMetadata import_metadata;
   import_metadata.origin = origin;
-  form_data_importer->GetAddressFormDataImporter()
-      .multi_step_import_merger()
-      .AddMultiStepImportCandidate(profile, import_metadata,
-                                   /*is_imported=*/true);
+  import_merger.AddMultiStepImportCandidate(profile, import_metadata,
+                                            /*is_imported=*/true);
 }
 
 AutofillClient::SaveAddressBubbleType AutofillProfileImportTypeToBubbleType(
@@ -147,9 +154,9 @@
     const std::optional<AutofillProfile>& confirmed_import_candidate =
         import_process->confirmed_import_candidate();
     DCHECK(confirmed_import_candidate);
-    AddMultiStepComplementCandidate(client_->GetFormDataImporter(),
-                                    *confirmed_import_candidate,
-                                    import_process->import_metadata().origin);
+    MaybeAddMultiStepComplementCandidate(
+        client_->GetFormDataImporter(), *confirmed_import_candidate,
+        import_process->import_metadata().origin);
   }
 
   ClearPendingImport(std::move(import_process));
Loading diff…

Original Bug Report

reported by vm...@google.com

Cross-origin Autofill data leak via TimestampedSameOriginQueue origin check bypass

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 security team.

Overview: A potential cross-origin profile data leak exists in the Autofill MultiStepImportMerger due to an insufficient origin check in TimestampedSameOriginQueue::Push. By exploiting an asynchronous save prompt callback, an attacker could corrupt the multi-step import queue, leading to the merge of victim data into an attacker-controlled profile.

Affected files:

  • components/autofill/core/browser/form_import/form_data_importer_utils.h
  • components/autofill/core/browser/form_import/form_data_importer_utils.cc
  • components/autofill/core/browser/form_import/addresses/address_profile_save_manager.cc
  • components/autofill/core/browser/form_import/addresses/address_form_data_importer.cc

Estimated timestamp from git blame: 2024-11-20

Summary

A potential vulnerability in Chrome’s Autofill system could allow an attacker-controlled site to exfiltrate sensitive user data (such as email and phone numbers) typed into a cross-origin iframe. The issue is caused by an insufficient origin check in TimestampedSameOriginQueue::Push, which enforces its same-origin invariant using only a DCHECK. In release builds, this check is bypassed, allowing the internal state of the multi-step import queue to be corrupted by asynchronous callbacks with stale origin information.

Technical Details

  1. DCHECK Bypass in TimestampedSameOriginQueue::Push: In components/autofill/core/browser/form_import/form_data_importer_utils.h, the Push() method (lines 51-58) uses a DCHECK to ensure that new items match the existing queue’s origin. In release builds, this DCHECK is a no-op. If a push occurs with a different origin, the cross-origin item is added to the front of the queue, and origin_ is overwritten, but items from the previous origin remain in the queue. A subsequent call to RemoveOutdatedItems() will see the now-matching origin_ and fail to clear the stale cross-origin data.

  2. Unguarded Push via Asynchronous Callback: While the main path (ProcessMultiStepImport) calls RemoveOutdatedItems() before pushing, AddMultiStepImportCandidate() (in form_data_importer_utils.cc) calls Push() directly without any guard. This method is reachable via AddMultiStepComplementCandidate, which is called asynchronously when a user accepts an address save prompt in AddressProfileSaveManager::FinalizeProfileImport (address_profile_save_manager.cc). The origin used in this callback is captured when the prompt is first shown and is not updated if the user navigates or interacts with cross-origin content in the meantime.

  3. Cross-Origin Shared State: FormDataImporter is a per-WebContents singleton. All frames within a tab, including cross-origin iframes, share the same MultiStepImportMerger. This allows a victim’s form submission in an iframe to populate the same queue that is currently awaiting a user decision on an address prompt from the main frame (e.g., evil.com).

Potential Attack Scenario

Note: These are suggested steps; our tooling has not executed this as a live proof-of-concept.

  1. Preparation: An attacker on evil.com triggers an address save prompt (e.g., by submitting a form with name/address but empty email/phone). A prompt appears, and the ProfileImportProcess (with origin = evil.com) is stored in an asynchronous callback.
  2. Iframe Submission: The attacker lures the user into submitting a partial form (e.g., email and phone) in a cross-origin victim.com iframe. This submission correctly identifies the origin as victim.com and queues a partial profile. Since the partial profile doesn’t meet minimum requirements, no second prompt is shown to the user.
  3. Queue Corruption: The user clicks “Save” on the original evil.com bubble. The callback executes, calling Push() with the stale evil.com origin. Due to the DCHECK bypass, the evil.com profile is added to the queue containing the victim.com data, and the queue origin is set to evil.com.
  4. Exfiltration: When the attacker submits another form on evil.com, the logic merges the previously queued victim.com data into the attacker’s profile because they are now both associated with the evil.com origin state. The merged profile, containing the victim’s email/phone, is saved to the user’s profile storage and can be exfiltrated via JS when the user next uses Autofill on the attacker’s site.

Suggested Fix

Replace the DCHECK(!origin_ || *origin_ == item_origin); in TimestampedSameOriginQueue::Push with a runtime CHECK or explicitly call RemoveOutdatedItems() inside Push() if the origin_ does not match the incoming item_origin. Alternatively, ensure that AddMultiStepComplementCandidate validates the current queue origin against its stored origin before pushing.

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


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