CVE-2026-7986
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/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
Patch
From fc0dd2220ced570378f79d20c808e691c611e981 Mon Sep 17 00:00:00 2001 From: Florian Leimgruber <[email protected]> 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 <[email protected]> Reviewed-by: Jihad Hanna <[email protected]> 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));
Original Bug Report
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.hcomponents/autofill/core/browser/form_import/form_data_importer_utils.cccomponents/autofill/core/browser/form_import/addresses/address_profile_save_manager.cccomponents/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
-
DCHECK Bypass in
TimestampedSameOriginQueue::Push: Incomponents/autofill/core/browser/form_import/form_data_importer_utils.h, thePush()method (lines 51-58) uses aDCHECKto ensure that new items match the existing queue’s origin. In release builds, thisDCHECKis a no-op. If a push occurs with a different origin, the cross-origin item is added to the front of the queue, andorigin_is overwritten, but items from the previous origin remain in the queue. A subsequent call toRemoveOutdatedItems()will see the now-matchingorigin_and fail to clear the stale cross-origin data. -
Unguarded Push via Asynchronous Callback: While the main path (
ProcessMultiStepImport) callsRemoveOutdatedItems()before pushing,AddMultiStepImportCandidate()(inform_data_importer_utils.cc) callsPush()directly without any guard. This method is reachable viaAddMultiStepComplementCandidate, which is called asynchronously when a user accepts an address save prompt inAddressProfileSaveManager::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. -
Cross-Origin Shared State:
FormDataImporteris a per-WebContentssingleton. All frames within a tab, including cross-origin iframes, share the sameMultiStepImportMerger. 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.
- Preparation: An attacker on
evil.comtriggers an address save prompt (e.g., by submitting a form with name/address but empty email/phone). A prompt appears, and theProfileImportProcess(withorigin = evil.com) is stored in an asynchronous callback. - Iframe Submission: The attacker lures the user into submitting a partial form (e.g., email and phone) in a cross-origin
victim.comiframe. This submission correctly identifies the origin asvictim.comand queues a partial profile. Since the partial profile doesn’t meet minimum requirements, no second prompt is shown to the user. - Queue Corruption: The user clicks “Save” on the original
evil.combubble. The callback executes, callingPush()with the staleevil.comorigin. Due to theDCHECKbypass, theevil.comprofile is added to the queue containing thevictim.comdata, and the queue origin is set toevil.com. - Exfiltration: When the attacker submits another form on
evil.com, the logic merges the previously queuedvictim.comdata into the attacker’s profile because they are now both associated with theevil.comorigin 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.