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 0000000000000..0233626ee73c6
--- /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 36710dcdbde5d..5aa7a2c7f4dd6 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 @@ public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHand
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 @@ internal static unsafe SafeFileHandle Open(string fullPath, FileMode mode, FileA
{
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 @@ internal static unsafe SafeFileHandle Open(string fullPath, FileMode mode, FileA
}
}
- 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 a7ce085d9a065..2af41ff13424b 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