Bug #73884 [Com]: is_dir() returns false for junction / symlinkd

From: Date: Sun, 08 Jan 2017 22:13:13 +0000
Subject: Bug #73884 [Com]: is_dir() returns false for junction / symlinkd
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-206410@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=73884&edit=1

 ID:                 73884
 Comment by:         kulakov74 at yandex dot ru
 Reported by:        kulakov74 at yandex dot ru
 Summary:            is_dir() returns false for junction / symlinkd
 Status:             Feedback
 Type:               Bug
 Package:            Filesystem function related
 Operating System:   Windows 8.1
 PHP Version:        7.1.0
 Block user comment: N
 Private report:     N

 New Comment:

I did many tests and I finally came up with the conclusion the problem is at least in Cyrillic
(Russian) characters in directory names (even though I have the latest PHP where the UTF problem has
been resolved). I'll give the last lines of my tests for you to make it clear. I'm using
russian letters in my tests so make sure you see them. 

I have created 2 directories for tests: 
C:\Temp\Dir
C:\Temp\Кат    (this one is rus)

I'm giving 4 combinations for eng/rus: 

//1. eng -> eng: OK
c:\Temp>mklink /j linktoeng C:\Temp\Dir
Junction created for linktoeng <<===>> C:\Temp\Dir
c:\Temp>php.exe -n -r "$p = 'c:\Temp\linktoeng'; var_dump(is_dir($p),
readlink($p));"
bool(true)
string(11) "C:\Temp\Dir"

//2. rus -> eng: OK!
c:\Temp>mklink /j рус-ссылка-наангл C:\Temp\Dir
Junction created for рус-ссылка-наангл
<<===>> C:\Temp\Dir
c:\Temp>php.exe -n -r "$p =
'c:\Temp\рус-ссылка-наангл';
var_dump(is_dir($p), readlink($p));"
bool(true)
string(11) "C:\Temp\Dir"

//3. eng -> rus: NO
c:\Temp>mklink /j linktorus C:\Temp\Кат
Junction created for linktorus <<===>> C:\Temp\Кат
c:\Temp>php.exe -n -r "$p = 'c:\Temp\linktorus'; var_dump(is_dir($p),
readlink($p));"
bool(false)
string(14) "C:\Temp\Кат"

In this case note that readlink() does perfectly well, also, if I try 
c:\Temp>php.exe -n -r "$p = 'C:\Temp\Кат'; var_dump(is_dir($p),
readlink($p));"
bool(true)
string(14) "C:\Temp\Кат"

is_dir() is correct, so it's is_dir()'s only bug and it only shows with links, cause when
I give it the russian dir directly it knows it's a dir!

//4. rus -> rus: NO (this is formal, we know it won't work :)
c:\Temp>mklink /j рус-ссылка C:\Temp\Кат
Junction created for рус-ссылка <<===>> C:\Temp\Кат
c:\Temp>php.exe -n -r "$p = 'c:\Temp\рус-ссылка';
var_dump(is_dir($p), readlink($p));"
bool(false)
string(14) "C:\Temp\Кат"

And because I have a russian user name all of the links in my C:\Users fail with PHP. 

//-----------------

As for permissions (ACLs), when you run

icacls "c:\Users\All Users"

as "All Users" is a link icacls displays ACLs for the target, not for the link. If you try


icacls "c:\programdata"

you'll see the same ACLs, while in fact "All Users" has the same Deny rule you
mentioned (I must say all the built-in links have this Deny rule). So you should use

icacls "c:\Users\All Users" /L

>>With c:\users\default user - i cannot even view it in the explorer, "access
>>denied" popup
The same with me :) BUT if I open console (cmd.exe) I can do this: 

dir /A:L C:\Users
...
<JUNCTION>     Default User [C:\Users\Default]
and then I can do
c:\Users>cd "Default User"
c:\Users\Default User>

So, Explorer behaves different with links that have equal ACLs, so this is probably where junctions
and symlinks differ. It looks like PHP follows the logic used by Explorer and I don't see why
Explorer can't access the objects other tools can. 
                   
Other tools: cmd.exe, Total Commander.


Previous Comments:
------------------------------------------------------------------------
[2017-01-08 19:49:50] ab@php.net

Thanks for the effort so far. Maybe I expressed myself wrong. I was talking about the NTFS
permissions. Either in GUI by viewing advanced security, or by icacls or similar tools. Here is it
on my side, with c:\users\all users

php.exe -n -r "$p = 'c:\users\all users'; var_dump(is_dir($p), readlink($p));"
bool(true)
string(14) "C:\ProgramData"

