CVE-2026-8543
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/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
Patch
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);
Original Bug Report
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:
- Network Exfiltration:
FilePathWatcherChangeTrackercallsbase::GetFilePathType()(which usesGetFileAttributesExWon 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). - Renderer Exfiltration: The change is propagated up through the
FileSystemAccessWatcherManagertoFileSystemAccessObserverObservation::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 therelativePathComponentsof theFileSystemChangeRecord.
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.
- The attacker hosts a malicious SMB server and a malicious website.
- The website uses
window.showDirectoryPicker()to prompt the victim to select a directory on the attacker’s SMB share. - The website’s JavaScript calls
observe({ recursive: true })on the resultingFileSystemDirectoryHandle. - The browser process begins monitoring the directory via
ReadDirectoryChangesW. - The attacker’s SMB server responds to the underlying
CHANGE_NOTIFYrequest with a small payload (e.g., 32 bytes) but setsNextEntryOffset = 0andFileNameLength = 60000. - 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’sFileSystemObservercallback 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.