High watch CVSS 5.3 chrome OOB 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
5.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in FileSystem
DescriptionOut of bounds read in FileSystem
ComponentFileSystem
Sub-componentTerracotta-Phase-1
Bug ClassOOB
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker497095799
Fix commit654653b2344a (chromium/src) +12/-0
CWECWE-125 (Out-of-bounds read)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-28 (per vendor advisory)
Fix landed2026-03-31 (public git)
Disclosed2026-05-12
Patch gap42 days — fix public in source before the advisory

Background

`FILE_NOTIFY_INFORMATION`
A Windows variable-length struct returned by directory-change APIs (e.g., ReadDirectoryChangesW), where FileNameLength (in bytes) and a trailing FileName[] make each entry’s true size data-dependent.
`NextEntryOffset`
A field chaining entries within one buffer; code walks the buffer by advancing a span by this offset until it is 0.
`base::span` / `subspan`
A bounds-carrying view over the notification buffer; sub_span.size_bytes() is the remaining byte count available to read.
File System Access `FilePathWatcher` (Windows)
file_path_watcher_win.cc consumes the raw OS notification buffer to deliver file-change events to the File System Access implementation.

Root Cause Analysis

While iterating the notification buffer (the enclosing function name is not shown in the @@ -648 hunk header), the code did *reinterpret_cast<FILE_NOTIFY_INFORMATION*>(sub_span.data()) and then read FileNameLength/NextEntryOffset without first confirming that a full variable-length entry (fixed header plus FileName bytes) actually fit within sub_span. Because FILE_NOTIFY_INFORMATION is variable-length, a malformed or truncated buffer could make the struct’s declared FileNameLength or the subsequent name bytes extend past the end of sub_span, so reading the name (or advancing/reading beyond it) reads out of bounds.

The fix computes file_name_bytes = file_notify_info.FileNameLength / sizeof(wchar_t) and, if file_name_bytes + offsetof(FILE_NOTIFY_INFORMATION, FileNameLength) > sub_span.size_bytes(), treats the entry as malformed and breaks out of the walk before any further use. This re-establishes the invariant that the current entry lies entirely within the remaining span before its contents are consumed. The commit message explicitly notes the change is “speculative,” so the exact triggering path is not fully pinned down in the diff.

Key insight
The core mistake was trusting a variable-length OS struct’s length fields and walking the buffer without verifying each entry fits inside the remaining span; the fix adds an explicit size check that breaks on a malformed entry before it is read.

Attack Path

  1. Watch a directory Code using the File System Access FilePathWatcher on Windows registers for directory-change notifications, so the OS fills a FILE_NOTIFY_INFORMATION buffer.
  2. Malformed/truncated buffer An entry’s FileNameLength/NextEntryOffset describes a name or next entry that extends past the valid data in sub_span (how such a buffer arises is not established in the diff; the CL is labeled speculative, so the source is (inferred)).
  3. Unbounded read Pre-patch, the walk reinterpret-casts and reads the entry’s name/fields beyond sub_span’s bounds, an out-of-bounds read.
  4. Disclosure/crash (inferred) The over-read could expose adjacent process memory to the file-change consumer or crash the process; the diff demonstrates only the missing bounds check, not a concrete leak primitive.

Impact Assessment

An attacker able to influence the contents/layout of directory-change notifications consumed by a watched File System Access path could induce an out-of-bounds read, which the advisory classifies as “Out of bounds read in FileSystem” at high severity. The effect is in the browser process’s file-watcher parsing; the precondition (what produces a malformed buffer) and any resulting disclosure are (inferred), consistent with the commit’s “speculative” description. The advisory states no exploited-in-the-wild status.

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

Audit Directions

  • Variable-length OS structs over spans
    Audit every reinterpret_cast of a buffer to a trailing-array struct (FILE_NOTIFY_INFORMATION, *_INFORMATION, protocol TLVs) for a fit-check against the remaining span before field access.
  • Offset-chained buffer walks
    Where iteration advances by a struct-supplied offset/length (NextEntryOffset), verify the full current entry fits before dereference and that the offset cannot walk backward or past the end.
  • Byte-vs-element length units
    Scrutinize conversions like FileNameLength / sizeof(wchar_t) for unit mismatches when summed with byte offsets, since a wrong unit can under- or over-estimate the required size.
From 654653b2344af151589d6148594931f994047029 Mon Sep 17 00:00:00 2001
From: Kalvin Lee <kdlee@chromium.org>
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 <kdlee@chromium.org>
Reviewed-by: Mingyu Lei <leimy@chromium.org>
Commit-Queue: Mingyu Lei <leimy@chromium.org>
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 vm...@google.com

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