CVE-2025-12729
Overview
Background
- Browser controls
- on Android, the top and bottom browser UI (toolbar/omnibox and bottom controls) that slide in and out as the page scrolls; their position is described by a height and a shown ratio (0 = fully hidden, 1 = fully shown).
- `RenderWidgetHostViewAndroid::UpdateControls`
- the browser-process method that receives controls heights, shown ratios and min-height offsets, compares them with cached
prev_*values, and returns whether anything changed so that a browser-controls update gets pushed. - `prev_bottom_shown_pix_`
- the cached visible bottom-controls height in pixels (
bottom_controls_pix * bottom_controls_shown_ratio), which was previously the only bottom-controls value used for change detection. - `cc::MathUtil::IsFloatNearlyTheSame`
- a float comparison with tolerance, used here to decide whether a cached value has changed.
Root Cause Analysis
The enclosing function name is not in the hunk, but the regression test calls rwhva->UpdateControls(...) and the hunk ends with return top_changed || bottom_changed;, so the method is RenderWidgetHostViewAndroid::UpdateControls. In that method, bottom_changed was computed only by comparing bottom_shown_pix (height × ratio) with prev_bottom_shown_pix_. When the shown ratio is 0, bottom_shown_pix is 0 for any height. A change in bottom_controls_pix therefore produced no detected change, the method returned false, and no update was pushed. Per the commit message, the bottom-controls offset should become the new height in that case; without the update, the offset stayed stale and the controls were left partially visible.
The fix adds prev_bottom_controls_pix_ (initialised to 0.f in the constructor, stored at the end of UpdateControls) and ORs a height-changed comparison into bottom_changed. This matches how top controls already worked and tightens change detection.
Attack Path
- Bottom controls fully hidden On Android, the page scrolls so the bottom browser controls reach a 0% shown ratio.
- Bottom controls height changes
While hidden, the bottom-controls height changes (test step 7 changes it from
50.fto60.fat ratio0.0f). The diff does not show what triggers the height change in practice. - Update suppressed
Before the fix,
bottom_shown_pixstayed 0,bottom_changedwas false,UpdateControlsreturnedfalse, and no browser-controls update was pushed. - Controls left partially visible (inferred)
With an offset computed for the old height, part of the bottom browser UI (which can contain omnibox elements) stays on screen, misaligned with or overlapping page content. A page could combine this with its own content to confuse the user about browser UI
(inferred).
Impact Assessment
(inferred). The vendor rates this medium (“Inappropriate implementation in Omnibox”). The provided metadata does not say whether it was exploited in the wild.Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fcontent/browser/renderer_host/render_widget_host_view_android_unittest.cc |
modified | |
RenderWidgetHostViewAndroidScalingTestcontent/browser/renderer_host/render_widget_host_view_android_unittest.cc |
modified |
Files Changed
content/browser/renderer_host/render_widget_host_view_android.cccontent/browser/renderer_host/render_widget_host_view_android.hcontent/browser/renderer_host/render_widget_host_view_android_unittest.cc
Audit Directions
- Change detection on derived productsLook for “did it change?” checks that compare a product or ratio (height × ratio, size × scale) and so miss changes in one factor when the other is 0. Compare the raw inputs as well.
- Top/bottom asymmetryWhere top controls and bottom controls have parallel logic (offsets, min-heights, caches), diff the two paths for fields one side tracks and the other does not.
- Partially visible browser chromeAudit any path where a suppressed or skipped layout update could leave the omnibox, security indicators or bottom controls half-rendered over web content.
Patch
From e81ab324244b7c87c500cc0dc022c40089f86bee Mon Sep 17 00:00:00 2001 From: Patrick Noland <[email protected]> Date: Fri, 24 Oct 2025 09:00:23 -0700 Subject: [PATCH] Push browser controls update whenever bottom controls height changes The old logic failed to push updates when height changed at 0% shown ratio. This was a problem because the offset needs to change when the height changes at 0% to reflect the new height (i.e. the offset should become the new height) to avoid leaving the controls partially visible. The new logic matches existing logic for top controls. Bug: 454354281 Change-Id: I733d103e8cda58a4dbeefe1fa71282924f7bcd27 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7081525 Commit-Queue: Patrick Noland <[email protected]> Reviewed-by: Nasko Oskov <[email protected]> Cr-Commit-Position: refs/heads/main@{#1535059} --- diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc index 5b904f6..f91c4c48 100644 --- a/content/browser/renderer_host/render_widget_host_view_android.cc +++ b/content/browser/renderer_host/render_widget_host_view_android.cc @@ -668,6 +668,7 @@ prev_top_controls_translate_(0.f), prev_top_controls_min_height_offset_pix_(0.f), prev_bottom_shown_pix_(0.f), + prev_bottom_controls_pix_(0.f), prev_bottom_controls_translate_(0.f), prev_bottom_controls_min_height_offset_pix_(0.f), page_scale_(1.f), @@ -2253,6 +2254,8 @@ float bottom_shown_pix = bottom_controls_pix * bottom_controls_shown_ratio; bool bottom_changed = !cc::MathUtil::IsFloatNearlyTheSame( bottom_shown_pix, prev_bottom_shown_pix_); + bottom_changed |= !cc::MathUtil::IsFloatNearlyTheSame( + bottom_controls_pix, prev_bottom_controls_pix_); float bottom_translate = bottom_controls_pix - bottom_shown_pix; float bottom_min_height_offset_pix = bottom_controls_min_height_offset; @@ -2268,6 +2271,7 @@ prev_bottom_shown_pix_ = bottom_shown_pix; prev_bottom_controls_translate_ = bottom_translate; prev_bottom_controls_min_height_offset_pix_ = bottom_min_height_offset_pix; + prev_bottom_controls_pix_ = bottom_controls_pix; controls_initialized_ = true; return top_changed || bottom_changed; } diff --git a/content/browser/renderer_host/render_widget_host_view_android.h b/content/browser/renderer_host/render_widget_host_view_android.h index baa9346..7e1cbb1 100644 --- a/content/browser/renderer_host/render_widget_host_view_android.h +++ b/content/browser/renderer_host/render_widget_host_view_android.h @@ -539,6 +539,7 @@ FRIEND_TEST_ALL_PREFIXES(SitePerProcessBrowserTest, GestureManagerListensToChildFrames); FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAndroidTest, DisplayFeature); + FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAndroidTest, UpdateControls); FRIEND_TEST_ALL_PREFIXES(RenderWidgetHostViewAndroidFluidResizeBrowserTest, ResizeDefersSynchronizationToNextFrame); @@ -763,6 +764,7 @@ float prev_top_controls_translate_; float prev_top_controls_min_height_offset_pix_; float prev_bottom_shown_pix_; + float prev_bottom_controls_pix_; float prev_bottom_controls_translate_; float prev_bottom_controls_min_height_offset_pix_; float page_scale_; diff --git a/content/browser/renderer_host/render_widget_host_view_android_unittest.cc b/content/browser/renderer_host/render_widget_host_view_android_unittest.cc index 4a243d7..f53031d 100644 --- a/content/browser/renderer_host/render_widget_host_view_android_unittest.cc +++ b/content/browser/renderer_host/render_widget_host_view_android_unittest.cc @@ -667,6 +667,85 @@ base::RunLoop().RunUntilIdle(); } +TEST_F(RenderWidgetHostViewAndroidTest, UpdateControls) { + float dip_scale = 1.0f; + float top_height = 90.f; + float top_ratio = 1.f; + float top_min_height = 0.f; + float bottom_height = 50.f; + float bottom_ratio = 1.f; + float bottom_min_height = 0.f; + + // Get the test view instance from the fixture. + RenderWidgetHostViewAndroid* rwhva = render_widget_host_view_android(); + + // 1. First call should return true as controls are uninitialized. + EXPECT_TRUE(rwhva->UpdateControls(dip_scale, top_height, top_ratio, + top_min_height, bottom_height, bottom_ratio, + bottom_min_height)); + + EXPECT_FALSE(rwhva->UpdateControls(dip_scale, top_height, top_ratio, + top_min_height, bottom_height, + bottom_ratio, bottom_min_height)); + + // 3. Change top_controls_height. + float new_top_height = 100.f; + EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, top_ratio, + top_min_height, bottom_height, bottom_ratio, + bottom_min_height)); + // Call again with same values, should return false. + EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, top_ratio, + top_min_height, bottom_height, + bottom_ratio, bottom_min_height)); + + // 4. Change top_controls_shown_ratio. + float new_top_ratio = 0.5f; + EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio, + top_min_height, bottom_height, bottom_ratio, + bottom_min_height)); + EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio, + top_min_height, bottom_height, + bottom_ratio, bottom_min_height)); + + // 5. Change top_controls_min_height_offset. + float new_top_min_height = 10.f; + EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio, + new_top_min_height, bottom_height, + bottom_ratio, bottom_min_height)); + // Call again with same values, should return false. + EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio, + new_top_min_height, bottom_height, + bottom_ratio, bottom_min_height)); + + // 6. Change bottom_controls_shown_ratio. + float new_bottom_ratio = 0.0f; + EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio, + new_top_min_height, bottom_height, + new_bottom_ratio, bottom_min_height)); + + EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio, + new_top_min_height, bottom_height, + new_bottom_ratio, bottom_min_height)); + + // 7. Change bottom_controls_height while at 0% shown ratio. + float new_bottom_height = 60.f; + EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio, + new_top_min_height, new_bottom_height, + new_bottom_ratio, bottom_min_height)); + EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio, + new_top_min_height, new_bottom_height, + new_bottom_ratio, bottom_min_height)); + + // 8. Change bottom_controls_min_height_offset. + float new_bottom_min_height = 10.f; + EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio, + new_top_min_height, new_bottom_height, + new_bottom_ratio, new_bottom_min_height)); + EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio, + new_top_min_height, new_bottom_height, + new_bottom_ratio, new_bottom_min_height)); +} + // Test for scaling. class RenderWidgetHostViewAndroidScalingTest : public RenderWidgetHostViewAndroidTest {
Regression Test / PoC
diff --git a/content/browser/renderer_host/render_widget_host_view_android_unittest.cc b/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
index 4a243d7..f53031d 100644
--- a/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
@@ -667,6 +667,85 @@
base::RunLoop().RunUntilIdle();
}
+TEST_F(RenderWidgetHostViewAndroidTest, UpdateControls) {
+ float dip_scale = 1.0f;
+ float top_height = 90.f;
+ float top_ratio = 1.f;
+ float top_min_height = 0.f;
+ float bottom_height = 50.f;
+ float bottom_ratio = 1.f;
+ float bottom_min_height = 0.f;
+
+ // Get the test view instance from the fixture.
+ RenderWidgetHostViewAndroid* rwhva = render_widget_host_view_android();
+
+ // 1. First call should return true as controls are uninitialized.
+ EXPECT_TRUE(rwhva->UpdateControls(dip_scale, top_height, top_ratio,
+ top_min_height, bottom_height, bottom_ratio,
+ bottom_min_height));
+
+ EXPECT_FALSE(rwhva->UpdateControls(dip_scale, top_height, top_ratio,
+ top_min_height, bottom_height,
+ bottom_ratio, bottom_min_height));
+
+ // 3. Change top_controls_height.
+ float new_top_height = 100.f;
+ EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, top_ratio,
+ top_min_height, bottom_height, bottom_ratio,
+ bottom_min_height));
+ // Call again with same values, should return false.
+ EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, top_ratio,
+ top_min_height, bottom_height,
+ bottom_ratio, bottom_min_height));
+
+ // 4. Change top_controls_shown_ratio.
+ float new_top_ratio = 0.5f;
+ EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio,
+ top_min_height, bottom_height, bottom_ratio,
+ bottom_min_height));
+ EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio,
+ top_min_height, bottom_height,
+ bottom_ratio, bottom_min_height));
+
+ // 5. Change top_controls_min_height_offset.
+ float new_top_min_height = 10.f;
+ EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio,
+ new_top_min_height, bottom_height,
+ bottom_ratio, bottom_min_height));
+ // Call again with same values, should return false.
+ EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio,
+ new_top_min_height, bottom_height,
+ bottom_ratio, bottom_min_height));
+
+ // 6. Change bottom_controls_shown_ratio.
+ float new_bottom_ratio = 0.0f;
+ EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio,
+ new_top_min_height, bottom_height,
+ new_bottom_ratio, bottom_min_height));
+
+ EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio,
+ new_top_min_height, bottom_height,
+ new_bottom_ratio, bottom_min_height));
+
+ // 7. Change bottom_controls_height while at 0% shown ratio.
+ float new_bottom_height = 60.f;
+ EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio,
+ new_top_min_height, new_bottom_height,
+ new_bottom_ratio, bottom_min_height));
+ EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio,
+ new_top_min_height, new_bottom_height,
+ new_bottom_ratio, bottom_min_height));
+
+ // 8. Change bottom_controls_min_height_offset.
+ float new_bottom_min_height = 10.f;
+ EXPECT_TRUE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio,
+ new_top_min_height, new_bottom_height,
+ new_bottom_ratio, new_bottom_min_height));
+ EXPECT_FALSE(rwhva->UpdateControls(dip_scale, new_top_height, new_top_ratio,
+ new_top_min_height, new_bottom_height,
+ new_bottom_ratio, new_bottom_min_height));
+}
+
// Test for scaling.
class RenderWidgetHostViewAndroidScalingTest
: public RenderWidgetHostViewAndroidTest {
Original Bug Report
Chrome on Android: spoofing issue caused by bottom address bar
Steps to reproduce the problem
(Similar to issue issue 437147699).
- Open index.html or https://lbstyle.github.io/bin.html in an incognito window (to reproduce constantly).
- Tap the “Click” button.
- Tap inside the first input field and wait.
- When the alert pops up, tap OK.
Problem Description
The address bar (omnibox) shows google.com, but the page content is incorrect.
Summary
Chrome on Android: spoofing issue caused by bottom address bar
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \