Low watch CVSS 4.3 chrome Spoofing 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in Omnibox
DescriptionIncorrect security UI in Omnibox
ComponentOmnibox
Sub-componentMobar
Bug ClassSpoofing
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker484082189
Fix commiteaf32e20c880 (chromium/src) +239/-4
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
Creditedmohamedhesham9173
Reported2026-02-13 (per vendor advisory)
Fix landed2026-02-27 (public git)
Disclosed2026-04-07
Patch gap39 days — fix public in source before the advisory

Background

Mini origin bar (mobar)
the compact Android toolbar element managed by MiniOriginBarController that displays the current page’s origin/URL and animates (shrinks and slides) as the keyboard appears.
`translationX` / minimization animation
the location bar is moved horizontally from a starting x to a final x by interpolating with minimizationProgress, computed in setMinimizationProgress.
RTL layout
right-to-left locales lay out views from the right edge, so a view’s default “resting” left edge is not 0; LocalizationUtils.isLayoutRtl() reports this.
Security UI relevance
the origin bar is anti-spoofing UI, so any animation that leaves the origin text mis-positioned or clipped can misrepresent the displayed origin.

Root Cause Analysis

In MiniOriginBarController, the animation code computed mStartingLocationBarX as the raw mDefaultLocationBarLayoutParams.leftMargin and the target mFinalLocationBarX as an absolute centered coordinate (controlContainerWidth - finalLocationBarWidth) / 2, then in setMinimizationProgress applied these directly via setTranslationX. That math implicitly assumed the location bar’s resting left edge is 0, which is only true in LTR; in RTL the view rests at controlContainerWidth - viewWidth, so both the start value and the interpolated target were wrong, producing an incorrect horizontal translation during the keyboard animation.

The fix introduces baseLayoutLeftX (= controlContainerWidth - viewWidth in RTL, else 0), subtracts it from both mStartingLocationBarX and the renamed mFinalLocationBarTranslationX so both are expressed as translations relative to the resting position, and interpolates between those translation deltas. This is a positioning-correctness fix (no check is tightened or relaxed); the diff changes only float geometry and a field name, and the shipped RTL test asserts setTranslationX(startX + fraction * positionDelta) with RTL-aware baseLayoutLeftX.

Key insight
The single mistake was treating the location bar’s resting left edge as 0 (an LTR-only assumption) when computing start/final translation, which skews the origin bar’s position in RTL; the fix reframes both endpoints relative to an RTL-aware baseLayoutLeftX.

Attack Path

  1. RTL device/locale The user runs Chrome for Android in a right-to-left locale, where the mini origin bar’s resting position differs from the LTR assumption. (precondition, not attacker-controlled)
  2. Trigger the mobar animation Focusing a form field / showing the keyboard drives setMinimizationProgress, which interpolates translationX using the LTR-biased start and final values.
  3. Origin bar renders at the wrong horizontal position The incorrect translationX can leave the origin text shifted, mis-centered, or partly clipped during/after the animation. (inferred, from the positioning math)
  4. Possible origin misrepresentation Because the mobar is security UI for the displayed origin, a mis-positioned or truncated origin could be read incorrectly by the user. (inferred — the patch demonstrates only wrong geometry, not a concrete spoof primitive)

Impact Assessment

The flaw causes incorrect rendering/positioning of the origin-display UI in the Android browser UI under RTL layouts, which the advisory classes as “Incorrect security UI in Omnibox”; a user in an RTL locale could be shown a mis-positioned origin bar. The advisory rates this low severity and reports no in-the-wild exploitation. The patch itself demonstrates only a layout/animation miscalculation — no memory safety issue — and any resulting origin-spoofing is an inferred UI consequence, not a demonstrated primitive.

Files Changed

  • chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java
  • chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java

Audit Directions

  • LTR-only geometry assumptions
    Audit Android view-animation code that uses raw leftMargin/absolute x coordinates without consulting LocalizationUtils.isLayoutRtl(), especially for security-relevant UI.
  • Origin/omnibox rendering under animation
    Review any transform (translate/scale/clip) applied to the URL or origin display for states where text can be shifted, clipped, or hidden, since these are spoofing surfaces.
  • Mixed-direction and measured-width math
    Check calculations mixing getMeasuredWidth, container width, and margins for correctness in both layout directions and for off-by-base-offset errors.
From eaf32e20c8801aa6cc4494186fd4b2b2580aaa02 Mon Sep 17 00:00:00 2001
From: Patrick Noland <[email protected]>
Date: Thu, 26 Feb 2026 17:02:44 -0800
Subject: [PATCH] [mobar] Fix RTL mobar animation

The starting position of the location bar is different for RTL, which
we need to account for when calculating the final translationX.

