From 9db169d5ad22ad88365207cc035c91194b005f1d Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 5 Jan 2023 14:11:47 +0100 Subject: [PATCH 1/5] Fix NativeMethods.IsSymLink --- src/Framework/NativeMethods.cs | 85 ++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs index f871d073876..0f85185e378 100644 --- a/src/Framework/NativeMethods.cs +++ b/src/Framework/NativeMethods.cs @@ -45,6 +45,8 @@ internal static class NativeMethods internal const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; internal const int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400; + internal const uint IO_REPARSE_TAG_SYMLINK = 0xA000000C; + /// /// Default buffer size to use when dealing with the Windows API. /// @@ -208,6 +210,12 @@ internal enum SymbolicLink AllowUnprivilegedCreate = 2, } + // https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ne-minwinbase-file_info_by_handle_class + private enum FileInfoByHandleClass : int + { + FileAttributeTagInfo = 9 + } + #endregion #region Structs @@ -575,6 +583,17 @@ private unsafe static int GetLogicalCoreCountOnWindows() return -1; } + /// + /// Receives the requested file attribute information. Used for any handles. + /// Use only when calling GetFileInformationByHandleEx. + /// + [StructLayout(LayoutKind.Sequential)] + internal class FileAttributeTagInfo + { + internal int fileAttributes; + internal int reparseTag; + } + #endregion #region Member data @@ -1072,12 +1091,38 @@ internal static bool IsSymLink(FileInfo fileInfo) WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); - return NativeMethods.GetFileAttributesEx(fileInfo.FullName, 0, ref data) && - (data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0 && - (data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT; + return + NativeMethods.GetFileAttributesEx(fileInfo.FullName, 0, ref data) && + (data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0 && + // This is fast but unspecific check - there are multiple types of reparse points. + (data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT && + // Specific check for a symlink. + IsSymLinkFileInternal(fileInfo.FullName); #endif } + private static bool IsSymLinkFileInternal(string path) + { + using SafeFileHandle handle = CreateFile(path, + GENERIC_READ, + FILE_SHARE_READ, + IntPtr.Zero, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, + IntPtr.Zero); + + if (handle.IsInvalid) + { + // Link is broken. Details can be obtained via GetLastError. + return false; + } + + return + GetFileAttributeTagInfoByHandle(handle, out FileAttributeTagInfo attributes) && + (attributes.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT && + (attributes.reparseTag & NativeMethods.IO_REPARSE_TAG_SYMLINK) == NativeMethods.IO_REPARSE_TAG_SYMLINK; + } + internal static bool IsSymLink(string path) { return IsSymLink(new FileInfo(path)); @@ -1625,6 +1670,40 @@ internal static void VerifyThrowWin32Result(int result) [SupportedOSPlatform("windows")] internal static extern bool GetFileAttributesEx(String name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation); + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + [SupportedOSPlatform("windows")] + private static extern bool GetFileInformationByHandleEx( + SafeFileHandle fileHandle, + FileInfoByHandleClass fileInfoByHandleClass, + [Out] IntPtr lpFileInformation, + int dwBufferSize); + + [SupportedOSPlatform("windows")] + static bool GetFileAttributeTagInfoByHandle(SafeFileHandle fileHandle, out FileAttributeTagInfo fileAttributeTagInfo) + { + IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FileAttributeTagInfo))); + try + { + bool ret = GetFileInformationByHandleEx(fileHandle, FileInfoByHandleClass.FileAttributeTagInfo, ptr, Marshal.SizeOf(typeof(FileAttributeTagInfo))); + if (ret) + { + fileAttributeTagInfo = (FileAttributeTagInfo)Marshal.PtrToStructure(ptr, typeof(FileAttributeTagInfo)); + } + else + { + fileAttributeTagInfo = new FileAttributeTagInfo(); + } + + return ret; + } + finally + { + Marshal.FreeHGlobal(ptr); + } + } + + [DllImport("kernel32.dll", PreserveSig = true, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] [SupportedOSPlatform("windows")] From 1da2eac9b31099c4da25eef23a2bc33564a98218 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 5 Jan 2023 14:19:24 +0100 Subject: [PATCH 2/5] Dedupe Marshal.SizeOf calls --- src/Framework/NativeMethods.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs index 0f85185e378..78b5a184bd5 100644 --- a/src/Framework/NativeMethods.cs +++ b/src/Framework/NativeMethods.cs @@ -1682,10 +1682,11 @@ private static extern bool GetFileInformationByHandleEx( [SupportedOSPlatform("windows")] static bool GetFileAttributeTagInfoByHandle(SafeFileHandle fileHandle, out FileAttributeTagInfo fileAttributeTagInfo) { - IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(FileAttributeTagInfo))); + int typeSize = Marshal.SizeOf(typeof(FileAttributeTagInfo)); + IntPtr ptr = Marshal.AllocHGlobal(typeSize); try { - bool ret = GetFileInformationByHandleEx(fileHandle, FileInfoByHandleClass.FileAttributeTagInfo, ptr, Marshal.SizeOf(typeof(FileAttributeTagInfo))); + bool ret = GetFileInformationByHandleEx(fileHandle, FileInfoByHandleClass.FileAttributeTagInfo, ptr, typeSize); if (ret) { fileAttributeTagInfo = (FileAttributeTagInfo)Marshal.PtrToStructure(ptr, typeof(FileAttributeTagInfo)); From 8eb6be2c3ad4baa1bd2982a0071759fa5febc5fe Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 5 Jan 2023 15:01:14 +0100 Subject: [PATCH 3/5] Fix OS contract --- src/Framework/NativeMethods.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs index 78b5a184bd5..df945ce4a04 100644 --- a/src/Framework/NativeMethods.cs +++ b/src/Framework/NativeMethods.cs @@ -1101,6 +1101,7 @@ internal static bool IsSymLink(FileInfo fileInfo) #endif } + [SupportedOSPlatform("windows")] private static bool IsSymLinkFileInternal(string path) { using SafeFileHandle handle = CreateFile(path, From f2d7b8d221921276c71aea13ba284661281d8533 Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 5 Jan 2023 16:15:14 +0100 Subject: [PATCH 4/5] Improve marshaling --- src/Framework/NativeMethods.cs | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs index df945ce4a04..a4d816c5d02 100644 --- a/src/Framework/NativeMethods.cs +++ b/src/Framework/NativeMethods.cs @@ -588,7 +588,7 @@ private unsafe static int GetLogicalCoreCountOnWindows() /// Use only when calling GetFileInformationByHandleEx. /// [StructLayout(LayoutKind.Sequential)] - internal class FileAttributeTagInfo + internal struct FileAttributeTagInfo { internal int fileAttributes; internal int reparseTag; @@ -1681,27 +1681,12 @@ private static extern bool GetFileInformationByHandleEx( int dwBufferSize); [SupportedOSPlatform("windows")] - static bool GetFileAttributeTagInfoByHandle(SafeFileHandle fileHandle, out FileAttributeTagInfo fileAttributeTagInfo) + static unsafe bool GetFileAttributeTagInfoByHandle(SafeFileHandle fileHandle, out FileAttributeTagInfo fileAttributeTagInfo) { - int typeSize = Marshal.SizeOf(typeof(FileAttributeTagInfo)); - IntPtr ptr = Marshal.AllocHGlobal(typeSize); - try - { - bool ret = GetFileInformationByHandleEx(fileHandle, FileInfoByHandleClass.FileAttributeTagInfo, ptr, typeSize); - if (ret) - { - fileAttributeTagInfo = (FileAttributeTagInfo)Marshal.PtrToStructure(ptr, typeof(FileAttributeTagInfo)); - } - else - { - fileAttributeTagInfo = new FileAttributeTagInfo(); - } - - return ret; - } - finally + fileAttributeTagInfo = new FileAttributeTagInfo(); + fixed (FileAttributeTagInfo* ptr = &fileAttributeTagInfo) { - Marshal.FreeHGlobal(ptr); + return GetFileInformationByHandleEx(fileHandle, FileInfoByHandleClass.FileAttributeTagInfo, (IntPtr)ptr, sizeof(FileAttributeTagInfo)); } } From 1f1e2b2e290138121339e5ef6c05d27c9ca6f4ae Mon Sep 17 00:00:00 2001 From: Jan Krivanek Date: Thu, 5 Jan 2023 16:26:31 +0100 Subject: [PATCH 5/5] Decrease requested file access rights --- src/Framework/NativeMethods.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs index a4d816c5d02..51b6302fe91 100644 --- a/src/Framework/NativeMethods.cs +++ b/src/Framework/NativeMethods.cs @@ -1105,7 +1105,7 @@ internal static bool IsSymLink(FileInfo fileInfo) private static bool IsSymLinkFileInternal(string path) { using SafeFileHandle handle = CreateFile(path, - GENERIC_READ, + FILE_READ_ATTRIBUTES, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, @@ -1800,6 +1800,7 @@ internal static bool SetCurrentDirectory(string path) public static extern int CoWaitForMultipleHandles(COWAIT_FLAGS dwFlags, int dwTimeout, int cHandles, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] pHandles, out int pdwIndex); internal const uint GENERIC_READ = 0x80000000; + internal const uint FILE_READ_ATTRIBUTES = 0x80; internal const uint FILE_SHARE_READ = 0x1; internal const uint FILE_ATTRIBUTE_NORMAL = 0x80; internal const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;