CVE-2026-5878
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/web_contents/web_contents_impl.cc |
modified | |
SelectPickerBrowserTestcontent/browser/web_contents/web_contents_impl_browsertest.cc |
modified | |
SelectPickerBrowserTestcontent/browser/web_contents/web_contents_impl_browsertest.cc |
modified | |
IN_PROC_BROWSER_TEST_Fcontent/browser/web_contents/web_contents_impl_browsertest.cc |
modified | |
WebContentsImplInsecureLocalhostBrowserTestcontent/browser/web_contents/web_contents_impl_browsertest.cc |
modified | |
CommandLinecontent/common/content_switches_internal.h |
modified | |
ifcontent/public/test/browser_test_base.cc |
modified |
Files Changed
content/browser/web_contents/web_contents_impl.cccontent/browser/web_contents/web_contents_impl.hcontent/browser/web_contents/web_contents_impl_browsertest.cccontent/common/content_switches_internal.cccontent/common/content_switches_internal.hcontent/public/test/browser_test_base.cccontent/renderer/render_thread_impl.cc
Patch
From 9a7e491a5c91e9a42b78e2ac3fc444c534ba2456 Mon Sep 17 00:00:00 2001 From: Mason Freed <[email protected]> Date: Mon, 04 Nov 2024 17:33:17 +0000 Subject: [PATCH] Don't show popups (e.g. <select>) if the tab isn't focused It was previously possible for pickers like <select>'s picker to be shown on top of the not-currently-focused tab, confusing the user. With this change, the select picker must be the currently-focused tab for the picker to be opened. This is akin to existing protections for the tab being visible. Fixed: 365089001 Change-Id: Id2b15f5d310cce877341d7e9d5a5c5d6da882887 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5909884 Reviewed-by: Alexander Timin <[email protected]> Commit-Queue: Mason Freed <[email protected]> Cr-Commit-Position: refs/heads/main@{#1377731} --- diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index a5a0da2..9fba923 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -5037,6 +5037,13 @@ // Don't create popups for hidden tabs. http://crbug.com/1521345 return nullptr; } + const bool ignore_focus_state_for_testing = + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisablePopupFocusedWindowDetectionForTesting); + if (!web_contents_has_focus_ && !ignore_focus_state_for_testing) { + // Don't create popups for non-focused tabs. http://crbug.com/365089001 + return nullptr; + } RenderWidgetHostImpl* widget_host = RenderWidgetHostFactory::CreateSelfOwned( &primary_frame_tree_, this, site_instance_group, route_id, IsHidden()); @@ -6314,6 +6321,7 @@ RenderWidgetHost* render_widget_host) { OPTIONAL_TRACE_EVENT1("content", "WebContentsImpl::NotifyWebContentsFocused", "render_widget_host", render_widget_host); + web_contents_has_focus_ = true; observers_.NotifyObservers(&WebContentsObserver::OnWebContentsFocused, render_widget_host); } @@ -6323,6 +6331,7 @@ OPTIONAL_TRACE_EVENT1("content", "WebContentsImpl::NotifyWebContentsLostFocus", "render_widget_host", render_widget_host); + web_contents_has_focus_ = false; observers_.NotifyObservers(&WebContentsObserver::OnWebContentsLostFocus, render_widget_host); } diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h index 54a7f9578..881df057 100644 --- a/content/browser/web_contents/web_contents_impl.h +++ b/content/browser/web_contents/web_contents_impl.h @@ -2286,6 +2286,8 @@ std::unique_ptr<ColorChooserHolder> color_chooser_holder_; #endif + bool web_contents_has_focus_{false}; + // All live RenderWidgetHostImpls that are created by this object and may // outlive it. std::set<raw_ptr<RenderWidgetHostImpl, SetExperimental>> created_widgets_; diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc index 950f0b5..29f75ec 100644 --- a/content/browser/web_contents/web_contents_impl_browsertest.cc +++ b/content/browser/web_contents/web_contents_impl_browsertest.cc @@ -50,6 +50,7 @@ #include "content/browser/renderer_host/text_input_manager.h" #include "content/browser/web_contents/web_contents_view.h" #include "content/common/content_navigation_policy.h" +#include "content/common/content_switches_internal.h" #include "content/common/frame.mojom-test-utils.h" #include "content/common/frame.mojom.h" #include "content/public/browser/back_forward_cache.h" @@ -6053,6 +6054,53 @@ waiter.WaitForMediaDestroyed(); } +#if !BUILDFLAG(USE_EXTERNAL_POPUP_MENU) +class SelectPickerBrowserTest : public WebContentsImplBrowserTest { + public: + SelectPickerBrowserTest() { + scoped_feature_list_.InitAndEnableFeature( + blink::features::kCSSPseudoOpenClosed); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +// Tests that popup pickers only open when the webcontents is focused. +IN_PROC_BROWSER_TEST_F(SelectPickerBrowserTest, PickerOnlyOpensWhenFocused) { + base::CommandLine::ForCurrentProcess()->RemoveSwitch( + switches::kDisablePopupFocusedWindowDetectionForTesting); + ASSERT_FALSE(base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisablePopupFocusedWindowDetectionForTesting)); + WebContentsImpl* web_contents = + static_cast<WebContentsImpl*>(shell()->web_contents()); + + GURL url(R"(data:text/html,<select><option>one</option></select>)"); + ASSERT_TRUE(NavigateToURL(shell(), url)); + WaitForLoadStop(web_contents); + + // De-focus the window, which should keep popups from showing. + RenderWidgetHost* rwh = + web_contents->GetPrimaryMainFrame()->GetRenderViewHost()->GetWidget(); + web_contents->NotifyWebContentsLostFocus(rwh); + + // Open the picker - expect it not to open because window is not focused. + ASSERT_TRUE( + ExecJs(web_contents, "document.querySelector('select').showPicker();")); + EXPECT_EQ(false, EvalJs(web_contents, + "document.querySelector('select').matches(':open')")); + + // Now focus the window, which should enable popups. + web_contents->NotifyWebContentsFocused(rwh); + + // Open the picker, and make sure it opened. + ASSERT_TRUE( + ExecJs(web_contents, "document.querySelector('select').showPicker();")); + EXPECT_EQ(true, EvalJs(web_contents, + "document.querySelector('select').matches(':open')")); +} +#endif + class WebContentsImplInsecureLocalhostBrowserTest : public WebContentsImplBrowserTest { protected: diff --git a/content/common/content_switches_internal.cc b/content/common/content_switches_internal.cc index dada3bf..0c461b5 100644 --- a/content/common/content_switches_internal.cc +++ b/content/common/content_switches_internal.cc @@ -37,6 +37,14 @@ #include "base/win/windows_version.h" #endif +namespace switches { +// Disable popup focused-window detection. Done for tests to avoid +// focus-dependent behavior. +const char kDisablePopupFocusedWindowDetectionForTesting[] = + "disable-popup-focused-window-detection"; + +} // namespace switches + namespace content { namespace { diff --git a/content/common/content_switches_internal.h b/content/common/content_switches_internal.h index 1047b3f1..6a5e32564 100644 --- a/content/common/content_switches_internal.h +++ b/content/common/content_switches_internal.h @@ -13,6 +13,11 @@ class CommandLine; } +namespace switches { +CONTENT_EXPORT extern const char + kDisablePopupFocusedWindowDetectionForTesting[]; +} + namespace content { blink::mojom::V8CacheOptions GetV8CacheOptions(); diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc index e297693..4f5787a 100644 --- a/content/public/test/browser_test_base.cc +++ b/content/public/test/browser_test_base.cc @@ -56,6 +56,7 @@ #include "content/browser/tracing/memory_instrumentation_util.h" #include "content/browser/tracing/startup_tracing_controller.h" #include "content/browser/tracing/tracing_controller_impl.h" +#include "content/common/content_switches_internal.h" #include "content/public/app/content_main.h" #include "content/public/app/initialize_mojo_core.h" #include "content/public/browser/browser_main_parts.h" @@ -376,6 +377,11 @@ command_line->AppendSwitch( switches::kDisableBackgroundingOccludedWindowsForTesting); + // The focused state of windows is unpredictable during tests, so disable + // focused window detection for popups when running browser tests. + command_line->AppendSwitch( + switches::kDisablePopupFocusedWindowDetectionForTesting); + if (enable_pixel_output_) { DCHECK(!command_line->HasSwitch(switches::kForceDeviceScaleFactor)) << "--force-device-scale-factor flag already present. Tests using " diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc index e7db0f8a..5ac9504 100644 --- a/content/renderer/render_thread_impl.cc +++ b/content/renderer/render_thread_impl.cc @@ -541,7 +541,7 @@ #if BUILDFLAG(USE_EXTERNAL_POPUP_MENU) // On Mac and Android Java UI, the select popups are rendered by the browser. - blink::WebView::SetUseExternalPopupMenus(true);
Regression Test / PoC
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 950f0b5..29f75ec 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -50,6 +50,7 @@
#include "content/browser/renderer_host/text_input_manager.h"
#include "content/browser/web_contents/web_contents_view.h"
#include "content/common/content_navigation_policy.h"
+#include "content/common/content_switches_internal.h"
#include "content/common/frame.mojom-test-utils.h"
#include "content/common/frame.mojom.h"
#include "content/public/browser/back_forward_cache.h"
@@ -6053,6 +6054,53 @@
waiter.WaitForMediaDestroyed();
}
+#if !BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
+class SelectPickerBrowserTest : public WebContentsImplBrowserTest {
+ public:
+ SelectPickerBrowserTest() {
+ scoped_feature_list_.InitAndEnableFeature(
+ blink::features::kCSSPseudoOpenClosed);
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that popup pickers only open when the webcontents is focused.
+IN_PROC_BROWSER_TEST_F(SelectPickerBrowserTest, PickerOnlyOpensWhenFocused) {
+ base::CommandLine::ForCurrentProcess()->RemoveSwitch(
+ switches::kDisablePopupFocusedWindowDetectionForTesting);
+ ASSERT_FALSE(base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisablePopupFocusedWindowDetectionForTesting));
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(shell()->web_contents());
+
+ GURL url(R"(data:text/html,<select><option>one</option></select>)");
+ ASSERT_TRUE(NavigateToURL(shell(), url));
+ WaitForLoadStop(web_contents);
+
+ // De-focus the window, which should keep popups from showing.
+ RenderWidgetHost* rwh =
+ web_contents->GetPrimaryMainFrame()->GetRenderViewHost()->GetWidget();
+ web_contents->NotifyWebContentsLostFocus(rwh);
+
+ // Open the picker - expect it not to open because window is not focused.
+ ASSERT_TRUE(
+ ExecJs(web_contents, "document.querySelector('select').showPicker();"));
+ EXPECT_EQ(false, EvalJs(web_contents,
+ "document.querySelector('select').matches(':open')"));
+
+ // Now focus the window, which should enable popups.
+ web_contents->NotifyWebContentsFocused(rwh);
+
+ // Open the picker, and make sure it opened.
+ ASSERT_TRUE(
+ ExecJs(web_contents, "document.querySelector('select').showPicker();"));
+ EXPECT_EQ(true, EvalJs(web_contents,
+ "document.querySelector('select').matches(':open')"));
+}
+#endif
+
class WebContentsImplInsecureLocalhostBrowserTest
: public WebContentsImplBrowserTest {
protected:
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index e297693..4f5787a 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -56,6 +56,7 @@
#include "content/browser/tracing/memory_instrumentation_util.h"
#include "content/browser/tracing/startup_tracing_controller.h"
#include "content/browser/tracing/tracing_controller_impl.h"
+#include "content/common/content_switches_internal.h"
#include "content/public/app/content_main.h"
#include "content/public/app/initialize_mojo_core.h"
#include "content/public/browser/browser_main_parts.h"
@@ -376,6 +377,11 @@
command_line->AppendSwitch(
switches::kDisableBackgroundingOccludedWindowsForTesting);
+ // The focused state of windows is unpredictable during tests, so disable
+ // focused window detection for popups when running browser tests.
+ command_line->AppendSwitch(
+ switches::kDisablePopupFocusedWindowDetectionForTesting);
+
if (enable_pixel_output_) {
DCHECK(!command_line->HasSwitch(switches::kForceDeviceScaleFactor))
<< "--force-device-scale-factor flag already present. Tests using "
diff --git a/third_party/blink/web_tests/inspector-protocol/emulation/select-popup-auto-dark-mode.js b/third_party/blink/web_tests/inspector-protocol/emulation/select-popup-auto-dark-mode.js
index 5bd9964..5be48713 100644
--- a/third_party/blink/web_tests/inspector-protocol/emulation/select-popup-auto-dark-mode.js
+++ b/third_party/blink/web_tests/inspector-protocol/emulation/select-popup-auto-dark-mode.js
@@ -23,6 +23,7 @@
testRunner.log("=== Before auto dark mode (autoDarkMode and prefers-color-scheme override) is not enabled ===");
await session.evaluateAsync(`openPicker(document.querySelector("select"))`);
await logScreenshotData();
+ await session.evaluateAsync(`attemptToClosePicker(document.querySelector("select"))`);
await dp.Emulation.setAutoDarkModeOverride({enabled: true});
await dp.Emulation.setEmulatedMedia({
@@ -36,6 +37,7 @@
testRunner.log("\n=== After auto dark mode (autoDarkMode and prefers-color-scheme override) is enabled ===");
await session.evaluateAsync(`openPicker(document.querySelector("select"))`);
await logScreenshotData();
+ await session.evaluateAsync(`attemptToClosePicker(document.querySelector("select"))`);
testRunner.completeTest();
});
Original Bug Report
Select Option can be opened on top of Popups with Different Origins and can be used to Spoof important Security Prompts
SUMMARY
The Select option can be opened on popup with a different origin than the one that initiated it, which is simialr to the issue documented at https://issues.chromium.org/issues/41494315 (POC-1).
This issue also demonstrates different exploitation methods similar to https://issues.chromium.org/issues/342194497, where the permission prompt UI can be concealed (POC-2 and POC-3).
VULNERABILITY DETAILS
A malicious website can open a Select option on top of another website with an arbitrary origin via a popup, including trusted domains like google.com.
This allows the malicious website to display any text within the Select option on top of the opened popup.
The issue can be further exploited by opening a different permission or security prompt in this popup and hiding it with the Select option.
Below I have demonstrated this in three proof-of-concept files:
-
poc-1.html: Demonstrates that the Select option can appear on top of an origin like google.com. -
poc-2.htmlandpoc-3.html: Shows how the Select option can be used to cover the permission/security UI opened using a popup from a different website.
VERSION
-
Chrome Version:
- 130.0.6701.0 (Official Build) Canary (64-bit)
- 128.0.6613.120 (Official Build) (64-bit) Stable
-
Operating System: Windows 11
REPRODUCTION CASE
POC-1:
-
Download the attached
poc-1.htmlfile. -
Host the page on a local server or open
poc-1.htmldirectly from the folder. -
Visit the webpage using the latest Chrome browser.
-
Double-click on the website.
-
Observe that the Select option appears over a different origin opened as a popup.
POC-2:
-
Download the attached
poc-2.htmlandpermission.htmlfiles. -
Host the page on a local server or open
poc-2.htmldirectly from the folder. -
Visit the webpage using the latest Chrome browser.
-
Double-click on the website.
-
The Select option will request you to press Tab three times, then Enter to continue. Doing so will allow the hidden permission tab to proceed.
POC-3:
-
Download the attached
poc-3.htmlandpermission.htmlfiles. -
Host the page on a local server or open
poc-3.htmldirectly from the folder. -
Visit the webpage using the latest Chrome browser.
-
Double-click on the website.
-
The permission prompt from the popup window is concealed by the Select option, thus spoofing the user interface.
Observed:
The Select option can open on top of a different website via a popup, rather than being on the initiated page, thus allowing malicious websites to spoof other sites or security prompts. This can be exploited to obtain permissions without the user’s awareness.
Expected:
The Select option should not open on top of popups. It should strictly be tied to the opened website, and when popup is focused chrome should not allow the Select option to be placed on top of it.
CREDIT INFORMATION
Reporter credit: Shaheen Fazim