Chrome · SplitView
CVE-2026-14026
Logic Error in SplitView
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/file_select_helper.cc |
modified | |
ifchrome/browser/file_select_helper_unittest.cc |
modified | |
TEST_Fchrome/browser/file_select_helper_unittest.cc |
modified |
Files Changed
chrome/browser/file_select_helper.ccchrome/browser/file_select_helper.hchrome/browser/file_select_helper_unittest.cc
Patch
From d0a7e6cba457479e58b1f5b5c7c34333372b81ec Mon Sep 17 00:00:00 2001
From: Alison Gale <agale@chromium.org>
Date: Fri, 08 May 2026 09:04:56 -0700
Subject: [PATCH] [SxS] Close directory enumeration on tab switch
This is similar to the change I just made for RunFileChooser and ensures
when tab visibility changes within split views we close out open
file/directory pickers
Bug: 507263861
Change-Id: I9ee5520fe0de942bae4d7a4ae68dcd3599f9fca0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7805653
Reviewed-by: Bryan Oltman <bryanoltman@google.com>
Commit-Queue: Alison Gale <agale@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1627691}
---
diff --git a/chrome/browser/file_select_helper.cc b/chrome/browser/file_select_helper.cc
index a269fd1..1525b49 100644
--- a/chrome/browser/file_select_helper.cc
+++ b/chrome/browser/file_select_helper.cc
@@ -556,15 +556,7 @@
render_frame_host_ = render_frame_host;
web_contents_ = WebContents::FromRenderFrameHost(render_frame_host);
listener_ = std::move(listener);
- content::WebContentsObserver::Observe(web_contents_);
-
- tabs::TabInterface* tab_interface =
- tabs::TabInterface::MaybeGetFromContents(web_contents_);
- if (tab_interface) {
- tab_deactivated_subscription_ =
- tab_interface->RegisterWillDeactivate(base::BindRepeating(
- &FileSelectHelper::OnTabDeactivated, base::Unretained(this)));
- }
+ InitLifecycleObserver(web_contents_);
#if !BUILDFLAG(IS_ANDROID)
if (PictureInPictureWindowManager::GetInstance()
@@ -686,12 +678,14 @@
#endif // !BUILDFLAG(IS_ANDROID)
tab_deactivated_subscription_ = {};
+ directory_enumeration_.reset();
// If there are temporary files, then this instance needs to stick around
// until web_contents_ is destroyed, so that this instance can delete the
// temporary files.
- if (!temporary_files_.empty())
+ if (!temporary_files_.empty()) {
return;
+ }
if (listener_) {
listener_->FileSelectionCanceled();
@@ -717,6 +711,7 @@
dialog_type_ = ui::SelectFileDialog::SELECT_NONE;
web_contents_ = tab;
listener_ = std::move(listener);
+ InitLifecycleObserver(web_contents_);
// Because this class returns notifications to the RenderViewHost, it is
// difficult for callers to know how long to keep a reference to this
// instance. We keep a reference to ourself to keep the instance alive after
@@ -738,7 +733,7 @@
// code. Perform any cleanup and release the reference we added in
// EnumerateDirectoryImpl().
void FileSelectHelper::EnumerateDirectoryEnd() {
- self_ptr_.reset();
+ RunFileChooserEnd();
}
void FileSelectHelper::RenderFrameHostChanged(
@@ -768,6 +763,20 @@
CleanUp();
}
+void FileSelectHelper::InitLifecycleObserver(
+ content::WebContents* web_contents) {
+ DCHECK(web_contents);
+ content::WebContentsObserver::Observe(web_contents);
+
+ tabs::TabInterface* tab_interface =
+ tabs::TabInterface::MaybeGetFromContents(web_contents);
+ if (tab_interface) {
+ tab_deactivated_subscription_ =
+ tab_interface->RegisterWillDeactivate(base::BindRepeating(
+ &FileSelectHelper::OnTabDeactivated, base::Unretained(this)));
+ }
+}
+
void FileSelectHelper::OnTabDeactivated(tabs::TabInterface* tab) {
RunFileChooserEnd();
}
diff --git a/chrome/browser/file_select_helper.h b/chrome/browser/file_select_helper.h
index d28286f..19f9d43 100644
--- a/chrome/browser/file_select_helper.h
+++ b/chrome/browser/file_select_helper.h
@@ -119,6 +119,8 @@
FRIEND_TEST_ALL_PREFIXES(FileSelectHelperTest, ConfirmationDialog);
FRIEND_TEST_ALL_PREFIXES(FileSelectHelperTest,
WebContentsDestroyedDuringAsyncFileProcessing);
+ FRIEND_TEST_ALL_PREFIXES(FileSelectHelperTest,
+ EnumerateDirectory_TabDeactivated);
FRIEND_TEST_ALL_PREFIXES(policy::DlpFilesControllerAshBrowserTest,
FilesUploadCallerPassed);
@@ -150,6 +152,7 @@
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
void WebContentsDestroyed() override;
+ void InitLifecycleObserver(content::WebContents* web_contents);
void OnTabDeactivated(tabs::TabInterface* tab);
void EnumerateDirectoryImpl(
diff --git a/chrome/browser/file_select_helper_unittest.cc b/chrome/browser/file_select_helper_unittest.cc
index ea51163..5d18923 100644
--- a/chrome/browser/file_select_helper_unittest.cc
+++ b/chrome/browser/file_select_helper_unittest.cc
@@ -22,8 +22,12 @@
#include "build/build_config.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_profile.h"
+#include "components/tabs/public/mock_tab_interface.h"
#include "content/public/browser/file_select_listener.h"
+#include "content/public/browser/web_contents_user_data.h"
#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_web_contents_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/test/test_dialog_model_host.h"
@@ -42,17 +46,22 @@
std::vector<blink::mojom::FileChooserFileInfoPtr>* files)
: files_(files) {}
+ bool canceled() const { return canceled_; }
+
private:
~TestFileSelectListener() override = default;
// content::FileSelectListener overrides.
void FileSelected(std::vector<blink::mojom::FileChooserFileInfoPtr> files,
const base::FilePath& base_dir,
blink::mojom::FileChooserParams::Mode mode) override {
- *files_ = std::move(files);
+ if (files_) {
+ *files_ = std::move(files);
+ }
}
- void FileSelectionCanceled() override {}
+ void FileSelectionCanceled() override { canceled_ = true; }
raw_ptr<std::vector<blink::mojom::FileChooserFileInfoPtr>> files_;
+ bool canceled_ = false;
};
// Fill in the arguments to be passed to the ContentAnalysisCompletionCallback()
@@ -586,6 +595,37 @@
task_environment.RunUntilIdle();
EXPECT_FALSE(weak_ptr);
}
+
+TEST_F(FileSelectHelperTest, EnumerateDirectory_TabDeactivated) {
+ content::BrowserTaskEnvironment task_environment;
+ TestingProfile profile;
+ content::TestWebContentsFactory web_contents_factory;
+ content::WebContents* web_contents =
+ web_contents_factory.CreateWebContents(&profile);
+
+ tabs::MockTabInterface mock_tab;
+ EXPECT_CALL(mock_tab, GetContents())
+ .WillRepeatedly(testing::Return(web_contents));
+ tabs::TabLookupFromWebContents::CreateForWebContents(web_contents, &mock_tab);
+
+ base::RepeatingCallback<void(tabs::TabInterface*)> deactivation_callback;
+ EXPECT_CALL(mock_tab, RegisterWillDeactivate(testing::_))
+ .WillOnce([&](base::RepeatingCallback<void(tabs::TabInterface*)> cb) {
+ deactivation_callback = std::move(cb);
+ return base::CallbackListSubscription();
+ });
+
+ std::vector<blink::mojom::FileChooserFileInfoPtr> files;
+ auto listener = base::MakeRefCounted<TestFileSelectListener>(&files);
+
+ FileSelectHelper::EnumerateDirectory(web_contents, listener,
+ base::FilePath(FILE_PATH_LITERAL("/")));
+
+ ASSERT_FALSE(deactivation_callback.is_null());
+ deactivation_callback.Run(&mock_tab);
+
+ EXPECT_TRUE(listener->canceled());
+}
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
TEST_F(FileSelectHelperTest, GetFileTypesFromAcceptType) {
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/file_select_helper_unittest.cc b/chrome/browser/file_select_helper_unittest.cc
index ea51163..5d18923 100644
--- a/chrome/browser/file_select_helper_unittest.cc
+++ b/chrome/browser/file_select_helper_unittest.cc
@@ -22,8 +22,12 @@
#include "build/build_config.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_profile.h"
+#include "components/tabs/public/mock_tab_interface.h"
#include "content/public/browser/file_select_listener.h"
+#include "content/public/browser/web_contents_user_data.h"
#include "content/public/test/browser_task_environment.h"
+#include "content/public/test/test_web_contents_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/test/test_dialog_model_host.h"
@@ -42,17 +46,22 @@
std::vector<blink::mojom::FileChooserFileInfoPtr>* files)
: files_(files) {}
+ bool canceled() const { return canceled_; }
+
private:
~TestFileSelectListener() override = default;
// content::FileSelectListener overrides.
void FileSelected(std::vector<blink::mojom::FileChooserFileInfoPtr> files,
const base::FilePath& base_dir,
blink::mojom::FileChooserParams::Mode mode) override {
- *files_ = std::move(files);
+ if (files_) {
+ *files_ = std::move(files);
+ }
}
- void FileSelectionCanceled() override {}
+ void FileSelectionCanceled() override { canceled_ = true; }
raw_ptr<std::vector<blink::mojom::FileChooserFileInfoPtr>> files_;
+ bool canceled_ = false;
};
// Fill in the arguments to be passed to the ContentAnalysisCompletionCallback()
@@ -586,6 +595,37 @@
task_environment.RunUntilIdle();
EXPECT_FALSE(weak_ptr);
}
+
+TEST_F(FileSelectHelperTest, EnumerateDirectory_TabDeactivated) {
+ content::BrowserTaskEnvironment task_environment;
+ TestingProfile profile;
+ content::TestWebContentsFactory web_contents_factory;
+ content::WebContents* web_contents =
+ web_contents_factory.CreateWebContents(&profile);
+
+ tabs::MockTabInterface mock_tab;
+ EXPECT_CALL(mock_tab, GetContents())
+ .WillRepeatedly(testing::Return(web_contents));
+ tabs::TabLookupFromWebContents::CreateForWebContents(web_contents, &mock_tab);
+
+ base::RepeatingCallback<void(tabs::TabInterface*)> deactivation_callback;
+ EXPECT_CALL(mock_tab, RegisterWillDeactivate(testing::_))
+ .WillOnce([&](base::RepeatingCallback<void(tabs::TabInterface*)> cb) {
+ deactivation_callback = std::move(cb);
+ return base::CallbackListSubscription();
+ });
+
+ std::vector<blink::mojom::FileChooserFileInfoPtr> files;
+ auto listener = base::MakeRefCounted<TestFileSelectListener>(&files);
+
+ FileSelectHelper::EnumerateDirectory(web_contents, listener,
+ base::FilePath(FILE_PATH_LITERAL("/")));
+
+ ASSERT_FALSE(deactivation_callback.is_null());
+ deactivation_callback.Run(&mock_tab);
+
+ EXPECT_TRUE(listener->canceled());
+}
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
TEST_F(FileSelectHelperTest, GetFileTypesFromAcceptType) {
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