Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to specify file allocation size #51111

Merged
merged 42 commits into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fe24ee2
add the new ctor to the ref assembly
adamsitnik Apr 12, 2021
c127580
add tests
adamsitnik Apr 12, 2021
f3af047
add new ctor
adamsitnik Apr 12, 2021
5d004fd
initial Windows implementation
adamsitnik Apr 12, 2021
bbac6d7
fix the build
adamsitnik Apr 12, 2021
5451439
remove duplicated Interop code
adamsitnik Apr 13, 2021
29b1223
Merge remote-tracking branch 'upstream/main' into fileAllocationSize
adamsitnik Apr 13, 2021
07cfc75
fix the full framework build
adamsitnik Apr 13, 2021
945c917
initial Unix implementation
adamsitnik Apr 20, 2021
0b566aa
Merge remote-tracking branch 'upstream/main' into fileAllocationSize
adamsitnik Apr 20, 2021
3bf1f95
some polishing
adamsitnik Apr 21, 2021
ed1f18d
remove unused using and extra empty lines from existing tests
adamsitnik Apr 21, 2021
f645e67
the active issue link should point to a valid issue
adamsitnik Apr 21, 2021
909c86e
the tests should actually verify whether FileStream is async or not
adamsitnik Apr 21, 2021
6789382
remove duplicated consts
adamsitnik Apr 21, 2021
dbe44d4
proper tests for Windows
adamsitnik Apr 21, 2021
3571a23
implement Unix tests and make them pass by moving fallocate call afte…
adamsitnik Apr 21, 2021
cbe2277
fix the Windows build...
adamsitnik Apr 21, 2021
c490439
align macOS implementation with Linux
adamsitnik Apr 21, 2021
7842eba
polishing
adamsitnik Apr 22, 2021
b2cc9f3
handle files that are too large for the current file system
adamsitnik Apr 22, 2021
ed5808f
add a test for "file was too large" exception
adamsitnik Apr 23, 2021
e250d96
and handling for EFBIG
adamsitnik Apr 23, 2021
df155e2
fix the Unix build
adamsitnik Apr 23, 2021
b6929e3
fix a wrong assert
adamsitnik Apr 23, 2021
6875a9f
try to enable the new tests for Browser build
adamsitnik Apr 23, 2021
0a0edad
Apply suggestions from code review
adamsitnik Apr 27, 2021
c46ac66
apply other suggestions for the code review, get rid of the managed a…
adamsitnik Apr 27, 2021
142fbb1
Merge remote-tracking branch 'upstream/main' into fileAllocationSize
adamsitnik Apr 27, 2021
83f119d
disable it for WASM
adamsitnik Apr 27, 2021
37b9245
address code review feedback: remove unused path parameter, use stack…
adamsitnik Apr 28, 2021
c2f995e
address code review feedback: if NtCreateFile fails, we should immedi…
adamsitnik Apr 28, 2021
2b00680
Apply suggestions from code review
adamsitnik May 14, 2021
60102b0
Merge remote-tracking branch 'upstream/main' into fileAllocationSize
adamsitnik May 14, 2021
4065f43
introduce FileStreamOptions
adamsitnik May 14, 2021
b0c8243
address code review feedback: throw ArgumentOutOfRangeException when …
adamsitnik May 14, 2021
2145dc1
address code review feedback: move error codes
adamsitnik May 14, 2021
5eed718
SECURITY_ANONYMOUS
adamsitnik May 14, 2021
4993dbb
Merge remote-tracking branch 'upstream/main' into fileAllocationSize
adamsitnik May 14, 2021
b9987a8
address code review feedback
adamsitnik May 17, 2021
9728078
address code review feedback and clarify which values are supported a…
adamsitnik May 18, 2021
8648f65
fix XML docs
adamsitnik May 18, 2021
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

internal static partial class Interop
{
internal static partial class Sys
{
/// <summary>
/// Returns -1 on ENOSPC, -2 on EFBIG. On success or ignorable error, 0 is returned.
/// </summary>
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PosixFAllocate", SetLastError = false)]
internal static extern int PosixFAllocate(SafeFileHandle fd, long offset, long length);
}
}
2 changes: 2 additions & 0 deletions src/libraries/Common/src/Interop/Windows/Interop.Errors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ internal static partial class Errors
internal const int ERROR_FILE_EXISTS = 0x50;
internal const int ERROR_INVALID_PARAMETER = 0x57;
internal const int ERROR_BROKEN_PIPE = 0x6D;
internal const int ERROR_DISK_FULL = 0x70;
internal const int ERROR_SEM_TIMEOUT = 0x79;
internal const int ERROR_CALL_NOT_IMPLEMENTED = 0x78;
internal const int ERROR_INSUFFICIENT_BUFFER = 0x7A;
Expand All @@ -43,6 +44,7 @@ internal static partial class Errors
internal const int ERROR_ENVVAR_NOT_FOUND = 0xCB;
internal const int ERROR_FILENAME_EXCED_RANGE = 0xCE;
internal const int ERROR_EXE_MACHINE_TYPE_MISMATCH = 0xD8;
internal const int ERROR_FILE_TOO_LARGE = 0xDF;
internal const int ERROR_PIPE_BUSY = 0xE7;
internal const int ERROR_NO_DATA = 0xE8;
internal const int ERROR_PIPE_NOT_CONNECTED = 0xE9;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,19 @@ internal unsafe struct OBJECT_ATTRIBUTES
/// Optional quality of service to be applied to the object. Used to indicate
/// security impersonation level and context tracking mode (dynamic or static).
/// </summary>
public void* SecurityQualityOfService;
public SECURITY_QUALITY_OF_SERVICE* SecurityQualityOfService;

