Skip to content

Commit 85b144a

Browse files
authored
Fix FileSystemAclExtensions.Create when passing a null FileSecurity (#61297)
* Make FileSecurity parameter nullable. * Add missing ArgumentException message for FileMode.Append. * Refactor tests to ensure FileSecurity is tested with all FileMode and FileSystemRights combinations. Separate special cases. * Remove exception that throws when FileSecurity is null. Ensure we have logic that can create a FileHandle when FileSecurity is null. Fix bug where FileShare.Inheritable causes IOException because it is being unexpectedly passed to the P/Invoke (it should just be saved in the SECURITY_ATTRIBUTES struct). Add documentation to mention this parameter as optional. Ensure all exceptions match exactly what we have in .NET Framework, with simpler logic. * Address suggestions Co-authored-by: carlossanlop <[email protected]>
1 parent 73bf54f commit 85b144a

File tree

4 files changed

+333
-187
lines changed

4 files changed

+333
-187
lines changed

src/libraries/System.IO.FileSystem.AccessControl/ref/System.IO.FileSystem.AccessControl.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace System.IO
99
public static partial class FileSystemAclExtensions
1010
{
1111
public static void Create(this System.IO.DirectoryInfo directoryInfo, System.Security.AccessControl.DirectorySecurity directorySecurity) { }
12-
public static System.IO.FileStream Create(this System.IO.FileInfo fileInfo, System.IO.FileMode mode, System.Security.AccessControl.FileSystemRights rights, System.IO.FileShare share, int bufferSize, System.IO.FileOptions options, System.Security.AccessControl.FileSecurity fileSecurity) { throw null; }
12+
public static System.IO.FileStream Create(this System.IO.FileInfo fileInfo, System.IO.FileMode mode, System.Security.AccessControl.FileSystemRights rights, System.IO.FileShare share, int bufferSize, System.IO.FileOptions options, System.Security.AccessControl.FileSecurity? fileSecurity) { throw null; }
1313
public static System.IO.DirectoryInfo CreateDirectory(this System.Security.AccessControl.DirectorySecurity directorySecurity, string path) { throw null; }
1414
public static System.Security.AccessControl.DirectorySecurity GetAccessControl(this System.IO.DirectoryInfo directoryInfo) { throw null; }
1515
public static System.Security.AccessControl.DirectorySecurity GetAccessControl(this System.IO.DirectoryInfo directoryInfo, System.Security.AccessControl.AccessControlSections includeSections) { throw null; }

src/libraries/System.IO.FileSystem.AccessControl/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@
122122
<data name="Arg_MustBeIdentityReferenceType" xml:space="preserve">
123123
<value>Type must be an IdentityReference, such as NTAccount or SecurityIdentifier.</value>
124124
</data>
125+
<data name="Argument_InvalidAppendMode" xml:space="preserve">
126+
<value>Append access can be requested only in write-only mode.</value>
127+
</data>
125128
<data name="Argument_InvalidEnumValue" xml:space="preserve">
126129
<value>The value '{0}' is not valid for this usage of the type {1}.</value>
127130
</data>

src/libraries/System.IO.FileSystem.AccessControl/src/System/IO/FileSystemAclExtensions.cs

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -159,29 +159,24 @@ public static void Create(this DirectoryInfo directoryInfo, DirectorySecurity di
159159
/// <param name="share">One of the enumeration values for controlling the kind of access other FileStream objects can have to the same file.</param>
160160
/// <param name="bufferSize">The number of bytes buffered for reads and writes to the file.</param>
161161
/// <param name="options">One of the enumeration values that describes how to create or overwrite the file.</param>
162-
/// <param name="fileSecurity">An object that determines the access control and audit security for the file.</param>
162+
/// <param name="fileSecurity">An optional object that determines the access control and audit security for the file.</param>
163163
/// <returns>A file stream for the newly created file.</returns>
164164
/// <exception cref="ArgumentException">The <paramref name="rights" /> and <paramref name="mode" /> combination is invalid.</exception>
165-
/// <exception cref="ArgumentNullException"><paramref name="fileInfo" /> or <paramref name="fileSecurity" /> is <see langword="null" />.</exception>
165+
/// <exception cref="ArgumentNullException"><paramref name="fileInfo" /> is <see langword="null" />.</exception>
166166
/// <exception cref="ArgumentOutOfRangeException"><paramref name="mode" /> or <paramref name="share" /> are out of their legal enum range.
167167
///-or-
168168
/// <paramref name="bufferSize" /> is not a positive number.</exception>
169169
/// <exception cref="DirectoryNotFoundException">Could not find a part of the path.</exception>
170170
/// <exception cref="IOException">An I/O error occurs.</exception>
171171
/// <exception cref="UnauthorizedAccessException">Access to the path is denied.</exception>
172172
/// <remarks>This extension method was added to .NET Core to bring the functionality that was provided by the `System.IO.FileStream.#ctor(System.String,System.IO.FileMode,System.Security.AccessControl.FileSystemRights,System.IO.FileShare,System.Int32,System.IO.FileOptions,System.Security.AccessControl.FileSecurity)` .NET Framework constructor.</remarks>
173-
public static FileStream Create(this FileInfo fileInfo, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options, FileSecurity fileSecurity)
173+
public static FileStream Create(this FileInfo fileInfo, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options, FileSecurity? fileSecurity)
174174
{
175175
if (fileInfo == null)
176176
{
177177
throw new ArgumentNullException(nameof(fileInfo));
178178
}
179179

180-
if (fileSecurity == null)
181-
{
182-
throw new ArgumentNullException(nameof(fileSecurity));
183-
}
184-
185180
// don't include inheritable in our bounds check for share
186181
FileShare tempshare = share & ~FileShare.Inheritable;
187182

@@ -200,18 +195,33 @@ public static FileStream Create(this FileInfo fileInfo, FileMode mode, FileSyste
200195
throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
201196
}
202197

203-
// Do not allow using combinations of non-writing file system rights with writing file modes
198+
// Do not combine writing modes with exclusively read rights
199+
// Write contains AppendData, WriteAttributes, WriteData and WriteExtendedAttributes
204200
if ((rights & FileSystemRights.Write) == 0 &&
205201
(mode == FileMode.Truncate || mode == FileMode.CreateNew || mode == FileMode.Create || mode == FileMode.Append))
206202
{
207203
throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndFileSystemRightsCombo, mode, rights));
208204
}
209205

206+
// Additionally, append is disallowed if any read rights are provided
207+
// ReadAndExecute contains ExecuteFile, ReadAttributes, ReadData, ReadExtendedAttributes and ReadPermissions
208+
if ((rights & FileSystemRights.ReadAndExecute) != 0 && mode == FileMode.Append)
209+
{
210+
throw new ArgumentException(SR.Argument_InvalidAppendMode);
211+
}
212+
213+
// Cannot truncate unless all of the write rights are provided
214+
// Write contains AppendData, WriteAttributes, WriteData and WriteExtendedAttributes
215+
if (mode == FileMode.Truncate && (rights & FileSystemRights.Write) != FileSystemRights.Write)
216+
{
217+
throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndFileSystemRightsCombo, mode, rights));
218+
}
219+
210220
SafeFileHandle handle = CreateFileHandle(fileInfo.FullName, mode, rights, share, options, fileSecurity);
211221

