CVE-2025-12728
Overview
Background
- Mini origin bar ("mobar")
- an Android toolbar mode, driven by
MiniOriginBarController, that shrinks the location bar to a compact origin display (tracked throughsetMinimizationProgress) while the user is typing into a form. - Keyboard accessory
- the autofill/manual-filling bar shown above the soft keyboard; its height is published through
mKeyboardAccessoryHeightSupplierand computed inManualFillingMediator. - `ToolbarControlContainer` / `mControlContainer`
- the Android view that hosts the toolbar and omnibox; when the toolbar is at the bottom,
ToolbarPositionControllerpositions it with Y-translation and layout params. - Multi-window
- Android split-screen/freeform mode, in which (per the commit) the control container was not drawn when its pre-translation position overlapped the keyboard accessory, and no insets animation runs.
Root Cause Analysis
With the toolbar at the bottom, ToolbarPositionController.updateViewOffset placed the control container above the keyboard accessory by subtracting mKeyboardAccessoryHeightSupplier.get() from layerYOffset, so the container’s untranslated layout position overlapped the accessory. The commit reports that in multi-window Android then did not draw ToolbarControlContainer at all. The omnibox/origin display therefore disappeared exactly while the user was interacting with a page form, which breaks the rule that browser UI showing the page’s identity stays visible.
The fix stops folding the accessory height into the translation. A new mKeyboardAccessoryHeightObserver (registered in place of mKeyboardHeightToolbarCallback) instead sets mControlContainer’s bottomMargin to the accessory height when controls are at ControlsPosition.BOTTOM, and to 0 when they are at ControlsPosition.TOP.
The patch also makes ManualFillingMediator’s reported heights aware of AUTOFILL_ENABLE_KEYBOARD_ACCESSORY_CHIP_REDESIGN, since the redesigned accessory had been reporting the old dimension. It also reorders MiniOriginBarController so setMinimizationProgress(1.0f) runs after showMiniOriginBar() when the visibility change happens without an animation.
Attack Path
- Preconditions on device The victim uses Chrome on Android with the toolbar at the bottom, in multi-window mode, with the mini-origin-bar behaviour active.
- Page focuses a form field Attacker-controlled page content shows an input. When it is focused, the soft keyboard and keyboard accessory appear, and the mini origin bar is supposed to show the page origin.
- Omnibox not drawn
Before the fix,
updateViewOffsetsubtracted the accessory height from the translation. The container’s pre-translation bounds overlapped the accessory, and in multi-window the control container (and its origin display) was not rendered. - Origin hidden during data entry (inferred)
With no visible URL or origin, the page could present itself as a different site while the user types credentials or other data, giving a UI-spoofing opportunity
(inferred).
Impact Assessment
(inferred) from the omnibox becoming invisible. The vendor rates this medium (“Inappropriate implementation in Omnibox”). The provided metadata does not say whether it was exploited in the wild.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java |
modified | |
ifchrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java |
modified |
Files Changed
chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.javachrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.javachrome/browser/keyboard_accessory/android/internal/java/res/values/dimens.xmlchrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.javachrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.javachrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionControllerTest.java
Audit Directions
- Security UI positioned only by translationLook for omnibox, origin, permission-chip or security-indicator views that are moved with
setTranslationYwhile their layout bounds overlap other windows or bars. Rendering optimisations, especially in multi-window or freeform modes, may skip drawing them. - Stale or feature-unaware dimension sourcesFind places where a redesigned component (behind a
ChromeFeatureListflag) still reports a legacyR.dimensize to layout code that positions browser chrome. - Animation-only state transitionsAudit controllers that only finish a state (progress, visibility, offsets) in animation callbacks, and confirm the end state is still applied in modes where animations or insets callbacks never fire.
Patch
From 316d6696542c7cd0307eeb8df3a914a8eda9989f Mon Sep 17 00:00:00 2001
From: Patrick Noland <pnoland@google.com>
Date: Wed, 22 Oct 2025 12:30:26 -0700
Subject: [PATCH] [mobar] Fix keyboard accessory overlap in multiwindow
For reasons as yet unknown to me, multi-window seems to cause
ToolbarControlContainer to not be drawn when its pre-translation
position overlaps with the keyboard accessory. This may be an
optimization of sorts; I was not able to turn it off by fiddling with
clipChildren/clipToBounds.
Instead, we resolve it by removing kb accessory height from the
translation calculation, using it instead to set the bottom margin. This
should be safe since we don't use the bottom margin for another purpose
and performant since the keyboard accessory doesn't change height often.
Two related issues are resolved at the same time:
* The revamped kb accessory reports the wrong height, referencing the
old dimen. This is changed to be feature aware.
* There is no insets animation in multiwindow, meaning minimization
progress isn't set properly. We now explicitly call it when skipping
the animation.
Bug: 452392032, 438136964
Change-Id: I0df6bffdbbb0885e70b5c7ff0614faa5447b93e1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7069866
Reviewed-by: Tomasz Wiszkowski <ender@google.com>
Commit-Queue: Patrick Noland <pnoland@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1533841}
---
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
index 33eeae5..95eacd57 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
@@ -1064,12 +1064,22 @@
}
private @Px int getBarHeightWithoutShadow() {
- return mActivity
- .getResources()
- .getDimensionPixelSize(R.dimen.keyboard_accessory_suggestion_height);
+ if (ChromeFeatureList.isEnabled(
+ ChromeFeatureList.AUTOFILL_ENABLE_KEYBOARD_ACCESSORY_CHIP_REDESIGN)) {
+ return mActivity
+ .getResources()
+ .getDimensionPixelSize(R.dimen.keyboard_accessory_height_redesign);
+ }
+ return mActivity.getResources().getDimensionPixelSize(R.dimen.keyboard_accessory_height);
}
private @Px int getHeaderHeight() {
+ if (ChromeFeatureList.isEnabled(
+ ChromeFeatureList.AUTOFILL_ENABLE_KEYBOARD_ACCESSORY_CHIP_REDESIGN)) {
+ return mActivity
+ .getResources()
+ .getDimensionPixelSize(R.dimen.keyboard_accessory_height_with_shadow_redesign);
+ }
return mActivity
.getResources()
.getDimensionPixelSize(R.dimen.keyboard_accessory_height_with_shadow);
diff --git a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java
index c53e72f1..334a699 100644
--- a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java
+++ b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java
@@ -123,7 +123,8 @@
@Config(manifest = Config.NONE)
@Features.EnableFeatures({
ChromeFeatureList.AUTOFILL_ANDROID_DESKTOP_SUPPRESS_ACCESSORY_ON_EMPTY,
- ChromeFeatureList.AUTOFILL_ANDROID_DESKTOP_KEYBOARD_ACCESSORY_REVAMP
+ ChromeFeatureList.AUTOFILL_ANDROID_DESKTOP_KEYBOARD_ACCESSORY_REVAMP,
+ ChromeFeatureList.AUTOFILL_ENABLE_KEYBOARD_ACCESSORY_CHIP_REDESIGN
})
public class ManualFillingControllerTest {
private static final int sKeyboardHeightDp = 100;
@@ -1701,6 +1702,8 @@
// Return the correct keyboard_accessory_height for the current density:
when(mMockResources.getDimensionPixelSize(R.dimen.keyboard_accessory_suggestion_height))
.thenReturn((int) (density * 48));
+ when(mMockResources.getDimensionPixelSize(R.dimen.keyboard_accessory_height_redesign))
+ .thenReturn((int) (density * 48));
}
/**
diff --git a/chrome/browser/keyboard_accessory/android/internal/java/res/values/dimens.xml b/chrome/browser/keyboard_accessory/android/internal/java/res/values/dimens.xml
index 270a49f..32e0eb4 100644
--- a/chrome/browser/keyboard_accessory/android/internal/java/res/values/dimens.xml
+++ b/chrome/browser/keyboard_accessory/android/internal/java/res/values/dimens.xml
@@ -11,6 +11,7 @@
<dimen name="keyboard_accessory_horizontal_padding">6dp</dimen>
<dimen name="keyboard_accessory_height">48dp</dimen>
<dimen name="keyboard_accessory_height_redesign">64dp</dimen>
+ <dimen name="keyboard_accessory_height_with_shadow_redesign">72dp</dimen>
<dimen name="keyboard_accessory_chip_min_height_redesign">64dp</dimen>
<dimen name="keyboard_accessory_chip_vertical_insets">8dp</dimen>
<dimen name="keyboard_accessory_chip_vertical_insets_large_font">2dp</dimen>
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java
index f8e7527..9394ad06 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java
@@ -243,17 +243,19 @@
boolean finishedShowing = newMiniOriginState == MiniOriginState.SHOWING;
mMiniOriginBarState = newMiniOriginState;
- if (finishedShowing) {
- setMinimizationProgress(1.0f);
+ if (!isChangingVisibility) {
+ if (finishedShowing) setMinimizationProgress(1.0f);
+ return;
}
- if (!isChangingVisibility) return;
-
if (isMiniOriginBarVisibleForState(newMiniOriginState)) {
// Cache the location bar's layout params now, since we are about to mutate them.
mDefaultLocationBarLayoutParams =
(FrameLayout.LayoutParams) mLocationBar.getContainerView().getLayoutParams();
showMiniOriginBar();
+ if (finishedShowing) {
+ setMinimizationProgress(1.0f);
+ }
} else {
hideMiniOriginBar();
}
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java
index 553dd369..1f0f955 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java
@@ -54,6 +54,9 @@
/** Class responsible for managing the position (top, bottom) of the browsing mode toolbar. */
@NullMarked
public class ToolbarPositionController implements OnSharedPreferenceChangeListener {
+
+ private final Callback<Integer> mKeyboardAccessoryHeightObserver;
+
@IntDef({
ToolbarPositionAndSource.TOP_LONG_PRESS,
ToolbarPositionAndSource.TOP_SETTINGS,
@@ -332,11 +335,19 @@
(focused) -> updateViewOffset(mProgressBarLayer, mToolbarProgressBarContainer);
mControlContainerTranslationCallback =
(offset) -> updateViewOffset(mBottomToolbarLayer, mControlContainer.getView());
+ mKeyboardAccessoryHeightObserver =
+ (height) -> {
+ if (mCurrentPosition.get() == ControlsPosition.TOP) {
+ mControlContainer.mutateLayoutParams().bottomMargin = 0;
+ return;
+ }
+ mControlContainer.mutateLayoutParams().bottomMargin = height;
+ };
mControlContainerHeightCallback = this::updateControlContainerHeight;
mControlContainerHeightSupplier.addSyncObserverAndCallIfNonNull(
mControlContainerHeightCallback);
- mKeyboardAccessoryHeightSupplier.addObserver(mKeyboardHeightToolbarCallback);
+ mKeyboardAccessoryHeightSupplier.addObserver(mKeyboardAccessoryHeightObserver);
mKeyboardAccessoryHeightSupplier.addObserver(mKeyboardHeightProgressBarCallback);
mKeyboardVisibilityDelegate.addKeyboardVisibilityListener(
mKeyboardVisibilityViewOffsetCallback);
@@ -374,6 +385,7 @@
mIsFormFieldFocusedSupplier.removeObserver(mFormFieldViewOffsetCallback);
mControlContainerTranslationSupplier.removeObserver(mControlContainerTranslationCallback);
mControlContainerHeightSupplier.removeObserver(mControlContainerHeightCallback);
+ mKeyboardAccessoryHeightSupplier.removeObserver(mKeyboardAccessoryHeightObserver);
if (mTopInsetCoordinatorObserver != null) {
var topInsetCoordinator = mTopInsetCoordinatorSupplier.get();
if (topInsetCoordinator != null) {
@@ -636,18 +648,15 @@
private void updateViewOffset(BottomControlsLayerWithOffset layer, View viewForLayer) {
if (mLayerVisibility != LayerVisibility.VISIBLE) return;
- int keyboardAccessoryHeight = mKeyboardAccessoryHeightSupplier.get();
- int layerYOffset =
- layer.getLayerOffsetPx()
- - keyboardAccessoryHeight
- + mControlContainerTranslationSupplier.get();
+ int layerYOffset = layer.getLayerOffsetPx() + mControlContainerTranslationSupplier.get();
int chinHeight =
mBottomControlsStacker.isLayerVisible(LayerType.BOTTOM_CHIN)
? mBottomControlsStacker.getHeightFromLayerToBottom(LayerType.BOTTOM_CHIN)
: 0;
// The chin overlaps with the accessory when they're both visible. To avoid double counting,
// remove the chin's height from the final offset.
- boolean chinVisibleWithAccessory = keyboardAccessoryHeight > 0 && chinHeight > 0;
+ boolean chinVisibleWithAccessory =
+ mKeyboardAccessoryHeightSupplier.get() > 0 && chinHeight > 0;
if (chinVisibleWithAccessory) {
layerYOffset += chinHeight;
}
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionControllerTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionControllerTest.java
index 967632d..00876e1e 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionControllerTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionControllerTest.java
@@ -998,7 +998,8 @@
verify(mControlContainerView).setTranslationY(baseTranslation);
final int chinHeight = 36;
- int keybpardAccessoryHeight = 100;
+ int keyboardAccessoryHeight = 100;
+ mKeyboardAccessoryHeightSupplier.set(keyboardAccessoryHeight);
Original Bug Report
When in split-view mode, the mini address bar does not appear above the virtual keyboard, leading to a spoof.
VULNERABILITY DETAILS This vulnerability is similar to https://issues.chromium.org/issues/439262604 (iOS version), but this bug occurs on the Android version. When in split-view mode, the mini address bar does not appear above the virtual keyboard.
VERSION Chrome Version: [143.0.7472.0] + [Canary] Operating System: Android 16 Device: Samsung S25 Edge
REPRODUCTION CASE
- Open https://you-can-billowy-nimble-login-secure-docs-google-source-attacker.com/spoofnewxiov.html or open spoofnewxiov.html on a local web server using the https:// protocol and open it in split-view mode.
- Click on the “google” link.