From: Adam Sitnik Date: Wed, 23 Jun 2021 06:53:55 +0000 (+0200) Subject: [FileStream] handle UNC and device paths (#54483) X-Git-Tag: submit/tizen/20210909.063632~634 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0aafceb47a2a26783e3b3ece891e45bf49354fe6;p=platform%2Fupstream%2Fdotnet%2Fruntime.git [FileStream] handle UNC and device paths (#54483) * stop using NtCreateFile as there is no public and reliable way of mapping DOS to NT paths --- diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_ALLOCATION_INFO.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_ALLOCATION_INFO.cs new file mode 100644 index 0000000..0233626 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FILE_ALLOCATION_INFO.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + // Value taken from https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileinformationbyhandle#remarks: + internal const int FileAllocationInfo = 5; + + internal struct FILE_ALLOCATION_INFO + { + internal long AllocationSize; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs index 36710dc..5aa7a2c 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs @@ -4,7 +4,8 @@ using System; using System.Diagnostics; using System.IO; -using System.Text; +using System.IO.Strategies; +using System.Runtime.InteropServices; using System.Threading; namespace Microsoft.Win32.SafeHandles @@ -24,13 +25,6 @@ namespace Microsoft.Win32.SafeHandles SetHandle(preexistingHandle); } - private SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle, FileOptions fileOptions) : base(ownsHandle) - { - SetHandle(preexistingHandle); - - _fileOptions = fileOptions; - } - public bool IsAsync => (GetFileOptions() & FileOptions.Asynchronous) != 0; internal bool CanSeek => !IsClosed && GetFileType() == Interop.Kernel32.FileTypes.FILE_TYPE_DISK; @@ -43,10 +37,14 @@ namespace Microsoft.Win32.SafeHandles { using (DisableMediaInsertionPrompt.Create()) { - SafeFileHandle fileHandle = new SafeFileHandle( - NtCreateFile(fullPath, mode, access, share, options, preallocationSize), - ownsHandle: true, - options); + // we don't use NtCreateFile as there is no public and reliable way + // of converting DOS to NT file paths (RtlDosPathNameToRelativeNtPathName_U_WithStatus is not documented) + SafeFileHandle fileHandle = CreateFile(fullPath, mode, access, share, options); + + if (FileStreamHelpers.ShouldPreallocate(preallocationSize, access, mode)) + { + Preallocate(fullPath, preallocationSize, fileHandle); + } fileHandle.InitThreadPoolBindingIfNeeded(); @@ -54,48 +52,91 @@ namespace Microsoft.Win32.SafeHandles } } - private static IntPtr NtCreateFile(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize) + private static unsafe SafeFileHandle CreateFile(string fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options) { - uint ntStatus; - IntPtr fileHandle; + Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default; + if ((share & FileShare.Inheritable) != 0) + { + secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES + { + nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES), + bInheritHandle = Interop.BOOL.TRUE + }; + } - const string MandatoryNtPrefix = @"\??\"; - if (fullPath.StartsWith(MandatoryNtPrefix, StringComparison.Ordinal)) + int fAccess = + ((access & FileAccess.Read) == FileAccess.Read ? Interop.Kernel32.GenericOperations.GENERIC_READ : 0) | + ((access & FileAccess.Write) == FileAccess.Write ? Interop.Kernel32.GenericOperations.GENERIC_WRITE : 0); + + // Our Inheritable bit was stolen from Windows, but should be set in + // the security attributes class. Don't leave this bit set. + share &= ~FileShare.Inheritable; + + // Must use a valid Win32 constant here... + if (mode == FileMode.Append) { - (ntStatus, fileHandle) = Interop.NtDll.NtCreateFile(fullPath, mode, access, share, options, preallocationSize); + mode = FileMode.OpenOrCreate; } - else + + int flagsAndAttributes = (int)options; + + // For mitigating local elevation of privilege attack through named pipes + // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the + // named pipe server can't impersonate a high privileged client security context + // (note that this is the effective default on CreateFile2) + flagsAndAttributes |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS); + + SafeFileHandle fileHandle = Interop.Kernel32.CreateFile(fullPath, fAccess, share, &secAttrs, mode, flagsAndAttributes, IntPtr.Zero); + if (fileHandle.IsInvalid) { - var vsb = new ValueStringBuilder(stackalloc char[256]); - vsb.Append(MandatoryNtPrefix); + // Return a meaningful exception with the full path. - if (fullPath.StartsWith(@"\\?\", StringComparison.Ordinal)) // NtCreateFile does not support "\\?\" prefix, only "\??\" - { - vsb.Append(fullPath.AsSpan(4)); - } - else + // NT5 oddity - when trying to open "C:\" as a Win32FileStream, + // we usually get ERROR_PATH_NOT_FOUND from the OS. We should + // probably be consistent w/ every other directory. + int errorCode = Marshal.GetLastPInvokeError(); + + if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND && fullPath!.Length == PathInternal.GetRootLength(fullPath)) { - vsb.Append(fullPath); + errorCode = Interop.Errors.ERROR_ACCESS_DENIED; } - (ntStatus, fileHandle) = Interop.NtDll.NtCreateFile(vsb.AsSpan(), mode, access, share, options, preallocationSize); - vsb.Dispose(); + throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); } - switch (ntStatus) - { - case Interop.StatusOptions.STATUS_SUCCESS: - return fileHandle; - case Interop.StatusOptions.STATUS_DISK_FULL: - throw new IOException(SR.Format(SR.IO_DiskFull_Path_AllocationSize, fullPath, preallocationSize)); - // NtCreateFile has a bug and it reports STATUS_INVALID_PARAMETER for files - // that are too big for the current file system. Example: creating a 4GB+1 file on a FAT32 drive. - case Interop.StatusOptions.STATUS_INVALID_PARAMETER when preallocationSize > 0: - case Interop.StatusOptions.STATUS_FILE_TOO_LARGE: - throw new IOException(SR.Format(SR.IO_FileTooLarge_Path_AllocationSize, fullPath, preallocationSize)); - default: - int error = (int)Interop.NtDll.RtlNtStatusToDosError((int)ntStatus); - throw Win32Marshal.GetExceptionForWin32Error(error, fullPath); + fileHandle._fileOptions = options; + return fileHandle; + } + + private static unsafe void Preallocate(string fullPath, long preallocationSize, SafeFileHandle fileHandle) + { + var allocationInfo = new Interop.Kernel32.FILE_ALLOCATION_INFO + { + AllocationSize = preallocationSize + }; + + if (!Interop.Kernel32.SetFileInformationByHandle( + fileHandle, + Interop.Kernel32.FileAllocationInfo, + &allocationInfo, + (uint)sizeof(Interop.Kernel32.FILE_ALLOCATION_INFO))) + { + int errorCode = Marshal.GetLastPInvokeError(); + + // we try to mimic the atomic NtCreateFile here: + // if preallocation fails, close the handle and delete the file + fileHandle.Dispose(); + Interop.Kernel32.DeleteFile(fullPath); + + switch (errorCode) + { + case Interop.Errors.ERROR_DISK_FULL: + throw new IOException(SR.Format(SR.IO_DiskFull_Path_AllocationSize, fullPath, preallocationSize)); + case Interop.Errors.ERROR_FILE_TOO_LARGE: + throw new IOException(SR.Format(SR.IO_FileTooLarge_Path_AllocationSize, fullPath, preallocationSize)); + default: + throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index a7ce085..2af41ff 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1426,6 +1426,9 @@ Common\Interop\Windows\Kernel32\Interop.FILE_BASIC_INFO.cs + + Common\Interop\Windows\Kernel32\Interop.FILE_ALLOCATION_INFO.cs + Common\Interop\Windows\Kernel32\Interop.FILE_END_OF_FILE_INFO.cs @@ -1612,6 +1615,9 @@ Common\Interop\Windows\Interop.UNICODE_STRING.cs + + Common\Interop\Windows\Kernel32\Interop.SecurityOptions.cs + Common\Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs