Chrome · DevTools
CVE-2026-87492
Logic Error in DevTools
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/devtools/devtools_ui_bindings.cc |
modified | |
Delegatechrome/browser/devtools/devtools_ui_bindings.h |
modified | |
DevToolsUIBindingsNavigationTestchrome/browser/devtools/devtools_ui_bindings_unittest.cc |
modified | |
DevToolsUIBindingsSyncInfoTestchrome/browser/devtools/devtools_ui_bindings_unittest.cc |
modified |
Files Changed
chrome/browser/devtools/devtools_ui_bindings.ccchrome/browser/devtools/devtools_ui_bindings.hchrome/browser/devtools/devtools_ui_bindings_unittest.cc
Patch
From b781be4b5981905b69c69643709baa64781a6bc8 Mon Sep 17 00:00:00 2001 From: Alex Rudenko <[email protected]> Date: Wed, 02 Sep 2026 03:10:37 -0700 Subject: [PATCH] Improve origin validation in devtools_ui_bindings Fixed: 529123409 Change-Id: Ie16a65f099e66db08b5bd271f5af547e411b7cd1 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8303113 Reviewed-by: Danil Somsikov <[email protected]> Commit-Queue: Alex Rudenko <[email protected]> Cr-Commit-Position: refs/heads/main@{#1690574} --- diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc index 6a47474d..157d0cff 100644 --- a/chrome/browser/devtools/devtools_ui_bindings.cc +++ b/chrome/browser/devtools/devtools_ui_bindings.cc @@ -3211,9 +3211,13 @@ if (frontend_host_) { return; } - if (content::RenderFrameHost* opener = web_contents_->GetOpener()) { + // If the window was opened by another window, ensure the root opener in + // the live opener chain is a DevTools WebContents with active DevTools + // frontend bindings. This also covers cases where `window.opener` was + // severed (e.g. via `window.opener = null` or `rel="noopener"`). + if (web_contents_->HasLiveOriginalOpenerChain()) { content::WebContents* opener_wc = - content::WebContents::FromRenderFrameHost(opener); + web_contents_->GetFirstWebContentsInLiveOriginalOpenerChain(); DevToolsUIBindings* opener_bindings = opener_wc ? DevToolsUIBindings::ForWebContents(opener_wc) : nullptr; if (!opener_bindings || !opener_bindings->frontend_host_) { diff --git a/chrome/browser/devtools/devtools_ui_bindings.h b/chrome/browser/devtools/devtools_ui_bindings.h index 260add27..0b6b6d3 100644 --- a/chrome/browser/devtools/devtools_ui_bindings.h +++ b/chrome/browser/devtools/devtools_ui_bindings.h @@ -81,6 +81,7 @@ friend class DevToolsUIBindingsDispatchHttpRequestTest; friend class DevToolsUIBindingsDispatchHttpRequestStreamingTest; friend class DevToolsUIBindingsLoadNetworkResourceTest; + friend class DevToolsUIBindingsNavigationTest; public: class Delegate { @@ -168,6 +169,13 @@ const std::string& script) { RegisterExtensionsAPI(origin, script); } + bool has_frontend_host_for_testing() const { + return frontend_host_ != nullptr; + } + void ReadyToCommitNavigationForTesting( + content::NavigationHandle* navigation_handle) { + ReadyToCommitNavigation(navigation_handle); + } void ShowDevToolsInfoBarForTesting( const std::u16string& message, diff --git a/chrome/browser/devtools/devtools_ui_bindings_unittest.cc b/chrome/browser/devtools/devtools_ui_bindings_unittest.cc index 716d446..2949666 100644 --- a/chrome/browser/devtools/devtools_ui_bindings_unittest.cc +++ b/chrome/browser/devtools/devtools_ui_bindings_unittest.cc @@ -26,9 +26,11 @@ #include "components/sync/test/test_sync_service.h" #include "content/public/browser/web_contents.h" #include "content/public/test/browser_task_environment.h" +#include "content/public/test/mock_navigation_handle.h" #include "content/public/test/navigation_simulator.h" #include "content/public/test/test_web_contents_factory.h" #include "content/public/test/url_loader_interceptor.h" +#include "content/public/test/web_contents_tester.h" #include "google_apis/gaia/google_service_auth_error.h" #include "net/base/net_errors.h" #include "net/cookies/site_for_cookies.h" @@ -393,6 +395,109 @@ } } +class DevToolsUIBindingsNavigationTest : public testing::Test { + public: + content::WebContents* CreateWebContents() { + return web_contents_factory_.CreateWebContents(&profile_); + } + + protected: + content::BrowserTaskEnvironment task_environment_; + TestingProfile profile_; + content::TestWebContentsFactory web_contents_factory_; +}; + +TEST_F(DevToolsUIBindingsNavigationTest, + BrowserInitiatedNavigationCreatesFrontendHost) { + content::WebContents* web_contents = CreateWebContents(); + auto bindings = std::make_unique<DevToolsUIBindings>(web_contents); + EXPECT_FALSE(bindings->has_frontend_host_for_testing()); + + content::MockNavigationHandle handle( + GURL("devtools://devtools/bundled/devtools_app.html"), + web_contents->GetPrimaryMainFrame()); + handle.set_is_in_primary_main_frame(true); + handle.set_is_renderer_initiated(false); + + bindings->ReadyToCommitNavigationForTesting(&handle); + + EXPECT_TRUE(bindings->has_frontend_host_for_testing()); +} + +TEST_F(DevToolsUIBindingsNavigationTest, + OpenerWithoutDevToolsBindingsRejected) { + content::WebContents* opener_contents = CreateWebContents(); + content::WebContents* web_contents = CreateWebContents(); + content::WebContentsTester::For(web_contents)->SetOpener(opener_contents); + content::WebContentsTester::For(web_contents) + ->SetOriginalOpener(opener_contents); + + auto bindings = std::make_unique<DevToolsUIBindings>(web_contents); + + content::MockNavigationHandle handle( + GURL("devtools://devtools/bundled/devtools_app.html"), + web_contents->GetPrimaryMainFrame()); + handle.set_is_in_primary_main_frame(true); + handle.set_is_renderer_initiated(false); + + bindings->ReadyToCommitNavigationForTesting(&handle); + + EXPECT_FALSE(bindings->has_frontend_host_for_testing()); +} + +TEST_F(DevToolsUIBindingsNavigationTest, + OriginalOpenerWithoutDevToolsBindingsRejectedEvenIfOpenerSevered) { + content::WebContents* original_opener_contents = CreateWebContents(); + content::WebContents* web_contents = CreateWebContents(); + content::WebContentsTester::For(web_contents) + ->SetOriginalOpener(original_opener_contents); + // Ensure the live opener is null (simulating `window.opener = null`). + EXPECT_EQ(nullptr, web_contents->GetOpener()); + EXPECT_TRUE(web_contents->HasLiveOriginalOpenerChain()); + + auto bindings = std::make_unique<DevToolsUIBindings>(web_contents); + + content::MockNavigationHandle handle( + GURL("devtools://devtools/bundled/devtools_app.html"), + web_contents->GetPrimaryMainFrame()); + handle.set_is_in_primary_main_frame(true); + handle.set_is_renderer_initiated(false); + + bindings->ReadyToCommitNavigationForTesting(&handle); + + EXPECT_FALSE(bindings->has_frontend_host_for_testing()); +} + +TEST_F(DevToolsUIBindingsNavigationTest, + OpenerWithValidDevToolsBindingsAccepted) { + content::WebContents* opener_contents = CreateWebContents(); + auto opener_bindings = std::make_unique<DevToolsUIBindings>(opener_contents); + content::MockNavigationHandle opener_handle( + GURL("devtools://devtools/bundled/devtools_app.html"), + opener_contents->GetPrimaryMainFrame()); + opener_handle.set_is_in_primary_main_frame(true); + opener_handle.set_is_renderer_initiated(false); + opener_bindings->ReadyToCommitNavigationForTesting(&opener_handle); + ASSERT_TRUE(opener_bindings->has_frontend_host_for_testing()); + + content::WebContents* web_contents = CreateWebContents(); + content::WebContentsTester::For(web_contents)->SetOpener(opener_contents); + content::WebContentsTester::For(web_contents) + ->SetOriginalOpener(opener_contents); + + auto bindings = std::make_unique<DevToolsUIBindings>(web_contents); + + content::MockNavigationHandle handle( + GURL("devtools://devtools/bundled/devtools_app.html"), + web_contents->GetPrimaryMainFrame()); + handle.set_is_in_primary_main_frame(true); + handle.set_is_renderer_initiated(false); + + bindings->ReadyToCommitNavigationForTesting(&handle); + + EXPECT_TRUE(bindings->has_frontend_host_for_testing()); +} + class DevToolsUIBindingsSyncInfoTest : public testing::Test { public: void SetUp() override {
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/devtools/devtools_ui_bindings_unittest.cc b/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
index 716d446..2949666 100644
--- a/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings_unittest.cc
@@ -26,9 +26,11 @@
#include "components/sync/test/test_sync_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_web_contents_factory.h"
#include "content/public/test/url_loader_interceptor.h"
+#include "content/public/test/web_contents_tester.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/base/net_errors.h"
#include "net/cookies/site_for_cookies.h"
@@ -393,6 +395,109 @@
}
}
+class DevToolsUIBindingsNavigationTest : public testing::Test {
+ public:
+ content::WebContents* CreateWebContents() {
+ return web_contents_factory_.CreateWebContents(&profile_);
+ }
+
+ protected:
+ content::BrowserTaskEnvironment task_environment_;
+ TestingProfile profile_;
+ content::TestWebContentsFactory web_contents_factory_;
+};
+
+TEST_F(DevToolsUIBindingsNavigationTest,
+ BrowserInitiatedNavigationCreatesFrontendHost) {
+ content::WebContents* web_contents = CreateWebContents();
+ auto bindings = std::make_unique<DevToolsUIBindings>(web_contents);
+ EXPECT_FALSE(bindings->has_frontend_host_for_testing());
+
+ content::MockNavigationHandle handle(
+ GURL("devtools://devtools/bundled/devtools_app.html"),
+ web_contents->GetPrimaryMainFrame());
+ handle.set_is_in_primary_main_frame(true);
+ handle.set_is_renderer_initiated(false);
+
+ bindings->ReadyToCommitNavigationForTesting(&handle);
+
+ EXPECT_TRUE(bindings->has_frontend_host_for_testing());
+}
+
+TEST_F(DevToolsUIBindingsNavigationTest,
+ OpenerWithoutDevToolsBindingsRejected) {
+ content::WebContents* opener_contents = CreateWebContents();
+ content::WebContents* web_contents = CreateWebContents();
+ content::WebContentsTester::For(web_contents)->SetOpener(opener_contents);
+ content::WebContentsTester::For(web_contents)
+ ->SetOriginalOpener(opener_contents);
+
+ auto bindings = std::make_unique<DevToolsUIBindings>(web_contents);
+
+ content::MockNavigationHandle handle(
+ GURL("devtools://devtools/bundled/devtools_app.html"),
+ web_contents->GetPrimaryMainFrame());
+ handle.set_is_in_primary_main_frame(true);
+ handle.set_is_renderer_initiated(false);
+
+ bindings->ReadyToCommitNavigationForTesting(&handle);
+
+ EXPECT_FALSE(bindings->has_frontend_host_for_testing());
+}
+
+TEST_F(DevToolsUIBindingsNavigationTest,
+ OriginalOpenerWithoutDevToolsBindingsRejectedEvenIfOpenerSevered) {
+ content::WebContents* original_opener_contents = CreateWebContents();
+ content::WebContents* web_contents = CreateWebContents();
+ content::WebContentsTester::For(web_contents)
+ ->SetOriginalOpener(original_opener_contents);
+ // Ensure the live opener is null (simulating `window.opener = null`).
+ EXPECT_EQ(nullptr, web_contents->GetOpener());
+ EXPECT_TRUE(web_contents->HasLiveOriginalOpenerChain());
+
+ auto bindings = std::make_unique<DevToolsUIBindings>(web_contents);
+
+ content::MockNavigationHandle handle(
+ GURL("devtools://devtools/bundled/devtools_app.html"),
+ web_contents->GetPrimaryMainFrame());
+ handle.set_is_in_primary_main_frame(true);
+ handle.set_is_renderer_initiated(false);
+
+ bindings->ReadyToCommitNavigationForTesting(&handle);
+
+ EXPECT_FALSE(bindings->has_frontend_host_for_testing());
+}
+
+TEST_F(DevToolsUIBindingsNavigationTest,
+ OpenerWithValidDevToolsBindingsAccepted) {
+ content::WebContents* opener_contents = CreateWebContents();
+ auto opener_bindings = std::make_unique<DevToolsUIBindings>(opener_contents);
+ content::MockNavigationHandle opener_handle(
+ GURL("devtools://devtools/bundled/devtools_app.html"),
+ opener_contents->GetPrimaryMainFrame());
+ opener_handle.set_is_in_primary_main_frame(true);
+ opener_handle.set_is_renderer_initiated(false);
+ opener_bindings->ReadyToCommitNavigationForTesting(&opener_handle);
+ ASSERT_TRUE(opener_bindings->has_frontend_host_for_testing());
+
+ content::WebContents* web_contents = CreateWebContents();
+ content::WebContentsTester::For(web_contents)->SetOpener(opener_contents);
+ content::WebContentsTester::For(web_contents)
+ ->SetOriginalOpener(opener_contents);
+
+ auto bindings = std::make_unique<DevToolsUIBindings>(web_contents);
+
+ content::MockNavigationHandle handle(
+ GURL("devtools://devtools/bundled/devtools_app.html"),
+ web_contents->GetPrimaryMainFrame());
+ handle.set_is_in_primary_main_frame(true);
+ handle.set_is_renderer_initiated(false);
+
+ bindings->ReadyToCommitNavigationForTesting(&handle);
+
+ EXPECT_TRUE(bindings->has_frontend_host_for_testing());
+}
+
class DevToolsUIBindingsSyncInfoTest : public testing::Test {
public:
void SetUp() override {
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