Chrome · Select
CVE-2026-14077
Logic Error in Select
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
Elementthird_party/blink/renderer/core/html/forms/external_popup_menu.h |
modified | |
HTMLSelectElementthird_party/blink/renderer/core/html/forms/external_popup_menu.h |
modified | |
LocalFramethird_party/blink/renderer/core/html/forms/external_popup_menu.h |
modified | |
WebMouseEventthird_party/blink/renderer/core/html/forms/external_popup_menu.h |
modified | |
TEST_Fthird_party/blink/renderer/core/html/forms/external_popup_menu_test.cc |
modified | |
forthird_party/blink/renderer/core/html/forms/external_popup_menu_test.cc |
modified | |
ifthird_party/blink/renderer/core/html/forms/external_popup_menu_test.cc |
modified |
Files Changed
third_party/blink/renderer/core/html/forms/external_popup_menu.ccthird_party/blink/renderer/core/html/forms/external_popup_menu.hthird_party/blink/renderer/core/html/forms/external_popup_menu_test.cc
Patch
From 9dd7d49061ff6271c74f4dba9d90e11ae2a3dafc Mon Sep 17 00:00:00 2001 From: Joey Arhar <[email protected]> Date: Mon, 01 Jun 2026 13:20:11 -0700 Subject: [PATCH] Make select popup anchor stay within web contents This prevents the popup from being rendered entirely outside of the web contents which could overlap the browser ui. Fixed: 511869411 Change-Id: I239e1ff4d7212666153d432e3c10e18ba860dbec Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7876426 Reviewed-by: Joey Arhar <[email protected]> Reviewed-by: Mason Freed <[email protected]> Commit-Queue: Joey Arhar <[email protected]> Cr-Commit-Position: refs/heads/main@{#1639621} --- diff --git a/third_party/blink/renderer/core/html/forms/external_popup_menu.cc b/third_party/blink/renderer/core/html/forms/external_popup_menu.cc index 63acc60..211da4a 100644 --- a/third_party/blink/renderer/core/html/forms/external_popup_menu.cc +++ b/third_party/blink/renderer/core/html/forms/external_popup_menu.cc @@ -42,6 +42,7 @@ #include "third_party/blink/renderer/core/exported/web_view_impl.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" +#include "third_party/blink/renderer/core/frame/visual_viewport.h" #include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h" #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" #include "third_party/blink/renderer/core/html/forms/html_opt_group_element.h" @@ -59,9 +60,8 @@ namespace blink { -namespace { - -float GetDprForSizeAdjustment(const Element& owner_element) { +// static +float ExternalPopupMenu::GetDprForSizeAdjustment(const Element& owner_element) { float dpr = 1.0f; // Android doesn't need these adjustments and it makes tests fail. #ifndef OS_ANDROID @@ -83,8 +83,6 @@ return dpr; } -} // namespace - ExternalPopupMenu::ExternalPopupMenu(LocalFrame& frame, HTMLSelectElement& owner_element) : owner_element_(owner_element), @@ -142,6 +140,22 @@ rect_in_viewport = gfx::ScaleToRoundedRect(rect_in_viewport, 1 / dpr); } + // Adjust anchor position to stay within web contents, otherwise the popup + // could be rendered entirely outside of the web contents. If this select + // is in a cross-origin iframe, then the anchor will be confined to the + // bounds of the iframe rather than the entire web contents. + if (RuntimeEnabledFeatures::SelectAnchorInViewportEnabled() && + local_frame_->GetPage()) { + gfx::Rect viewport_rect( + local_frame_->GetPage()->GetVisualViewport().Size()); + // rect_in_viewport should always overlap with viewport_rect. If the + // select element is positioned outside of the viewport, then + // MenuListSelectType::ShowPopup has an early return which prevents us + // from getting here. + CHECK(viewport_rect.Intersects(rect_in_viewport)); + rect_in_viewport.Intersect(viewport_rect); + } + gfx::Rect bounds = gfx::Rect(rect_in_viewport.x() * scale_for_emulation, rect_in_viewport.y() * scale_for_emulation, diff --git a/third_party/blink/renderer/core/html/forms/external_popup_menu.h b/third_party/blink/renderer/core/html/forms/external_popup_menu.h index b74da76..e520025a 100644 --- a/third_party/blink/renderer/core/html/forms/external_popup_menu.h +++ b/third_party/blink/renderer/core/html/forms/external_popup_menu.h @@ -42,6 +42,7 @@ namespace blink { +class Element; class HTMLSelectElement; class LocalFrame; class WebMouseEvent; @@ -67,6 +68,7 @@ bool* allow_multiple_selection); static int ToPopupMenuItemIndex(int index, HTMLSelectElement&); static int ToExternalPopupMenuItemIndex(int index, HTMLSelectElement&); + static float GetDprForSizeAdjustment(const Element& owner_element); void Trace(Visitor*) const override; diff --git a/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc b/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc index c74a437..ea628c2d 100644 --- a/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc +++ b/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc @@ -6,6 +6,7 @@ #include <memory> +#include "base/strings/stringprintf.h" #include "content/test/test_blink_web_unit_test_support.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/mojom/choosers/popup_menu.mojom-blink.h" @@ -348,6 +349,104 @@ EXPECT_EQ(expected_y, ShownBounds().y()); } +TEST_F(ExternalPopupMenuTest, PopupClippedToViewportVariations) { + struct TestCase { + int left; + int top; + int width; + int height; + float dpr; + bool expect_shown; + int expected_x; + int expected_y; + int expected_width; + int expected_height; + } test_cases[] = { + // Top left partial + {-50, -50, 200, 200, 1.0f, true, 0, 0, 150, 150}, + // Top left partial with DPR +// The android differences here and below correspond to the OS_ANDROID check in +// ExternalPopupMenu::GetDprForSizeAdjustment. +#ifdef OS_ANDROID + {-50, -50, 200, 200, 2.0f, true, 0, 0, 300, 300}, +#else + {-50, -50, 200, 200, 2.0f, true, 0, 0, 150, 150}, +#endif + // Top left complete + {-250, -250, 200, 200, 1.0f, false, 0, 0, 0, 0}, + // Top left complete with DPR + {-250, -250, 200, 200, 2.0f, false, 0, 0, 0, 0}, + // Bottom right partial + {750, 550, 200, 200, 1.0f, true, 750, 550, 50, 50}, + // Bottom right partial with DPR +#ifdef OS_ANDROID + {750, 550, 200, 200, 2.0f, true, 1500, 1100, 100, 100}, +#else + {750, 550, 200, 200, 2.0f, true, 750, 550, 200, 200}, +#endif + // Bottom right complete + {850, 650, 200, 200, 1.0f, false, 0, 0, 0, 0}, + // Bottom right complete with DPR + {850, 650, 200, 200, 2.0f, false, 0, 0, 0, 0}, + }; + + for (const auto& test_case : test_cases) { + SCOPED_TRACE(testing::Message() + << "left: " << test_case.left << ", top: " << test_case.top + << ", dpr: " << test_case.dpr); + + WebView()->MainFrameWidget()->SetDeviceScaleFactorForTesting(test_case.dpr); + + frame_test_helpers::LoadHTMLString( + MainFrame(), + base::StringPrintf(R"HTML( + <!DOCTYPE html> + <html> + <style> + body { margin: 0; } + select { + position: fixed; + left: %dpx; + top: %dpx; + width: %dpx; + height: %dpx; + } + </style> + <body> + <select id=select> + <option>option</option> + </select> + </body> + </html> + )HTML", + test_case.left, test_case.top, test_case.width, test_case.height), + url_test_helpers::ToKURL("http://www.test.com/")); + + WebView()->MainFrameViewWidget()->Resize( + gfx::Size(800 * test_case.dpr, 600 * test_case.dpr)); + WebView()->MainFrameWidget()->UpdateAllLifecyclePhases( + DocumentUpdateReason::kTest); + + auto* select = To<HTMLSelectElement>( + MainFrame()->GetFrame()->GetDocument()->getElementById( + AtomicString("select"))); + auto* layout_object = select->GetLayoutObject(); + ASSERT_TRUE(layout_object); + + select->ShowPopup(); + + if (test_case.expect_shown) { + WaitUntilShowedPopup(); + EXPECT_EQ(test_case.expected_x, ShownBounds().x()); + EXPECT_EQ(test_case.expected_y, ShownBounds().y()); + EXPECT_EQ(test_case.expected_width, ShownBounds().width());
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc b/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc
index c74a437..ea628c2d 100644
--- a/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc
+++ b/third_party/blink/renderer/core/html/forms/external_popup_menu_test.cc
@@ -6,6 +6,7 @@
#include <memory>
+#include "base/strings/stringprintf.h"
#include "content/test/test_blink_web_unit_test_support.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/choosers/popup_menu.mojom-blink.h"
@@ -348,6 +349,104 @@
EXPECT_EQ(expected_y, ShownBounds().y());
}
+TEST_F(ExternalPopupMenuTest, PopupClippedToViewportVariations) {
+ struct TestCase {
+ int left;
+ int top;
+ int width;
+ int height;
+ float dpr;
+ bool expect_shown;
+ int expected_x;
+ int expected_y;
+ int expected_width;
+ int expected_height;
+ } test_cases[] = {
+ // Top left partial
+ {-50, -50, 200, 200, 1.0f, true, 0, 0, 150, 150},
+ // Top left partial with DPR
+// The android differences here and below correspond to the OS_ANDROID check in
+// ExternalPopupMenu::GetDprForSizeAdjustment.
+#ifdef OS_ANDROID
+ {-50, -50, 200, 200, 2.0f, true, 0, 0, 300, 300},
+#else
+ {-50, -50, 200, 200, 2.0f, true, 0, 0, 150, 150},
+#endif
+ // Top left complete
+ {-250, -250, 200, 200, 1.0f, false, 0, 0, 0, 0},
+ // Top left complete with DPR
+ {-250, -250, 200, 200, 2.0f, false, 0, 0, 0, 0},
+ // Bottom right partial
+ {750, 550, 200, 200, 1.0f, true, 750, 550, 50, 50},
+ // Bottom right partial with DPR
+#ifdef OS_ANDROID
+ {750, 550, 200, 200, 2.0f, true, 1500, 1100, 100, 100},
+#else
+ {750, 550, 200, 200, 2.0f, true, 750, 550, 200, 200},
+#endif
+ // Bottom right complete
+ {850, 650, 200, 200, 1.0f, false, 0, 0, 0, 0},
+ // Bottom right complete with DPR
+ {850, 650, 200, 200, 2.0f, false, 0, 0, 0, 0},
+ };
+
+ for (const auto& test_case : test_cases) {
+ SCOPED_TRACE(testing::Message()
+ << "left: " << test_case.left << ", top: " << test_case.top
+ << ", dpr: " << test_case.dpr);
+
+ WebView()->MainFrameWidget()->SetDeviceScaleFactorForTesting(test_case.dpr);
+
+ frame_test_helpers::LoadHTMLString(
+ MainFrame(),
+ base::StringPrintf(R"HTML(
+ <!DOCTYPE html>
+ <html>
+ <style>
+ body { margin: 0; }
+ select {
+ position: fixed;
+ left: %dpx;
+ top: %dpx;
+ width: %dpx;
+ height: %dpx;
+ }
+ </style>
+ <body>
+ <select id=select>
+ <option>option</option>
+ </select>
+ </body>
+ </html>
+ )HTML",
+ test_case.left, test_case.top, test_case.width, test_case.height),
+ url_test_helpers::ToKURL("http://www.test.com/"));
+
+ WebView()->MainFrameViewWidget()->Resize(
+ gfx::Size(800 * test_case.dpr, 600 * test_case.dpr));
+ WebView()->MainFrameWidget()->UpdateAllLifecyclePhases(
+ DocumentUpdateReason::kTest);
+
+ auto* select = To<HTMLSelectElement>(
+ MainFrame()->GetFrame()->GetDocument()->getElementById(
+ AtomicString("select")));
+ auto* layout_object = select->GetLayoutObject();
+ ASSERT_TRUE(layout_object);
+
+ select->ShowPopup();
+
+ if (test_case.expect_shown) {
+ WaitUntilShowedPopup();
+ EXPECT_EQ(test_case.expected_x, ShownBounds().x());
+ EXPECT_EQ(test_case.expected_y, ShownBounds().y());
+ EXPECT_EQ(test_case.expected_width, ShownBounds().width());
+ EXPECT_EQ(test_case.expected_height, ShownBounds().height());
+ } else {
+ EXPECT_FALSE(select->PopupIsVisible());
+ }
+ }
+}
+
// Android doesn't use this position data and we don't adjust it for DPR there..
#ifdef OS_ANDROID
#define MAYBE_PopupAccountsForDeviceScaleFactor \
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page