0

I have a project where I have to get the target of a junction. This is some code I came up with:

#include "stdafx.h"
#include <iostream>
#include <Windows.h>

#define BUFSIZE MAX_PATH

using namespace std;


int main()
{
TCHAR Path[BUFSIZE];
DWORD dwRet;
HANDLE hFile;

hFile = CreateFile(L"C:\\Users\\Test\\Documents\\My Videos",
    GENERIC_READ,
    FILE_SHARE_READ,
    0,
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
    0);

if (hFile == INVALID_HANDLE_VALUE)
{
    printf("Could not open file (error %d)\n", GetLastError());
    return 0;
}
dwRet = GetFinalPathNameByHandle(hFile, Path, BUFSIZE, VOLUME_NAME_DOS);
if (dwRet < BUFSIZE)
{
    _tprintf(TEXT("\nThe final path is: %s\n"), Path);
}


CloseHandle(hFile);

//wcout << Path;

return 0;
}

Now, the weird thing is that the code returns nicely the GetFinalPathNameByHandle for every directory EXCEPT the junction / reparse point Documents\My Videos. For the junctions it throws an "error 5" with the GetLastError(). Has anyone an idea what can cause this?

Eryk Sun
  • 33,190
  • 5
  • 92
  • 111
Erwin
  • 1,484
  • 1
  • 18
  • 32
  • 1
    are you sure that you got error on call `GetFinalPathNameByHandle` but not on call `CreateFile` ? and say for `My Music` you not got error ? – RbMm Oct 21 '17 at 23:38
  • 2
    Don't use `GENERIC_READ`; it's not necessary. The junction has an ACE that denies read (list directory) access. – Eryk Sun Oct 21 '17 at 23:42
  • 1
    @eryksun - yes. this is true. deny read for everyone. if we not have backup privilege `CreateFile` fail. but `CreateFile` is fail, not `GetFinalPathNameByHandle` which accept file handle with **any** access – RbMm Oct 21 '17 at 23:47
  • 1
    If you want the final resolved path of the junction, you shouldn't be using `FILE_FLAG_OPEN_REPARSE_POINT`. Only use that flag if you need to open the reparse point and read its immediate target using `DeviceIoControl` with `FSCTL_GET_REPARSE_POINT`. – Eryk Sun Oct 21 '17 at 23:55
  • Thank you for these comments. I solved the issue (more or less). – Erwin Oct 22 '17 at 00:07

1 Answers1

-1

I got to the bottom of it. You first have to do takeown /f "C:\users\test\Documents\My Videos" /r /d y before any C++ API can open a handle on the filesystem object.

EDIT 2:

For anyone who reads this in the future. The above code can work, but only when you use the takeown command on the juncture. Before the takeown usage there is a Everyone:(DENY)(S,RD) policy on the standards Windows junctions which denies all users read access. After takeown that policy is gone and the junction is also usable in Windows Explorer.

EDIT: This is the working solution in C++ without using the takeown command:

#define BUFSIZE MAX_PATH

using namespace std;

int main()
{
TCHAR Path[BUFSIZE];
DWORD dwRet;
HANDLE hFile;

hFile = CreateFile(L"C:\\Users\\Test\\Documents\\My Music",
    0,
    0,
    0,
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS,
    0);

if (hFile == INVALID_HANDLE_VALUE)
{
    printf("Could not open file (error %d)\n", GetLastError());
    return 0;
}
dwRet = GetFinalPathNameByHandle(hFile, Path, BUFSIZE, VOLUME_NAME_DOS);
if (dwRet < BUFSIZE)
{
    _tprintf(TEXT("\nThe final path is: %s\n"), Path);
}

CloseHandle(hFile);

return 0;
}

The product of this code is the target path of C:\users\test\Documents\My Music

