Skip to content

Windows equality check does not guarantee uniqueness on ReFS #63

Open

Description

@landaire

This issue is a bit pedantic (mostly because nobody actually uses ReFS), but while discussing file equality checks with a friend I remarked on how this crate conducts the check on Windows by calling GetInformationByHandle and checking some of its output members.

I remarked that the checks done by this crate should be sufficient considering the docs say:

You can compare the VolumeSerialNumber and FileIndex members returned in the BY_HANDLE_FILE_INFORMATION structure to determine if two paths map to the same target; for example, you can compare two file paths and determine if they map to the same directory.

However, he noted that the BY_HANDLE_FILE_INFORMATION docs state:

The ReFS file system, introduced with Windows Server 2012, includes 128-bit file identifiers. To retrieve the 128-bit file identifier use the GetFileInformationByHandleEx function with FileIdInfo to retrieve the FILE_ID_INFO structure. The 64-bit identifier in this structure is not guaranteed to be unique on ReFS.

i.e. for those trailblazers out there who might be using ReFS, the underlying winapi-util crate should probably be using data returned by GetFileInformationByHandleEx and using a 128-bit identifier instead. Since the context really only matters for this crate though, I'm filing this as an issue here.

Some additional context, I used to work on the Hyper-V security team and my friend worked on the Windows Sandbox security team. I never really had to deal with untrusted file paths considering Hyper-V threat model, but he mentioned that apparently we would call GetFinalPathNameByHandle() with the FILE_NAME_NORMALIZED flag when doing security-sensitive file operations and comparing the output path. I'm not trying to suggest that the checks done by this crate shouldn't be sufficient (especially since the docs explicitly say otherwise), but now I can't help but wonder if there's a reason why we weren't checking similar info.

Activity

  1. BurntSushi commented on Oct 17, 2025

    @BurntSushi
    Owner

    Yes, this is called out explicitly in the code:

    same-file/src/win.rs

    Lines 17 to 25 in e7d851b

    // It gets worse. It appears that the index numbers are not always
    // guaranteed to be unique. Namely, ReFS uses 128 bit numbers for unique
    // identifiers. This requires a distinct syscall to get `FILE_ID_INFO`
    // documented here:
    // https://msdn.microsoft.com/en-us/library/windows/desktop/hh802691(v=vs.85).aspx
    //
    // It seems straight-forward enough to modify this code to use
    // `FILE_ID_INFO` when available (minimum Windows Server 2012), but I don't
    // have access to such Windows machines.

    I can't remember why I didn't follow through with that. Maybe something about it not being supported on older versions of Windows? It looks like it should probably be fine to use for now.

    Some additional context, I used to work on the Hyper-V security team and my friend worked on the Windows Sandbox security team. I never really had to deal with untrusted file paths considering Hyper-V threat model, but he mentioned that apparently we would call GetFinalPathNameByHandle() with the FILE_NAME_NORMALIZED flag when doing security-sensitive file operations and comparing the output path. I'm not trying to suggest that the checks done by this crate shouldn't be sufficient (especially since the docs explicitly say otherwise), but now I can't help but wonder if there's a reason why we weren't checking similar info.

    I've seen folks do a similar thing on Unix as well.

    I think that technique works, but it may be more costly. In contrast, the technique used in this crate doesn't require comparing file paths or doing potentially costly canonicalization on the path itself. Indeed, this crate doesn't even require a path at all. You can just pass in a File. Or even use stdin/stdout/stderr. Indeed, this latter thing is a critical piece that ripgrep uses to determine if a file it wants to search is equivalent to the process's stdout (because if it is, searching and printing to it can result in an infinite positive feedback loop). If ripgrep had to canonicalize every path just to do this check, the resulting performance would be disastrous.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions