Medium watch CVSS 8.2 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
8.2
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Omnibox
DescriptionInappropriate implementation in Omnibox
ComponentOmnibox
Sub-componentMobar
Bug ClassLogic Error
Action tierwatch — dos-class only — no demonstrated corruption path
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.
Tracker438226517
Fix commitcb2f93701240 (chromium/src) +57/-1
CWECWE-290
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
CISA KEVNot listed
CreditedHafiizh
Reported2025-08-13 · bounty $3000 (per vendor advisory)
Fix landed2025-08-28 (public git)
Disclosed2025-09-30
Patch gap33 days — fix public in source before the advisory

Background

Location-bar-only mode ("mobar")
an Android toolbar mode in which ToolbarControlContainer hides the full toolbar and progress bar and reparents the location bar view (mLocationBarView) directly into the toolbar container. Leaving the mode reverses this.
`CoordinatorLayout`
an Android ViewGroup that keeps its own processed list of children, refreshed only at measure time, and expects each child’s LayoutParams to be its own CoordinatorLayout.LayoutParams type.
Reparenting with new `LayoutParams`
When a view is removed from one parent and added to another, it gets that parent’s LayoutParams type (here MarginLayoutParams from getMarginLayoutParams()). Any code that still treats it as a child of the old parent will cast to the wrong type.
`ClassCastException`
a Java runtime exception thrown when an object is cast to a type it is not; uncaught on the UI thread, it crashes the browser.

Root Cause Analysis

The vulnerable path is in ToolbarControlContainer.java, in the branch that leaves location-bar-only mode. The enclosing method’s signature is outside the hunk; the test drives it through toggleLocationBarOnlyMode(false). That branch calls mToolbarContainer.removeView(mLocationBarView) and then mToolbar.restoreLocationBarView(), which moves the location bar back into the toolbar with different LayoutParams (MarginLayoutParams, as the test sets up). As the added comment explains, CoordinatorLayout updates its processed list of children only at measure time. After removeView its cached list still contains the removed location bar, and the next layout pass can cast that view’s new MarginLayoutParams to CoordinatorLayout.LayoutParams, throwing a ClassCastException.

The fix calls forceLayout() and measure(...) on mToolbarContainer between removeView and restoreLocationBarView(), so the stale list is rebuilt before the view is reparented. The diff only shows how the crash is avoided. It does not show why the advisory classes this as an Omnibox “inappropriate implementation”. A crash or a broken toolbar state while the location bar is being restored could leave the omnibox missing or wrong, but that link is not shown in the diff (inferred).

Key insight
The code reparented the location bar out of a CoordinatorLayout without letting the layout refresh its cached child list. A stale entry with incompatible LayoutParams could therefore be processed. The fix forces a re-measure right after removal so the cache is current before restoreLocationBarView() runs.

Attack Path

  1. Enter location-bar-only mode Chrome on Android switches the toolbar into mobar mode through toggleLocationBarOnlyMode(true), which parents mLocationBarView into mToolbarContainer. The diff does not show what triggers this mode.
  2. Leave the mode On toggleLocationBarOnlyMode(false), the container restores the toolbar and progress bar, removes mLocationBarView, and calls mToolbar.restoreLocationBarView(), which gives the view new MarginLayoutParams.
  3. Stale child list in `CoordinatorLayout` Before the fix, the container’s processed child list still referred to the reparented location bar, so a later measure or layout cast its params to the wrong type and threw a ClassCastException.
  4. Omnibox left broken or browser crashes (inferred) The uncaught exception crashes the browser, or interrupts the location-bar restore and leaves the omnibox state wrong. The security consequence the advisory has in mind is not shown in the diff.

Impact Assessment

The vendor rates the bug medium (“Inappropriate implementation in Omnibox”) and gives no in-the-wild exploitation status. The diff itself demonstrates only a Java ClassCastException in Chrome’s Android browser UI, meaning a crash (denial of service) in the browser process’s UI layer. Any URL-display or spoofing effect on the omnibox is (inferred). The bug affects only Chrome on Android, and only when the location-bar-only (mobar) toolbar mode is entered and then left. Whether ordinary web content can force those transitions, and whether the mode is behind a feature flag, cannot be determined from the diff.