Bug: 484082189
Change-Id: I3ed5c3954c0e4c7a290936521ad2a101c42b7f14
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7610025
Reviewed-by: Sky Malice <[email protected]>
Commit-Queue: Patrick Noland <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1591207}
---

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 2e33b82..948ed009 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
@@ -32,6 +32,7 @@
 import org.chromium.components.browser_ui.widget.TouchEventObserver;
 import org.chromium.ui.KeyboardVisibilityDelegate;
 import org.chromium.ui.KeyboardVisibilityDelegate.KeyboardVisibilityListener;
+import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.base.ViewUtils;
 import org.chromium.ui.insets.InsetObserver;
 import org.chromium.ui.insets.InsetObserver.WindowInsetsAnimationListener;
@@ -127,7 +128,7 @@
     private float mStartingLocationBarX;
     // The final horizontal position of the location bar when the mini origin bar is in its
     // fully-minimized state.
-    private float mFinalLocationBarX;
+    private float mFinalLocationBarTranslationX;
     private boolean mShowingMiniOriginBar;
 
     /**
@@ -309,9 +310,17 @@
         locationBarView.measure(
                 MeasureSpec.makeMeasureSpec(controlContainerWidth, MeasureSpec.AT_MOST),
                 MeasureSpec.makeMeasureSpec(newLocationBarHeight, MeasureSpec.AT_MOST));
-        mStartingLocationBarX = mDefaultLocationBarLayoutParams.leftMargin;
+
+        boolean isRtl = LocalizationUtils.isLayoutRtl();
+        int viewWidth = locationBarView.getMeasuredWidth();
+        // The "resting position" of the left edge of the location bar assuming no translation.
+        float baseLayoutLeftX = isRtl ? controlContainerWidth - viewWidth : 0;
+
+        mStartingLocationBarX = mDefaultLocationBarLayoutParams.leftMargin - baseLayoutLeftX;
         float finalLocationBarWidth = locationBarView.getMeasuredWidth() * LOCATION_BAR_FINAL_SCALE;
-        mFinalLocationBarX = (controlContainerWidth - finalLocationBarWidth) / 2;
+        // The final x coordinate of the left edge that centers it horizontally.
+        float targetAbsoluteLeftX = (controlContainerWidth - finalLocationBarWidth) / 2f;
+        mFinalLocationBarTranslationX = targetAbsoluteLeftX - baseLayoutLeftX;
     }
 
     private void hideMiniOriginBar() {
@@ -460,7 +469,8 @@
     private void setMinimizationProgress(float minimizationProgress) {
         float translationX =
                 mStartingLocationBarX
-                        + minimizationProgress * (mFinalLocationBarX - mStartingLocationBarX);
+                        + minimizationProgress
+                                * (mFinalLocationBarTranslationX - mStartingLocationBarX);
         mLocationBar.getContainerView().setTranslationX(translationX);
 
         float scale = 1.0f - minimizationProgress / LOCATION_BAR_SCALE_DENOMINATOR;
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 e0c99fe..a4ab3b8 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
@@ -53,6 +53,7 @@
 import org.chromium.components.browser_ui.widget.TouchEventObserver;
 import org.chromium.content.browser.input.ImeAdapterImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.insets.InsetObserver;
 
 import java.util.Collections;
@@ -467,6 +468,230 @@
     }
 
     @Test
+    public void testAnimateWithKeyboard_RTL() {
+        LocalizationUtils.setRtlForTesting(true);
+        try {
+            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);
+
+            final int locationBarStartPosition = 50; // leftMargin
+            final int rightMargin = 50;
+            mLocationBarLayoutParams.leftMargin = locationBarStartPosition;
+            mLocationBarLayoutParams.rightMargin = rightMargin;
+
+            final int locationBarMiniWidth = 100;
+            doReturn(locationBarMiniWidth).when(mLocationBarView).getMeasuredWidth();
+
+            float finalLocationBarWidth =
+                    locationBarMiniWidth * MiniOriginBarController.LOCATION_BAR_FINAL_SCALE;
+
+            // Calculate RTL specific coordinates and translations
+            float baseLayoutLeftX = CONTROL_CONTAINER_WIDTH - locationBarMiniWidth;
+            float targetAbsoluteLeftX = (CONTROL_CONTAINER_WIDTH - finalLocationBarWidth) / 2f;
+
+            final float startX = locationBarStartPosition - baseLayoutLeftX;
+            final float finalTranslationX = targetAbsoluteLeftX - baseLayoutLeftX;
+            final float positionDelta = finalTranslationX - startX;
+
+            // The url bar height is smaller than the total height of the mobar due to vertical
+            // margin.
+            final float urlBarHeight =
+                    mContext.getResources().getDimensionPixelSize(R.dimen.mini_origin_bar_height)
+                            - 6;
+            doReturn(urlBarHeight).when(mLocationBar).getUrlBarHeight();
+
+            animationListener.onPrepare(mImeAnimation);
+            mKeyboardVisibilityDelegate.setVisibilityForTests(true);
+            animationListener.onStart(mImeAnimation, bounds);
+            Assert.assertEquals(
+                    MiniOriginState.ANIMATING,
+                    mMiniOriginBarController.getCurrentStateForTesting());
+
+            int currentKeyboardHeight = 10;
+            final int systemBarsHeight = 13;
+            WindowInsetsCompat.Builder insetsBuilder =
+                    new WindowInsetsCompat.Builder()
+                            .setInsets(
+                                    WindowInsetsCompat.Type.ime(),
+                                    Insets.of(0, 0, 0, currentKeyboardHeight))
+                            .setInsets(
+                                    WindowInsetsCompat.Type.systemBars(),
+                                    Insets.of(0, 0, 0, systemBarsHeight));
+            WindowInsetsCompat insets = insetsBuilder.build();
+
+            // --- PROGRESS 0.1 ---
+            mImeAnimation.setFraction(0.1f);
+            animationListener.onProgress(insets, Collections.singletonList(mImeAnimation));
+            assertEquals(
+                    finalKeyboardHeight - currentKeyboardHeight,
+                    (int) mControlContainerTranslationSupplier.get());
+            verify(mLocationBarView)
+                    .setTranslationX(startX + mImeAnimation.getFraction() * positionDelta);
+            verify(mLocationBarView)
+                    .setScaleX(
+                            1.0f
+                                    - mImeAnimation.getFraction()
+                                            / MiniOriginBarController
+                                                    .LOCATION_BAR_SCALE_DENOMINATOR);
+            verify(mLocationBarView)
+                    .setScaleY(
+                            1.0f
+                                    - mImeAnimation.getFraction()
+                                            / MiniOriginBarController
+                                                    .LOCATION_BAR_SCALE_DENOMINATOR);
+            verify(mLocationBarView).setPivotY(urlBarHeight / 2);
+
+            // --- PROGRESS 0.4 ---
+            currentKeyboardHeight = 40;
+            insets =
+                    insetsBuilder
+                            .setInsets(
+                                    WindowInsetsCompat.Type.ime(),
+                                    Insets.of(0, 0, 0, currentKeyboardHeight))
+                            .build();
+            mImeAnimation.setFraction(0.4f);
+            animationListener.onProgress(insets, Collections.singletonList(mImeAnimation));
+            assertEquals(
+                    finalKeyboardHeight - currentKeyboardHeight,
+                    (int) mControlContainerTranslationSupplier.get());
+            verify(mLocationBarView)
+                    .setTranslationX(startX + mImeAnimation.getFraction() * positionDelta);
+            verify(mLocationBarView)
+                    .setScaleX(
+                            1.0f
+                                    - mImeAnimation.getFraction()
+                                            / MiniOriginBarController
+                                                    .LOCATION_BAR_SCALE_DENOMINATOR);
+            verify(mLocationBarView)
+                    .setScaleY(
+                            1.0f
+                                    - mImeAnimation.getFraction()
+                                            / MiniOriginBarController
+                                                    .LOCATION_BAR_SCALE_DENOMINATOR);
+
+            // --- PROGRESS 0.9 ---
+            currentKeyboardHeight = 90;
+            insets =
+                    insetsBuilder
+                            .setInsets(
+                                    WindowInsetsCompat.Type.ime(),
+                                    Insets.of(0, 0, 0, currentKeyboardHeight))
+                            .build();
Loading diff…

Original Bug Report

reported by [email protected]

Security Regression: Trusted UI (Omnibox) fails to render/disappears due to CoordinatorLayout migration logic failure.

  1. Executive Summary

A rendering regression was identified in Chromium for Android (arm64), causing the URL Bar (Omnibox) to disappear or render incorrectly when positioned at the bottom of the screen. The issue was introduced in Revision 1501003 during a structural migration of the toolbar container from OptimizedFrameLayout to CoordinatorLayout. This change conflicts with high-DPI resource handling on specific devices (e.g., Realme 12), leading to layout inflation errors and breaking the Trusted UI boundary.

  1. Issue Description

Affected Version: Chromium Builds starting from Revision 1501003.

Device Specifics: Verified on Realme 12 (Android 15), likely affects other high-DPI devices.

Trigger Condition: The issue manifests specifically when the “Bottom Toolbar” setting is enabled (or forced via flags), moving the URL bar to the bottom of the screen.

  1. Bisect & Regression Range

Using manual binary search (bisect) on Android_Arm64 snapshots, the regression was isolated to a specific range:

Last Known Good Revision: 1500987

First Bad Revision: 1501016

Culprit Commit Position: refs/heads/main@{#1501003}

  1. Identification of the Culprit

Commit Hash: e981dd46821f563acaa6a5a3d8fd51e046e311e2.

Title: [Toolbar] Rework hairline positioning.

Change Log: “Rework hairline positioning… This does require switching the toolbar container to be a CoordinatorLayout… Instead of manual manipulation.”

  1. Technical Root Cause Analysis

The root cause is a logic failure in how the new CoordinatorLayout handles the toolbar’s vertical positioning compared to the previous FrameLayout implementation, specifically when interacting with high-density display resources.

A. Structural Change The culprit commit replaced the root container of the toolbar in control_container.xml:

Removed: org.chromium.components.browser_ui.widget.ViewResourceFrameLayout

Added: androidx.coordinatorlayout.widget.CoordinatorLayout

Additional Evidence in ToolbarControlContainer.java: The file now explicitly casts layout params to CoordinatorLayout.LayoutParams in mutateLayoutParams(), confirming that the container strictly enforces the new layout hierarchy which fails to handle the Hi-DPI scaling correctly.

B. Logic Regression in ToolbarPositionController.java Upon inspecting the source code of ToolbarPositionController.java, specifically the updateCurrentPosition() method:

Removal of Explicit Offsets: The legacy code used to manually calculate and set the Y-translation (setTranslationY) based on screen height and offsets. This guaranteed the view was placed within visible bounds.

Reliance on Gravity: The new implementation relies entirely on CoordinatorLayout.LayoutParams and gravity anchors: // Code Snippet from ToolbarPositionController.java (New Logic) CoordinatorLayout.LayoutParams hairlineLayoutParams = mControlContainer.mutateHairlineLayoutParams();

// Logic relies solely on flipping gravity hairlineLayoutParams.anchorGravity = newControlsPosition == ControlsPosition.TOP ? Gravity.BOTTOM : Gravity.TOP;

LayoutParams layoutParams = mControlContainer.mutateLayoutParams(); int verticalGravity = newControlsPosition == ControlsPosition.TOP ? Gravity.TOP : Gravity.BOTTOM;

layoutParams.gravity = Gravity.START | verticalGravity;

C. The Conflict The reliance on Gravity.BOTTOM failed because of a preceding commit 1500950 (Enable hi-dpi resources).

The new Hi-DPI assets likely altered the measured height of the toolbar children.

CoordinatorLayout requires precise measurement passes (onMeasure). If the inner views (Omnibox icons) have mismatched dimensions due to the new resources, the CoordinatorLayout may calculate a zero height or push the view off-screen, whereas the old FrameLayout would have forced it to render regardless of measurement errors.

Security Impact Analysis: According to Chrome’s security guidelines, a UI spoof is critical if it “convinces the user they are currently on origin A when in fact they are on origin B”.

This regression (caused by CoordinatorLayout migration) actively corrupts the primary ‘Security Cue’ (the Origin/URL text) that a “reasonable and prudent user” relies on.

Specifically, the failure in CoordinatorLayout to handle Hi-DPI measurements causes the domain text view to be shifted off-screen or rendered with incorrect padding, effectively truncating the origin or leaving the URL bar blank during keyboard interaction. This renders the domain unreadable, denying the user the ability to verify the origin and directly facilitating the criteria mentioned in your guidelines.

Unlike a design choice, this is a code failure in ToolbarPositionController.java that breaks the trusted UI boundary involuntarily.

  1. Reproduction Steps

Install Chromium Android_Arm64 build 1501016 (or any build after 1501003).

Launch the browser.

Navigate to chrome://flags or Settings.

Enable “Bottom Toolbar” (or Android Bottom Toolbar flag).

Relaunch the browser and open any webpage.

Tap on an input field to trigger the virtual keyboard.

Observation: Upon triggering the virtual keyboard, the bottom-anchored Omnibox layout fails, causing the domain origin text to be either shifted off-screen, or rendered with zero height, making the origin unreadable.

  1. Proposed Fix / Recommendation

To resolve this regression, the CoordinatorLayout implementation in ToolbarPositionController.java must account for the safe area insets and potential measurement discrepancies caused by Hi-DPI assets.

Immediate Mitigation: Revert commit e981dd4 to restore OptimizedFrameLayout stability.

Long-term Fix: Modify updateCurrentPosition() to ensure a minimum valid height is enforced and that layoutParams.bottomMargin correctly accounts for the device’s navigation bar/gesture area when Gravity.BOTTOM is applied.

View on issue tracker