Chrome · WebUI
CVE-2026-87584
Logic Error in WebUI
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
AdditionalSchemesWebUINavigationBrowserTestcontent/browser/webui/web_ui_navigation_browsertest.cc |
modified | |
AdditionalSchemesWebUINavigationBrowserTestcontent/browser/webui/web_ui_navigation_browsertest.cc |
modified |
Files Changed
content/browser/renderer_host/navigator.cccontent/browser/webui/web_ui_navigation_browsertest.cc
Patch
From 140c7324157ca9e303d196e3f96568abf741962b Mon Sep 17 00:00:00 2001 From: Giovanni Ortuño Urquidi <[email protected]> Date: Mon, 03 Aug 2026 13:07:25 -0700 Subject: [PATCH] webui: Mark untrusted webui navigations as renderer initiated We use is_renderer_initiated for untrustworhty navigations, so we shouldn't set it to false for chrome-untrusted://. Bug: 537466493 Change-Id: Ie1b10be89066be6cc9186ba8261b7c3d7271c947 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8182929 Commit-Queue: Giovanni Ortuno Urquidi <[email protected]> Reviewed-by: Alex Moshchuk <[email protected]> Cr-Commit-Position: refs/heads/main@{#1672853} --- diff --git a/content/browser/renderer_host/navigator.cc b/content/browser/renderer_host/navigator.cc index 425ad4aa..6375e2d1 100644 --- a/content/browser/renderer_host/navigator.cc +++ b/content/browser/renderer_host/navigator.cc @@ -48,6 +48,7 @@ #include "content/public/browser/page_navigator.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/restore_type.h" +#include "content/public/browser/web_ui_controller.h" #include "content/public/common/bindings_policy.h" #include "content/public/common/content_client.h" #include "content/public/common/content_constants.h" @@ -1149,15 +1150,19 @@ params.source_render_process_id = render_frame_host->GetProcess()->GetDeprecatedID(); - if (render_frame_host->web_ui()) { + if (WebUI* web_ui = render_frame_host->web_ui()) { // Note that we hide the referrer for Web UI pages. We don't really want // web sites to see a referrer of "chrome://blah" (and some chrome: URLs // might have search terms or other stuff we don't want to send to the // site), so we send no referrer. params.referrer = Referrer(); - // Navigations in Web UI pages count as browser-initiated navigations. - params.is_renderer_initiated = false; + // Navigations in trusted Web UI pages count as browser-initiated + // navigations. + if (web_ui->GetController()->GetTrustPolicy() == + WebUIController::TrustPolicy::kTrusted) { + params.is_renderer_initiated = false; + } } params.blob_url_loader_factory = std::move(blob_url_loader_factory); @@ -1212,15 +1217,19 @@ // navigation. See https://crbug.com/495161. bool is_renderer_initiated = true; Referrer referrer_to_use(referrer); - if (render_frame_host->web_ui()) { + if (WebUI* web_ui = render_frame_host->web_ui()) { // Note that we hide the referrer for Web UI pages. We don't really want // web sites to see a referrer of "chrome://blah" (and some chrome: URLs // might have search terms or other stuff we don't want to send to the // site), so we send no referrer. referrer_to_use = Referrer(); - // Navigations in Web UI pages count as browser-initiated navigations. - is_renderer_initiated = false; + // Navigations in trusted Web UI pages count as browser-initiated + // navigations. + if (web_ui->GetController()->GetTrustPolicy() == + WebUIController::TrustPolicy::kTrusted) { + is_renderer_initiated = false; + } } if (is_renderer_initiated && diff --git a/content/browser/webui/web_ui_navigation_browsertest.cc b/content/browser/webui/web_ui_navigation_browsertest.cc index 0748c319..f6e1f81 100644 --- a/content/browser/webui/web_ui_navigation_browsertest.cc +++ b/content/browser/webui/web_ui_navigation_browsertest.cc @@ -25,6 +25,7 @@ #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_content_browser_client.h" #include "content/public/test/content_browser_test_utils.h" +#include "content/public/test/navigation_handle_observer.h" #include "content/public/test/scoped_web_ui_controller_factory_registration.h" #include "content/public/test/test_frame_navigation_observer.h" #include "content/public/test/test_navigation_observer.h" @@ -1225,6 +1226,46 @@ } } +// Verify that renderer-initiated navigations from chrome-untrusted:// frames +// have is_renderer_initiated() == true. +IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, + UntrustedWebUIsAreRendererInitiated) { + WebUIConfigMap::GetInstance().AddUntrustedWebUIConfig( + std::make_unique<ui::TestUntrustedWebUIConfig>("test-host")); + + GURL untrusted_url1(GetChromeUntrustedUIURL("test-host/title1.html")); + GURL untrusted_url2(GetChromeUntrustedUIURL("test-host/title2.html")); + + EXPECT_TRUE(NavigateToURL(shell(), untrusted_url1)); + + NavigationHandleObserver untrusted_observer(shell()->web_contents(), + untrusted_url2); + TestNavigationObserver untrusted_nav_observer(shell()->web_contents(), 1); + EXPECT_TRUE( + ExecJs(shell(), JsReplace("location.href = $1;", untrusted_url2))); + untrusted_nav_observer.Wait(); + EXPECT_TRUE(untrusted_observer.has_committed()); + EXPECT_TRUE(untrusted_observer.is_renderer_initiated()); +} + +// Verify that renderer-initiated navigations from trusted chrome:// frames +// count as browser-initiated (is_renderer_initiated() == false). +IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest, + TrustedWebUIsAreBrowserInitiated) { + GURL trusted_url1(GetWebUIURL("web-ui/title1.html")); + GURL trusted_url2(GetWebUIURL("web-ui/title2.html")); + + EXPECT_TRUE(NavigateToURL(shell(), trusted_url1)); + + NavigationHandleObserver trusted_observer(shell()->web_contents(), + trusted_url2); + TestNavigationObserver trusted_nav_observer(shell()->web_contents(), 1); + EXPECT_TRUE(ExecJs(shell(), JsReplace("location.href = $1;", trusted_url2))); + trusted_nav_observer.Wait(); + EXPECT_TRUE(trusted_observer.has_committed()); + EXPECT_FALSE(trusted_observer.is_renderer_initiated()); +} + class AdditionalSchemesWebUINavigationBrowserTest : public ContentBrowserTest { public: AdditionalSchemesWebUINavigationBrowserTest() {
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/webui/web_ui_navigation_browsertest.cc b/content/browser/webui/web_ui_navigation_browsertest.cc
index 0748c319..f6e1f81 100644
--- a/content/browser/webui/web_ui_navigation_browsertest.cc
+++ b/content/browser/webui/web_ui_navigation_browsertest.cc
@@ -25,6 +25,7 @@
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/scoped_web_ui_controller_factory_registration.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
@@ -1225,6 +1226,46 @@
}
}
+// Verify that renderer-initiated navigations from chrome-untrusted:// frames
+// have is_renderer_initiated() == true.
+IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest,
+ UntrustedWebUIsAreRendererInitiated) {
+ WebUIConfigMap::GetInstance().AddUntrustedWebUIConfig(
+ std::make_unique<ui::TestUntrustedWebUIConfig>("test-host"));
+
+ GURL untrusted_url1(GetChromeUntrustedUIURL("test-host/title1.html"));
+ GURL untrusted_url2(GetChromeUntrustedUIURL("test-host/title2.html"));
+
+ EXPECT_TRUE(NavigateToURL(shell(), untrusted_url1));
+
+ NavigationHandleObserver untrusted_observer(shell()->web_contents(),
+ untrusted_url2);
+ TestNavigationObserver untrusted_nav_observer(shell()->web_contents(), 1);
+ EXPECT_TRUE(
+ ExecJs(shell(), JsReplace("location.href = $1;", untrusted_url2)));
+ untrusted_nav_observer.Wait();
+ EXPECT_TRUE(untrusted_observer.has_committed());
+ EXPECT_TRUE(untrusted_observer.is_renderer_initiated());
+}
+
+// Verify that renderer-initiated navigations from trusted chrome:// frames
+// count as browser-initiated (is_renderer_initiated() == false).
+IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest,
+ TrustedWebUIsAreBrowserInitiated) {
+ GURL trusted_url1(GetWebUIURL("web-ui/title1.html"));
+ GURL trusted_url2(GetWebUIURL("web-ui/title2.html"));
+
+ EXPECT_TRUE(NavigateToURL(shell(), trusted_url1));
+
+ NavigationHandleObserver trusted_observer(shell()->web_contents(),
+ trusted_url2);
+ TestNavigationObserver trusted_nav_observer(shell()->web_contents(), 1);
+ EXPECT_TRUE(ExecJs(shell(), JsReplace("location.href = $1;", trusted_url2)));
+ trusted_nav_observer.Wait();
+ EXPECT_TRUE(trusted_observer.has_committed());
+ EXPECT_FALSE(trusted_observer.is_renderer_initiated());
+}
+
class AdditionalSchemesWebUINavigationBrowserTest : public ContentBrowserTest {
public:
AdditionalSchemesWebUINavigationBrowserTest() {
Loading diff…
Original Bug Report
reported by [email protected]
Stop treating navigations from chrome-untrusted:// as browser-initiated
Splitting this part of issue 497957278 out:
Navigator::RequestOpenURL contains a flawed check:
if (render_frame_host->web_ui()) {
// ...
// Navigations in Web UI pages count as browser-initiated navigations.
params.is_renderer_initiated = false;
}
Since the chrome-untrusted:// frame has a WebUI object, the code unconditionally sets params.is_renderer_initiated = false, turning potentially malicious renderer-initiated navigation requests into trusted browser-initiated requests.
AI-suggested fix:
Navigator::RequestOpenURL should not blindly set params.is_renderer_initiated = false for all WebUIs. It should ideally check WebUIController::GetTrustPolicy() and only launder navigations for trusted WebUIs.
References
On This Page