Get the immediate target path from symlink/reparse point
Asked Answered
T

2

6

I am aware that GetFinalPathNameByHandle can be used to obtain the target of a symbolic link or a reparse point, but there are situations where its use is not desirable:

  • If the target is not available, doesn't exist or cannot be opened, CreateFile on a symlink fails, and thus the path cannot be obtained.
  • If I point a symlink "a" to file "b" and create a symlink "b" to file "c", the function follows the whole chain, returning "c".
  • The function is not useful much when I already have the handle to the actual symlink at hand.

It seems that DeviceIoControl can be used together with FSCTL_GET_REPARSE_POINT to obtain the actual reparse data of the file, but that gets me the REPARSE_DATA_BUFFER, and I would have to parse that.

I don't know how the system actually processes reparse points, but I think that the target location is a piece of information that should be available at some point. The dir command, for example, can display the target path correctly for any reparse point... well I have already seen it handle just symlinks and mount points (junctions).

Tender answered 23/9, 2017 at 19:21 Comment(3)
After some reverse engineering, it really seems that cmd.exe does call DeviceIoControl and parse the data, but only for a symlink or a junction.Tender
you need open file with FILE_FLAG_OPEN_REPARSE_POINT option. only in this case you can send FSCTL_GET_REPARSE_POINT. otherwise will be ERROR_NOT_A_REPARSE_POINTFreeliving
@Freeliving Yes, I know that. Forgot to mention.Tender
F
4

how the system actually processes reparse points

this is done inside file system and file system filter drivers. result depend from are FILE_FLAG_OPEN_REPARSE_POINT option used in call CreateFile (or FILE_OPEN_REPARSE_POINT in NT calls).

when FILE_FLAG_OPEN_REPARSE_POINT is specified - file system bypass normal reparse point processing for the file and attempts to directly open the reparse point file as is.

If the FILE_OPEN_REPARSE_POINT flag is not specified - file-system attempts to open a file to which reparse point is point (if fs understand format of reparse point - primary only Microsoft reparse points)

the data format saved in reparse point is REPARSE_DATA_BUFFER (Microsoft reparse point format) or REPARSE_GUID_DATA_BUFFER - need look for ReparseTag at begin.

to determine whether a reparse point tag corresponds to a tag owned by Microsoft we use IsReparseTagMicrosoft macro.

code for test/print reparse point data:

volatile UCHAR guz;

ULONG TestReparsePoint(PCWSTR FileName)
{
    HANDLE hFile = CreateFile(FileName, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 
        FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);

    if (hFile == INVALID_HANDLE_VALUE) 
    {
        return GetLastError();
    }

    union {
        PVOID pv;
        PULONG ReparseTag;
        PREPARSE_DATA_BUFFER prdb;
        PREPARSE_GUID_DATA_BUFFER prgdb;
    };

    PVOID stack = alloca(guz);

    ULONG cb = 0, rcb = sizeof(REPARSE_DATA_BUFFER) + 0x100, BytesReturned;

    ULONG dwError;

    do 
    {
        if (cb < rcb) cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);

        if (DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, 0, 0, pv, cb, &BytesReturned, 0))
        {
            dwError = NOERROR;

            if (IsReparseTagMicrosoft(*ReparseTag))
            {
                char cc[16];
                LPCSTR name;

                switch (*ReparseTag)
                {
                case IO_REPARSE_TAG_SYMLINK:
                    name = " SYMLINK";
                    stack = prdb->SymbolicLinkReparseBuffer.PathBuffer;
                    break;
                case IO_REPARSE_TAG_MOUNT_POINT:
                    name = " MOUNT_POINT";
                    stack = prdb->MountPointReparseBuffer.PathBuffer;
                    break;
                default:
                    sprintf(cc, " %08x", prdb->ReparseTag);
                    name = cc;
                }

                DbgPrint(" %s->%.*S <%.*S>\n", name, 
                    prdb->MountPointReparseBuffer.SubstituteNameLength >> 1,
                    RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.SubstituteNameOffset),
                    prdb->MountPointReparseBuffer.PrintNameLength >> 1,
                    RtlOffsetToPointer(stack, prdb->MountPointReparseBuffer.PrintNameOffset)
                    );
            }
            else
            {
                PGUID g = &prgdb->ReparseGuid;
                DbgPrint(" tag=%x {%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} size=%x\n", *ReparseTag, 
                    g->Data1, g->Data2, g->Data3, 
                    g->Data4[0],g->Data4[1],g->Data4[2],g->Data4[3],g->Data4[4],g->Data4[5],g->Data4[6],g->Data4[7],
                    prgdb->ReparseDataLength);

            }
            break;
        }

        rcb = IsReparseTagMicrosoft(*ReparseTag) 
            ? REPARSE_DATA_BUFFER_HEADER_SIZE + prdb->ReparseDataLength 
            : REPARSE_GUID_DATA_BUFFER_HEADER_SIZE + prgdb->ReparseDataLength;

    } while((dwError = GetLastError()) == ERROR_MORE_DATA);

    CloseHandle(hFile);

    return dwError;
}
Freeliving answered 23/9, 2017 at 20:27 Comment(0)
D
0

Microsoft reparse points can be read with REPARSE_DATA_BUFFER instead. The MS open protocol specification might also be useful.

Parsing other GUID based tags can only be done if you know the format.

Dictation answered 23/9, 2017 at 19:36 Comment(1)
I have just edited the question to clarify I meant REPARSE_DATA_BUFFER, which is the one used for symlinks and junctions.Tender

© 2022 - 2024 — McMap. All rights reserved.