212222
try
213223
{
214-
return new FileStream(handle, GetFileStreamFileAccess(rights), bufferSize, (options & FileOptions.Asynchronous) != 0);
224+
return new FileStream(handle, GetFileAccessFromRights(rights), bufferSize, (options & FileOptions.Asynchronous) != 0);
215225
}
216226
catch
217227
{
@@ -258,23 +268,48 @@ public static DirectoryInfo CreateDirectory(this DirectorySecurity directorySecu
258268

259269
// In the context of a FileStream, the only ACCESS_MASK ACE rights we care about are reading/writing data and the generic read/write rights.
260270
// See: https://docs.microsoft.com/en-us/windows/win32/secauthz/access-mask
261-
private static FileAccess GetFileStreamFileAccess(FileSystemRights rights)
271+
private static FileAccess GetFileAccessFromRights(FileSystemRights rights)
262272
{
263273
FileAccess access = 0;
264-
if ((rights & FileSystemRights.ReadData) != 0 || ((int)rights & Interop.Kernel32.GenericOperations.GENERIC_READ) != 0)
274+
275+
if ((rights & FileSystemRights.FullControl) != 0 ||
276+
(rights & FileSystemRights.Modify) != 0)
277+
{
278+
return FileAccess.ReadWrite;
279+
}
280+
281+
if ((rights & FileSystemRights.ReadData) != 0 || // Same as ListDirectory
282+
(rights & FileSystemRights.ReadExtendedAttributes) != 0 ||
283+
(rights & FileSystemRights.ExecuteFile) != 0 || // Same as Traverse
284+
(rights & FileSystemRights.ReadAttributes) != 0 ||
285+
(rights & FileSystemRights.ReadPermissions) != 0 ||
286+
(rights & FileSystemRights.TakeOwnership) != 0 ||
287+
((int)rights & Interop.Kernel32.GenericOperations.GENERIC_READ) != 0)
265288
{
266289
access = FileAccess.Read;
267290
}
268291

269-
if ((rights & FileSystemRights.WriteData) != 0 || ((int)rights & Interop.Kernel32.GenericOperations.GENERIC_WRITE) != 0)
292+
if ((rights & FileSystemRights.AppendData) != 0 || // Same as CreateDirectories
293+
(rights & FileSystemRights.ChangePermissions) != 0 ||
294+
(rights & FileSystemRights.Delete) != 0 ||
295+
(rights & FileSystemRights.DeleteSubdirectoriesAndFiles) != 0 ||
296+
(rights & FileSystemRights.WriteAttributes) != 0 ||
297+
(rights & FileSystemRights.WriteData) != 0 || // Same as CreateFiles
298+
(rights & FileSystemRights.WriteExtendedAttributes) != 0 ||
299+
((int)rights & Interop.Kernel32.GenericOperations.GENERIC_WRITE) != 0)
300+
{
301+
access |= FileAccess.Write;
302+
}
303+
304+
if (access == 0)
270305
{
271-
access = access == FileAccess.Read ? FileAccess.ReadWrite : FileAccess.Write;
306+
throw new ArgumentOutOfRangeException(nameof(rights));
272307
}
273308

274309
return access;
275310
}
276311

277-
private static unsafe SafeFileHandle CreateFileHandle(string fullPath, FileMode mode, FileSystemRights rights, FileShare share, FileOptions options, FileSecurity security)
312+
private static unsafe SafeFileHandle CreateFileHandle(string fullPath, FileMode mode, FileSystemRights rights, FileShare share, FileOptions options, FileSecurity? security)
278313
{
279314
Debug.Assert(fullPath != null);
280315

@@ -291,23 +326,39 @@ private static unsafe SafeFileHandle CreateFileHandle(string fullPath, FileMode
291326

292327
SafeFileHandle handle;
293328

294-
fixed (byte* pSecurityDescriptor = security.GetSecurityDescriptorBinaryForm())
329+
var secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES
295330
{
296-
var secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES
331+
nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES),
332+
bInheritHandle = ((share & FileShare.Inheritable) != 0) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE,
333+
};
334+
335+
if (security != null)
336+
{
337+
fixed (byte* pSecurityDescriptor = security.GetSecurityDescriptorBinaryForm())
297338
{
298-
nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES),
299-
bInheritHandle = ((share & FileShare.Inheritable) != 0) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE,
300-
lpSecurityDescriptor = (IntPtr)pSecurityDescriptor
301-
};
339+
secAttrs.lpSecurityDescriptor = (IntPtr)pSecurityDescriptor;
340+
handle = CreateFileHandleInternal(fullPath, mode, rights, share, flagsAndAttributes, &secAttrs);
341+
}
342+
}
343+
else
344+
{
345+
handle = CreateFileHandleInternal(fullPath, mode, rights, share, flagsAndAttributes, &secAttrs);
346+
}
347+
348+
return handle;
302349

350+
static unsafe SafeFileHandle CreateFileHandleInternal(string fullPath, FileMode mode, FileSystemRights rights, FileShare share, int flagsAndAttributes, Interop.Kernel32.SECURITY_ATTRIBUTES* secAttrs)
351+
{
352+
SafeFileHandle handle;
303353
using (DisableMediaInsertionPrompt.Create())
304354
{
305-
handle = Interop.Kernel32.CreateFile(fullPath, (int)rights, share, &secAttrs, mode, flagsAndAttributes, IntPtr.Zero);
355+
// The Inheritable bit is only set in the SECURITY_ATTRIBUTES struct,
356+
// and should not be passed to the CreateFile P/Invoke.
357+
handle = Interop.Kernel32.CreateFile(fullPath, (int)rights, (share & ~FileShare.Inheritable), secAttrs, mode, flagsAndAttributes, IntPtr.Zero);
306358
ValidateFileHandle(handle, fullPath);
307359
}
360+
return handle;
308361
}
309-
310-
return handle;
311362
}
312363

313364
private static void ValidateFileHandle(SafeFileHandle handle, string fullPath)

0 commit comments

Comments
 (0)