Skip to content

Commit

Permalink
Ensure Directory.Delete works fine even if user has no ListDirectory …
Browse files Browse the repository at this point in the history
…permissions (dotnet#56996)
  • Loading branch information
adamsitnik authored Aug 9, 2021
1 parent e543859 commit 27059fb
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text;
using Xunit;
using Microsoft.DotNet.XUnitExtensions;
using System.Security.Principal;
using System.Security.AccessControl;

namespace System.IO.Tests
{
public partial class Directory_Delete_str_bool : Directory_Delete_str
{
[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public void RecursiveDelete_NoListDirectoryPermission() // https://github.com/dotnet/runtime/issues/56922
{
string parentPath = GetTestFilePath();
var parent = Directory.CreateDirectory(parentPath);
var ac = parent.GetAccessControl();
ac.SetAccessRule(new FileSystemAccessRule(WindowsIdentity.GetCurrent().User, FileSystemRights.ListDirectory, AccessControlType.Deny));
parent.SetAccessControl(ac);

var subDir = parent.CreateSubdirectory("subdir");
File.Create(Path.Combine(subDir.FullName, GetTestFileName())).Dispose();
Delete(subDir.FullName, recursive: true);
Assert.False(subDir.Exists);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ public void Unix_NotFoundDirectory_ReadOnlyVolume()
#endregion
}

public class Directory_Delete_str_bool : Directory_Delete_str
public partial class Directory_Delete_str_bool : Directory_Delete_str
{
#region Utilities

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
Expand Down Expand Up @@ -75,6 +75,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Include="Base\SymbolicLinks\BaseSymbolicLinks.Windows.cs" />
<Compile Include="Directory\Delete.Windows.cs" />
<Compile Include="FileSystemTest.Windows.cs" />
<Compile Include="FileStream\ctor_options_as.Windows.cs" />
<Compile Include="FileStream\FileStreamConformanceTests.Windows.cs" />
Expand All @@ -94,6 +95,7 @@
<Compile Include="$(CoreLibSharedDir)System\IO\PathInternal.cs" Link="Common\System\IO\PathInternal.cs" />
<Compile Include="$(CoreLibSharedDir)System\IO\PathInternal.Windows.cs" Link="Common\System\IO\PathInternal.Windows.cs" />
<ProjectReference Include="$(LibrariesProjectRoot)System.ServiceProcess.ServiceController\src\System.ServiceProcess.ServiceController.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)System.IO.FileSystem.AccessControl\src\System.IO.FileSystem.AccessControl.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
<Compile Include="Base\SymbolicLinks\BaseSymbolicLinks.Unix.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,10 @@ public static void RemoveDirectory(string fullPath, bool recursive)
}

Interop.Kernel32.WIN32_FIND_DATA findData = default;
GetFindData(fullPath, isDirectory: true, ref findData);
// FindFirstFile($path) (used by GetFindData) fails with ACCESS_DENIED when user has no ListDirectory rights
// but FindFirstFile($path/*") (used by RemoveDirectoryRecursive) works fine in such scenario.
// So we ignore it here and let RemoveDirectoryRecursive throw if FindFirstFile($path/*") fails with ACCESS_DENIED.
GetFindData(fullPath, isDirectory: true, ignoreAccessDenied: true, ref findData);
if (IsNameSurrogateReparsePoint(ref findData))
{
// Don't recurse
Expand All @@ -200,7 +203,7 @@ public static void RemoveDirectory(string fullPath, bool recursive)
RemoveDirectoryRecursive(fullPath, ref findData, topLevel: true);
}

private static void GetFindData(string fullPath, bool isDirectory, ref Interop.Kernel32.WIN32_FIND_DATA findData)
private static void GetFindData(string fullPath, bool isDirectory, bool ignoreAccessDenied, ref Interop.Kernel32.WIN32_FIND_DATA findData)
{
using SafeFindHandle handle = Interop.Kernel32.FindFirstFile(Path.TrimEndingDirectorySeparator(fullPath), ref findData);
if (handle.IsInvalid)
Expand All @@ -209,6 +212,8 @@ private static void GetFindData(string fullPath, bool isDirectory, ref Interop.K
// File not found doesn't make much sense coming from a directory.
if (isDirectory && errorCode == Interop.Errors.ERROR_FILE_NOT_FOUND)
errorCode = Interop.Errors.ERROR_PATH_NOT_FOUND;
if (isDirectory && errorCode == Interop.Errors.ERROR_ACCESS_DENIED && ignoreAccessDenied)
return;
throw Win32Marshal.GetExceptionForWin32Error(errorCode, fullPath);
}
}
Expand Down Expand Up @@ -530,7 +535,7 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i
private static unsafe string? GetFinalLinkTarget(string linkPath, bool isDirectory)
{
Interop.Kernel32.WIN32_FIND_DATA data = default;
GetFindData(linkPath, isDirectory, ref data);
GetFindData(linkPath, isDirectory, ignoreAccessDenied: false, ref data);

// The file or directory is not a reparse point.
if ((data.dwFileAttributes & (uint)FileAttributes.ReparsePoint) == 0 ||
Expand Down

0 comments on commit 27059fb

Please sign in to comment.