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 <alexrudenko@chromium.org>
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 <dsv@chromium.org>
Commit-Queue: Alex Rudenko <alexrudenko@chromium.org>
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