CVE-2026-13995
Overview
Files Changed
components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.javacomponents/android_autofill/browser/junit/src/org/chromium/components/autofill/AutofillProviderTest.java
Patch
From 5ebfd7687bf83aba0ee5e96dc522f306b18a7607 Mon Sep 17 00:00:00 2001
From: Jihad Hanna <jihadghanna@google.com>
Date: Wed, 20 May 2026 01:16:03 -0700
Subject: [PATCH] Validate field bounds are visible when querying Autofill suggestions
Fixed: 514067524
Change-Id: I68a84d5fb79a68cb1229e7c556a73420e1490838
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7859672
Reviewed-by: Friedrich Hauser <friedrichh@chromium.org>
Commit-Queue: Jihad Hanna <jihadghanna@google.com>
Cr-Commit-Position: refs/heads/main@{#1633410}
---
diff --git a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java
index 1f630d665..0f7fd95 100644
--- a/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java
+++ b/components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.java
@@ -270,11 +270,13 @@
public void queryAutofillSuggestion() {
if (shouldQueryAutofillSuggestion()) {
FocusField focusField = mRequest.getFocusField();
+ @Nullable Rect clampedBounds = clampToVisibleBounds(focusField.absBound);
+ if (clampedBounds == null) return;
getAutofillManagerWrapper()
.requestAutofill(
mContainerView,
mRequest.getFieldVirtualId(focusField.fieldIndex),
- focusField.absBound);
+ clampedBounds);
}
}
@@ -494,9 +496,13 @@
private void notifyVirtualViewEntered(View parent, int index, Rect absBounds) {
// Refer to notifyVirtualValueChanged() for the reason of the datalist's special handling.
if (isDatalistField(index)) return;
+
+ @Nullable Rect clampedBounds = clampToVisibleBounds(absBounds);
+ if (clampedBounds == null) return;
+
getAutofillManagerWrapper()
.notifyVirtualViewEntered(
- parent, mRequest.getFieldVirtualId((short) index), absBounds);
+ parent, mRequest.getFieldVirtualId((short) index), clampedBounds);
}
private void notifyVirtualViewExited(View parent, int index) {
@@ -797,6 +803,50 @@
return mDatalistPopup;
}
+ /**
+ * Calculates the visible bounds of the container view in screen coordinates.
+ *
+ * @return a {@link Rect} representing the visible area of the container in screen coordinates,
+ * or an empty {@link Rect} if the container is not visible.
+ */
+ private Rect getContainerVisibleScreenBounds() {
+ Rect rect = new Rect();
+ if (mContainerView.getGlobalVisibleRect(rect)) {
+ int[] windowLocation = new int[2];
+ mContainerView.getLocationInWindow(windowLocation);
+ int[] screenLocation = new int[2];
+ mContainerView.getLocationOnScreen(screenLocation);
+ int offsetX = screenLocation[0] - windowLocation[0];
+ int offsetY = screenLocation[1] - windowLocation[1];
+ rect.offset(offsetX, offsetY);
+ }
+ return rect;
+ }
+
+ /**
+ * Clamps the given absolute bounds to the visible screen bounds of the container view.
+ *
+ * <p>Calculates the intersection of {@code absBounds} with the visible area of {@code
+ * mContainerView}. If the bounds are entirely outside the visible area, returns {@code null}.
+ *
+ * <p>This method returns new coordinates and does not mutate the input.
+ *
+ * @param absBounds the absolute bounds in screen coordinates to clamp.
+ * @return coordinates representing the clamped bounds, or {@code null} if the bounds do not
+ * intersect with the container's visible area.
+ */
+ private @Nullable Rect clampToVisibleBounds(Rect absBounds) {
+ Rect visibleBounds = getContainerVisibleScreenBounds();
+ if (absBounds.isEmpty()) {
+ return visibleBounds.contains(absBounds.left, absBounds.top)
+ ? new Rect(absBounds)
+ : null;
+ }
+
+ Rect clampedBounds = new Rect(absBounds);
+ return clampedBounds.intersect(visibleBounds) ? clampedBounds : null;
+ }
+
private Rect transformToWindowBounds(RectF rect) {
// Refer to crbug.com/1085294 for the reason of offset.
// The current version of Mockito didn't support mock static method, adding extra method so
diff --git a/components/android_autofill/browser/junit/src/org/chromium/components/autofill/AutofillProviderTest.java b/components/android_autofill/browser/junit/src/org/chromium/components/autofill/AutofillProviderTest.java
index c09543c..eb52e671 100644
--- a/components/android_autofill/browser/junit/src/org/chromium/components/autofill/AutofillProviderTest.java
+++ b/components/android_autofill/browser/junit/src/org/chromium/components/autofill/AutofillProviderTest.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -38,10 +39,8 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import org.mockito.stubbing.Answer;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
@@ -79,6 +78,7 @@
// Virtual Id of the field with focus.
private int mFocusVirtualId;
+ private Rect mFocusBounds;
// Virtual Id of the field to show the bottom sheet for.
private int mDialogVirtualId;
@@ -113,6 +113,7 @@
@Override
public void notifyVirtualViewEntered(View parent, int childId, Rect absBounds) {
mFocusVirtualId = childId;
+ mFocusBounds = absBounds;
super.notifyVirtualViewEntered(parent, childId, absBounds);
}
@@ -154,19 +155,34 @@
when(mContainerView.getScrollX()).thenReturn(SCROLL_X);
when(mContainerView.getScrollY()).thenReturn(SCROLL_Y);
doAnswer(
- new Answer<>() {
- @Override
- public Void answer(InvocationOnMock invocation) {
- Object[] args = invocation.getArguments();
- int[] location = (int[]) args[0];
- location[0] = LOCATION_X;
- location[1] = LOCATION_Y;
- return null;
- }
+ invocation -> {
+ int[] location = invocation.getArgument(0);
+ location[0] = LOCATION_X;
+ location[1] = LOCATION_Y;
+ return null;
})
.when(mContainerView)
.getLocationOnScreen(any());
+ doAnswer(
+ invocation -> {
+ Rect rect = invocation.getArgument(0);
+ rect.set(0, 0, 2000, 2000);
+ return true;
+ })
+ .when(mContainerView)
+ .getGlobalVisibleRect(any(Rect.class));
+
+ doAnswer(
+ invocation -> {
+ int[] location = invocation.getArgument(0);
+ location[0] = LOCATION_X;
+ location[1] = LOCATION_Y;
+ return null;
+ })
+ .when(mContainerView)
+ .getLocationInWindow(any());
+
RenderCoordinatesImpl.setInstanceForTesting(mRenderCoordinates);
when(mRenderCoordinates.getContentOffsetYPixInt()).thenReturn(0);
@@ -439,6 +455,85 @@
verify(mNativeMock).hasPasskeyRequest(eq(mMockedNativeAndroidAutofillProvider));
}
+ @Test
+ public void testSuppressNotificationOutsideBounds() {
+ // Mock container view to be at [0, 0, 100, 100] on screen
+ doAnswer(
+ invocation -> {
+ Rect rect = invocation.getArgument(0);
+ rect.set(0, 0, 100, 100);
+ return true;
+ })
+ .when(mContainerView)
+ .getGlobalVisibleRect(any(Rect.class));
+
+ doAnswer(
+ invocation -> {
+ int[] location = invocation.getArgument(0);
Original Bug Report
Potential for Android Autofill UI Spoofing via Unclamped Field Bounds
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 vulnerability in Chromium for Android allows the system Autofill UI to be positioned outside the WebView’s visible area by providing unvalidated form field bounds. This occurs because renderer-supplied coordinates are transformed to screen space without being clamped or intersected with the WebView’s boundaries. An attacker could leverage this to spoof native UI elements, such as the address bar, or perform credential tapjacking attacks.
Affected files:
components/android_autofill/browser/java/src/org/chromium/components/autofill/AutofillProvider.javacomponents/android_autofill/browser/android_autofill_provider.cccomponents/autofill/core/common/mojom/autofill_types_mojom_traits.cccomponents/autofill/core/common/autofill_data_validation.cccomponents/autofill/content/browser/content_autofill_driver.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
Potential Vulnerability: Unclamped Android Autofill Bounds
A vulnerability is suspected in the Android implementation of Autofill where renderer-supplied FormFieldData::bounds are passed to the Android framework without proper validation or clamping against the WebView’s visible content area. This enables a malicious or compromised renderer to anchor the trusted system Autofill UI (FillUI) outside the WebView’s designated region, potentially overlapping native browser UI components.
Root Cause Analysis
The transformation of field coordinates from the renderer to the Android system lacks clipping at several stages:
- Mojo Traits: In
components/autofill/core/common/mojom/autofill_types_mojom_traits.cc, the bounds are read from the renderer-supplied Mojo message without any range validation or viewport checks. - Coordinate Lifting: In
components/autofill/content/browser/content_autofill_driver.cc, theLiftfunction transforms coordinates from local frame space to the root frame space but does not intersect the resulting rectangle with the visible viewport. - Android Provider (C++): In
components/android_autofill/browser/android_autofill_provider.cc, theToClientAreaBoundmethod adds the container view’s offset but does not validate if the field is actually visible. - Java Implementation: The final conversion to screen-space coordinates occurs in
AutofillProvider.java(lines 813-828). ThetransformToWindowBoundsWithOffsetYmethod performs scaling and translation based on themContainerView’s screen location but fails to clamp the resultingRectto themContainerView’s boundaries.
Because of this, the Android AutofillManager can be notified of a “virtual view” entry at coordinates that lie outside the WebView. The system then displays the Autofill suggestion picker at those coordinates.
Potential Impact
The Android Autofill UI is a system-owned window and is trusted by the user. By positioning this UI outside the WebView, a malicious renderer could:
- Spoof Primary Security UI: Overlap the browser’s Omnibox or origin indicator to misrepresent the current site’s identity.
- Credential Tapjacking: Align Autofill suggestion rows with native UI buttons (e.g., a ‘Cancel’ or ‘OK’ button in the app). A user attempting to tap the native button might inadvertently select an Autofill suggestion, filling their credentials into an attacker-controlled form.
Suggested Reproduction Steps (Potential)
Note: These steps are based on code analysis; our tooling cannot currently execute them to provide a verified PoC.
- On an Android device, enable a third-party Autofill service.
- Load a malicious page where an
<input>field is positioned at negative coordinates via CSS (e.g.,position: fixed; top: -150px;). - Programmatically focus the field or use a label to trigger focus.
- Observe that the system Autofill picker is displayed above the WebView, potentially overlapping the native browser toolbar.
Suggested Fix
The browser process must validate that the absBound passed to AutofillManager.notifyViewEntered is contained within the visible bounds of the WebView. This validation should ideally be implemented in AutofillProvider.java or android_autofill_provider.cc by intersecting the field bounds with the container’s visible rectangle. If the field is entirely outside the visible area, the notification to the Android Autofill system should be suppressed.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
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.