PowerToys icon indicating copy to clipboard operation
PowerToys copied to clipboard

[FileLocksmith.Interop] Enhance File Path Resolution with GetFinalPathNameByHandle

Open PolarGoose opened this issue 1 year ago • 3 comments

Description of the new feature / enhancement

Context

In the current implementation of the FileLocksmith module NtdllExtensions::path_to_kernel_name is utilized to translate NT-style paths to a more conventional, drive-based format. This translation is necessary after fetching file paths using the NtDll.NtQuerySystemInformation function.

While this manual approach is functional, Windows API offers a more robust and potentially less error-prone method for achieving the same goal: GetFinalPathNameByHandleW. This function, given a file handle, returns the full drive-based path of the file, including handling various path nuances and edge cases automatically.

Proposal

Replace the current manual method implemented in path_to_kernel_name with GetFinalPathNameByHandleW

Scenario when this would be used?

Convert the NT device object path to the path with the drive letter.

Supporting information

Reference implementation in pseudo C#

internal static class WinApi {
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
    [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
    private static extern int GetFinalPathNameByHandleW(SafeFileHandle hFile, [Out] StringBuilder filePathBuffer, int filePathBufferSize, int flags);

    public static string? GetFinalPathNameByHandle(SafeFileHandle hFile)
    {
        var buf = new StringBuilder();
        var result = GetFinalPathNameByHandleW(hFile, buf, buf.Capacity, 0);
        if(result == 0)
        {
            return null;
        }

        buf.EnsureCapacity(result);
        result = GetFinalPathNameByHandleW(hFile, buf, buf.Capacity, 0);
        if (result == 0)
        {
            return null;
        }

        var str = buf.ToString();
        return str.StartsWith(@"\\?\") ? str.Substring(4) : str;
    }
}

public void Test()
{
    var handles = NtDll.QuerySystemHandleInformation();
    foreach (var h in handles)
    {
        using var openedProcess = WinApi.OpenProcess(...);
        var curProcess = WinApi.GetCurrentProcess();
        var res = WinApi.DuplicateHandle(out var dupHandle);

        // If the handle type is a File, then the driveLetterBasedFileFullName will have a value like "\\?\C:\Windows\System32\en-US\combase.dll.mui"
        var driveLetterBasedFileFullName = WinApi.GetFinalPathNameByHandle(dupHandle)
    }
}

PolarGoose avatar Feb 11 '24 18:02 PolarGoose

@jaimecbernardo-msft - if you agree with this change I am going to take off the needs-triage label and add the Help-Wanted tag

joadoumie avatar Feb 23 '24 20:02 joadoumie

@jaimecbernardo-msft - if you agree with this change I am going to take off the needs-triage label and add the Help-Wanted tag

Sounds good to me, @joadoumie . Thank you!

jaimecbernardo avatar Feb 26 '24 10:02 jaimecbernardo

I also described another improvement here.

In general, the Interop C++ library is not needed. It is possible to write the lock-finding code in C# like I have done in one of my projects here

PolarGoose avatar Apr 28 '24 19:04 PolarGoose