Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in FileSystem
DescriptionOut of bounds read in FileSystem
ComponentFileSystem
Bug ClassOOB
Tracker497095799
Fix commit654653b2344a (chromium/src) +12/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-12

Changed Functions

FunctionChangeNotes
if
content/browser/file_system_access/file_path_watcher/file_path_watcher_win.cc
modified

Files Changed

  • content/browser/file_system_access/file_path_watcher/file_path_watcher_win.cc
From 654653b2344af151589d6148594931f994047029 Mon Sep 17 00:00:00 2001
From: Kalvin Lee <[email protected]>
Date: Tue, 31 Mar 2026 00:18:01 -0700
Subject: [PATCH] Terracotta-Phase-1: Check size of `FILE_NOTIFY_INFORMATION`

This CL is speculative. Please see the linked bug for details.

Bug: 497095799
Change-Id: Id78dc5298433994f834c20e30dd89c2278bb6689
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7711592
Auto-Submit: Kalvin Lee <[email protected]>
Reviewed-by: Mingyu Lei <[email protected]>
Commit-Queue: Mingyu Lei <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1607683}
---

diff --git a/content/browser/file_system_access/file_path_watcher/file_path_watcher_win.cc b/content/browser/file_system_access/file_path_watcher/file_path_watcher_win.cc
index a9c672a..f8c8166 100644
--- a/content/browser/file_system_access/file_path_watcher/file_path_watcher_win.cc
+++ b/content/browser/file_system_access/file_path_watcher/file_path_watcher_win.cc
@@ -8,6 +8,7 @@
 
 #include <winnt.h>
 
+#include <cstddef>
 #include <cstdint>
 #include <map>
 #include <memory>
@@ -648,6 +649,17 @@
     const auto& file_notify_info =
         *reinterpret_cast<FILE_NOTIFY_INFORMATION*>(sub_span.data());
 
