Chrome · New Tab Page
CVE-2026-11660
Logic Error in New Tab Page
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
IN_PROC_BROWSER_TEST_Fchrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_browsertest.cc |
modified |
Files Changed
chrome/browser/ui/webui/new_tab_footer/BUILD.gnchrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler.ccchrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler.hchrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_browsertest.cc
Patch
From 52ede47ddb56df2d872d9c411aee15e5a49d3288 Mon Sep 17 00:00:00 2001 From: Jilin Yang <[email protected]> Date: Thu, 21 May 2026 21:48:50 -0700 Subject: [PATCH] Gate renderer-supplied OpenUrlInCurrentTab by scheme A compromised chrome://newtab-footer renderer could call this Mojo Method with any URL and get a browser-initiated navigation after only is_valid(). Allow only https://, otherwise report a bad message; browser-owned fixed chrome:// navigations bypass the gate via a private OpenUrlInCurrentTabInternal. Fixed: b:513731890 Change-Id: If1453df6796a4316c6163125ad5044f5f4145638 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7857036 Reviewed-by: Tibor Goldschwendt <[email protected]> Reviewed-by: Nasko Oskov <[email protected]> Commit-Queue: Jilin Yang <[email protected]> Cr-Commit-Position: refs/heads/main@{#1634721} --- diff --git a/chrome/browser/ui/webui/new_tab_footer/BUILD.gn b/chrome/browser/ui/webui/new_tab_footer/BUILD.gn index 5ca9b24..8dcf44cf 100644 --- a/chrome/browser/ui/webui/new_tab_footer/BUILD.gn +++ b/chrome/browser/ui/webui/new_tab_footer/BUILD.gn @@ -56,6 +56,7 @@ "//components/search", "//content/public/browser", "//ui/base", + "//url", ] } @@ -84,6 +85,7 @@ ":test_support", "//chrome/browser/ui/webui:webui_util", "//chrome/browser/ui/webui/top_chrome:test_support", + "//mojo/public/cpp/test_support:test_utils", ] } diff --git a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler.cc b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler.cc index cbdb448..32db2faf 100644 --- a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler.cc +++ b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler.cc @@ -36,6 +36,7 @@ #include "components/prefs/pref_service.h" #include "components/strings/grit/components_strings.h" #include "content/public/browser/web_contents.h" +#include "mojo/public/cpp/bindings/message.h" #include "net/base/url_util.h" #include "ui/base/interaction/element_identifier.h" #include "ui/base/l10n/l10n_util.h" @@ -45,6 +46,7 @@ #include "ui/color/color_provider.h" #include "ui/gfx/image/image_skia_rep_default.h" #include "ui/gfx/paint_vector_icon.h" +#include "url/url_constants.h" NewTabFooterHandler::NewTabFooterHandler( mojo::PendingReceiver<new_tab_footer::mojom::NewTabFooterHandler> @@ -123,11 +125,11 @@ options_url = net::AppendOrReplaceQueryParameter(options_url, "id", curr_ntp_extension_id_); } - OpenUrlInCurrentTab(options_url); + OpenUrlInCurrentTabInternal(options_url); } void NewTabFooterHandler::OpenManagementPage() { - OpenUrlInCurrentTab(GURL(chrome::kChromeUIManagementURL)); + OpenUrlInCurrentTabInternal(GURL(chrome::kChromeUIManagementURL)); } void NewTabFooterHandler::ShowContextMenu(const gfx::Point& point) { @@ -165,6 +167,16 @@ } void NewTabFooterHandler::OpenUrlInCurrentTab(const GURL& url) { + // Mojo entry: renderer-supplied URLs become browser-initiated navigations, + // so only https:// is allowed; anything else is treated as a bad message. + if (!url.SchemeIs(url::kHttpsScheme)) { + mojo::ReportBadMessage("OpenUrlInCurrentTab: scheme must be https"); + return; + } + OpenUrlInCurrentTabInternal(url); +} + +void NewTabFooterHandler::OpenUrlInCurrentTabInternal(const GURL& url) { auto* browser_window = webui::GetBrowserWindowInterface(web_contents_); if (!browser_window || !url.is_valid()) { return; diff --git a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler.h b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler.h index e983fd7..1b173011 100644 --- a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler.h +++ b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler.h @@ -71,6 +71,8 @@ std::string GetManagementNoticeText(); std::string GetManagementNoticeIconDataUrl(); + void OpenUrlInCurrentTabInternal(const GURL& url); + // extensions::ExtensionRegistryObserver. void OnExtensionUnloaded(content::BrowserContext* browser_context, const extensions::Extension* extension, diff --git a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_browsertest.cc b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_browsertest.cc index 27307b1..4ebb0440a 100644 --- a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_browsertest.cc +++ b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_browsertest.cc @@ -25,6 +25,8 @@ #include "content/public/test/browser_test_utils.h" #include "extensions/common/extension.h" #include "extensions/test/test_extension_dir.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "mojo/public/cpp/test_support/test_utils.h" #include "net/base/url_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/models/menu_model.h" @@ -42,19 +44,23 @@ InProcessBrowserTest::SetUpOnMainThread(); embedder_ = std::make_unique<TestEmbedder>(); handler_ = std::make_unique<NewTabFooterHandler>( - mojo::PendingReceiver<new_tab_footer::mojom::NewTabFooterHandler>(), + handler_remote_.BindNewPipeAndPassReceiver(), document_.BindAndGetRemote(), embedder_->GetWeakPtr(), NtpCustomBackgroundServiceFactory::GetForProfile(profile()), web_contents()); } void TearDownOnMainThread() override { + handler_remote_.reset(); handler_.reset(); InProcessBrowserTest::TearDownOnMainThread(); } TestEmbedder& embedder() { return *embedder_; } NewTabFooterHandler& handler() { return *handler_; } + mojo::Remote<new_tab_footer::mojom::NewTabFooterHandler>& handler_remote() { + return handler_remote_; + } content::WebContents* web_contents() { return chrome_test_utils::GetActiveWebContents(this); } @@ -63,6 +69,7 @@ base::test::ScopedFeatureList feature_list_; std::unique_ptr<TestEmbedder> embedder_; std::unique_ptr<NewTabFooterHandler> handler_; + mojo::Remote<new_tab_footer::mojom::NewTabFooterHandler> handler_remote_; testing::NiceMock<MockNewTabFooterDocument> document_; }; @@ -130,6 +137,40 @@ EXPECT_EQ(expected_url, web_contents()->GetLastCommittedURL()); } +// One test per rejected scheme: ReportBadMessage breaks the pipe, so reusing a +// single Remote across iterations doesn't work. +IN_PROC_BROWSER_TEST_F(NewTabFooterHandlerBrowserTest, + OpenUrlInCurrentTab_RejectsHttp) { + mojo::test::BadMessageObserver bad_message_observer; + handler_remote()->OpenUrlInCurrentTab(GURL("http://example.test/")); + EXPECT_EQ("OpenUrlInCurrentTab: scheme must be https", + bad_message_observer.WaitForBadMessage()); +} + +IN_PROC_BROWSER_TEST_F(NewTabFooterHandlerBrowserTest, + OpenUrlInCurrentTab_RejectsWss) { + mojo::test::BadMessageObserver bad_message_observer; + handler_remote()->OpenUrlInCurrentTab(GURL("wss://example.test/")); + EXPECT_EQ("OpenUrlInCurrentTab: scheme must be https", + bad_message_observer.WaitForBadMessage()); +} + +IN_PROC_BROWSER_TEST_F(NewTabFooterHandlerBrowserTest, + OpenUrlInCurrentTab_RejectsChromeScheme) { + mojo::test::BadMessageObserver bad_message_observer; + handler_remote()->OpenUrlInCurrentTab(GURL("chrome://version/")); + EXPECT_EQ("OpenUrlInCurrentTab: scheme must be https", + bad_message_observer.WaitForBadMessage()); +} + +IN_PROC_BROWSER_TEST_F(NewTabFooterHandlerBrowserTest, + OpenUrlInCurrentTab_RejectsJavascript) { + mojo::test::BadMessageObserver bad_message_observer; + handler_remote()->OpenUrlInCurrentTab(GURL("javascript:void(0);")); + EXPECT_EQ("OpenUrlInCurrentTab: scheme must be https", + bad_message_observer.WaitForBadMessage()); +} + IN_PROC_BROWSER_TEST_F(NewTabFooterHandlerBrowserTest, ShowContextMenu) { ASSERT_FALSE(embedder().context_menu_shown());
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_browsertest.cc b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_browsertest.cc
index 27307b1..4ebb0440a 100644
--- a/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/new_tab_footer/new_tab_footer_handler_browsertest.cc
@@ -25,6 +25,8 @@
#include "content/public/test/browser_test_utils.h"
#include "extensions/common/extension.h"
#include "extensions/test/test_extension_dir.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
#include "net/base/url_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/models/menu_model.h"
@@ -42,19 +44,23 @@
InProcessBrowserTest::SetUpOnMainThread();
embedder_ = std::make_unique<TestEmbedder>();
handler_ = std::make_unique<NewTabFooterHandler>(
- mojo::PendingReceiver<new_tab_footer::mojom::NewTabFooterHandler>(),
+ handler_remote_.BindNewPipeAndPassReceiver(),
document_.BindAndGetRemote(), embedder_->GetWeakPtr(),
NtpCustomBackgroundServiceFactory::GetForProfile(profile()),
web_contents());
}
void TearDownOnMainThread() override {
+ handler_remote_.reset();
handler_.reset();
InProcessBrowserTest::TearDownOnMainThread();
}
TestEmbedder& embedder() { return *embedder_; }
NewTabFooterHandler& handler() { return *handler_; }
+ mojo::Remote<new_tab_footer::mojom::NewTabFooterHandler>& handler_remote() {
+ return handler_remote_;
+ }
content::WebContents* web_contents() {
return chrome_test_utils::GetActiveWebContents(this);
}
@@ -63,6 +69,7 @@
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<TestEmbedder> embedder_;
std::unique_ptr<NewTabFooterHandler> handler_;
+ mojo::Remote<new_tab_footer::mojom::NewTabFooterHandler> handler_remote_;
testing::NiceMock<MockNewTabFooterDocument> document_;
};
@@ -130,6 +137,40 @@
EXPECT_EQ(expected_url, web_contents()->GetLastCommittedURL());
}
+// One test per rejected scheme: ReportBadMessage breaks the pipe, so reusing a
+// single Remote across iterations doesn't work.
+IN_PROC_BROWSER_TEST_F(NewTabFooterHandlerBrowserTest,
+ OpenUrlInCurrentTab_RejectsHttp) {
+ mojo::test::BadMessageObserver bad_message_observer;
+ handler_remote()->OpenUrlInCurrentTab(GURL("http://example.test/"));
+ EXPECT_EQ("OpenUrlInCurrentTab: scheme must be https",
+ bad_message_observer.WaitForBadMessage());
+}
+
+IN_PROC_BROWSER_TEST_F(NewTabFooterHandlerBrowserTest,
+ OpenUrlInCurrentTab_RejectsWss) {
+ mojo::test::BadMessageObserver bad_message_observer;
+ handler_remote()->OpenUrlInCurrentTab(GURL("wss://example.test/"));
+ EXPECT_EQ("OpenUrlInCurrentTab: scheme must be https",
+ bad_message_observer.WaitForBadMessage());
+}
+
+IN_PROC_BROWSER_TEST_F(NewTabFooterHandlerBrowserTest,
+ OpenUrlInCurrentTab_RejectsChromeScheme) {
+ mojo::test::BadMessageObserver bad_message_observer;
+ handler_remote()->OpenUrlInCurrentTab(GURL("chrome://version/"));
+ EXPECT_EQ("OpenUrlInCurrentTab: scheme must be https",
+ bad_message_observer.WaitForBadMessage());
+}
+
+IN_PROC_BROWSER_TEST_F(NewTabFooterHandlerBrowserTest,
+ OpenUrlInCurrentTab_RejectsJavascript) {
+ mojo::test::BadMessageObserver bad_message_observer;
+ handler_remote()->OpenUrlInCurrentTab(GURL("javascript:void(0);"));
+ EXPECT_EQ("OpenUrlInCurrentTab: scheme must be https",
+ bad_message_observer.WaitForBadMessage());
+}
+
IN_PROC_BROWSER_TEST_F(NewTabFooterHandlerBrowserTest, ShowContextMenu) {
ASSERT_FALSE(embedder().context_menu_shown());
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