CVE-2026-113298
Overview
Files Changed
testing/variations/fieldtrial_testing_config.jsoncomponents/autofill/core/common/autofill_features.ccchrome/browser/ui/autofill/autofill_popup_controller_impl.cctools/metrics/histograms/metadata/autofill/histograms.xmlchrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cccomponents/autofill/core/common/autofill_features.hchrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc
Patch
From 43939844e1bb97e0a7101e675f47741263a8a80d Mon Sep 17 00:00:00 2001 From: Christoph Schwering <[email protected]> Date: Wed, 16 Sep 2026 05:41:45 -0700 Subject: [PATCH] [Autofill] Require focus in the suggestions' anchor frame This CL requires that the anchor frame has the focus before Autofill shows suggestions. This unfocused renderers from displaying suggestions. That could otherwise happen due to race conditions (e.g., because AskForValuesToFill() is delayed or because of asynchronous parsing or suggestion generation) or because of a malicious renderer. The change is behind a Finch experiment. Bug: 536607604, 553117374 Change-Id: Ibcc6821f5b3e06119e813ee9d9f9ee9e22463055 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8413948 Reviewed-by: Jan Keitel <[email protected]> Commit-Queue: Christoph Schwering <[email protected]> Cr-Commit-Position: refs/heads/main@{#1698861} --- diff --git a/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc index 6412bd9..3d41357c 100644 --- a/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc +++ b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc @@ -731,6 +731,14 @@ Hide(SuggestionHidingReason::kNoFrameHasFocus); return; } + if (rfh != web_contents_->GetFocusedFrame() && + base::FeatureList::IsEnabled( + features::kAutofillRequireFocusInFrameForSuggestions)) { + base::UmaHistogramEnumeration("Autofill.SuggestionSuppressionDueToNoFocus", + trigger_source); + Hide(SuggestionHidingReason::kNoFrameHasFocus); + return; + } if (IsPointerLocked(web_contents_.get())) { Hide(SuggestionHidingReason::kMouseLocked); diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc index 00ca37b..98d1afe 100644 --- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc +++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc @@ -336,6 +336,14 @@ Hide(SuggestionHidingReason::kNoFrameHasFocus); return; } + if (rfh != web_contents_->GetFocusedFrame() && !should_ignore_focus_loss && + base::FeatureList::IsEnabled( + features::kAutofillRequireFocusInFrameForSuggestions)) { + base::UmaHistogramEnumeration("Autofill.SuggestionSuppressionDueToNoFocus", + trigger_source); + Hide(SuggestionHidingReason::kNoFrameHasFocus); + return; + } if (IsPointerLocked(web_contents_.get())) { Hide(SuggestionHidingReason::kMouseLocked); diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc index 479dfd28..d1f8370 100644 --- a/components/autofill/core/common/autofill_features.cc +++ b/components/autofill/core/common/autofill_features.cc @@ -996,6 +996,13 @@ BASE_FEATURE(kAutofillReplaceFormElementObserver, base::FEATURE_DISABLED_BY_DEFAULT); +// If enabled, AutofillPopupControllerImpl and +// AutofillKeyboardAccessoryControllerImpl require that the frame to which the +// suggestions are anchored has focus. +// TODO(crbug.com/536607604): Clean up when launched. +BASE_FEATURE(kAutofillRequireFocusInFrameForSuggestions, + base::FEATURE_DISABLED_BY_DEFAULT); + // Restricts OTP fields detection and fetching to forms that are in a frame // with the same TLD+1 as the main frame. BASE_FEATURE(kAutofillRestrictOtpToSameTldPlusOne, diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h index 32c1c99..d528f189 100644 --- a/components/autofill/core/common/autofill_features.h +++ b/components/autofill/core/common/autofill_features.h @@ -372,6 +372,8 @@ COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillReplaceFormElementObserver); COMPONENT_EXPORT(AUTOFILL) +BASE_DECLARE_FEATURE(kAutofillRequireFocusInFrameForSuggestions); +COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillRestrictOtpToSameTldPlusOne); COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillServerExperimentalSignatures); diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 80d5125..d91e892a 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json @@ -2677,6 +2677,25 @@ ] } ], + "AutofillRequireFocusInFrameForSuggestions": [ + { + "platforms": [ + "android", + "chromeos", + "linux", + "mac", + "windows" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "AutofillRequireFocusInFrameForSuggestions" + ] + } + ] + } + ], "AutofillRestrictOtpToSameTldPlusOne": [ { "platforms": [ diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml index bc002858..d2324b84 100644 --- a/tools/metrics/histograms/metadata/autofill/histograms.xml +++ b/tools/metrics/histograms/metadata/autofill/histograms.xml @@ -9635,6 +9635,16 @@ </summary> </histogram> +<histogram name="Autofill.SuggestionSuppressionDueToNoFocus" + enum="AutofillSuggestionTriggerSource" expires_after="2027-03-01"> + <owner>[email protected]</owner> + <owner>[email protected]</owner> + <summary> + Records the trigger source when Autofill suggestions are suppressed because + the suggestions' anchor frame does not have focus. + </summary> +</histogram> + <histogram name="Autofill.TestAddressesEvent" enum="AutofillInDevtoolsTestAddressesEvents" expires_after="2027-03-07"> <owner>[email protected]</owner> diff --git a/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc b/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc index 2246855c..f8d16dc 100644 --- a/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc +++ b/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc @@ -408,15 +408,17 @@ // elements in a form to determine if the form is ready for interaction. feature_list_.InitWithFeaturesAndParameters( /*enabled_features=*/ - {{features::debug::kAutofillServerCommunication, {}}, + {{features::kAutofillRequireFocusInFrameForSuggestions, {}}, + {features::debug::kAutofillCapturedSiteTestsUseAutofillFlow, {}}, + {features::debug::kAutofillServerCommunication, {}}, {features::debug::kAutofillShowTypePredictions, { - // TODO(crbug.com/410879924): Investigate why the test fails when - // kAutofillShowTypePredictions is enabled without parameters. + // TODO(crbug.com/410879924): Investigate why the test fails + // when kAutofillShowTypePredictions is enabled without + // parameters. {features::debug::kAutofillShowTypePredictionsAsTitleParam.name, "true"}, - }}, - {features::debug::kAutofillCapturedSiteTestsUseAutofillFlow, {}}}, + }}}, /*disabled_features=*/{}); command_line->AppendSwitchASCII( variations::switches::kVariationsOverrideCountry, "us");
Regression Test / PoC
diff --git a/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc b/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc
index 2246855c..f8d16dc 100644
--- a/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc
+++ b/chrome/browser/autofill/autofill_captured_sites_interactive_uitest.cc
@@ -408,15 +408,17 @@
// elements in a form to determine if the form is ready for interaction.
feature_list_.InitWithFeaturesAndParameters(
/*enabled_features=*/
- {{features::debug::kAutofillServerCommunication, {}},
+ {{features::kAutofillRequireFocusInFrameForSuggestions, {}},
+ {features::debug::kAutofillCapturedSiteTestsUseAutofillFlow, {}},
+ {features::debug::kAutofillServerCommunication, {}},
{features::debug::kAutofillShowTypePredictions,
{
- // TODO(crbug.com/410879924): Investigate why the test fails when
- // kAutofillShowTypePredictions is enabled without parameters.
+ // TODO(crbug.com/410879924): Investigate why the test fails
+ // when kAutofillShowTypePredictions is enabled without
+ // parameters.
{features::debug::kAutofillShowTypePredictionsAsTitleParam.name,
"true"},
- }},
- {features::debug::kAutofillCapturedSiteTestsUseAutofillFlow, {}}},
+ }}},
/*disabled_features=*/{});
command_line->AppendSwitchASCII(
variations::switches::kVariationsOverrideCountry, "us");
Original Bug Report
Autofill Popup Recycling Hijack Leading to Cross-Origin Payment Data Exfiltration
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 compromised root frame renderer can potentially hijack an active Autofill credit card suggestion popup opened for an embedded cross-origin payment iframe. Due to missing frame token and field bounds validation during popup recycling in AutofillPopupControllerImpl::MayRecycle and unconstrained root-frame field bounds in ContentAutofillDriver::Lift, the popup can be silently retargeted to an attacker-controlled field at the same screen coordinates. When the victim accepts the suggestion, sensitive payment information is filled directly into the attacker’s origin.
Affected files:
chrome/browser/ui/autofill/autofill_popup_controller_impl.cccomponents/autofill/content/browser/content_autofill_driver.ccchrome/browser/ui/autofill/chrome_autofill_client.ccchrome/browser/ui/autofill/autofill_suggestion_controller.cccomponents/autofill/core/browser/ui/autofill_external_delegate.cccomponents/autofill/core/browser/foundations/autofill_driver_router.cc
Estimated timestamp from git blame: 2026-06-09
Summary
A security weakness in Chromium’s Autofill subsystem allows a compromised root frame renderer to deterministically retarget an active credit card Autofill popup originally opened for an embedded cross-origin payment iframe (PSP). When the user clicks the suggestion to fill payment details, the sensitive data (full PAN, expiry, cardholder name, and CVC) is delivered to the attacker’s origin rather than the intended payment iframe.
Root Cause Analysis
The issue arises from the combination of three mechanisms in the browser-side Autofill architecture:
-
Unvalidated Root-Frame Field Bounds (
content_autofill_driver.cc:89-95): WhenContentAutofillDriver::Liftconverts renderer-providedFormFieldData::boundsto root coordinates,TransformPointToRootCoordSpaceFacts as an identity function for the root frame. A compromised root renderer can supply arbitrary bounds for its own fields that overlap an embedded subframe’s screen rect. -
FormForest Shared Delegate Routing (
autofill_driver_router.cc:343-372): When a cross-origin payment iframe is enclosed inside a top-level<form>,FormForestflattens the frame hierarchy and identifies the top-level frame asbrowser_form.host_frame(). Consequently, autofill queries from both the subframe and the root frame are dispatched to the root frame’sBrowserAutofillManagerand share the sameAutofillExternalDelegateinstance. -
Popup Recycling Without Origin/Field Validation (
autofill_popup_controller_impl.cc:259-277):AutofillPopupControllerImpl::MayRecyclechecks only thatdelegate_.get() == delegate.get(),container_view() == web_contents->GetNativeView(), andGetSuggestionTriggerSource() == trigger_source. Because both the legitimate query and the subsequent attacker query route to the same delegate,MayRecyclereturnstrue.Recycle()silently replacescontroller_common_with the attacker field’s token and claimed bounds, andShow()reuses the active view viaOnSuggestionsChanged(), updating internallast_query_state to the attacker’s field. -
Same-Origin Sensitive Field Fill Safety (
autofill_driver_router.cc:87-124): When the user clicks the suggestion,AutofillExternalDelegate::DidAcceptSuggestioncallspayments::FillOrPreviewCardwithlast_query_.field_id(the attacker’s field). InAutofillDriverRouter::IsSafeToFill,field.origin() == trigger_originevaluates totruebecause both target and trigger belong to the attacker origin. As a result, sensitive credit card values are dispatched directly to the attacker’s renderer process.
Suggested / Potential Reproduction Steps
(Note: These are potential steps deduced from code analysis; our analysis tooling has not executed this exploit dynamically.)
- From a compromised root renderer (
https://attacker.com), embed a cross-origin payment form (https://payment-provider.com) inside a top-level<form>tag, and include hidden credit card inputs (<input autocomplete="cc-number">, etc.). - User clicks the credit card input inside the payment iframe, triggering
mojom::AutofillDriver::AskForValuesToFilland opening popup P1 showing saved cards over the iframe. - While P1 is visible, the attacker renderer sends a forged
mojom::AutofillDriver::AskForValuesToFillfor its own fieldA_cc_field, settingboundsto the on-screen rect of the payment iframe field. BrowserAutofillManagergenerates identical saved-card suggestions,MayRecycleevaluates totrue, andAutofillPopupControllerImplupdates in place without closing the view.- The user clicks a card suggestion row believing it targets the payment iframe.
- Browser fills the credit card PAN, expiration date, and name into
A_cc_fieldand sends the data via Mojo to the attacker renderer.
Suggested Fix
- Strengthen
MayRecycleConditions: InAutofillPopupControllerImpl::MayRecycle, enforce that the frame token and triggering field ID match the existing controller’s target before permitting in-place recycling, or dismiss the popup if the triggering frame origin changes. - Validate Element Bounds Against Frame Geometry: In
ContentAutofillDriver::Liftand associated validation layers, verify that a field’s claimed bounds lie strictly within the visible layout rect of the source frame and do not overlap unrelated or cross-origin subframes without authorization.
Evaluated with Chrome root at commit: c6718ebbf2c677c6f63691e28c7801c563f6b19c
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.