CVE-2025-12435
Overview
Background
- Location-bar-only mode ("mobar")
- an Android toolbar state, tracked by
mShowLocationBarOnlyinToolbarControlContainer, in which themLocationBarViewis detached from the toolbar and shown on its own insidemToolbarContainer, with the container painting its own background color. - Toolbar primary color
- the background color of the Android top toolbar, which varies with tab state (e.g., incognito, page theme color) and is exposed by
ToolbarDataProvider.getPrimaryColor(), which the commit names as the source of truth. - `LocationBarModel`
- the Android model that implements the location bar and toolbar data providers and notifies registered observers when data such as the primary color changes.
- `ToolbarDataProvider.Observer`
- the observer interface for toolbar data consumers, which before the patch only offered
onIncognitoStateChanged().
Root Cause Analysis
When ToolbarControlContainer switched into location-bar-only mode, it set its background once with setBackgroundColor(mToolbar.getPrimaryColor()) and never updated it afterwards, because it had no subscription to primary-color changes. LocationBarModel notified only LocationBarDataProvider observers of onPrimaryColorChanged(), so if the primary color changed while the mobar was showing (the commit calls this “the rare but bad case”), the container background stayed stale while other location-bar consumers updated to the new color. This breaks the invariant that the security-relevant URL/origin display is drawn against the background color its styling was computed for; a mismatch could make the displayed origin or security indicators misleading or hard to read (inferred; the diff does not show how text/icon colors are derived).
The fix adds onPrimaryColorChanged() to ToolbarDataProvider.Observer, has LocationBarModel also notify mToolbarDataObservers, passes the ToolbarDataProvider into ToolbarControlContainer, which registers itself as an observer (and unregisters on teardown), and re-applies mToolbarDataProvider.getPrimaryColor() whenever the color changes while mShowLocationBarOnly is true. The initial background in location-bar-only mode is also taken from mToolbarDataProvider rather than mToolbar, making the data provider the single source of truth.
ToolbarDataProvider primary-color observer path so the container repaints whenever the color changes.Attack Path
- Location-bar-only mode shown
The Android browser enters the state where
ToolbarControlContainershows only the location bar (mShowLocationBarOnly) and sets its background from the current primary color. - Primary color changes While that mode is visible, the toolbar primary color changes; a page influencing its theme color is one plausible trigger (inferred; the diff does not show what drives the change).
- Stale background
Pre-patch,
LocationBarModelonly notifiedLocationBarDataProviderobservers, so the container kept the old color while location-bar contents updated for the new one. - Misleading security UI The URL/origin and security indicators are drawn against a background they were not styled for, which could make them hard to read or misleading to the user (inferred).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
forchrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java |
modified | |
ifchrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java |
modified |
Files Changed
chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.javachrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.javachrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.javachrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.javachrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
Audit Directions
- One-shot sampling of security UI stateSearch Android toolbar/omnibox code for places that read
getPrimaryColor(), security level, or URL text once when entering a special layout mode (location-bar-only, fullscreen, custom tabs, desktop windowing) without subscribing to later changes. - Split observer listsWhere a model like
LocationBarModelmaintains multiple observer lists for overlapping data, check that every change notification fans out to all lists whose consumers render that data. - Background/foreground color couplingVerify that any view painting its own background behind omnibox text or security icons derives both from the same provider and updates them together, so attacker-influenced theme changes cannot desynchronize them.
Patch
From b6d8c675b3417e6e3bafb37921d4f914eb8b25f6 Mon Sep 17 00:00:00 2001
From: Patrick Noland <pnoland@google.com>
Date: Tue, 23 Sep 2025 16:27:56 -0700
Subject: [PATCH] [mobar] Update mobar bg color in sync with toolbar primary color
The source of truth for this is is ToolbarDataProvider. This avoids the
rare but bad case of color changes while the mobar is showing.
Bug: 446463993
Change-Id: I6268839d362865245473ccef31220b90a8af0d82
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6976339
Commit-Queue: Patrick Noland <pnoland@chromium.org>
Reviewed-by: Tomasz Wiszkowski <ender@google.com>
Cr-Commit-Position: refs/heads/main@{#1519663}
---
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index fea74b3..42853d0 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -598,6 +598,9 @@
for (LocationBarDataProvider.Observer observer : mLocationBarDataObservers) {
observer.onPrimaryColorChanged();
}
+ for (ToolbarDataProvider.Observer observer : mToolbarDataObservers) {
+ observer.onPrimaryColorChanged();
+ }
}
@Override
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
index f3cf95b8..cec5c7fd 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java
@@ -21,6 +21,8 @@
/** Observer interface for consumers who wish to subscribe to updates of ToolbarData. */
interface Observer {
default void onIncognitoStateChanged() {}
+
+ default void onPrimaryColorChanged() {}
}
/** Adds an observer of changes to ToolbarDataProvider's data. */
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 aadf251e..4ca03b5b 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
@@ -54,12 +54,14 @@
import org.chromium.chrome.browser.toolbar.ControlContainer;
import org.chromium.chrome.browser.toolbar.R;
import org.chromium.chrome.browser.toolbar.ToolbarCaptureType;
+import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
+import org.chromium.chrome.browser.toolbar.ToolbarDataProvider.Observer;
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.top.CaptureReadinessResult.TopToolbarBlockCaptureReason;
import org.chromium.components.browser_ui.desktop_windowing.AppHeaderState;
-import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateManager;
+import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateManager.AppHeaderObserver;
import org.chromium.components.browser_ui.widget.ClipDrawableProgressBar.DrawingInfo;
import org.chromium.components.browser_ui.widget.TouchEventObserver;
import org.chromium.components.browser_ui.widget.ViewResourceCoordinatorLayout;
@@ -80,7 +82,7 @@
/** Layout for the browser controls (omnibox, menu, tab strip, etc..). */
@NullMarked
public class ToolbarControlContainer extends OptimizedFrameLayout
- implements ControlContainer, DesktopWindowStateManager.AppHeaderObserver, TopControlLayer {
+ implements ControlContainer, AppHeaderObserver, TopControlLayer, Observer {
private boolean mIncognito;
private boolean mMidVisibilityToggle;
private boolean mIsCompositorInitialized;
@@ -104,6 +106,7 @@
private @Nullable ObservableSupplier<Boolean> mXrSpaceModeObservableSupplier;
private @Nullable ObservableSupplierImpl<Integer> mHeightChangedSupplier;
private @Nullable TopControlsStacker mTopControlsStacker;
+ private ToolbarDataProvider mToolbarDataProvider;
/**
* Constructs a new control container.
@@ -250,7 +253,7 @@
mToolbarView.setVisibility(View.GONE);
mToolbarView.removeView(mLocationBarView);
mToolbarContainer.addView(mLocationBarView);
- setBackgroundColor(mToolbar.getPrimaryColor());
+ setBackgroundColor(mToolbarDataProvider.getPrimaryColor());
} else {
assert mLocationBarView != null
: "Trying to restore location bar view to toolbar without removing it first";
@@ -291,6 +294,9 @@
if (mXrSpaceModeObservableSupplier != null) {
mXrSpaceModeObservableSupplier.removeObserver(mOnXrSpaceModeChanged);
}
+ if (mToolbarDataProvider != null) {
+ mToolbarDataProvider.removeToolbarDataProviderObserver(this);
+ }
}
@Override
@@ -381,10 +387,13 @@
browserStateBrowserControlsVisibilityDelegate,
OneshotSupplier<LayoutStateProvider> layoutStateProviderSupplier,
FullscreenManager fullscreenManager,
- TopControlsStacker topControlsStacker) {
+ TopControlsStacker topControlsStacker,
+ ToolbarDataProvider toolbarDataProvider) {
mToolbar = toolbar;
mIncognito = isIncognito;
mTopControlsStacker = topControlsStacker;
+ mToolbarDataProvider = toolbarDataProvider;
+ mToolbarDataProvider.addToolbarDataProviderObserver(this);
BooleanSupplier isVisible = () -> this.getVisibility() == View.VISIBLE;
mToolbarContainer.setPostInitializationDependencies(
@@ -477,6 +486,13 @@
mToolbarContainer.setOnDragListener(mToolbarContainerDragListener);
}
+ @Override
+ public void onPrimaryColorChanged() {
+ if (mShowLocationBarOnly) {
+ setBackgroundColor(mToolbarDataProvider.getPrimaryColor());
+ }
+ }
+
/** The layout that handles generating the toolbar view resource. */
// Only publicly visible due to lint warnings.
public static class ToolbarViewResourceCoordinatorLayout extends ViewResourceCoordinatorLayout {
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 7a79938..aac01ab 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
@@ -549,7 +549,7 @@
@Test
public void testShowLocationBarOnly() {
doReturn(mLocationBarView).when(mToolbar).removeLocationBarView();
- doReturn(Color.RED).when(mToolbar).getPrimaryColor();
+ doReturn(Color.RED).when(mToolbarDataProvider).getPrimaryColor();
ToolbarControlContainer controlContainer =
(ToolbarControlContainer)
mActivity.getLayoutInflater().inflate(R.layout.control_container, null);
@@ -564,7 +564,8 @@
mBrowserStateBrowserControlsVisibilityDelegate,
mLayoutStateProviderSupplier,
mFullscreenManager,
- mTopControlsStacker);
+ mTopControlsStacker,
+ mToolbarDataProvider);
ToolbarPhone toolbarPhone = controlContainer.findViewById(R.id.toolbar);
doReturn(mLocationBarCoordinatorPhone).when(mLocationBarCoordinator).getPhoneCoordinator();
@@ -595,7 +596,12 @@
verify(mProgressBar).setVisibility(View.GONE);
verify(mToolbarView).setVisibility(View.GONE);
verify(mToolbarView).removeView(mLocationBarView);
+
assertEquals(Color.RED, ((ColorDrawable) controlContainer.getBackground()).getColor());
+ doReturn(Color.GREEN).when(mToolbarDataProvider).getPrimaryColor();
+ controlContainer.onPrimaryColorChanged();
+ assertEquals(Color.GREEN, ((ColorDrawable) controlContainer.getBackground()).getColor());
+
ToolbarViewResourceCoordinatorLayout toolbarViewResourceFrameLayout =
controlContainer.getToolbarContainerForTesting();
assertEquals(
@@ -629,7 +635,8 @@
mBrowserStateBrowserControlsVisibilityDelegate,
mLayoutStateProviderSupplier,
mFullscreenManager,
- mTopControlsStacker);
+ mTopControlsStacker,
+ mToolbarDataProvider);
ToolbarControlContainer.ToolbarViewResourceCoordinatorLayout toolbarContainer =
controlContainer.findViewById(R.id.toolbar_container);
toolbarContainer.setVisibility(View.GONE);
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
index 1cd82ab..13ba4e7b 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
@@ -243,7 +243,8 @@
browserStateBrowserControlsVisibilityDelegate,
layoutStateProviderSupplier,
fullscreenManager,
- topControlsStacker);
+ topControlsStacker,
+ toolbarDataProvider);
mToolbarLayout.initialize(
toolbarDataProvider,
tabController,
Original Bug Report
Spoof on virtual keyboard
VULNERABILITY DETAILS
when the virtual keyboard is displayed and at the same time the navigation is directed to the previous page (history.go(-1)) the virtual keyboard still appears and the color of the minibar above the virtual keyboard becomes dynamic (the color follows the –theme-color of the background color so that the writing in the minibar is the same so that the domain is not visible in this bug I use white color) , leading to a spoof.
OS: Android 15 Device: Samsung A56 Chrome Version: 142.0.7396.0 Canary
Steps to reproduce:
- download detx.html,spoofkeyboard.html in same folder / host detx.html,spoofkeyboard.html on local web server using https:// protocol
- open https://you-can-billowy-nimble-login-secure-docs-google-source-attacker.com/detx.html or open detx.html on local web server using https: protocol
- click the button
- clik the go to google button