Files Changed

  • chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java
  • chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java

Audit Directions

  • View reparenting out of `CoordinatorLayout`
    Search the Android UI for removeView followed by addView into another parent where the source is a CoordinatorLayout (or another layout that caches child lists) and no measure pass happens in between.
  • Security-critical UI restored in multiple steps
    Check other omnibox and toolbar mode toggles (location-bar-only mode, fullscreen and app-header states, desktop windowing) to make sure an exception or early exit partway through cannot leave the URL bar hidden, detached, or out of date.
  • Unchecked `LayoutParams` casts
    Review custom layouts and behaviors that cast getLayoutParams() without an instanceof check, because any reparented child can turn those casts into a crash.
From cb2f937012403bbfa165cc067d2923223afcf86e Mon Sep 17 00:00:00 2001
From: Patrick Noland <pnoland@google.com>
Date: Thu, 28 Aug 2025 14:12:21 -0700
Subject: [PATCH] [mobar] Force re-measure of coordinator layout on leaving mobar

This avoids a bug in CoordinatorLayout where a stale list of children
can cause a ClassCastException after reparenting.

Bug: 438226517
Change-Id: I2924109bfeb7fd8c855317a27dff730446b78a0b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6896264
Reviewed-by: Tomasz Wiszkowski <ender@google.com>
Commit-Queue: Patrick Noland <pnoland@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1508003}
---

diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java
index b129393d..f81cc14b 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java
@@ -241,6 +241,14 @@
             mToolbar.getProgressBar().setVisibility(View.VISIBLE);
             mToolbarView.setVisibility(View.VISIBLE);
             mToolbarContainer.removeView(mLocationBarView);
+            // CoordinatorLayout only updates its processed list of children at measure time, even
+            // if a child is removed. This can cause problems if a reparented former child has a new
+            // type of LayoutParams, triggering a ClassCastException. We work around this by forcing
+            // a re-measure.
+            mToolbarContainer.forceLayout();
+            mToolbarContainer.measure(
+                    mToolbarContainer.getMeasuredWidthAndState(),
+                    mToolbarContainer.getMeasuredHeightAndState());
             mToolbar.restoreLocationBarView();
             setBackgroundColor(Color.TRANSPARENT);
         }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java
index c237447..502e57c59 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java
@@ -22,6 +22,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -45,11 +46,21 @@
 import org.chromium.chrome.browser.fullscreen.FullscreenManager;
 import org.chromium.chrome.browser.layouts.LayoutStateProvider;
 import org.chromium.chrome.browser.layouts.LayoutType;
+import org.chromium.chrome.browser.omnibox.LocationBarCoordinator;
+import org.chromium.chrome.browser.omnibox.LocationBarCoordinatorPhone;
+import org.chromium.chrome.browser.omnibox.NewTabPageDelegate;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.IncognitoStateProvider;
 import org.chromium.chrome.browser.theme.SurfaceColorUpdateUtils;
+import org.chromium.chrome.browser.theme.ThemeColorProvider;
+import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
 import org.chromium.chrome.browser.toolbar.ToolbarFeatures;
 import org.chromium.chrome.browser.toolbar.ToolbarHairlineView;
 import org.chromium.chrome.browser.toolbar.ToolbarProgressBar;
+import org.chromium.chrome.browser.toolbar.back_button.BackButtonCoordinator;
+import org.chromium.chrome.browser.toolbar.forward_button.ForwardButtonCoordinator;
+import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator;
+import org.chromium.chrome.browser.toolbar.reload_button.ReloadButtonCoordinator;
 import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult.TopToolbarAllowCaptureReason;
 import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult.TopToolbarBlockCaptureReason;
 import org.chromium.chrome.browser.toolbar.top.ToolbarControlContainer.ToolbarViewResourceAdapter;