+    // `FILE_NOTIFY_INFORMATION` is a variable-length struct. Make note
+    // of the size of the current `file_notify_info` to ensure that
+    // we're not running off the end of `sub_span`.
+    const size_t file_name_bytes =
+        file_notify_info.FileNameLength / sizeof(wchar_t);
+    if (file_name_bytes + offsetof(FILE_NOTIFY_INFORMATION, FileNameLength) >
+        sub_span.size_bytes()) {
+      // Malformed info.
+      break;
+    }
+
     has_next_entry = file_notify_info.NextEntryOffset != 0;
     if (has_next_entry) {
       sub_span = sub_span.subspan(file_notify_info.NextEntryOffset);
Loading diff…

Original Bug Report

reported by [email protected]

OOB Heap Read in Windows FilePathWatcher via SMB

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A potential out-of-bounds (OOB) heap read exists in the Windows implementation of FilePathWatcher when processing directory change notifications. A malicious SMB server can return a crafted FILE_NOTIFY_INFORMATION structure with an oversized FileNameLength, causing the browser to read past the allocated notification buffer. The leaked browser heap memory can then be exfiltrated to the renderer process via the File System Access API or back to the SMB server via UNC path requests.

Affected files:

  • content/browser/file_system_access/file_path_watcher/file_path_watcher_win.cc

Estimated timestamp from git blame: 2024-05-29

Description

There is a potential out-of-bounds (OOB) heap read vulnerability in FilePathWatcherImpl::ProcessNotificationBatch within content/browser/file_system_access/file_path_watcher/file_path_watcher_win.cc.

When a user selects a directory to observe (e.g., via the File System Access API’s FileSystemObserver), the browser uses ReadDirectoryChangesW to monitor for changes. When a change occurs, GetQueuedCompletionStatus returns the number of bytes transferred, and a base::HeapArray<uint8_t> named notification_batch is allocated to this exact size and filled with the OS response.

In ProcessNotificationBatch, the code iterates over this buffer, casting chunks of it to FILE_NOTIFY_INFORMATION. A vulnerability occurs because the FileNameLength field from this struct is used to construct a std::basic_string_view<wchar_t> without verifying that the length fits within the remaining bytes of the notification_batch buffer.

// content/browser/file_system_access/file_path_watcher/file_path_watcher_win.cc
void FilePathWatcherImpl::ProcessNotificationBatch(
    base::FilePath watched_path,
    base::HeapArray<uint8_t> notification_batch) {
  // ...
  auto sub_span = notification_batch.as_span();
  bool has_next_entry = true;

  while (has_next_entry) {
    const auto& file_notify_info =
        *reinterpret_cast<FILE_NOTIFY_INFORMATION*>(sub_span.data());

    has_next_entry = file_notify_info.NextEntryOffset != 0;
    if (has_next_entry) {
      // This is the only bounds check (subspan will crash if OOB),
      // but it is skipped if the attacker sets NextEntryOffset to 0.
      sub_span = sub_span.subspan(file_notify_info.NextEntryOffset);
    }

    base::FilePath change_path =
        watched_path.Append(std::basic_string_view<wchar_t>(
            file_notify_info.FileName,
            file_notify_info.FileNameLength / sizeof(wchar_t))); // <--- VULNERABILITY: No bounds check

    change_tracker_->AddChange(std::move(change_path), file_notify_info.Action);
  }
  // ...
}

If the watched directory is on an attacker-controlled SMB share, the attacker can manipulate the FILE_NOTIFY_INFORMATION response. By setting NextEntryOffset to 0 and providing a small overall response buffer but a massive FileNameLength, the std::basic_string_view will point far past the end of the notification_batch heap array.

Impact and Exfiltration

The out-of-bounds heap data is appended to the watched path. This path containing the leaked memory is then processed, leading to two potential exfiltration channels:

  1. Network Exfiltration: FilePathWatcherChangeTracker calls base::GetFilePathType() (which uses GetFileAttributesExW on Windows) to determine if the modified path is a file or directory. This triggers an SMB request back to the attacker’s server for the path \\attacker-share\dir\<leaked_heap_bytes>, exposing the memory over the network (subject to null-byte truncation).
  2. Renderer Exfiltration: The change is propagated up through the FileSystemAccessWatcherManager to FileSystemAccessObserverObservation::OnChanges. The leaked bytes are converted to UTF-8 strings and sent across the Mojo IPC boundary to the sandboxed renderer process, where malicious JavaScript can read the relativePathComponents of the FileSystemChangeRecord.

Potential Attack Steps

Note: These are suggested steps to trigger the vulnerability based on static analysis. Our tooling agent does not have the ability to run code or verify a live exploit.

  1. The attacker hosts a malicious SMB server and a malicious website.
  2. The website uses window.showDirectoryPicker() to prompt the victim to select a directory on the attacker’s SMB share.
  3. The website’s JavaScript calls observe({ recursive: true }) on the resulting FileSystemDirectoryHandle.
  4. The browser process begins monitoring the directory via ReadDirectoryChangesW.
  5. The attacker’s SMB server responds to the underlying CHANGE_NOTIFY request with a small payload (e.g., 32 bytes) but sets NextEntryOffset = 0 and FileNameLength = 60000.
  6. The browser allocates a 32-byte HeapArray, copies the response, and processes it. The missing bounds check allows an OOB read of ~60KB of the browser process heap, which is then exfiltrated to the renderer’s FileSystemObserver callback or over the network.

Suggested Fix

Add an explicit bounds check in FilePathWatcherImpl::ProcessNotificationBatch before constructing the std::basic_string_view. Ensure that file_notify_info.FileNameLength plus the structural offset of the FileName field does not exceed sub_span.size_bytes().

    size_t required_bytes = offsetof(FILE_NOTIFY_INFORMATION, FileName) + file_notify_info.FileNameLength;
    if (required_bytes > sub_span.size_bytes()) {
      // Handle error: Malformed notification buffer
      break;
    }

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.

View on issue tracker