Skip to content

Commit

Permalink
Fix Windows AccessViolationException in FileSystemWatcher when monito…
Browse files Browse the repository at this point in the history
…ring network share for changes (#42419)

* Fix FileSystemWatcher crash due to malformed data coming from Windows.

* Address PR suggestions.

* Bring back assert for numBytes.

Co-authored-by: carlossanlop <[email protected]>
  • Loading branch information
carlossanlop and carlossanlop authored Sep 18, 2020
1 parent 22d206c commit 2a1735b
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,8 @@ internal struct FILE_NOTIFY_INFORMATION
internal FileAction Action;

// Note that the file name is not null terminated
private uint FileNameLength;
private char _FileName;

internal unsafe ReadOnlySpan<char> FileName
{
get { fixed (char* c = &_FileName) { return new ReadOnlySpan<char>(c, (int)FileNameLength / sizeof(char)); } }
}
internal readonly uint FileNameLength;
internal readonly char FileName;
}

internal enum FileAction : uint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ private void ReadDirectoryChangesCallback(uint errorCode, uint numBytes, AsyncRe
}
else
{
ParseEventBufferAndNotifyForEach(state.Buffer);
ParseEventBufferAndNotifyForEach(state.Buffer, numBytes);
}
}
finally
Expand All @@ -245,20 +245,41 @@ private void ReadDirectoryChangesCallback(uint errorCode, uint numBytes, AsyncRe
}
}

private unsafe void ParseEventBufferAndNotifyForEach(byte[] buffer)
private unsafe void ParseEventBufferAndNotifyForEach(byte[] buffer, uint numBytes)
{
Debug.Assert(buffer != null);
Debug.Assert(buffer.Length > 0);
Debug.Assert(numBytes <= buffer.Length);

numBytes = Math.Min(numBytes, (uint)buffer.Length);

fixed (byte* b = buffer)
{
byte* pBufferLimit = b + numBytes;

Interop.Kernel32.FILE_NOTIFY_INFORMATION* info = (Interop.Kernel32.FILE_NOTIFY_INFORMATION*)b;

ReadOnlySpan<char> oldName = ReadOnlySpan<char>.Empty;

// Parse each event from the buffer and notify appropriate delegates
do
{
// Validate the data we received in case it's corrupted. This can happen
// if we are watching files over the network with a poor connection.

// Verify the info object is within the expected bounds
if (info < b || (((byte*)info) + sizeof(Interop.Kernel32.FILE_NOTIFY_INFORMATION)) > pBufferLimit)
{
break;
}

// Verify the file path is within the bounds
byte* pFileEnd = ((byte*)&(info->FileName)) + info->FileNameLength;
if (pFileEnd < &(info->FileName) || pFileEnd > pBufferLimit)
{
break;
}

// A slightly convoluted piece of code follows. Here's what's happening:
//
// We wish to collapse the poorly done rename notifications from the
Expand All @@ -284,17 +305,19 @@ private unsafe void ParseEventBufferAndNotifyForEach(byte[] buffer)
//
// (Phew!)

ReadOnlySpan<char> fileName = new ReadOnlySpan<char>(&(info->FileName), (int)info->FileNameLength / sizeof(char));

switch (info->Action)
{
case Interop.Kernel32.FileAction.FILE_ACTION_RENAMED_OLD_NAME:
// Action is renamed from, save the name of the file
oldName = info->FileName;
oldName = fileName;
break;
case Interop.Kernel32.FileAction.FILE_ACTION_RENAMED_NEW_NAME:
// oldName may be empty if we didn't receive FILE_ACTION_RENAMED_OLD_NAME first
NotifyRenameEventArgs(
WatcherChangeTypes.Renamed,
info->FileName,
fileName,
oldName);
oldName = ReadOnlySpan<char>.Empty;
break;
Expand All @@ -309,13 +332,13 @@ private unsafe void ParseEventBufferAndNotifyForEach(byte[] buffer)
switch (info->Action)
{
case Interop.Kernel32.FileAction.FILE_ACTION_ADDED:
NotifyFileSystemEventArgs(WatcherChangeTypes.Created, info->FileName);
NotifyFileSystemEventArgs(WatcherChangeTypes.Created, fileName);
break;
case Interop.Kernel32.FileAction.FILE_ACTION_REMOVED:
NotifyFileSystemEventArgs(WatcherChangeTypes.Deleted, info->FileName);
NotifyFileSystemEventArgs(WatcherChangeTypes.Deleted, fileName);
break;
case Interop.Kernel32.FileAction.FILE_ACTION_MODIFIED:
NotifyFileSystemEventArgs(WatcherChangeTypes.Changed, info->FileName);
NotifyFileSystemEventArgs(WatcherChangeTypes.Changed, fileName);
break;
default:
Debug.Fail($"Unknown FileSystemEvent action type! Value: {info->Action}");
Expand Down

0 comments on commit 2a1735b

Please sign in to comment.