CVE-2026-79276
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/profile_resetter/BUILD.gn |
modified | |
PinnedTabsResetTestchrome/browser/profile_resetter/profile_resetter_browsertest.cc |
modified |
Files Changed
chrome/browser/profile_resetter/BUILD.gnchrome/browser/profile_resetter/profile_resetter.ccchrome/browser/profile_resetter/profile_resetter_browsertest.cc
Patch
From c802d09da19967a00e4aee491b76e4707f9bb8e7 Mon Sep 17 00:00:00 2001
From: Eriko Kurimoto <elkurin@google.com>
Date: Wed, 01 Jul 2026 20:42:23 -0700
Subject: [PATCH] Revoke active File System Access grants on profile reset
ProfileResetter::ResetContentSettings clears every type in
HostContentSettingsMap, but ChromeFileSystemAccessPermissionContext
keeps active read/write grants in an in-memory map outside the
content-settings store. Clear Browsing Data already calls
RevokeAllActiveGrants() for the DATA_TYPE_CONTENT_SETTINGS path; mirror
that here so that resetting site permissions via chrome://settings/reset
also drops any in-memory File System Access grants for still-open tabs.
Add a browser test that obtains a write grant via showSaveFilePicker(),
runs the CONTENT_SETTINGS reset, and checks that the origin no longer
has read or write access.
Bug: 517580738
Change-Id: I9e26e10388d6d2263eb3541603ea005193badbac
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8029722
Reviewed-by: Dominic Battré <battre@chromium.org>
Commit-Queue: Eriko Kurimoto <elkurin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1655878}
---
diff --git a/chrome/browser/profile_resetter/BUILD.gn b/chrome/browser/profile_resetter/BUILD.gn
index 14af49d..5777f77 100644
--- a/chrome/browser/profile_resetter/BUILD.gn
+++ b/chrome/browser/profile_resetter/BUILD.gn
@@ -60,6 +60,7 @@
"//chrome/browser/browsing_data:constants",
"//chrome/browser/content_settings:content_settings_factory",
"//chrome/browser/extensions",
+ "//chrome/browser/file_system_access",
"//chrome/browser/google",
"//chrome/browser/prefs:util",
"//chrome/browser/profiles:profile",
@@ -166,12 +167,15 @@
":profile_resetter",
":profile_resetter_test_base",
"//base",
+ "//chrome/browser/file_system_access",
"//chrome/browser/ui",
"//chrome/browser/ui/tabs:tab_strip",
"//chrome/test:test_support_ui",
"//content/test:test_support",
"//testing/gmock",
"//testing/gtest",
+ "//ui/shell_dialogs",
+ "//url",
]
if (is_chromeos) {
diff --git a/chrome/browser/profile_resetter/profile_resetter.cc b/chrome/browser/profile_resetter/profile_resetter.cc
index f10ed08b..37b80b3 100644
--- a/chrome/browser/profile_resetter/profile_resetter.cc
+++ b/chrome/browser/profile_resetter/profile_resetter.cc
@@ -21,6 +21,8 @@
#include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"
+#include "chrome/browser/file_system_access/file_system_access_permission_context_factory.h"
#include "chrome/browser/google/google_brand.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profile_resetter/brandcode_config_fetcher.h"
@@ -313,6 +315,15 @@
map->SetDefaultContentSetting(info->website_settings_info()->type(),
CONTENT_SETTING_DEFAULT);
}
+
+ // Active File System Access grants are kept in memory by the permission
+ // context rather than in HostContentSettingsMap, so they need to be revoked
+ // explicitly.
+ if (auto* permission_context =
+ FileSystemAccessPermissionContextFactory::GetForProfile(profile_)) {
+ permission_context->RevokeAllActiveGrants();
+ }
+
MarkAsDone(CONTENT_SETTINGS);
}
diff --git a/chrome/browser/profile_resetter/profile_resetter_browsertest.cc b/chrome/browser/profile_resetter/profile_resetter_browsertest.cc
index d776c3b..48a77c4 100644
--- a/chrome/browser/profile_resetter/profile_resetter_browsertest.cc
+++ b/chrome/browser/profile_resetter/profile_resetter_browsertest.cc
@@ -6,8 +6,13 @@
#include <memory>
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"
+#include "chrome/browser/file_system_access/file_system_access_permission_context_factory.h"
#include "chrome/browser/profile_resetter/profile_resetter_test_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
@@ -16,6 +21,8 @@
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/file_system_chooser_test_helpers.h"
#include "content/public/test/test_utils.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/cookies/canonical_cookie.h"
@@ -23,6 +30,8 @@
#include "net/cookies/cookie_util.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
+#include "ui/shell_dialogs/select_file_dialog.h"
+#include "url/origin.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "base/containers/to_vector.h"
@@ -178,6 +187,42 @@
EXPECT_FALSE(tester.GetCookie(kCookieHostname, &cookie));
}
+IN_PROC_BROWSER_TEST_F(
+ ProfileResetTest,
+ ResetContentSettingsRevokesActiveFileSystemAccessGrants) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath test_file;
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(), &test_file));
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ const GURL url = embedded_test_server()->GetURL("/title1.html");
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+ ui::SelectFileDialog::SetFactory(
+ std::make_unique<content::FakeSelectFileDialogFactory>(
+ std::vector<base::FilePath>{test_file}));
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ ASSERT_TRUE(content::ExecJs(
+ web_contents,
+ "(async () => { self.handle = await self.showSaveFilePicker(); })()"));
+ ui::SelectFileDialog::SetFactory(nullptr);
+
+ ChromeFileSystemAccessPermissionContext* permission_context =
+ FileSystemAccessPermissionContextFactory::GetForProfile(
+ browser()->profile());
+ const url::Origin origin = url::Origin::Create(url);
+ ASSERT_TRUE(permission_context->OriginHasReadAccess(origin));
+ ASSERT_TRUE(permission_context->OriginHasWriteAccess(origin));
+
+ ResetAndWait(ProfileResetter::CONTENT_SETTINGS);
+
+ EXPECT_FALSE(permission_context->OriginHasReadAccess(origin));
+ EXPECT_FALSE(permission_context->OriginHasWriteAccess(origin));
+}
+
// PinnedTabsResetTest --------------------------------------------------------
class PinnedTabsResetTest : public InProcessBrowserTest,
Regression Test / PoC
diff --git a/chrome/browser/profile_resetter/profile_resetter_browsertest.cc b/chrome/browser/profile_resetter/profile_resetter_browsertest.cc
index d776c3b..48a77c4 100644
--- a/chrome/browser/profile_resetter/profile_resetter_browsertest.cc
+++ b/chrome/browser/profile_resetter/profile_resetter_browsertest.cc
@@ -6,8 +6,13 @@
#include <memory>
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"
+#include "chrome/browser/file_system_access/file_system_access_permission_context_factory.h"
#include "chrome/browser/profile_resetter/profile_resetter_test_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
@@ -16,6 +21,8 @@
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/file_system_chooser_test_helpers.h"
#include "content/public/test/test_utils.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/cookies/canonical_cookie.h"
@@ -23,6 +30,8 @@
#include "net/cookies/cookie_util.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
+#include "ui/shell_dialogs/select_file_dialog.h"
+#include "url/origin.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "base/containers/to_vector.h"
@@ -178,6 +187,42 @@
EXPECT_FALSE(tester.GetCookie(kCookieHostname, &cookie));
}
+IN_PROC_BROWSER_TEST_F(
+ ProfileResetTest,
+ ResetContentSettingsRevokesActiveFileSystemAccessGrants) {
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ base::FilePath test_file;
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(), &test_file));
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ const GURL url = embedded_test_server()->GetURL("/title1.html");
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+ ui::SelectFileDialog::SetFactory(
+ std::make_unique<content::FakeSelectFileDialogFactory>(
+ std::vector<base::FilePath>{test_file}));
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ ASSERT_TRUE(content::ExecJs(
+ web_contents,
+ "(async () => { self.handle = await self.showSaveFilePicker(); })()"));
+ ui::SelectFileDialog::SetFactory(nullptr);
+
+ ChromeFileSystemAccessPermissionContext* permission_context =
+ FileSystemAccessPermissionContextFactory::GetForProfile(
+ browser()->profile());
+ const url::Origin origin = url::Origin::Create(url);
+ ASSERT_TRUE(permission_context->OriginHasReadAccess(origin));
+ ASSERT_TRUE(permission_context->OriginHasWriteAccess(origin));
+
+ ResetAndWait(ProfileResetter::CONTENT_SETTINGS);
+
+ EXPECT_FALSE(permission_context->OriginHasReadAccess(origin));
+ EXPECT_FALSE(permission_context->OriginHasWriteAccess(origin));
+}
+
// PinnedTabsResetTest --------------------------------------------------------
class PinnedTabsResetTest : public InProcessBrowserTest,
Original Bug Report
Potential bypass of Profile Reset via active File System Access grants
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: When performing a profile reset, in-memory File System Access (FSA) active permission grants are not revoked. While the persistent settings are deleted from HostContentSettingsMap, the active grants remain in-memory outside it, potentially allowing open tabs to retain unauthorized file read/write access.
Affected files:
chrome/browser/profile_resetter/profile_resetter.cc
Estimated timestamp from git blame: 2024-03-07
Root Cause Analysis
ChromeFileSystemAccessPermissionContext maintains active File System Access (FSA) read/write permission grants in an in-memory map (active_permissions_map_). This map resides entirely outside of HostContentSettingsMap (HCSM).
When a user triggers a profile reset via chrome://settings/reset, ProfileResetter::ResetContentSettings() is called to revert content settings to defaults:
// chrome/browser/profile_resetter/profile_resetter.cc
void ProfileResetter::ResetContentSettings() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
HostContentSettingsMap* map =
HostContentSettingsMapFactory::GetForProfile(profile_);
for (const content_settings::WebsiteSettingsInfo* info :
*content_settings::WebsiteSettingsRegistry::GetInstance()) {
map->ClearSettingsForOneType(info->type());
}
...
}
While clearing HCSM removes the persistent permission registries (FILE_SYSTEM_ACCESS_CHOOSER_DATA and FILE_SYSTEM_ACCESS_EXTENDED_PERMISSION), ProfileResetter fails to clear the active in-memory grants. Because ChromeFileSystemAccessPermissionContext does not observe or receive automatic notifications of HCSM changes, the active grants inside active_permissions_map_ continue to remain active with their status set to PermissionStatus::GRANTED.
In contrast, the Clear Browsing Data flow explicitly handles this out-of-HCSM cleanup inside chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc:
if (remove_mask & constants::DATA_TYPE_CONTENT_SETTINGS) {
...
if (auto* permission_context =
FileSystemAccessPermissionContextFactory::GetForProfile(profile_)) {
permission_context->RevokeAllActiveGrants();
}
}
Potential Attack Scenario / Reproduction Steps
Note: The following are suggested/potential steps compiled from static code analysis, as our tooling environment does not support executing runtime tests or running code.
- A user visits an attacker-controlled page (e.g.,
https://attacker.example). - The page calls
window.showSaveFilePicker()and the user chooses a local file, granting active write permission. - The attacker page stores the file handle in JavaScript (keeping the Mojo connection and the corresponding browser-side
FileSystemAccessPermissionGrantImplref-counts alive). - In another tab, the user goes to
chrome://settings/resetand clicks “Reset settings”. - After the reset completes, the attacker page (still open in the background/foreground) attempts to write to the file via the retained handle (e.g., calling
handle.createWritable()). - Since the browser-side active grant status was not revoked,
GetStatus()continues to returnPermissionStatus::GRANTED, bypassing the reset guard setting and silently executing the write operation.
Security Impact
This is a potential permission/privilege persistence issue. A user who performs a profile reset expects all active capabilities and permissions across all sites to be revoked. Surviving active FSA grants violate this expectation, potentially letting untrusted pages silently read or write to previously-granted local files or directories for the remainder of the tab’s lifetime.
Suggested Remediation
Modify ProfileResetter::ResetContentSettings to explicitly retrieve the ChromeFileSystemAccessPermissionContext and call RevokeAllActiveGrants() when resetting content settings, matching the behavior of the Clear Browsing Data flow:
// In chrome/browser/profile_resetter/profile_resetter.cc
#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"
#include "chrome/browser/file_system_access/file_system_access_permission_context_factory.h"
void ProfileResetter::ResetContentSettings() {
...
if (auto* permission_context =
FileSystemAccessPermissionContextFactory::GetForProfile(profile_)) {
permission_context->RevokeAllActiveGrants();
}
MarkAsDone(CONTENT_SETTINGS);
}
Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.