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 <[email protected]> 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 <[email protected]> Commit-Queue: Alison Gale <[email protected]> Reviewed-by: Avi Drissman <[email protected]> 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