High watch CVSS 4.3 chrome Spoofing 🔧 Commit mapped

Overview

High
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in Omnibox
DescriptionUI misrepresentation in Omnibox
ComponentOmnibox
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.
Tracker551668264
Fix commit82b00091f852 (chromium/src) +57/-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
Creditedjodyritonga
Reported2026-08-24 (per vendor advisory)
Fix landed2026-09-18 (public git)
Disclosed2026-09-29
Patch gap11 days — fix public in source before the advisory

Background

`UrlBar` scroll positioning
the Android omnibox view computes and caches a horizontal scroll offset (mPreviousScrollResultXPosition) so the URL is aligned to show the effective TLD/origin, and reuses the cached value when text and view metrics are unchanged.
`originChanged` signal
a flag passed into the scroll request indicating the displayed URL’s origin changed (e.g., a cross-origin navigation), which must force a recompute rather than reuse of a cached scroll offset.
Deferred (pending) scroll
when layout is not ready (isLayoutRequested() or getLayout() == null), the scroll is postponed via mPendingScroll and later re-run from layout(), which carries no origin-change context of its own.
`mOriginEndIndex` / `mPreviousScrollOriginEndIndex`
the current index at which the origin ends versus the index the cached scroll position was computed for; equality is required for a cached position to be valid.

Root Cause Analysis

In the scroll-request path, when mPendingScroll became true the method returned early without preserving originChanged, so the later deferred scroll run from layout() had no knowledge that the origin had changed. When the deferred scroll executed, the reuse condition (matching scroll type, font size, RTL, and isVisibleTextTheSame(text)) could be satisfied and the code would scrollTo(mPreviousScrollResultXPosition, 0), applying the scroll offset computed for the previous origin to the new one. This violated the invariant that a cached scroll offset is only valid for the origin it was computed for, letting the URL bar keep the previous origin’s alignment across a navigation to a different origin.

The fix latches the dropped signal into a new field mPendingScrollOriginChanged on the deferred path and ORs it back into originChanged (then clears it) when the scroll actually runs, and it also clears the latch on focus. As a defense-in-depth for callers lacking origin-change context, the reuse condition additionally requires mOriginEndIndex == mPreviousScrollOriginEndIndex, so a position computed for an origin ending at a different index is never reused.

Key insight
The core mistake was discarding the originChanged signal when a scroll had to be deferred, so the deferred run reused a scroll offset computed for the prior origin; the fix latches the signal in mPendingScrollOriginChanged and additionally gates cache reuse on mOriginEndIndex == mPreviousScrollOriginEndIndex.

Attack Path

  1. Prime a cached scroll for origin A A page causes the URL bar to display and compute a scroll offset for an initial origin (e.g., a short domain), caching mPreviousScrollResultXPosition and mPreviousScrollOriginEndIndex.
  2. Navigate to origin B while layout is pending Trigger a cross-origin navigation whose text change requests a new layout pass (e.g., a wrap_content view), so the scroll request is deferred with mPendingScroll = true and, pre-patch, originChanged is dropped.
  3. Deferred scroll reuses stale offset When layout() re-runs the scroll, the origin-change signal is absent and the reuse condition holds, so the URL bar applies origin A’s scroll offset to origin B’s text via scrollTo(...).
  4. Outcome: origin misrepresentation The omnibox shows the new origin scrolled/aligned as the previous one, misleading the user about the site they are on (URL spoofing); no memory-safety primitive is involved.

Impact Assessment

An attacker controlling navigation timing can cause the Android URL bar to retain the previous origin’s scroll alignment after navigating to a different origin, producing a UI misrepresentation that can hide or misstate the true origin in the omnibox. The advisory classifies this as UI misrepresentation (spoofing) in Omnibox and reports no exploited-in-the-wild status; impact is confined to displayed URL presentation on Android.

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
modified

Files Changed

  • chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
  • chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarUnitTest.java

Audit Directions

  • Signals dropped across deferral/async boundaries
    Look for state (like originChanged) computed at request time but not carried into a deferred/posted continuation that later makes a security- or presentation-relevant decision.
  • Cache-validity keys that omit an identity field
    Audit position/metric caches whose reuse condition checks appearance (visible text, size, RTL) but not the identity the value was computed for (here, origin end index).
  • Omnibox/URL presentation across navigations
    Review other places where scroll offset, elision, or highlight state can survive a cross-origin navigation and misrepresent the displayed origin.
From 82b00091f852d33b3d6c7e16721136d5ba4b3590 Mon Sep 17 00:00:00 2001
From: Yoshisato Yanagisawa <[email protected]>
Date: Fri, 18 Sep 2026 12:13:51 -0700
Subject: [PATCH] Preserve origin changed state for deferred URL bar scroll requests

UrlBar reuses previously computed scroll positions when the current
text extends visible text and view metrics remain unchanged. When a
scroll update is deferred waiting for a pending layout pass, the
signal indicating an origin change was dropped because the deferred
invocation does not retain origin change information. As a result, the
URL bar could retain the previous scroll offset across navigations to
a different origin.

Latch the origin change signal for deferred scroll requests so it is
preserved and consumed when the deferred scroll runs. Additionally,
require the origin end index to match the index of the cached position
before reusing it, ensuring callers without origin change context do
not apply a scroll offset computed for a different origin.

