Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions FileEmulationFramework.Lib/Utilities/Native.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ namespace FileEmulationFramework.Lib.Utilities;
[ExcludeFromCodeCoverage(Justification = "Definitions for Interop.")]
public static class Native
{
/// <summary>
/// The value of a handle that is invalid
/// </summary>
public static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

/// <summary>
/// <para>
/// Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded.
Expand Down
100 changes: 57 additions & 43 deletions FileEmulationFramework/FileAccessServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static unsafe class FileAccessServer
private static List<IEmulator> Emulators { get; } = new();

private static readonly ConcurrentDictionary<IntPtr, FileInformation> HandleToInfoMap = new();
private static readonly ConcurrentDictionary<string, FileInformation?> PathToVirtualFileMap = new(StringComparer.OrdinalIgnoreCase);
private static readonly ConcurrentDictionary<string, FileInformation> PathToVirtualFileMap = new(StringComparer.OrdinalIgnoreCase);
private static readonly ConcurrentDictionary<string, IntPtr> PathToHandleMap = new(StringComparer.OrdinalIgnoreCase);

private static readonly object ThreadLock = new();
Expand Down Expand Up @@ -148,44 +148,11 @@ private static int QueryFullAttributesFileImpl(OBJECT_ATTRIBUTES* attributes, FI

var path = Strings.TrimWindowsPrefixes(attributes->ObjectName);

SafeFileHandle? safeHandle = null;

if (!PathToHandleMap.TryGetValue(path, out var hfile) || !HandleToInfoMap.TryGetValue(hfile, out var info))
{
// We haven't tried to create an emulated file for this yet, try it
if (!PathToVirtualFileMap.TryGetValue(path, out info))
{
// Prevent recursion
PathToVirtualFileMap[path] = null;

// There is a virtual file but no handle exists for it, we need to make one
try
{
safeHandle = File.OpenHandle(path);
}
catch (Exception e)
{
// If we couldn't make the handle this probably isn't a file we can emulate
return result;
}

hfile = safeHandle.DangerousGetHandle();

// We tried to make one but failed, this file isn't emulated
if (!HandleToInfoMap.TryGetValue(hfile, out info))
{
safeHandle.Close();
return result;
}
}

// We've tried to create an emulated file for this before but failed, this file isn't emulated
if (info == null)
return result;
}
if (!TryGetFileInfoFromPath(path, out var hfile, out var info, out var newFileHandle))
return result;

var oldSize = information->EndOfFile;
var newSize = info.File.GetFileSize(hfile, info);
var newSize = info!.File.GetFileSize(hfile, info);
if (newSize != -1)
information->EndOfFile = newSize;

Expand All @@ -200,8 +167,7 @@ private static int QueryFullAttributesFileImpl(OBJECT_ATTRIBUTES* attributes, FI
}

// Clean up if we needed to make a new handle
if (safeHandle != null)
safeHandle.Close();
newFileHandle?.Close();

return result;
}
Expand All @@ -212,16 +178,19 @@ private static int QueryAttributesFileImpl(OBJECT_ATTRIBUTES* attributes, FILE_B
var result = _getFileAttributesHook.OriginalFunction.Value.Invoke(attributes, information);
var path = Strings.TrimWindowsPrefixes(attributes->ObjectName);

if (!PathToHandleMap.TryGetValue(path, out var hfile) || !HandleToInfoMap.TryGetValue(hfile, out var info))
if (!TryGetFileInfoFromPath(path, out var hfile, out var info, out var newFileHandle))
return result;

if (info.File.TryGetLastWriteTime(hfile, info, out var newWriteTime))
if (info!.File.TryGetLastWriteTime(hfile, info, out var newWriteTime))
{
var oldWriteTime = information->LastWriteTime.ToDateTime();
information->LastWriteTime = new LARGE_INTEGER(newWriteTime!.Value.ToFileTimeUtc());
_logger.Info("[FileAccessServer] File Last Write Override | Old: {0}, New: {1} | {2}", oldWriteTime,
newWriteTime, path);
}

// Clean up if we needed to make a new handle
newFileHandle?.Close();

return result;
}
Expand Down Expand Up @@ -323,7 +292,7 @@ private static int NtCreateFileImpl(IntPtr* handle, FileAccess access, OBJECT_AT
_logger.Debug("[FileAccessServer] Accessing: {0}, {1}, Route: {2}", hndl, newFilePath, _currentRoute.FullPath);

// Try Accept New File (virtual override)
if (PathToVirtualFileMap.TryGetValue(newFilePath, out var fileInfo) && fileInfo != null)
if (PathToVirtualFileMap.TryGetValue(newFilePath, out var fileInfo))
{
// Reuse of emulated file (backed by stream) is safe because file access is single threaded.
lock (ThreadLock)
Expand All @@ -343,7 +312,8 @@ private static int NtCreateFileImpl(IntPtr* handle, FileAccess access, OBJECT_AT

lock (ThreadLock)
{
HandleToInfoMap[hndl] = new(newFilePath, 0, emulatedFile);
fileInfo = new(newFilePath, 0, emulatedFile);
HandleToInfoMap[hndl] = fileInfo;
PathToHandleMap[newFilePath] = hndl;
}
return ntStatus;
Expand All @@ -357,6 +327,50 @@ private static int NtCreateFileImpl(IntPtr* handle, FileAccess access, OBJECT_AT
}
}

/// <summary>
/// Tries to get the FileInformation for an emulated file based on its path.
/// If a file at the specified path has never been created before this will attempt to create it, making the emulated file.
/// </summary>
/// <param name="path">The path to the file to try and get information for</param>
/// <param name="hfile">The handle to the emulated file if there was one</param>
/// <param name="fileInfo">The FileInformation for the emulated file if there is one, null otherwise</param>
/// <param name="newFileHandle">A safe handle to the new file that was created, if one had to be created. Make sure to close this when you are done with the file info!</param>
/// <returns>True if the file information for an emulated file with the specified path was found, false otherwise.</returns>
private static bool TryGetFileInfoFromPath(string path, out IntPtr hfile, out FileInformation? fileInfo, out SafeFileHandle? newFileHandle)
{
newFileHandle = null;
fileInfo = null;

// We haven't tried to create an emulated file for this yet, try it
if (!PathToHandleMap.TryGetValue(path, out hfile) || (hfile != INVALID_HANDLE_VALUE && !HandleToInfoMap.TryGetValue(hfile, out fileInfo)))
{
// Prevent recursion
PathToHandleMap[path] = INVALID_HANDLE_VALUE;

// There is a virtual file but no handle exists for it, we need to make one
try
{
newFileHandle = File.OpenHandle(path);
}
catch (Exception e)
{
// If we couldn't make the handle this probably isn't a file we can emulate
return false;
}

hfile = newFileHandle.DangerousGetHandle();

// We tried to make one but failed, this file isn't emulated
if (!HandleToInfoMap.TryGetValue(hfile, out fileInfo))
{
newFileHandle.Close();
return false;
}
}

return fileInfo != null;
}

/// <summary>
/// Determines if the given handle refers to a directory.
/// </summary>
Expand Down
Loading