/// <summary>
/// Equivalent of InitializeObjectAttributes macro with the exception that you can directly set SQOS.
/// </summary>
public unsafe OBJECT_ATTRIBUTES(UNICODE_STRING* objectName, ObjectAttributes attributes, IntPtr rootDirectory)
public unsafe OBJECT_ATTRIBUTES(UNICODE_STRING* objectName, ObjectAttributes attributes, IntPtr rootDirectory, SECURITY_QUALITY_OF_SERVICE* securityQualityOfService = null)
{
Length = (uint)sizeof(OBJECT_ATTRIBUTES);
RootDirectory = rootDirectory;
ObjectName = objectName;
Attributes = attributes;
SecurityDescriptor = null;
SecurityQualityOfService = null;
SecurityQualityOfService = securityQualityOfService;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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
{
/// <summary>
/// <a href="https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_quality_of_service">SECURITY_QUALITY_OF_SERVICE</a> structure.
/// Used to support client impersonation. Client specifies this to a server to allow
/// it to impersonate the client.
/// </summary>
internal unsafe struct SECURITY_QUALITY_OF_SERVICE
{
public uint Length;
public ImpersonationLevel ImpersonationLevel;
public ContextTrackingMode ContextTrackingMode;
public BOOLEAN EffectiveOnly;

public unsafe SECURITY_QUALITY_OF_SERVICE(ImpersonationLevel impersonationLevel, ContextTrackingMode contextTrackingMode, bool effectiveOnly)
{
Length = (uint)sizeof(SECURITY_QUALITY_OF_SERVICE);
ImpersonationLevel = impersonationLevel;
ContextTrackingMode = contextTrackingMode;
EffectiveOnly = effectiveOnly ? BOOLEAN.TRUE : BOOLEAN.FALSE;
}
}

/// <summary>
/// <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa379572.aspx">SECURITY_IMPERSONATION_LEVEL</a> enumeration values.
/// [SECURITY_IMPERSONATION_LEVEL]
/// </summary>
public enum ImpersonationLevel : uint
{
/// <summary>
/// The server process cannot obtain identification information about the client and cannot impersonate the client.
/// [SecurityAnonymous]
/// </summary>
Anonymous,

/// <summary>
/// The server process can obtain identification information about the client, but cannot impersonate the client.
/// [SecurityIdentification]
/// </summary>
Identification,

/// <summary>
/// The server process can impersonate the client's security context on it's local system.
/// [SecurityImpersonation]
/// </summary>
Impersonation,

/// <summary>
/// The server process can impersonate the client's security context on remote systems.
/// [SecurityDelegation]
/// </summary>
Delegation
}

/// <summary>
/// <a href="https://msdn.microsoft.com/en-us/library/cc234317.aspx">SECURITY_CONTEXT_TRACKING_MODE</a>
/// </summary>
public enum ContextTrackingMode : byte
{
/// <summary>
/// The server is given a snapshot of the client's security context.
/// [SECURITY_STATIC_TRACKING]
/// </summary>
Static = 0x00,

/// <summary>
/// The server is continually updated with changes.
/// [SECURITY_DYNAMIC_TRACKING]
/// </summary>
Dynamic = 0x01
}
}
145 changes: 135 additions & 10 deletions src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,47 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class NtDll
{
internal const uint NT_ERROR_STATUS_DISK_FULL = 0xC000007F;
internal const uint NT_ERROR_STATUS_FILE_TOO_LARGE = 0xC0000904;
internal const uint NT_STATUS_INVALID_PARAMETER = 0xC000000D;

// https://msdn.microsoft.com/en-us/library/bb432380.aspx
// https://msdn.microsoft.com/en-us/library/windows/hardware/ff566424.aspx
[DllImport(Libraries.NtDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern unsafe int NtCreateFile(
private static extern unsafe uint NtCreateFile(
out IntPtr FileHandle,
DesiredAccess DesiredAccess,
ref OBJECT_ATTRIBUTES ObjectAttributes,
out IO_STATUS_BLOCK IoStatusBlock,
long* AllocationSize,
System.IO.FileAttributes FileAttributes,
System.IO.FileShare ShareAccess,
FileAttributes FileAttributes,
FileShare ShareAccess,
CreateDisposition CreateDisposition,
CreateOptions CreateOptions,
void* EaBuffer,
uint EaLength);

internal static unsafe (int status, IntPtr handle) CreateFile(
internal static unsafe (uint status, IntPtr handle) CreateFile(
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
ReadOnlySpan<char> path,
IntPtr rootDirectory,
CreateDisposition createDisposition,
DesiredAccess desiredAccess = DesiredAccess.FILE_GENERIC_READ | DesiredAccess.SYNCHRONIZE,
System.IO.FileShare shareAccess = System.IO.FileShare.ReadWrite | System.IO.FileShare.Delete,
System.IO.FileAttributes fileAttributes = 0,
FileShare shareAccess = FileShare.ReadWrite | FileShare.Delete,
FileAttributes fileAttributes = 0,
CreateOptions createOptions = CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT,
ObjectAttributes objectAttributes = ObjectAttributes.OBJ_CASE_INSENSITIVE,
void* eaBuffer = null,
uint eaLength = 0)
uint eaLength = 0,
long* preallocationSize = null,
SECURITY_QUALITY_OF_SERVICE* securityQualityOfService = null)
{
fixed (char* c = &MemoryMarshal.GetReference(path))
{
Expand All @@ -48,14 +56,15 @@ internal static unsafe (int status, IntPtr handle) CreateFile(
OBJECT_ATTRIBUTES attributes = new OBJECT_ATTRIBUTES(
&name,
objectAttributes,
rootDirectory);
rootDirectory,
securityQualityOfService);

int status = NtCreateFile(
uint status = NtCreateFile(
out IntPtr handle,
desiredAccess,
ref attributes,
out IO_STATUS_BLOCK statusBlock,
AllocationSize: null,
AllocationSize: preallocationSize,
fileAttributes,
shareAccess,
createDisposition,
Expand All @@ -67,6 +76,122 @@ internal static unsafe (int status, IntPtr handle) CreateFile(
}
}

internal static unsafe (uint status, IntPtr handle) CreateFile(ReadOnlySpan<char> path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long preallocationSize)
{
// For mitigating local elevation of privilege attack through named pipes
// make sure we always call NtCreateFile with SECURITY_ANONYMOUS so that the
// named pipe server can't impersonate a high privileged client security context
SECURITY_QUALITY_OF_SERVICE securityQualityOfService = new SECURITY_QUALITY_OF_SERVICE(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carlossanlop @jozkee this is to mimic the following logic:

// 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);

ImpersonationLevel.Anonymous, // SECURITY_ANONYMOUS
ContextTrackingMode.Static,
effectiveOnly: false);

return CreateFile(
path: path,
rootDirectory: IntPtr.Zero,
createDisposition: GetCreateDisposition(mode),
desiredAccess: GetDesiredAccess(access, mode, options),
shareAccess: GetShareAccess(share),
fileAttributes: GetFileAttributes(options),
createOptions: GetCreateOptions(options),
objectAttributes: GetObjectAttributes(share),
preallocationSize: &preallocationSize,
securityQualityOfService: &securityQualityOfService);
}

private static CreateDisposition GetCreateDisposition(FileMode mode)
{
switch (mode)
{
case FileMode.CreateNew:
return CreateDisposition.FILE_CREATE;
case FileMode.Create:
return CreateDisposition.FILE_SUPERSEDE;
case FileMode.OpenOrCreate:
case FileMode.Append: // has extra handling in GetDesiredAccess
return CreateDisposition.FILE_OPEN_IF;
case FileMode.Truncate:
return CreateDisposition.FILE_OVERWRITE;
default:
Debug.Assert(mode == FileMode.Open); // the enum value is validated in FileStream ctor
return CreateDisposition.FILE_OPEN;
}
}

private static DesiredAccess GetDesiredAccess(FileAccess access, FileMode fileMode, FileOptions options)
{
DesiredAccess result = 0;

if ((access & FileAccess.Read) != 0)
{
result |= DesiredAccess.FILE_GENERIC_READ;
}
if ((access & FileAccess.Write) != 0)
{
result |= DesiredAccess.FILE_GENERIC_WRITE;
}
if (fileMode == FileMode.Append)
{
result |= DesiredAccess.FILE_APPEND_DATA;
}
if ((options & FileOptions.Asynchronous) == 0)
{
result |= DesiredAccess.SYNCHRONIZE; // required by FILE_SYNCHRONOUS_IO_NONALERT
}
if ((options & FileOptions.DeleteOnClose) != 0 || fileMode == FileMode.Create)
{
result |= DesiredAccess.DELETE; // required by FILE_DELETE_ON_CLOSE and FILE_SUPERSEDE (which deletes a file if it exists)
}

return result;
}

private static FileShare GetShareAccess(FileShare share)
=> share & ~FileShare.Inheritable; // FileShare.Inheritable is handled in GetObjectAttributes

private static FileAttributes GetFileAttributes(FileOptions options)
=> (options & FileOptions.Encrypted) != 0 ? FileAttributes.Encrypted : 0;

// FileOptions.Encrypted is handled in GetFileAttributes
private static CreateOptions GetCreateOptions(FileOptions options)
{
// Every directory is just a directory FILE.
// FileStream does not allow for opening directories on purpose.
// FILE_NON_DIRECTORY_FILE is used to ensure that
CreateOptions result = CreateOptions.FILE_NON_DIRECTORY_FILE;

if ((options & FileOptions.WriteThrough) != 0)
{
result |= CreateOptions.FILE_WRITE_THROUGH;
}
if ((options & FileOptions.RandomAccess) != 0)
{
result |= CreateOptions.FILE_RANDOM_ACCESS;
}
if ((options & FileOptions.SequentialScan) != 0)
{
result |= CreateOptions.FILE_SEQUENTIAL_ONLY;
}
if ((options & FileOptions.DeleteOnClose) != 0)
{
result |= CreateOptions.FILE_DELETE_ON_CLOSE; // has extra handling in GetDesiredAccess
}
if ((options & FileOptions.Asynchronous) == 0)
{
// it's async by default, so we need to disable it when async was not requested
result |= CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT; // has extra handling in GetDesiredAccess
}
if (((int)options & 0x20000000) != 0) // NoBuffering
{
result |= CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING;
}

return result;
}

private static ObjectAttributes GetObjectAttributes(FileShare share)
=> (share & FileShare.Inheritable) != 0 ? ObjectAttributes.OBJ_INHERIT : 0;

/// <summary>
/// File creation disposition when calling directly to NT APIs.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ internal struct IO_STATUS_BLOCK
}

internal const uint FileModeInformation = 16;
internal const uint FILE_SYNCHRONOUS_IO_ALERT = 0x00000010;
internal const uint FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020;

internal const int STATUS_INVALID_HANDLE = unchecked((int)0xC0000008);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@
Link="Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs"
Link="Common\Interop\Windows\Interop.UNICODE_STRING.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs"
Link="Common\Interop\Windows\Interop.SECURITY_QUALITY_OF_SERVICE.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CopyFile.cs"
Link="Common\Interop\Windows\Interop.CopyFile.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CopyFileEx.cs"
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/Native/Unix/Common/pal_config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
#cmakedefine01 HAVE_STRLCAT
#cmakedefine01 HAVE_SHM_OPEN_THAT_WORKS_WELL_ENOUGH_WITH_MMAP
#cmakedefine01 HAVE_POSIX_ADVISE
#cmakedefine01 HAVE_POSIX_FALLOCATE
#cmakedefine01 HAVE_POSIX_FALLOCATE64
#cmakedefine01 PRIORITY_REQUIRES_INT_WHO
#cmakedefine01 KEVENT_REQUIRES_INT_PARAMS
#cmakedefine01 HAVE_IOCTL
Expand Down
1 change: 1 addition & 0 deletions src/libraries/Native/Unix/System.Native/entrypoints.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_FTruncate)
DllImportEntry(SystemNative_Poll)
DllImportEntry(SystemNative_PosixFAdvise)
DllImportEntry(SystemNative_PosixFAllocate)
DllImportEntry(SystemNative_Read)
DllImportEntry(SystemNative_ReadLink)
DllImportEntry(SystemNative_Rename)
Expand Down
Loading