Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in FileSystem
DescriptionUse after free in FileSystem
ComponentFileSystem
Bug ClassUAF
Tracker501115599
Fix commitf72060ed133f (chromium/src) +3/-69
CISA KEVNot listed
Creditedasjidkalam
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
content/browser/file_system_access/file_system_access_bucket_path_watcher.cc
modified
FileSystemAccessBucketPathWatcher
content/browser/file_system_access/file_system_access_bucket_path_watcher.h
modified
MockFileChangeObserver
storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc
modified
TEST_F
storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc
modified

Files Changed

  • content/browser/file_system_access/file_system_access_bucket_path_watcher.cc
  • content/browser/file_system_access/file_system_access_bucket_path_watcher.h
  • storage/browser/file_system/sandbox_file_system_backend_delegate.cc
  • storage/browser/file_system/sandbox_file_system_backend_delegate.h
  • storage/browser/file_system/sandbox_file_system_backend_delegate_unittest.cc
  • storage/browser/file_system/task_runner_bound_observer_list.h
From f72060ed133f01a64f8f3f1827673a15ed4d4c32 Mon Sep 17 00:00:00 2001
From: Fergal Daly <fergal@chromium.org>
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 <fergal@chromium.org>
> Reviewed-by: Ming-Ying Chung <mych@chromium.org>
> Reviewed-by: Kalvin Lee <kdlee@chromium.org>
> 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: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
Auto-Submit: chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com <chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com>
Commit-Queue: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
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);
Loading diff…

Regression Test / PoC

shipped with the fix
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
Loading diff…

Original Bug Report

reported by as...@gmail.com

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:

  1. OnRawChange() calls observation_group.NotifyOfChanges(...)
  2. FileSystemAccessObserverObservation::OnChanges() may call HandleError() when the observation root disappears.
  3. HandleError() sends an errored event and calls host_->RemoveObservation(this).
  4. Removing the last observation triggers watcher_manager_->RemoveObservationGroup(...).
  5. 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.

  1. Extract the provided files into a directory and serve them locally using a python web server: python3 -m http.server 8001

  2. 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'
    
  3. On the loaded page, click Start.

  4. In the directory picker, choose any writable local directory.

  5. The PoC will rapidly create, observe, and delete files to drive the disappeared -> errored -> observation teardown path 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

View on issue tracker