@@ -57,7 +68,9 @@
 import org.chromium.chrome.browser.toolbar.top.ToolbarControlContainer.ToolbarViewResourceCoordinatorLayout;
 import org.chromium.components.browser_ui.desktop_windowing.AppHeaderState;
 import org.chromium.components.browser_ui.widget.TouchEventObserver;
+import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.ui.base.TestActivity;
+import org.chromium.url.GURL;
 
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BooleanSupplier;
@@ -79,11 +92,23 @@
     @Mock private View mLocationBarView;
     @Mock private ToolbarHairlineView mToolbarHairline;
     @Mock private Toolbar mToolbar;
+    @Mock private ToggleTabStackButtonCoordinator mTabSwitcherButtonCoordinator;
     @Mock private ToolbarProgressBar mProgressBar;
     @Mock private Tab mTab;
     @Mock private LayoutStateProvider mLayoutStateProvider;
     @Mock private FullscreenManager mFullscreenManager;
     @Mock private TouchEventObserver mTouchEventObserver;
+    @Mock private LocationBarCoordinator mLocationBarCoordinator;
+    @Mock private LocationBarCoordinatorPhone mLocationBarCoordinatorPhone;
+    @Mock private MenuButtonCoordinator mMenuButtonCoordinator;
+    @Mock private ToolbarDataProvider mToolbarDataProvider;
+    @Mock private ReloadButtonCoordinator mReloadButtonCoordinator;
+    @Mock private BackButtonCoordinator mBackButtonCoordinator;
+    @Mock private ForwardButtonCoordinator mForwardButtonCoordinator;
+    @Mock private HomeButtonDisplay mHomeButtonDisplay;
+    @Mock private ThemeColorProvider mThemeColorProvider;
+    @Mock private IncognitoStateProvider mIncognitoStateProvider;
+    @Mock private NewTabPageDelegate mNewTabPageDelegate;
 
     private final Supplier<Tab> mTabSupplier = () -> mTab;
     private final ObservableSupplierImpl<Boolean> mCompositorInMotionSupplier =
@@ -535,6 +560,28 @@
                 mLayoutStateProviderSupplier,
                 mFullscreenManager);
 
+        ToolbarPhone toolbarPhone = controlContainer.findViewById(R.id.toolbar);
+        doReturn(mLocationBarCoordinatorPhone).when(mLocationBarCoordinator).getPhoneCoordinator();
+        doReturn(mNewTabPageDelegate).when(mToolbarDataProvider).getNewTabPageDelegate();
+        doReturn(new GURL(UrlConstants.ABOUT_URL)).when(mToolbarDataProvider).getCurrentGurl();
+        toolbarPhone.setLocationBarCoordinator(mLocationBarCoordinator);
+        toolbarPhone.initialize(
+                mToolbarDataProvider,
+                null,
+                mMenuButtonCoordinator,
+                mTabSwitcherButtonCoordinator,
+                null,
+                null,
+                null,
+                mProgressBar,
+                mReloadButtonCoordinator,
+                mBackButtonCoordinator,
+                mForwardButtonCoordinator,
+                mHomeButtonDisplay,
+                /* extensionToolbarCoordinator= */ null,
+                mThemeColorProvider,
+                mIncognitoStateProvider);
+
         controlContainer.toggleLocationBarOnlyMode(true);
         verify(mProgressBar).setVisibility(View.GONE);
         verify(mToolbarView).setVisibility(View.GONE);
@@ -546,8 +593,9 @@
                 mLocationBarView,
                 toolbarViewResourceFrameLayout.getChildAt(
                         toolbarViewResourceFrameLayout.getChildCount() - 1));
-        // check that location bar is parented to container view
 
+        MarginLayoutParams layoutParams = new MarginLayoutParams(500, 100);
+        doReturn(layoutParams).when(mLocationBarCoordinatorPhone).getMarginLayoutParams();
         controlContainer.toggleLocationBarOnlyMode(false);
         verify(mProgressBar).setVisibility(View.VISIBLE);
         verify(mToolbarView).setVisibility(View.VISIBLE);
Loading diff…

Original Bug Report

reported by sa...@gmail.com