CVE-2026-10931
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/file_system_access/file_system_access_bucket_path_watcher.cc |
modified | |
FileSystemAccessBucketPathWatchercontent/browser/file_system_access/file_system_access_bucket_path_watcher.h |
modified | |
MockFileChangeObserverstorage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc |
modified | |
TEST_Fstorage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc |
modified |
Files Changed
content/browser/file_system_access/file_system_access_bucket_path_watcher.cccontent/browser/file_system_access/file_system_access_bucket_path_watcher.hstorage/browser/file_system/sandbox_file_system_backend_delegate.ccstorage/browser/file_system/sandbox_file_system_backend_delegate.hstorage/browser/file_system/sandbox_file_system_backend_delegate_unittest.ccstorage/browser/file_system/task_runner_bound_observer_list.h
Patch
From f72060ed133f01a64f8f3f1827673a15ed4d4c32 Mon Sep 17 00:00:00 2001 From: Fergal Daly <[email protected]> Date: Wed, 27 May 2026 02:59:23 -0700 Subject: [PATCH] [M149] Revert "[FSA] Quick fix UAF in FileSystemAccessBucketPathWatcher" Original change's description: > Revert "[FSA] Quick fix UAF in FileSystemAccessBucketPathWatcher" > > This reverts commit cfafd4297bbd2fad851e37fe145ab3eaf987dad6. > > This was an attempted fix but the POC still reproduces and it causes > other UAFs. > > Reverting it is a step towards landing a more comprehensive fix in > https://crrev.com/c/7805989/15 > > Bug: 497429850 > Fixed: 501115599 > Change-Id: I613c143bc0ae52ebf7103dcc0f8b3ccc5217fb8a > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7805573 > Commit-Queue: Fergal Daly <[email protected]> > Reviewed-by: Ming-Ying Chung <[email protected]> > Reviewed-by: Kalvin Lee <[email protected]> > Cr-Commit-Position: refs/heads/main@{#1631136} (cherry picked from commit 61f666cc52225e0386741b772b4fc60e7777aef4) Bug: 514929217,497429850,501115599 Change-Id: I613c143bc0ae52ebf7103dcc0f8b3ccc5217fb8a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7878605 Bot-Commit: [email protected] <[email protected]> Auto-Submit: chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com <chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com> Commit-Queue: [email protected] <[email protected]> Cr-Commit-Position: refs/branch-heads/7827@{#1840} Cr-Branched-From: 9f3e9aaccba63bd2ec30334e45e0bfd07ebcc8f1-refs/heads/main@{#1625079} --- diff --git a/content/browser/file_system_access/file_system_access_bucket_path_watcher.cc b/content/browser/file_system_access/file_system_access_bucket_path_watcher.cc index 43393b0..f72c91b 100644 --- a/content/browser/file_system_access/file_system_access_bucket_path_watcher.cc +++ b/content/browser/file_system_access/file_system_access_bucket_path_watcher.cc @@ -26,15 +26,8 @@ FileSystemAccessWatchScope::GetScopeForAllBucketFileSystems(), std::move(file_system_context)) {} -FileSystemAccessBucketPathWatcher::~FileSystemAccessBucketPathWatcher() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - storage::SandboxFileSystemBackendDelegate* sandbox_delegate = - file_system_context()->sandbox_delegate(); - if (sandbox_delegate) { - sandbox_delegate->RemoveFileChangeObserver( - storage::FileSystemType::kFileSystemTypeTemporary, this); - } -} +FileSystemAccessBucketPathWatcher::~FileSystemAccessBucketPathWatcher() = + default; void FileSystemAccessBucketPathWatcher::Initialize( base::OnceCallback<void(blink::mojom::FileSystemAccessErrorPtr)> diff --git a/content/browser/file_system_access/file_system_access_bucket_path_watcher.h b/content/browser/file_system_access/file_system_access_bucket_path_watcher.h index bf21e89..238232c0 100644 --- a/content/browser/file_system_access/file_system_access_bucket_path_watcher.h +++ b/content/browser/file_system_access/file_system_access_bucket_path_watcher.h @@ -5,7 +5,6 @@ #ifndef CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_BUCKET_PATH_WATCHER_H_ #define CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_BUCKET_PATH_WATCHER_H_ -#include "base/memory/advanced_memory_safety_checks.h" #include "base/thread_annotations.h" #include "base/types/pass_key.h" #include "content/browser/file_system_access/file_system_access_change_source.h" @@ -23,9 +22,6 @@ // sequence as the `FileSystemContext` it holds a reference to. class FileSystemAccessBucketPathWatcher : public FileSystemAccessChangeSource, public storage::FileChangeObserver { - // TODO(crbug.com/40105284): Remove once verified as fixed. - ADVANCED_MEMORY_SAFETY_CHECKS(); - public: FileSystemAccessBucketPathWatcher( scoped_refptr<storage::FileSystemContext> file_system_context, diff --git a/storage/browser/file_system/sandbox_file_system_backend_delegate.cc b/storage/browser/file_system/sandbox_file_system_backend_delegate.cc index 3401dd23f..9978b2d2 100644 --- a/storage/browser/file_system/sandbox_file_system_backend_delegate.cc +++ b/storage/browser/file_system/sandbox_file_system_backend_delegate.cc @@ -445,12 +445,6 @@ access_observers_[type].AddObserver(observer, task_runner); } -void SandboxFileSystemBackendDelegate::RemoveFileChangeObserver( - FileSystemType type, - FileChangeObserver* observer) { - change_observers_[type] = change_observers_[type].RemoveObserver(observer); -} - const UpdateObserverList* SandboxFileSystemBackendDelegate::GetUpdateObservers( FileSystemType type) const { auto iter = update_observers_.find(type); diff --git a/storage/browser/file_system/sandbox_file_system_backend_delegate.h b/storage/browser/file_system/sandbox_file_system_backend_delegate.h index 72000d7..ac2bb09 100644 --- a/storage/browser/file_system/sandbox_file_system_backend_delegate.h +++ b/storage/browser/file_system/sandbox_file_system_backend_delegate.h @@ -184,9 +184,6 @@ FileAccessObserver* observer, base::SequencedTaskRunner* task_runner); - virtual void RemoveFileChangeObserver(FileSystemType type, - FileChangeObserver* observer); - // Returns observer lists for the specified |type| of a file system. virtual const UpdateObserverList* GetUpdateObservers( FileSystemType type) const; diff --git a/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc b/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc index 8053021..a372ef0 100644 --- a/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc +++ b/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc @@ -12,7 +12,6 @@ #include "base/functional/bind.h" #include "base/task/single_thread_task_runner.h" #include "base/test/task_environment.h" -#include "storage/browser/file_system/file_observers.h" #include "storage/browser/file_system/file_system_url.h" #include "storage/browser/test/mock_quota_manager_proxy.h" #include "storage/browser/test/test_file_system_options.h" @@ -25,22 +24,6 @@ namespace { -class MockFileChangeObserver : public FileChangeObserver { - public: - MockFileChangeObserver() = default; - ~MockFileChangeObserver() override = default; - - void OnCreateFile(const FileSystemURL& url) override {} - void OnCreateFileFrom(const FileSystemURL& url, - const FileSystemURL& src) override {} - void OnMoveFileFrom(const FileSystemURL& url, - const FileSystemURL& src) override {} - void OnRemoveFile(const FileSystemURL& url) override {} - void OnModifyFile(const FileSystemURL& url) override {} - void OnCreateDirectory(const FileSystemURL& url) override {} - void OnRemoveDirectory(const FileSystemURL& url) override {} -}; - FileSystemURL CreateFileSystemURL(const char* path) { return FileSystemURL::CreateForTest( blink::StorageKey::CreateFromStringForTesting("http://foo/"), @@ -86,9 +69,6 @@ return quota_manager_proxy_.get(); } - protected: - std::unique_ptr<SandboxFileSystemBackendDelegate> delegate_; - private: void OpenFileSystemCallback(const GURL& root_url, const std::string& name, @@ -100,6 +80,7 @@ base::ScopedTempDir data_dir_; base::test::TaskEnvironment task_environment_; scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_; + std::unique_ptr<SandboxFileSystemBackendDelegate> delegate_; int callback_count_ = 0; base::File::Error last_error_ = base::File::FILE_OK; @@ -164,23 +145,4 @@ EXPECT_EQ(quota_manager_proxy()->last_notified_storage_key(), storage_key); } -TEST_F(SandboxFileSystemBackendDelegateTest, ObserverRegistration) { - MockFileChangeObserver observer; - delegate_->AddFileChangeObserver(kFileSystemTypeTemporary, &observer, - nullptr); - const ChangeObserverList* observers = - delegate_->GetChangeObservers(kFileSystemTypeTemporary); - ASSERT_TRUE(observers); - EXPECT_FALSE(observers->empty()); - - delegate_->RemoveFileChangeObserver(kFileSystemTypeTemporary, &observer); - observers = delegate_->GetChangeObservers(kFileSystemTypeTemporary); - ASSERT_TRUE(observers); - EXPECT_TRUE(observers->empty()); - - // Test that it returns nullptr for a type that was never added. - observers = delegate_->GetChangeObservers(kFileSystemTypePersistent); - EXPECT_FALSE(observers); -} - } // namespace storage diff --git a/storage/browser/file_system/task_runner_bound_observer_list.h b/storage/browser/file_system/task_runner_bound_observer_list.h index 952d8f4..8ffbd0fe 100644 --- a/storage/browser/file_system/task_runner_bound_observer_list.h +++ b/storage/browser/file_system/task_runner_bound_observer_list.h @@ -53,14 +53,6 @@ return TaskRunnerBoundObserverList(observers); } - TaskRunnerBoundObserverList RemoveObserver(Observer* observer) const { - ObserversListMap observers = observers_; - observers.erase(observer);
Regression Test / PoC
diff --git a/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc b/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc
index 8053021..a372ef0 100644
--- a/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc
+++ b/storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc
@@ -12,7 +12,6 @@
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
-#include "storage/browser/file_system/file_observers.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/browser/test/mock_quota_manager_proxy.h"
#include "storage/browser/test/test_file_system_options.h"
@@ -25,22 +24,6 @@
namespace {
-class MockFileChangeObserver : public FileChangeObserver {
- public:
- MockFileChangeObserver() = default;
- ~MockFileChangeObserver() override = default;
-
- void OnCreateFile(const FileSystemURL& url) override {}
- void OnCreateFileFrom(const FileSystemURL& url,
- const FileSystemURL& src) override {}
- void OnMoveFileFrom(const FileSystemURL& url,
- const FileSystemURL& src) override {}
- void OnRemoveFile(const FileSystemURL& url) override {}
- void OnModifyFile(const FileSystemURL& url) override {}
- void OnCreateDirectory(const FileSystemURL& url) override {}
- void OnRemoveDirectory(const FileSystemURL& url) override {}
-};
-
FileSystemURL CreateFileSystemURL(const char* path) {
return FileSystemURL::CreateForTest(
blink::StorageKey::CreateFromStringForTesting("http://foo/"),
@@ -86,9 +69,6 @@
return quota_manager_proxy_.get();
}
- protected:
- std::unique_ptr<SandboxFileSystemBackendDelegate> delegate_;
-
private:
void OpenFileSystemCallback(const GURL& root_url,
const std::string& name,
@@ -100,6 +80,7 @@
base::ScopedTempDir data_dir_;
base::test::TaskEnvironment task_environment_;
scoped_refptr<MockQuotaManagerProxy> quota_manager_proxy_;
+ std::unique_ptr<SandboxFileSystemBackendDelegate> delegate_;
int callback_count_ = 0;
base::File::Error last_error_ = base::File::FILE_OK;
@@ -164,23 +145,4 @@
EXPECT_EQ(quota_manager_proxy()->last_notified_storage_key(), storage_key);
}
-TEST_F(SandboxFileSystemBackendDelegateTest, ObserverRegistration) {
- MockFileChangeObserver observer;
- delegate_->AddFileChangeObserver(kFileSystemTypeTemporary, &observer,
- nullptr);
- const ChangeObserverList* observers =
- delegate_->GetChangeObservers(kFileSystemTypeTemporary);
- ASSERT_TRUE(observers);
- EXPECT_FALSE(observers->empty());
-
- delegate_->RemoveFileChangeObserver(kFileSystemTypeTemporary, &observer);
- observers = delegate_->GetChangeObservers(kFileSystemTypeTemporary);
- ASSERT_TRUE(observers);
- EXPECT_TRUE(observers->empty());
-
- // Test that it returns nullptr for a type that was never added.
- observers = delegate_->GetChangeObservers(kFileSystemTypePersistent);
- EXPECT_FALSE(observers);
-}
-
} // namespace storage
Original Bug Report
Browser-Process Heap Use-After-Free in FileSystemAccessWatcherManager
VULNERABILITY DETAILS
A browser-process heap use-after-free (UAF) vulnerability exists in the File System Access API due to iterator/lifetime invalidation during change-delivery event dispatch.
FileSystemAccessWatcherManager::OnRawChange(...) iterates over its internal observation groups to notify them of changes. However, the resulting callback chain can synchronously destroy the active FileSystemAccessObservationGroup.
When an observed file root disappears or errors out, the following sequence occurs:
OnRawChange()callsobservation_group.NotifyOfChanges(...)FileSystemAccessObserverObservation::OnChanges()may callHandleError()when the observation root disappears.HandleError()sends an errored event and callshost_->RemoveObservation(this).- Removing the last observation triggers
watcher_manager_->RemoveObservationGroup(...). - This erases the group from the manager-owned map while
OnRawChange()is still actively iterating over it.
When the callback chain finishes and control returns to OnRawChange(), the iterator or container state is invalid. The next operation inside the loop accesses freed memory, causing an immediate crash.
As indicated by the ASan log (MiraclePtr Status: NOT PROTECTED), no raw_ptr<T> access was detected for this specific memory operation. Consequently, this vulnerability is not mitigated by MiraclePtr and results in an exploitable browser-process Use-After-Free, which can potentially be leveraged by a compromised renderer or malicious web page to achieve sandbox escape/RCE.
VERSION
Chrome Version: Chromium 148.0.7763.0 (Official mac-arm64 ASan prebuilt)
Operating System: macOS arm64
REPRODUCTION CASE
Please see the attached poc.html and asan.log files.
-
Extract the provided files into a directory and serve them locally using a python web server:
python3 -m http.server 8001 -
In another terminal, create a fresh profile directory and launch ASan Chromium:
profile_dir=/tmp/fsa_obs_group_uaf_profile_final mkdir -p "$profile_dir" ASAN_OPTIONS='detect_odr_violation=0:abort_on_error=1:symbolize=1:external_symbolizer_path=/path/to/llvm-symbolizer:log_path=/tmp/fsa_obs_group_uaf_final' \ open -n '/path/to/.asan_mac_arm64_run/Chromium.app' --args \ --user-data-dir="$profile_dir" \ --enable-blink-features=FileSystemObserver \ --test-type \ --no-first-run \ --no-default-browser-check \ 'http://127.0.0.1:8001/poc.html?attempts=256&concurrency=16' -
On the loaded page, click Start.
-
In the directory picker, choose any writable local directory.
-
The PoC will rapidly create, observe, and delete files to drive the
disappeared -> errored -> observation teardownpath at high concurrency. The browser process will quickly crash with a UAF.
FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION
Type of crash: Browser Process
Crash State:
=================================================================
==63307==ERROR: AddressSanitizer: heap-use-after-free on address 0x6190006d1d40 at pc 0x000309218ea4 bp 0x00016d9bfff0 sp 0x00016d9bffe8
READ of size 4 at 0x6190006d1d40 thread T0 (Crash Stack):
#0 0x000309218ea0 in content::FileSystemAccessWatcherManager::OnRawChange(...)
#1 0x00030913b300 in content::FileSystemAccessChangeSource::NotifyOfChange(...)
#2 0x00030a812238 in content::FileSystemAccessLocalPathWatcher::OnFilePathChanged(...)
[... truncated sequence manager / run loop ...]
0x6190006d1d40 is located 704 bytes inside of 936-byte region[0x6190006d1a80,0x6190006d1e28)
freed by thread T0 here (Free Stack):
#0 0x0001027d1074 in __asan_memmove
#1 0x00030921a7c0 in content::FileSystemAccessWatcherManager::RemoveObserver(...)
#2 0x0003091f0990 in content::FileSystemAccessObservationGroup::~FileSystemAccessObservationGroup()
#3 0x000309224c50 in std::__Cr::__tree<...>::erase(...)
#4 0x00030921af14 in content::FileSystemAccessWatcherManager::RemoveObservationGroup(...)
#5 0x0003091f2d54 in base::ScopedObservation<...>::Reset()
#6 0x0003091efe70 in content::FileSystemAccessObservationGroup::Observer::~Observer()
#7 0x0003091fef58 in content::FileSystemAccessObserverObservation::~FileSystemAccessObserverObservation()
#8 0x0003091ff178 in content::FileSystemAccessObserverObservation::~FileSystemAccessObserverObservation()
#9 0x0003091fb598 in std::__Cr::vector<...>::erase(...)
#10 0x0003091f75d8 in content::FileSystemAccessObserverHost::RemoveObservation(...)
#11 0x0003091ffa38 in content::FileSystemAccessObserverObservation::HandleError()
#12 0x0003091fe4a8 in content::FileSystemAccessObserverObservation::OnChanges(...)
#13 0x000309201b94 in base::internal::Invoker<...>::Run(...)
#14 0x0003091f0388 in base::RepeatingCallback<...>::Run(...)
#15 0x0003091f1970 in content::FileSystemAccessObservationGroup::NotifyOfChanges(...)
#16 0x000309217da8 in content::FileSystemAccessWatcherManager::OnRawChange(...)
==63307==ADDITIONAL INFO
MiraclePtr Status: NOT PROTECTED
No raw_ptr<T> access to this region was detected prior to this crash.
This crash is still exploitable with MiraclePtr.
CREDIT INFORMATION
Reporter credit: asjidkalam