CVE-2026-13022
Overview
Files Changed
components/autofill/core/browser/foundations/browser_autofill_manager.cc
Patch
From 6e1a3c5dbaa5c8bc19a9d0ff07e7d416a70d7441 Mon Sep 17 00:00:00 2001 From: Przemek Perkowski <[email protected]> Date: Wed, 03 Jun 2026 05:11:24 -0700 Subject: [PATCH] Fix the state desynchronization bug in autofill manager. As explained in the attached bug, the current implementation leads to the popup not being hidden correctly. Bug: 516734537 Change-Id: If0370f3370240ef3904cbf7fbdc868766a6a6964 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7894738 Reviewed-by: Christoph Schwering <[email protected]> Reviewed-by: Atalyk Akash <[email protected]> Commit-Queue: Przemek Perkowski <[email protected]> Cr-Commit-Position: refs/heads/main@{#1640851} --- diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager.cc b/components/autofill/core/browser/foundations/browser_autofill_manager.cc index 5ac3ba3d..76b064c 100644 --- a/components/autofill/core/browser/foundations/browser_autofill_manager.cc +++ b/components/autofill/core/browser/foundations/browser_autofill_manager.cc @@ -1188,17 +1188,20 @@ return; } + // TODO(crbug.com/519061643): Rely on central atMemory eligibility logic + // instead. + if (IsAtMemoryTriggerSource(trigger_source) && + client().GetPersonalContextEnablementState() == + personal_context::PersonalContextEnablementState:: + kDisabledNotEligible) { + return; + } + const FormFieldData& field = CHECK_DEREF(form.FindFieldByGlobalId(field_id)); external_delegate_->OnQuery(form, field, caret_bounds, trigger_source, /*update_datalist=*/true); if (IsAtMemoryTriggerSource(trigger_source)) { - // Do not show the pop up at all for non eligible profiles. - if (client().GetPersonalContextEnablementState() == - personal_context::PersonalContextEnablementState:: - kDisabledNotEligible) { - return; - } std::vector<Suggestion> suggestions; GetAtMemoryManager().MaybeAppendPersonalContextNotice(suggestions);
Original Bug Report
Autofill Origin Bypass via State Desynchronization in BrowserAutofillManager
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 state desynchronization vulnerability in BrowserAutofillManager allows a compromised renderer in a cross-origin frame to overwrite active query state while an Autofill popup remains visible. Due to an early-return path that fails to hide the popup or restore state, a subsequent user acceptance can redirect sensitive credit card or address data to the attacker’s origin.
Affected files:
components/autofill/core/browser/foundations/browser_autofill_manager.cccomponents/autofill/core/browser/ui/autofill_external_delegate.cc
Estimated timestamp from git blame: 2026-04-28
Root Cause Analysis
In BrowserAutofillManager::OnAskForValuesToFillImpl (located in components/autofill/core/browser/foundations/browser_autofill_manager.cc), the method updates the active query state on the external delegate before evaluating the eligibility gate for memory-related trigger sources:
const FormFieldData& field = CHECK_DEREF(form.FindFieldByGlobalId(field_id));
external_delegate_->OnQuery(form, field, caret_bounds, trigger_source,
/*update_datalist=*/true);
if (IsAtMemoryTriggerSource(trigger_source)) {
// Do not show the pop up at all for non eligible profiles.
if (client().GetPersonalContextEnablementState() ==
personal_context::PersonalContextEnablementState::
kDisabledNotEligible) {
return; // Early return leaves popup visible and query_field_ mutated
}
external_delegate_->OnSuggestionsReturned(field_id, {});
return;
}
When client().GetPersonalContextEnablementState() == kDisabledNotEligible (which is the default in stock Chrome, as the kPersonalContext feature is disabled by default), the code performs an early return without closing any currently visible popup or restoring the previously active query_field_ state.
Potential Exploitation Mechanism
A compromised renderer could potentially exploit this behavior via the following sequence of events:
- Popup Initiation: A user clicks on an autofill-eligible field in a legitimate payment frame (
Frame MonOrigin M), causing the mainframe manager’sAutofillExternalDelegateto display a credit card autofill popup. - State Overwrite: A compromised renderer inside a subframe (
Frame AonOrigin A) directly calls the Mojo interfaceAskForValuesToFillwithtrigger_source = AutofillSuggestionTriggerSource::kAtMemoryandfield_id = field_A_id. - Bypassing Renderer-to-Browser Checks: The trigger source
kAtMemorysuccessfully passes browser-side validation inbad_message::CheckSingleValidTriggerSource(defined incomponents/autofill/content/browser/bad_message.cc), as the validation only blockskPlusAddressUpdatedInBrowserProcess. - Desynchronization Trigger: Because both frames are inside the same FormForest, the router routes the request to the mainframe’s
BrowserAutofillManager. This manager updates its sharedAutofillExternalDelegate’s state variables (query_form_andquery_field_) to point to the attacker’sFrame A. - Popup Remains Visible: The manager evaluates
IsAtMemoryTriggerSourceand hits thekDisabledNotEligibleearly return. Because it returns immediately, suggestions are never generated, andAutofillSuggestionController::GetOrCreateis never invoked for the second request. Consequently, the original popup (showing suggestions forFrame M) remains open and active. - Data Exfiltration: The user clicks on a suggestion in the still-visible popup. The delegate processes the acceptance via
AutofillExternalDelegate::DidAcceptSuggestion, which resolves the cached form using the mutatedquery_form_andquery_field_. InFormForest::IsSafeToFill, the security check compares the destination fields (Origin A) to thetriggered_origin(mutated to Origin A) and permits cross-frame autofill, sending the sensitive data directly to the compromised renderer.
Suggested Remediation
To prevent this state desynchronization, the early-return path should explicitly dismiss any active popups and reset the query state.
We recommend moving the eligibility gate check before modifying the delegate state, or ensuring that any early return explicitly closes the popup:
if (IsAtMemoryTriggerSource(trigger_source)) {
if (client().GetPersonalContextEnablementState() ==
personal_context::PersonalContextEnablementState::
kDisabledNotEligible) {
client().HideAutofillSuggestions(SuggestionHidingReason::kUserGesture);
return;
}
}
const FormFieldData& field = CHECK_DEREF(form.FindFieldByGlobalId(field_id));
external_delegate_->OnQuery(form, field, caret_bounds, trigger_source,
/*update_datalist=*/true);
Evaluated with Chrome root at commit: a2bea94528f4bd6cc57739c43fa3bb890b8367d3
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.