CVE-2025-9865
Overview
Background
- Mini origin bar ("mobar")
- an Android toolbar mode in
MiniOriginBarController.java. When the browser controls are at the bottom (ControlsPosition.BOTTOM) and a form field is focused with the keyboard up, the location bar is reduced to show only the origin (setShowOriginOnly) and is moved up to sit above the IME. - `WindowInsetsAnimationCompat`
- the AndroidX callback API (
onPrepare,onStart,onProgress,onEnd) that reports keyboard (IME) show and hide animations. The controller uses it to compute the toolbar translation each frame. - `mAnimation` / `mAnimationInProgress`
- the controller’s record of which inset animation it is tracking and whether one is running.
onProgressupdates are interpreted against this tracked animation. - Animation replacement
- Android can start a new IME animation before the previous one finishes, for example a “false start” that begins showing the keyboard and then hides it.
Root Cause Analysis
In the animation listener’s onStart, the old code returned at once whenever animation != mAnimation. Any newly started animation that was not the one the controller had tracked from onPrepare was ignored. When a second IME animation replaced the first mid-flight, mAnimation still pointed at the stale animation, so later progress updates were applied “as if the old one is still running”. The commit message says this miscalculates the translation of the bottom controls, the container that holds the origin display.
The patch relaxes the identity check: a mismatched animation is still ignored if its getTypeMask() lacks WindowInsetsCompat.Type.ime(), but a new IME animation is now adopted (mAnimation = animation), and mAnimationInProgress = true is set for it. The regression test testAnimationReplacement starts a show animation, replaces it with a hide animation, and asserts that the control container translation tracks the current keyboard height (-currentKeyboardHeight).
onStart, so the toolbar position stopped matching the keyboard. The fix adopts any new IME-typed animation as the current one.Attack Path
- Bottom-toolbar configuration
The victim uses Chrome on Android with the toolbar at the bottom, so the mini origin bar logic is active, as in the test’s
ControlsPosition.BOTTOMsetup. - Focus a form field A web page focuses an editable field, and the keyboard begins to animate in.
- Force a replacement animation The page quickly changes focus, for example focusing and then blurring, so a hide animation starts before the show animation finishes (the page-driven trigger is inferred).
- Mispositioned origin display The controller ignores the new animation and computes the control-container translation from stale state. The origin bar can end up misplaced relative to the keyboard, for example covered or offset, while the page keeps drawing the visible content. That URL visibility or spoofing effect is (inferred) from the Toolbar component and the translation bug.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java |
modified |
Files Changed
chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.javachrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java
Audit Directions
- Stale "current animation" trackingCheck other
WindowInsetsAnimationCompat.Callbackusers in Chrome Android, such as the keyboard accessory, bottom sheets and browser controls, for identity checks that drop replacement animations and leave security UI positioned from stale state. - Origin and security UI under layout transitionsLook for code paths where the URL bar or origin chip can be hidden, translated off-screen or covered while page content stays visible and interactive, especially around IME show and hide or fullscreen changes.
- Page-controlled UI churnTreat any focus, scroll or keyboard sequence that a page can drive as adversarial input to browser-chrome animation state machines, and test interrupted and reversed transitions.
Patch
From b583fe9162bd751d0c6bd065b4e7da0c76d66c6a Mon Sep 17 00:00:00 2001 From: Patrick Noland <[email protected]> Date: Tue, 12 Aug 2025 10:46:56 -0700 Subject: [PATCH] [mobar] Handle mid-animation replacement Although not explicitly documented, it's possible for a new IME animation to start without the old one finishing, e.g. a "false start" start-showing-then-hide. If we don't update to reflect the state of the new animation we mistakenly perform updates as if the old one is still running which can cause us to e.g. miscalculate the translation. Bug: 437147699 Change-Id: I56b22c121848acb61410d33b42e82b8c0b05cd1a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6832030 Reviewed-by: Tomasz Wiszkowski <[email protected]> Commit-Queue: Patrick Noland <[email protected]> Cr-Commit-Position: refs/heads/main@{#1500279} --- 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 567e5bd..04f67ba2 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 @@ -524,7 +524,10 @@ @Override public void onStart(WindowInsetsAnimationCompat animation, BoundsCompat bounds) { if (animation != mAnimation) { - return; + if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) == 0) { + return; + } + mAnimation = animation; } mAnimationInProgress = true; diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java index 451ceb7..ce3f59aa 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java @@ -593,5 +593,40 @@ verify(mLocationBar, never()).setShowOriginOnly(anyBoolean()); } - // show again, start, finish showing (predictive back) + @Test + public void testAnimationReplacement() { + doReturn(ControlsPosition.BOTTOM).when(mBrowserControlsSizer).getControlsPosition(); + mMiniOriginBarController.onControlsPositionChanged(ControlsPosition.BOTTOM); + final MiniOriginWindowInsetsAnimationListener animationListener = + mMiniOriginBarController.getAnimationListenerForTesting(); + + final int finalKeyboardHeight = 100; + final BoundsCompat bounds = + new BoundsCompat(Insets.NONE, Insets.of(0, 0, 0, finalKeyboardHeight)); + mIsFormFieldFocused.onNodeAttributeUpdated(true, false); + + animationListener.onPrepare(mImeAnimation); + mKeyboardVisibilityDelegate.setVisibilityForTests(true); + animationListener.onStart(mImeAnimation, bounds); + + int currentKeyboardHeight = 50; + WindowInsetsCompat insets = + new WindowInsetsCompat.Builder() + .setInsets( + WindowInsetsCompat.Type.ime(), + Insets.of(0, 0, 0, currentKeyboardHeight)) + .build(); + mImeAnimation.setFraction(0.5f); + animationListener.onProgress(insets, Collections.singletonList(mImeAnimation)); + + // Start a second animation, this time hiding the keyboard. + final WindowInsetsAnimationCompat secondImeAnimation = + new WindowInsetsAnimationCompat(WindowInsetsCompat.Type.ime(), null, 160); + mKeyboardVisibilityDelegate.setVisibilityForTests(false); + animationListener.onStart(secondImeAnimation, bounds); + + secondImeAnimation.setFraction(0.5f); + animationListener.onProgress(insets, Collections.singletonList(secondImeAnimation)); + assertEquals(-currentKeyboardHeight, (int) mControlContainerTranslationSupplier.get()); + } }
Original Bug Report
Chrome on Android: Spoof issue triggered by bottom address bar
Steps to reproduce the problem
- Navigate to https://lbstyle.github.io/sandbox.html
- Tap inside the input field
- Tap docwrite1
Problem Description
Note: This is very similar to issue 379652406 and issue 40064686.
The omnibox disappears, and the attack can trick the user into thinking they are seeing a fake omnibox of a secure website.
Summary
Chrome on Android: Spoof issue triggered by bottom address bar
Additional Data
Category: Security
Chrome Channel: Canary
Regression: N/A \