Erwin
  • 1,484
  • 1
  • 18
  • 32
  • 1
    so exactly `CreateFile` return error, not `GetFinalPathNameByHandle`. for resolve this you need or enable `SE_BACKUP_PRIVILEGE` (if you have it) or not ask for `FILE_LIST_DIRECTORY` access. and determinate - are you need open reparse point or it target – RbMm Oct 22 '17 at 00:17
  • 2
    Administrators, SYSTEM, and "test" have full access to this junction -- except an entry in the DACL denies everyone (S-1-1-0) the right to read (list) the directory. Taking ownership doesn't resolve this. You need to request no access, for which `CreateFile` will request the minimum for the Windows API (synchronize and read attributes). That's enough access whether you're calling `GetFinalPathNameByHandle` to get the final resolved path (do not open the reparse point in this case) or calling `DeviceIoControl` to read the immediate target of the junction. – Eryk Sun Oct 22 '17 at 00:55
  • I am very sorry, but I think you are wrong. @eryksun :) All calls from CreateFile to the "C:\users\test\Documents\My Videos" folder failed untill I took ownership. – Erwin Oct 22 '17 at 00:57
  • 1
    If you're not an administrator or running as "test", then obviously you're granted no access at all to the user profile of "test". Changing the owner of files and directories is not the answer. Run your program as an administrator, and request only the minimum required access. Also, check `icacls "my videos"` to see whether there's an ACE on the junction that denies read access. It should be there, but maybe someone modified the security. – Eryk Sun Oct 22 '17 at 01:09
  • Suppose I want the target path of the junction, how should the `CreateFile` to get the directory handle for `DeviceIoControl` should look like (without taking ownership first)? – Erwin Oct 22 '17 at 01:18
  • 2
    Request no desired access (0) to have `CreateFile` request its minimum allowed access, which has always included the right to synchronize and read attributes. If you can list the parent directory, then you're implicitly granted the right to read attributes, but nothing from the parent directory implicitly grants the right to synchronize. That said, if you don't have those two rights, as an administrator you can get them anyway by enabling SeBackupPrivilege and requesting backup semantics. You're already requesting the latter to be able to open a directory. (FYI, you forgot to @ notify me.) – Eryk Sun Oct 22 '17 at 04:53
  • 2
    Doesn't look like you got to the bottom of it. – David Heffernan Oct 22 '17 at 07:11
  • 1
    *Suppose I want the target path of the junction* - you need call `CreateFile` with `0` or `MAXIMUM_ALLOWED` in *dwDesiredAccess* and `FILE_FLAG_OPEN_REPARSE_POINT` option and call `FSCTL_GET_REPARSE_POINT` instead `GetFinalPathNameByHandle` – RbMm Oct 22 '17 at 07:23
  • 1
    or open file without `FILE_FLAG_OPEN_REPARSE_POINT` and call `GetFinalPathNameByHandle` (you still can have any access on file). however the first way more effective – RbMm Oct 22 '17 at 07:29
  • 2
    `GetFinalPathNameByHandle` returns the final resolved path, regardless of the number of reparse points that the `CreateFile` call had to traverse. On the other hand, opening the reparse point itself and querying the `FSCTL_GET_REPARSE_POINT` file-system control code lets you read the the immediate substitute path, which doesn't have to be accessible or even exist. – Eryk Sun Oct 22 '17 at 08:58
  • I wonder why someone gave me a "-1". The observed behavior with the `takeown` command and `CreateFile` API is exactly what happened. – Erwin Oct 22 '17 at 14:12
  • Thank you everybody. Junctions are getting resolved to the correct paths now. :) – Erwin Oct 22 '17 at 14:33
  • Regarding using takeown, did you run icacls.exe on the junction to see whether there's an entry that denies read/list access to everyone? Also, are you running your program as an administrator (i.e. group S-1-5-32-544 is enabled in your effective access token) or as the "test" user? – Eryk Sun Oct 22 '17 at 17:04
  • Don't use a `NULL` pointer for integer arguments such as `DWORD`. – Eryk Sun Oct 22 '17 at 17:08
  • Anyway. I stay by my comment. The code only worked after the `takeown` command. I ran the code as administrator and as a normal user, but only after `takeown` the code did not return a `access denied` anymore. Everybody is invited to test the first CreateFile implementation against `Documents\My Videos` directory to verify this. – Erwin Oct 22 '17 at 17:16
  • I keep asking for you to check icacls on the junction. I can probably help explain how takeown appears to have solved the problem, but I need an explicit example that includes what's in the DACL and the execution contexts (user, elevation state) that fail. As stated previously, on the Windows 10 system that I checked, these "My Music" and "My Videos" junctions grant full control to administrators, except for an entry that denies read/list access to everyone. The junction is meant only to be traversed in opening files and subfolders for legacy compatibility, not to be browsed in Explorer. – Eryk Sun Oct 22 '17 at 19:36
  • You have not got to the bottom of it. You don't understand what you have done. Do you want to understand or not? – David Heffernan Oct 22 '17 at 22:34
  • The code posted here above works for me but for the fun of it I did take a look on what `takeown` was doing and it turns out that it manipulates the access rights table used by `icacls`. Before `takeown` there is a `Everyone:(DENY)(S,RD)` policy which denies all users read access. After `takeown` that policy is gone and the junction is also usable in Windows Explorer. For me that is an unexpected lesson in how Microsoft organized the filesystem in Windows 7 onwards. – Erwin Oct 24 '17 at 09:32