Fixed: 551668264
Change-Id: I0c97c8ae43eb54fdf9a6f8c28fe5a6ba87f5af1d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8423533
Auto-Submit: Tomasz Wiszkowski <[email protected]>
Reviewed-by: Sky Malice <[email protected]>
Commit-Queue: Tomasz Wiszkowski <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1701008}
---
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
index e571254..ec38c02 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
@@ -151,6 +151,11 @@
 
     private boolean mPendingScroll;
 
+    // Whether an origin change was reported for a scroll request that had to be deferred until
+    // the next layout pass. The deferred scroll performed from layout() has no origin change
+    // information of its own, so the signal is latched here and consumed when the scroll runs.
+    private boolean mPendingScrollOriginChanged;
+
     // Captures the current intended text scroll type.
     // This may not be effective if mPendingScroll is true.
     @ScrollType private int mCurrentScrollType;
@@ -474,6 +479,7 @@
 
         if (focused) {
             mPendingScroll = false;
+            mPendingScrollOriginChanged = false;
         }
         fixupTextDirection();
 
@@ -1308,7 +1314,12 @@
         // Request scroll update in case scroll type or view dimensions have changed.
         mCurrentScrollType = scrollType;
         mPendingScroll = isLayoutRequested() || (getLayout() == null);
-        if (mPendingScroll) return;
+        if (mPendingScroll) {
+            mPendingScrollOriginChanged |= originChanged;
+            return;
+        }
+        originChanged |= mPendingScrollOriginChanged;
+        mPendingScrollOriginChanged = false;
 
         if (mFocused) return;
 
@@ -1337,6 +1348,9 @@
                 // therefore false negative using regular equality is unlikely.
                 && currentTextSize == mPreviousScrollFontSize
                 && currentIsRtl == mPreviousScrollWasRtl
+                // A previously computed scroll position is only valid for text whose origin ends
+                // at the same index the position was computed for.
+                && mOriginEndIndex == mPreviousScrollOriginEndIndex
                 && isVisibleTextTheSame(text)) {
             scrollTo(mPreviousScrollResultXPosition, 0);
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarUnitTest.java
index b4ac034a7..d15cecc 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarUnitTest.java
@@ -977,9 +977,7 @@
         assertEquals(0, mUrlBar.getSelectionEnd());
     }
 
-    @Test
-    public void scrollWhenOriginChanges() {
-        // Initialize the URL bar. Verify test conditions.
+    private void setupInitialDomainScroll() {
         mUrlBar.setText(SHORT_DOMAIN);
         mUrlBar.setScrollState(
                 UrlBar.ScrollType.SCROLL_TO_TLD,
@@ -989,6 +987,11 @@
         assertFalse(mUrlBar.hasPendingDisplayTextScrollForTesting());
         verify(mUrlBar).scrollToTLD();
         mUrlBar.setVisibleTextPrefixHintForTesting(SHORT_DOMAIN);
+    }
+
+    @Test
+    public void scrollWhenOriginChanges() {
+        setupInitialDomainScroll();
 
         mUrlBar.setText(SHORT_SUBDOMAIN);
         mUrlBar.setScrollState(
@@ -999,6 +1002,42 @@
     }
 
     @Test
+    public void scrollWhenOriginChanges_scrollDeferredUntilLayout() {
+        setupInitialDomainScroll();
+
+        // Simulate a text change that requests a new layout pass (e.g. a wrap_content view),
+        // deferring the scroll request until the pass completes.
+        mUrlBar.setText(SHORT_SUBDOMAIN);
+        mUrlBar.requestLayout();
+        mUrlBar.setScrollState(
+                UrlBar.ScrollType.SCROLL_TO_TLD,
+                /* scrollToIndex= */ SHORT_SUBDOMAIN.length(),
+                /* originChanged= */ true);
+        assertTrue(mUrlBar.hasPendingDisplayTextScrollForTesting());
+        verify(mUrlBar).scrollToTLD();
+
+        // The deferred scroll must honor the origin change and recompute the scroll position
+        // rather than restore the position computed for the previous origin.
+        measureAndLayoutUrlBar();
+        assertFalse(mUrlBar.hasPendingDisplayTextScrollForTesting());
+        verify(mUrlBar, times(2)).scrollToTLD();
+    }
+
+    @Test
+    public void scrollWhenOriginEndIndexChanges() {
+        setupInitialDomainScroll();
+
+        // Even without the origin change signal, a scroll position computed for an origin that
+        // ended at a different index must not be reused.
+        mUrlBar.setText(SHORT_SUBDOMAIN);
+        mUrlBar.setScrollState(
+                UrlBar.ScrollType.SCROLL_TO_TLD,
+                /* scrollToIndex= */ SHORT_SUBDOMAIN.length(),
+                /* originChanged= */ false);
+        verify(mUrlBar, times(2)).scrollToTLD();
+    }
+
+    @Test
     @EnableFeatures(ChromeFeatureList.ANDROID_NO_VISIBLE_HINT_FOR_DIFFERENT_TLD)
     public void scrollToTLD_sameTLD_calculateVisibleHint() {
         doReturn(mLayout).when(mUrlBar).getLayout();
Loading diff…