Chrome · Browser › WebUI Toolbar
CVE-2026-106288
Authorization Error in Browser (WebUI Toolbar)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
WebUIAppMenuBrowserTestchrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc |
modified | |
IN_PROC_BROWSER_TEST_Fchrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc |
modified |
Files Changed
chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.ccchrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc
Patch
From 7fc5516d7af3c3f55a6d131295aeed4a22b5e340 Mon Sep 17 00:00:00 2001
From: Tianyang Xu <xtlsheep@google.com>
Date: Wed, 09 Sep 2026 14:02:28 -0700
Subject: [PATCH] [WebUI Toolbar] Block renderer-originated file drops and reset stale drag state
WebUI Toolbar previously cached dragged file paths in
PreHandleDragUpdate without verifying whether the drag originated from
an untrusted renderer (!did_originate_from_renderer). This allowed a
malicious page to initiate a drag operation that tricked the toolbar
into caching a sensitive local file path, which would then be navigated
to upon drop.
Additionally, cached file paths were not cleared when the drag exited
the toolbar or when the drag session ended, opening a two-stage attack
vector where an untrusted renderer drag could consume a previously
cached file path. Furthermore, dropping on the Home button did not
validate drag origin.
This CL resolves these issues by:
- Requiring !did_originate_from_renderer before caching dragged file paths or positions in PreHandleDragUpdate, treating ChromeOS conservatively as renderer-originated.
- Proactively clearing cached file path and position when a renderer-originated drag is detected in PreHandleDragUpdate or CanDragEnter.
- Implementing PreHandleDragExit() and HandleDragEnded() overrides to clear cached drag state whenever the drag exits the view or terminates.
- Adding origin validation in ConsumeDroppedUrl() as defense-in-depth, and resetting the origin state flag after consumption to prevent state leakage.
- Checking drag_originated_from_renderer in OnHomeButtonDropUrl() to block renderer drags while preserving the ability for users to set local files as homepage via external file manager drag-and-drop.
Fixed: 553274076
Change-Id: Ibec8e5d074a42533e1257ac2667fa4f49d7de7d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8338491
Commit-Queue: Tianyang Xu <xtlsheep@google.com>
Reviewed-by: Paul Jensen <pauljensen@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1694855}
---
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc
index 30420f98..b477096 100644
--- a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc
+++ b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view.cc
@@ -226,26 +226,31 @@
// views::WebView:
void PreHandleDragUpdate(const content::DropData& drop_data,
const gfx::PointF& client_pt) override {
- if (!drop_data.filenames.empty()) {
+ bool did_originate_from_renderer = drop_data.did_originate_from_renderer;
+#if BUILDFLAG(IS_CHROMEOS)
+ // On ChromeOS, drag origin provenance cannot be reliably distinguished
+ // between OS-local and renderer sources (b/256022714). To ensure security
+ // by default, all drags are conservatively treated as renderer-originated.
+ did_originate_from_renderer = true;
+#endif
+ if (!did_originate_from_renderer && !drop_data.filenames.empty()) {
cached_dragged_file_path_ = drop_data.filenames.front().path;
cached_dragged_file_position_ = client_pt;
+ } else if (did_originate_from_renderer) {
+ ClearCachedDraggedFile();
}
webui_toolbar::WebUIToolbarDragState::GetOrCreateForWebContents(
web_contents())
- ->set_drag_originated_from_renderer(
- drop_data.did_originate_from_renderer);
+ ->set_drag_originated_from_renderer(did_originate_from_renderer);
}
+ void PreHandleDragExit() override { ClearCachedDraggedFile(); }
+
+ void HandleDragEnded() override { ClearCachedDraggedFile(); }
+
bool CanDragEnter(content::WebContents* source,
const content::DropData& data,
blink::DragOperationsMask operations_allowed) override {
- // Cache the drag origin on the WebContents. This is needed because the
- // subsequent Mojo navigation calls (Navigate/NavigateText) do not receive
- // did_originate_from_renderer information from the drop event directly.
- webui_toolbar::WebUIToolbarDragState::GetOrCreateForWebContents(
- web_contents())
- ->set_drag_originated_from_renderer(data.did_originate_from_renderer);
-
// TODO(xtlsheep): We ideally want to block `javascript:` text drags over
// the general toolbar area (showing a forbidden cursor) to prevent
// self-XSS, while still allowing them to be dropped specifically into the
@@ -327,12 +332,16 @@
url = net::FilePathToFileURL(*cached_dragged_file_path_);
}
}
- cached_dragged_file_path_.reset();
- cached_dragged_file_position_.reset();
+ ClearCachedDraggedFile();
return url;
}
private:
+ void ClearCachedDraggedFile() {
+ cached_dragged_file_path_.reset();
+ cached_dragged_file_position_.reset();
+ }
+
#if BUILDFLAG(IS_MAC)
std::unique_ptr<WebUIToolbarEventForwarder> forwarder_;
#endif
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc
index 9a564013..4ea5f9a 100644
--- a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc
+++ b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc
@@ -2499,8 +2499,6 @@
.ExtractBool();
});
}
-
-
};
class WebUIAppMenuBrowserTest : public WebUIToolbarWebViewBrowserTest {
@@ -4022,7 +4020,16 @@
committed_url.query().find("q=hello%20world") != std::string::npos);
}
-IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewBrowserTest, DropFileOnToolbar) {
+// On ChromeOS, drag origin provenance cannot be distinguished between OS-local
+// and renderer sources (b/256022714), so all drags are conservatively treated
+// as renderer-originated and local file navigation via drag is blocked.
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_DropFileOnToolbar DISABLED_DropFileOnToolbar
+#else
+#define MAYBE_DropFileOnToolbar DropFileOnToolbar
+#endif
+IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewBrowserTest,
+ MAYBE_DropFileOnToolbar) {
ui::TrackedElement* element = nullptr;
WebUIToolbarWebView* webui_toolbar_view = nullptr;
views::WebView* web_view = nullptr;
@@ -4072,6 +4079,109 @@
->GetLastCommittedURL());
}
+IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewBrowserTest,
+ DropFileOnToolbar_BlockedRendererOriginated) {
+ ui::TrackedElement* element = nullptr;
+ WebUIToolbarWebView* webui_toolbar_view = nullptr;
+ views::WebView* web_view = nullptr;
+ ASSERT_NO_FATAL_FAILURE(SetUpWebUI(kWebUIToolbarElementIdentifier, &element,
+ &webui_toolbar_view, &web_view,
+ browser()));
+
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath file_path = temp_dir.GetPath().AppendASCII("secret.html");
+ ASSERT_TRUE(base::WriteFile(file_path, "<html><body>secret</body></html>"));
+
+ gfx::Point click_point(10, 10);
+
+ content::DropData drop_data;
+ drop_data.did_originate_from_renderer = true;
+ drop_data.filenames.emplace_back(file_path, base::FilePath());
+
+ web_view->GetWebContents()->GetDelegate()->PreHandleDragUpdate(
+ drop_data, gfx::PointF(click_point));
+
+ content::WebContents* active_contents =
+ browser()->GetTabStripModel()->GetActiveWebContents();
+ GURL initial_url = active_contents->GetLastCommittedURL();
+ NavigationCounter counter(active_contents);
+
+ EXPECT_TRUE(
+ content::ExecJs(web_view->GetWebContents(),
+ base::StringPrintf(R"(
+ const toolbarApp = document.querySelector('toolbar-app');
+ const dataTransfer = new DataTransfer();
+ Object.defineProperty(dataTransfer, 'types', {value: ['Files']});
+ const dropEvent = new DragEvent('drop', {
+ bubbles: true,
+ cancelable: true,
+ dataTransfer: dataTransfer,
+ clientX: %d,
+ clientY: %d
+ });
+ toolbarApp.dispatchEvent(dropEvent);
+ )",
+ click_point.x(), click_point.y())));
+
+ counter.WaitForNoNavigations();
+ EXPECT_EQ(initial_url, active_contents->GetLastCommittedURL());
+}
+
+IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewBrowserTest,
+ DropFileOnToolbar_BlockedAfterDragExit) {
+ ui::TrackedElement* element = nullptr;
+ WebUIToolbarWebView* webui_toolbar_view = nullptr;
+ views::WebView* web_view = nullptr;
+ ASSERT_NO_FATAL_FAILURE(SetUpWebUI(kWebUIToolbarElementIdentifier, &element,
+ &webui_toolbar_view, &web_view,
+ browser()));
+
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath file_path = temp_dir.GetPath().AppendASCII("test.html");
+ ASSERT_TRUE(base::WriteFile(file_path, "<html><body>test</body></html>"));
+
+ gfx::Point click_point(10, 10);
+
+ content::DropData drop_data;
+ drop_data.did_originate_from_renderer = false;
+ drop_data.filenames.emplace_back(file_path, base::FilePath());
+
+ auto* delegate = web_view->GetWebContents()->GetDelegate();
+ delegate->PreHandleDragUpdate(drop_data, gfx::PointF(click_point));
+
+ // Drag exits the toolbar area before dropping.
+ delegate->PreHandleDragExit();
+
+ content::WebContents* active_contents =
+ browser()->GetTabStripModel()->GetActiveWebContents();
+ GURL initial_url = active_contents->GetLastCommittedURL();
+ NavigationCounter counter(active_contents);
+
+ EXPECT_TRUE(
+ content::ExecJs(web_view->GetWebContents(),
+ base::StringPrintf(R"(
+ const toolbarApp = document.querySelector('toolbar-app');
+ const dataTransfer = new DataTransfer();
+ Object.defineProperty(dataTransfer, 'types', {value: ['Files']});
+ const dropEvent = new DragEvent('drop', {
+ bubbles: true,
+ cancelable: true,
+ dataTransfer: dataTransfer,
+ clientX: %d,
+ clientY: %d
+ });
+ toolbarApp.dispatchEvent(dropEvent);
+ )",
+ click_point.x(), click_point.y())));
+
+ counter.WaitForNoNavigations();
+ EXPECT_EQ(initial_url, active_contents->GetLastCommittedURL());
+}
+
IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewBrowserTest, LoadExtension) {
ASSERT_TRUE(embedded_test_server()->Start());
@@ -4667,7 +4777,16 @@
GetOmniboxTextEventually(web_contents));
}
-IN_PROC_BROWSER_TEST_F(WebUIReadOnlyOmniboxDragDropBrowserTest, DropFilePath) {
+// On ChromeOS, drag origin provenance cannot be distinguished between OS-local
+// and renderer sources (b/256022714), so all drags are conservatively treated
+// as renderer-originated and local file drops on Omnibox are blocked.
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_DropFilePath DISABLED_DropFilePath
+#else
+#define MAYBE_DropFilePath DropFilePath
+#endif
+IN_PROC_BROWSER_TEST_F(WebUIReadOnlyOmniboxDragDropBrowserTest,
+ MAYBE_DropFilePath) {
ui::TrackedElement* element = nullptr;
WebUIToolbarWebView* webui_toolbar_view = nullptr;
views::WebView* web_view = nullptr;
@@ -4998,10 +5117,16 @@
EXPECT_TRUE(prefs->GetBoolean(prefs::kHomePageIsNewTabPage));
}
-// Verify that dropping a file on the home button sets it as the home page,
-// and the action can be undone.
+// On ChromeOS, drag origin provenance cannot be distinguished between OS-local
+// and renderer sources (b/256022714), so all drags are conservatively treated
+// as renderer-originated and home button file drop is blocked.
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_DropFileOnHomeButtonAndUndo DISABLED_DropFileOnHomeButtonAndUndo
+#else
+#define MAYBE_DropFileOnHomeButtonAndUndo DropFileOnHomeButtonAndUndo
+#endif
IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewHomeButtonBrowserTest,
- DropFileOnHomeButtonAndUndo) {
+ MAYBE_DropFileOnHomeButtonAndUndo) {
WebUIToolbarWebView* webui_toolbar_view = SetUpAndPinHomeButton(browser());
content::WebContents* web_contents =
webui_toolbar_view->GetWebViewForTesting()->GetWebContents();
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc
index 9a564013..4ea5f9a 100644
--- a/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc
+++ b/chrome/browser/ui/views/toolbar/webui_toolbar_web_view_browsertest.cc
@@ -2499,8 +2499,6 @@
.ExtractBool();
});
}
-
-
};
class WebUIAppMenuBrowserTest : public WebUIToolbarWebViewBrowserTest {
@@ -4022,7 +4020,16 @@
committed_url.query().find("q=hello%20world") != std::string::npos);
}
-IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewBrowserTest, DropFileOnToolbar) {
+// On ChromeOS, drag origin provenance cannot be distinguished between OS-local
+// and renderer sources (b/256022714), so all drags are conservatively treated
+// as renderer-originated and local file navigation via drag is blocked.
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_DropFileOnToolbar DISABLED_DropFileOnToolbar
+#else
+#define MAYBE_DropFileOnToolbar DropFileOnToolbar
+#endif
+IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewBrowserTest,
+ MAYBE_DropFileOnToolbar) {
ui::TrackedElement* element = nullptr;
WebUIToolbarWebView* webui_toolbar_view = nullptr;
views::WebView* web_view = nullptr;
@@ -4072,6 +4079,109 @@
->GetLastCommittedURL());
}
+IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewBrowserTest,
+ DropFileOnToolbar_BlockedRendererOriginated) {
+ ui::TrackedElement* element = nullptr;
+ WebUIToolbarWebView* webui_toolbar_view = nullptr;
+ views::WebView* web_view = nullptr;
+ ASSERT_NO_FATAL_FAILURE(SetUpWebUI(kWebUIToolbarElementIdentifier, &element,
+ &webui_toolbar_view, &web_view,
+ browser()));
+
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath file_path = temp_dir.GetPath().AppendASCII("secret.html");
+ ASSERT_TRUE(base::WriteFile(file_path, "<html><body>secret</body></html>"));
+
+ gfx::Point click_point(10, 10);
+
+ content::DropData drop_data;
+ drop_data.did_originate_from_renderer = true;
+ drop_data.filenames.emplace_back(file_path, base::FilePath());
+
+ web_view->GetWebContents()->GetDelegate()->PreHandleDragUpdate(
+ drop_data, gfx::PointF(click_point));
+
+ content::WebContents* active_contents =
+ browser()->GetTabStripModel()->GetActiveWebContents();
+ GURL initial_url = active_contents->GetLastCommittedURL();
+ NavigationCounter counter(active_contents);
+
+ EXPECT_TRUE(
+ content::ExecJs(web_view->GetWebContents(),
+ base::StringPrintf(R"(
+ const toolbarApp = document.querySelector('toolbar-app');
+ const dataTransfer = new DataTransfer();
+ Object.defineProperty(dataTransfer, 'types', {value: ['Files']});
+ const dropEvent = new DragEvent('drop', {
+ bubbles: true,
+ cancelable: true,
+ dataTransfer: dataTransfer,
+ clientX: %d,
+ clientY: %d
+ });
+ toolbarApp.dispatchEvent(dropEvent);
+ )",
+ click_point.x(), click_point.y())));
+
+ counter.WaitForNoNavigations();
+ EXPECT_EQ(initial_url, active_contents->GetLastCommittedURL());
+}
+
+IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewBrowserTest,
+ DropFileOnToolbar_BlockedAfterDragExit) {
+ ui::TrackedElement* element = nullptr;
+ WebUIToolbarWebView* webui_toolbar_view = nullptr;
+ views::WebView* web_view = nullptr;
+ ASSERT_NO_FATAL_FAILURE(SetUpWebUI(kWebUIToolbarElementIdentifier, &element,
+ &webui_toolbar_view, &web_view,
+ browser()));
+
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath file_path = temp_dir.GetPath().AppendASCII("test.html");
+ ASSERT_TRUE(base::WriteFile(file_path, "<html><body>test</body></html>"));
+
+ gfx::Point click_point(10, 10);
+
+ content::DropData drop_data;
+ drop_data.did_originate_from_renderer = false;
+ drop_data.filenames.emplace_back(file_path, base::FilePath());
+
+ auto* delegate = web_view->GetWebContents()->GetDelegate();
+ delegate->PreHandleDragUpdate(drop_data, gfx::PointF(click_point));
+
+ // Drag exits the toolbar area before dropping.
+ delegate->PreHandleDragExit();
+
+ content::WebContents* active_contents =
+ browser()->GetTabStripModel()->GetActiveWebContents();
+ GURL initial_url = active_contents->GetLastCommittedURL();
+ NavigationCounter counter(active_contents);
+
+ EXPECT_TRUE(
+ content::ExecJs(web_view->GetWebContents(),
+ base::StringPrintf(R"(
+ const toolbarApp = document.querySelector('toolbar-app');
+ const dataTransfer = new DataTransfer();
+ Object.defineProperty(dataTransfer, 'types', {value: ['Files']});
+ const dropEvent = new DragEvent('drop', {
+ bubbles: true,
+ cancelable: true,
+ dataTransfer: dataTransfer,
+ clientX: %d,
+ clientY: %d
+ });
+ toolbarApp.dispatchEvent(dropEvent);
+ )",
+ click_point.x(), click_point.y())));
+
+ counter.WaitForNoNavigations();
+ EXPECT_EQ(initial_url, active_contents->GetLastCommittedURL());
+}
+
IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewBrowserTest, LoadExtension) {
ASSERT_TRUE(embedded_test_server()->Start());
@@ -4667,7 +4777,16 @@
GetOmniboxTextEventually(web_contents));
}
-IN_PROC_BROWSER_TEST_F(WebUIReadOnlyOmniboxDragDropBrowserTest, DropFilePath) {
+// On ChromeOS, drag origin provenance cannot be distinguished between OS-local
+// and renderer sources (b/256022714), so all drags are conservatively treated
+// as renderer-originated and local file drops on Omnibox are blocked.
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_DropFilePath DISABLED_DropFilePath
+#else
+#define MAYBE_DropFilePath DropFilePath
+#endif
+IN_PROC_BROWSER_TEST_F(WebUIReadOnlyOmniboxDragDropBrowserTest,
+ MAYBE_DropFilePath) {
ui::TrackedElement* element = nullptr;
WebUIToolbarWebView* webui_toolbar_view = nullptr;
views::WebView* web_view = nullptr;
@@ -4998,10 +5117,16 @@
EXPECT_TRUE(prefs->GetBoolean(prefs::kHomePageIsNewTabPage));
}
-// Verify that dropping a file on the home button sets it as the home page,
-// and the action can be undone.
+// On ChromeOS, drag origin provenance cannot be distinguished between OS-local
+// and renderer sources (b/256022714), so all drags are conservatively treated
+// as renderer-originated and home button file drop is blocked.
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_DropFileOnHomeButtonAndUndo DISABLED_DropFileOnHomeButtonAndUndo
+#else
+#define MAYBE_DropFileOnHomeButtonAndUndo DropFileOnHomeButtonAndUndo
+#endif
IN_PROC_BROWSER_TEST_F(WebUIToolbarWebViewHomeButtonBrowserTest,
- DropFileOnHomeButtonAndUndo) {
+ MAYBE_DropFileOnHomeButtonAndUndo) {
WebUIToolbarWebView* webui_toolbar_view = SetUpAndPinHomeButton(browser());
content::WebContents* web_contents =
webui_toolbar_view->GetWebViewForTesting()->GetWebContents();
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