NTDirectoryFileSystem.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. /* Copyright (C) 2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Runtime.InteropServices;
  11. using System.Threading;
  12. using Microsoft.Win32.SafeHandles;
  13. using Utilities;
  14. namespace SMBLibrary.Win32
  15. {
  16. [StructLayout(LayoutKind.Sequential)]
  17. public struct UNICODE_STRING : IDisposable
  18. {
  19. public ushort Length;
  20. public ushort MaximumLength;
  21. private IntPtr Buffer;
  22. public UNICODE_STRING(string value)
  23. {
  24. Length = (ushort)(value.Length * 2);
  25. MaximumLength = (ushort)(value.Length + 2);
  26. Buffer = Marshal.StringToHGlobalUni(value);
  27. }
  28. public void Dispose()
  29. {
  30. Marshal.FreeHGlobal(Buffer);
  31. Buffer = IntPtr.Zero;
  32. }
  33. public override string ToString()
  34. {
  35. return Marshal.PtrToStringUni(Buffer);
  36. }
  37. }
  38. [StructLayoutAttribute(LayoutKind.Sequential)]
  39. public struct OBJECT_ATTRIBUTES
  40. {
  41. public int Length;
  42. public IntPtr RootDirectory;
  43. public IntPtr ObjectName;
  44. public uint Attributes;
  45. public IntPtr SecurityDescriptor;
  46. public IntPtr SecurityQualityOfService;
  47. }
  48. [StructLayoutAttribute(LayoutKind.Sequential)]
  49. public struct IO_STATUS_BLOCK
  50. {
  51. public UInt32 Status;
  52. public IntPtr Information;
  53. }
  54. internal class PendingRequest
  55. {
  56. public IntPtr FileHandle;
  57. public uint ThreadID;
  58. public IO_STATUS_BLOCK IOStatusBlock;
  59. public bool Cleanup;
  60. }
  61. public class NTDirectoryFileSystem : INTFileStore
  62. {
  63. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  64. private static extern NTStatus NtCreateFile(out IntPtr handle, uint desiredAccess, ref OBJECT_ATTRIBUTES objectAttributes, out IO_STATUS_BLOCK ioStatusBlock, ref long allocationSize, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, IntPtr eaBuffer, uint eaLength);
  65. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  66. private static extern NTStatus NtClose(IntPtr handle);
  67. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  68. private static extern NTStatus NtReadFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] buffer, uint length, ref long byteOffset, IntPtr key);
  69. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  70. private static extern NTStatus NtWriteFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] buffer, uint length, ref long byteOffset, IntPtr key);
  71. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  72. private static extern NTStatus NtFlushBuffersFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock);
  73. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  74. private static extern NTStatus NtQueryDirectoryFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] fileInformation, uint length, uint fileInformationClass, bool returnSingleEntry, ref UNICODE_STRING fileName, bool restartScan);
  75. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  76. private static extern NTStatus NtQueryInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fileInformation, uint length, uint fileInformationClass);
  77. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  78. private static extern NTStatus NtSetInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fileInformation, uint length, uint fileInformationClass);
  79. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  80. private static extern NTStatus NtQueryVolumeInformationFile(IntPtr handle, out IO_STATUS_BLOCK ioStatusBlock, byte[] fsInformation, uint length, uint fsInformationClass);
  81. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  82. private static extern NTStatus NtNotifyChangeDirectoryFile(IntPtr handle, IntPtr evt, IntPtr apcRoutine, IntPtr apcContext, out IO_STATUS_BLOCK ioStatusBlock, byte[] buffer, uint bufferSize, NotifyChangeFilter completionFilter, bool watchTree);
  83. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  84. private static extern NTStatus NtAlertThread(IntPtr threadHandle);
  85. // Available starting from Windows Vista.
  86. [DllImport("ntdll.dll", ExactSpelling = true, SetLastError = false)]
  87. private static extern NTStatus NtCancelSynchronousIoFile(IntPtr threadHandle, ref IO_STATUS_BLOCK ioRequestToCancel, out IO_STATUS_BLOCK ioStatusBlock);
  88. private DirectoryInfo m_directory;
  89. private PendingRequestCollection m_pendingRequests = new PendingRequestCollection();
  90. public NTDirectoryFileSystem(string path) : this(new DirectoryInfo(path))
  91. {
  92. }
  93. public NTDirectoryFileSystem(DirectoryInfo directory)
  94. {
  95. m_directory = directory;
  96. }
  97. private OBJECT_ATTRIBUTES InitializeObjectAttributes(UNICODE_STRING objectName)
  98. {
  99. OBJECT_ATTRIBUTES objectAttributes = new OBJECT_ATTRIBUTES();
  100. objectAttributes.RootDirectory = IntPtr.Zero;
  101. objectAttributes.ObjectName = Marshal.AllocHGlobal(Marshal.SizeOf(objectName));
  102. Marshal.StructureToPtr(objectName, objectAttributes.ObjectName, false);
  103. objectAttributes.SecurityDescriptor = IntPtr.Zero;
  104. objectAttributes.SecurityQualityOfService = IntPtr.Zero;
  105. objectAttributes.Length = Marshal.SizeOf(objectAttributes);
  106. return objectAttributes;
  107. }
  108. private NTStatus CreateFile(out IntPtr handle, out FileStatus fileStatus, string nativePath, AccessMask desiredAccess, long allocationSize, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions)
  109. {
  110. UNICODE_STRING objectName = new UNICODE_STRING(nativePath);
  111. OBJECT_ATTRIBUTES objectAttributes = InitializeObjectAttributes(objectName);
  112. IO_STATUS_BLOCK ioStatusBlock;
  113. NTStatus status = NtCreateFile(out handle, (uint)desiredAccess, ref objectAttributes, out ioStatusBlock, ref allocationSize, fileAttributes, shareAccess, createDisposition, createOptions, IntPtr.Zero, 0);
  114. fileStatus = (FileStatus)ioStatusBlock.Information;
  115. return status;
  116. }
  117. private string ToNativePath(string path)
  118. {
  119. if (!path.StartsWith(@"\"))
  120. {
  121. path = @"\" + path;
  122. }
  123. return @"\??\" + m_directory.FullName + path;
  124. }
  125. public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string path, AccessMask desiredAccess, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, SecurityContext securityContext)
  126. {
  127. IntPtr fileHandle;
  128. string nativePath = ToNativePath(path);
  129. // NtQueryDirectoryFile will return STATUS_PENDING if the directory handle was not opened with SYNCHRONIZE and FILE_SYNCHRONOUS_IO_ALERT or FILE_SYNCHRONOUS_IO_NONALERT.
  130. // Our usage of NtNotifyChangeDirectoryFile assumes the directory handle is opened with SYNCHRONIZE and FILE_SYNCHRONOUS_IO_ALERT (or FILE_SYNCHRONOUS_IO_NONALERT starting from Windows Vista).
  131. // Note: Sometimes a directory will be opened without specifying FILE_DIRECTORY_FILE.
  132. desiredAccess.Directory |= DirectoryAccessMask.SYNCHRONIZE;
  133. createOptions &= ~CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT;
  134. createOptions |= CreateOptions.FILE_SYNCHRONOUS_IO_ALERT;
  135. NTStatus status = CreateFile(out fileHandle, out fileStatus, nativePath, desiredAccess, 0, fileAttributes, shareAccess, createDisposition, createOptions);
  136. handle = fileHandle;
  137. return status;
  138. }
  139. public NTStatus CloseFile(object handle)
  140. {
  141. // [MS-FSA] 2.1.5.4 The close operation has to complete any pending ChangeNotify request with STATUS_NOTIFY_CLEANUP.
  142. // - When closing a synchronous handle we must explicitly cancel any pending ChangeNotify request, otherwise the call to NtClose will hang.
  143. // We use request.Cleanup to tell that we should complete such ChangeNotify request with STATUS_NOTIFY_CLEANUP.
  144. // - When closing an asynchronous handle Windows will implicitly complete any pending ChangeNotify request with STATUS_NOTIFY_CLEANUP as required.
  145. List<PendingRequest> pendingRequests = m_pendingRequests.GetRequestsByHandle((IntPtr)handle);
  146. foreach (PendingRequest request in pendingRequests)
  147. {
  148. request.Cleanup = true;
  149. Cancel(request);
  150. }
  151. return NtClose((IntPtr)handle);
  152. }
  153. public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCount)
  154. {
  155. IO_STATUS_BLOCK ioStatusBlock;
  156. data = new byte[maxCount];
  157. NTStatus status = NtReadFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, data, (uint)maxCount, ref offset, IntPtr.Zero);
  158. if (status == NTStatus.STATUS_SUCCESS)
  159. {
  160. int bytesRead = (int)ioStatusBlock.Information;
  161. if (bytesRead < maxCount)
  162. {
  163. data = ByteReader.ReadBytes(data, 0, bytesRead);
  164. }
  165. }
  166. return status;
  167. }
  168. public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offset, byte[] data)
  169. {
  170. IO_STATUS_BLOCK ioStatusBlock;
  171. NTStatus status = NtWriteFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, data, (uint)data.Length, ref offset, IntPtr.Zero);
  172. if (status == NTStatus.STATUS_SUCCESS)
  173. {
  174. numberOfBytesWritten = (int)ioStatusBlock.Information;
  175. }
  176. else
  177. {
  178. numberOfBytesWritten = 0;
  179. }
  180. return status;
  181. }
  182. public NTStatus FlushFileBuffers(object handle)
  183. {
  184. IO_STATUS_BLOCK ioStatusBlock;
  185. return NtFlushBuffersFile((IntPtr)handle, out ioStatusBlock);
  186. }
  187. public NTStatus QueryDirectory(out List<QueryDirectoryFileInformation> result, object handle, string fileName, FileInformationClass informationClass)
  188. {
  189. IO_STATUS_BLOCK ioStatusBlock;
  190. byte[] buffer = new byte[4096];
  191. UNICODE_STRING fileNameStructure = new UNICODE_STRING(fileName);
  192. result = new List<QueryDirectoryFileInformation>();
  193. bool restartScan = true;
  194. while (true)
  195. {
  196. NTStatus status = NtQueryDirectoryFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out ioStatusBlock, buffer, (uint)buffer.Length, (byte)informationClass, false, ref fileNameStructure, restartScan);
  197. if (status == NTStatus.STATUS_NO_MORE_FILES)
  198. {
  199. break;
  200. }
  201. else if (status != NTStatus.STATUS_SUCCESS)
  202. {
  203. return status;
  204. }
  205. restartScan = false;
  206. List<QueryDirectoryFileInformation> page = QueryDirectoryFileInformation.ReadFileInformationList(buffer, 0, informationClass);
  207. result.AddRange(page);
  208. }
  209. fileNameStructure.Dispose();
  210. return NTStatus.STATUS_SUCCESS;
  211. }
  212. public NTStatus GetFileInformation(out FileInformation result, object handle, FileInformationClass informationClass)
  213. {
  214. IO_STATUS_BLOCK ioStatusBlock;
  215. byte[] buffer = new byte[8192];
  216. NTStatus status = NtQueryInformationFile((IntPtr)handle, out ioStatusBlock, buffer, (uint)buffer.Length, (uint)informationClass);
  217. if (status == NTStatus.STATUS_SUCCESS)
  218. {
  219. result = FileInformation.GetFileInformation(buffer, 0, informationClass);
  220. }
  221. else
  222. {
  223. result = null;
  224. }
  225. return status;
  226. }
  227. public NTStatus SetFileInformation(object handle, FileInformation information)
  228. {
  229. IO_STATUS_BLOCK ioStatusBlock;
  230. if (information is FileRenameInformationType2)
  231. {
  232. FileRenameInformationType2 fileRenameInformation2 = (FileRenameInformationType2)information;
  233. fileRenameInformation2.FileName = ToNativePath(fileRenameInformation2.FileName);
  234. // Note: WOW64 process should use FILE_RENAME_INFORMATION_TYPE_1.
  235. // Note: Server 2003 x64 has issues with using FILE_RENAME_INFORMATION under WOW64.
  236. if (!ProcessHelper.Is64BitProcess)
  237. {
  238. FileRenameInformationType1 fileRenameInformation1 = new FileRenameInformationType1();
  239. fileRenameInformation1.ReplaceIfExists = fileRenameInformation2.ReplaceIfExists;
  240. fileRenameInformation1.FileName = fileRenameInformation2.FileName;
  241. information = fileRenameInformation1;
  242. }
  243. }
  244. else if (information is FileLinkInformationType2)
  245. {
  246. FileLinkInformationType2 fileLinkInformation2 = (FileLinkInformationType2)information;
  247. fileLinkInformation2.FileName = ToNativePath(fileLinkInformation2.FileName);
  248. if (!ProcessHelper.Is64BitProcess)
  249. {
  250. FileLinkInformationType1 fileLinkInformation1 = new FileLinkInformationType1();
  251. fileLinkInformation1.ReplaceIfExists = fileLinkInformation2.ReplaceIfExists;
  252. fileLinkInformation1.FileName = fileLinkInformation2.FileName;
  253. information = fileLinkInformation1;
  254. }
  255. }
  256. byte[] buffer = information.GetBytes();
  257. return NtSetInformationFile((IntPtr)handle, out ioStatusBlock, buffer, (uint)buffer.Length, (uint)information.FileInformationClass);
  258. }
  259. public NTStatus GetFileSystemInformation(out FileSystemInformation result, FileSystemInformationClass informationClass)
  260. {
  261. IO_STATUS_BLOCK ioStatusBlock;
  262. byte[] buffer = new byte[4096];
  263. IntPtr volumeHandle;
  264. FileStatus fileStatus;
  265. string nativePath = @"\??\" + m_directory.FullName.Substring(0, 3);
  266. NTStatus status = CreateFile(out volumeHandle, out fileStatus, nativePath, DirectoryAccessMask.GENERIC_READ, 0, (FileAttributes)0, ShareAccess.FILE_SHARE_READ, CreateDisposition.FILE_OPEN, (CreateOptions)0);
  267. result = null;
  268. if (status != NTStatus.STATUS_SUCCESS)
  269. {
  270. return status;
  271. }
  272. status = NtQueryVolumeInformationFile((IntPtr)volumeHandle, out ioStatusBlock, buffer, (uint)buffer.Length, (uint)informationClass);
  273. CloseFile(volumeHandle);
  274. if (status == NTStatus.STATUS_SUCCESS)
  275. {
  276. result = FileSystemInformation.GetFileSystemInformation(buffer, 0, informationClass);
  277. }
  278. return status;
  279. }
  280. public NTStatus NotifyChange(out object ioRequest, object handle, NotifyChangeFilter completionFilter, bool watchTree, int outputBufferSize, OnNotifyChangeCompleted onNotifyChangeCompleted, object context)
  281. {
  282. byte[] buffer = new byte[outputBufferSize];
  283. ManualResetEvent requestAddedEvent = new ManualResetEvent(false);
  284. PendingRequest request = new PendingRequest();
  285. Thread m_thread = new Thread(delegate()
  286. {
  287. request.FileHandle = (IntPtr)handle;
  288. request.ThreadID = ThreadingHelper.GetCurrentThreadId();
  289. m_pendingRequests.Add(request);
  290. // The request has been added, we can now return STATUS_PENDING.
  291. requestAddedEvent.Set();
  292. NTStatus status = NtNotifyChangeDirectoryFile((IntPtr)handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out request.IOStatusBlock, buffer, (uint)buffer.Length, completionFilter, watchTree);
  293. if (status == NTStatus.STATUS_SUCCESS)
  294. {
  295. int length = (int)request.IOStatusBlock.Information;
  296. buffer = ByteReader.ReadBytes(buffer, 0, length);
  297. }
  298. else
  299. {
  300. const NTStatus STATUS_ALERTED = (NTStatus)0x00000101;
  301. const NTStatus STATUS_OBJECT_TYPE_MISMATCH = (NTStatus)0xC0000024;
  302. buffer = new byte[0];
  303. if (status == STATUS_OBJECT_TYPE_MISMATCH)
  304. {
  305. status = NTStatus.STATUS_INVALID_HANDLE;
  306. }
  307. else if (status == STATUS_ALERTED)
  308. {
  309. status = NTStatus.STATUS_CANCELLED;
  310. }
  311. // If the handle is closing and we had to cancel a ChangeNotify request as part of a cleanup,
  312. // we return STATUS_NOTIFY_CLEANUP as specified in [MS-FSA] 2.1.5.4.
  313. if (status == NTStatus.STATUS_CANCELLED && request.Cleanup)
  314. {
  315. status = NTStatus.STATUS_NOTIFY_CLEANUP;
  316. }
  317. }
  318. onNotifyChangeCompleted(status, buffer, context);
  319. m_pendingRequests.Remove((IntPtr)handle, request.ThreadID);
  320. });
  321. m_thread.Start();
  322. // We must wait for the request to be added in order for Cancel to function properly.
  323. requestAddedEvent.WaitOne();
  324. ioRequest = request;
  325. return NTStatus.STATUS_PENDING;
  326. }
  327. public NTStatus Cancel(object ioRequest)
  328. {
  329. PendingRequest request = (PendingRequest)ioRequest;
  330. const uint THREAD_TERMINATE = 0x00000001;
  331. const uint THREAD_ALERT = 0x00000004;
  332. uint threadID = request.ThreadID;
  333. IntPtr threadHandle = ThreadingHelper.OpenThread(THREAD_TERMINATE | THREAD_ALERT, false, threadID);
  334. if (threadHandle == IntPtr.Zero)
  335. {
  336. Win32Error error = (Win32Error)Marshal.GetLastWin32Error();
  337. if (error == Win32Error.ERROR_INVALID_PARAMETER)
  338. {
  339. return NTStatus.STATUS_INVALID_HANDLE;
  340. }
  341. else
  342. {
  343. throw new Exception("OpenThread failed, Win32 error: " + error.ToString("D"));
  344. }
  345. }
  346. NTStatus status;
  347. if (Environment.OSVersion.Version.Major >= 6)
  348. {
  349. IO_STATUS_BLOCK ioStatusBlock;
  350. status = NtCancelSynchronousIoFile(threadHandle, ref request.IOStatusBlock, out ioStatusBlock);
  351. }
  352. else
  353. {
  354. // The handle was opened for synchronous operation so NtNotifyChangeDirectoryFile is blocking.
  355. // We MUST use NtAlertThread to send a signal to stop the wait. The handle cannot be closed otherwise.
  356. // Note: The handle was opened with CreateOptions.FILE_SYNCHRONOUS_IO_ALERT as required.
  357. status = NtAlertThread(threadHandle);
  358. }
  359. ThreadingHelper.CloseHandle(threadHandle);
  360. m_pendingRequests.Remove(request.FileHandle, request.ThreadID);
  361. return status;
  362. }
  363. public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out byte[] output, int maxOutputLength)
  364. {
  365. output = null;
  366. return NTStatus.STATUS_NOT_SUPPORTED;
  367. }
  368. }
  369. }