$ icacls "c:\Users\All Users"
c:\Users\All Users NT AUTHORITY\SYSTEM:(OI)(CI)(F)
                   BUILTIN\Administrators:(OI)(CI)(F)
                   CREATOR OWNER:(OI)(CI)(IO)(F)
                   BUILTIN\Users:(OI)(CI)(RX)
                   BUILTIN\Users:(CI)(WD,AD,WEA,WA)


With c:\users\default user - i cannot even view it in the explorer, "access denied" popup.
So that's what it gives 

php.exe -n -r "$p = 'c:\users\default user'; var_dump(is_dir($p),
readlink($p));"

Warning: readlink(): readlink failed to read the symbolic link (c:\users\default user), error 5) in
Command line code on line 1
bool(true)
bool(false)

And that's not a wonder, as 

$ icacls "c:\Users\Default User"
c:\Users\Default User Everyone:(DENY)(S,RD)
                      Everyone:(RX)
                      NT AUTHORITY\SYSTEM:(F)
                      BUILTIN\Administrators:(F)

Given you say, that a custom junction works as expected, but some particular path don't - it
still looks like a permission issue. I might be still wrong, though, but really need a reliable
reproducer for the case. Which other tools do you mean? Best way would be to have a synthetic case,
that creates an fs object with exact access privileges and shows the issue so then it's
repeatable on any system.

Thanks.

------------------------------------------------------------------------
[2017-01-07 20:19:56] kulakov74 at yandex dot ru

Note that readlink() returns an error for "c:\Users\Default User\": 

Warning: readlink(): readlink failed to read the symbolic link (C:\Users\Default User), error 5)

I must add that the "Default User" junction does have a Deny rule for everyone to list
folder/read data, but in practice it has no effect (if you use any other tool but PHP). After I
removed it, both is_dir() and readlink() produced correct results. So it looks like PHP checks the
permissions, finds the Deny rule and uses some wrong logic to conclude it has no right to access the
link. 

BUT I have another junction also created by me before (not for tests)
c:\Users\Sergei\ pointing to C:\Users\Серёжка (my name in Russian)
and it doesn't have the deny rule, while it has full control for myself, AND is_dir() returns
false for it (wrong!), while readlink() returns the right target. So there's some other kind of
bug with is_dir().

------------------------------------------------------------------------
[2017-01-07 19:37:35] kulakov74 at yandex dot ru

For tests, I tried "Default User", it was owned by System and had a right to read/execute
for everyone. After I took over ownership and removed the right, I could not "enter" it
and Dir stopped displaying its target (just showed "[..]"). is_dir() still returned false,
which is actully not correct because if it has no way to know and check the target, it can't
know if it's a dir or not, no error message displayed either. I readded the right to
read/execute for everyone and everything works as it did before. 

The junction I created myself (templink) has "read & execute" right for
"Users", not for everyone. Also it has Modify permission for Authenticated users. I added
both to "Default User" but it didn't help. "Default User" also had
"hs" attributes (hidden and system), but after I cleared them nothing changed. The only
difference left is that "Default User" has the permissions added to it directly while
templink inherits them from C:\. I disabled inheritance for templink but is_dir() still worked for
it. 

Still, I could make is_dir() return false for templink by removing all rights. 

So, this could be related with permissions, but I couldn't prove it, and even if I did, php
does smth wrong cause it shouldn't cause problems.

------------------------------------------------------------------------
[2017-01-06 21:41:54] ab@php.net

Thanks for the report. Might be good a permission issue, yep. Would you mind to investigate more on
it, maybe checking what difference the extended permissions tell, etc.?

Thanks.

------------------------------------------------------------------------
[2017-01-06 20:12:44] kulakov74 at yandex dot ru

Description:
------------
On my Windows PC, I scanned the whole directory structure in C:\ (both from browsers and in CLI
mode) and found out that for all of the junctions / symlinks that had been created by the system
is_dir() returns false. 

According to the manual, "If filename is a symbolic or hard link then the link will be resolved
and checked." but this is only the case with the junctions / symlinks I created manually for
testing. It might seem the problem is with permissions but I run the script as admin and also I can
see the fact that the items in question are junctions by using plain console (cmd.exe) even not as
admin, for ex.: 

c:\Users>dir /a:l
...
22.08.2013  17:45    <SYMLINKD>     All Users [C:\ProgramData]
22.08.2013  17:45    <JUNCTION>     Default User [C:\Users\Default]
...

BTW, In Windows, junctions and symlinks are almost the same. 

Test script:
---------------
$Dir="C:\Users\All Users";
if (is_dir($Dir)) echo("dir"); else echo("not a dir");



Expected result:
----------------
dir

Actual result:
--------------
not a dir


------------------------------------------------------------------------



--
Edit this bug report at https://bugs.php.net/bug.php?id=73884&edit=1


Thread (16 messages)

« previous php.bugs (#206410) next »