PHP and Solaris: getcwd() Behavior November 10, 2007
The Solaris OS has implemented, for quite some time, a permission restriction with respect to when getcwd() will return a full/real path under certain conditions. This has at least been since SunOS 5.8 and continuing on into 5.10. Many functions within the PHP codebase relied upon a universally working getcwd() [C] call to expand paths and to find out where a script is being executed. In particular, Solaris does not assume that getcwd() is a privilege that should be granted to users in directories that don’t have ‘r’ (read) permission, even if it has ‘x’ (execute) permissions. For example, in Solaris, getcwd() will fail if a directory anywhere underneath your current path has ‘–x’.
pwd vs. getcwd()
The ‘pwd’ command under most shells cannot be used to reliably test for this behavior since it caches and keeps track of ‘cd’ operations. ‘pwd’ will let you see the name of your current location in Solaris, but it does not do this via a getcwd(). Instead, it is simply spitting back a path that it recorded and cached from when you have issued a ‘cd /directory’. Therefore, it is necessary to explicitly call getcwd() to test this behavior out. The following code can be used to test the operation of getcwd() in a directory:
#include <stdio.h> main() { char str[200]; if (getcwd(str, sizeof(str)) == 0) printf("getcwd failed!!!\n"); else printf("CWD = %s\n",str); }
Under Linux, getcwd() behaves normally with the restrictive permissions:
# uname -a Linux x.y.z 2.6.9-42.ELsmp #1 SMP Wed Jul 12 23:27:17 EDT 2006 i686 athlon i386 GNU/Linux # find ./localfs -ls 32001 8 d--x--x--x 3 nobody nobody 4096 Oct 1 13:05 ./localfs 32002 8 d--x--x--x 2 nobody nobody 4096 Oct 1 13:12 ./localfs/test123 32004 12 -rwsr-xr-x 1 nobody nobody 4878 Oct 1 13:06 ./localfs/test123/cwdtest # su nobody # cd /localfs/test123 # ./cwdtest CWD = /localfs/test123 # pwd /localfs/test123 #
Under Solaris, getcwd() does not work with the –x restrictive permissions:
# uname -a SunOS opteron 5.10 Generic_118855-14 i86pc i386 i86pc # find ./localfs -ls 449384 1 d--x--x--x 3 nobody nobody 512 Oct 1 13:13 ./localfs 449386 1 d--x--x--x 2 nobody nobody 512 Oct 1 13:13 ./localfs/test123 449388 7 -rwsr-xr-x 1 nobody nobody 6552 Oct 1 12:57 ./localfs/test123/cwdtest # su nobody # cd /localfs/test123 # ./cwdtest getcwd failed!!! # pwd /localfs/test123 #
Bug or “security” feature?
There are some who feel that this is a bug, not a feature. I tend to agree with it being termed a bug, especially since I cannot find a single other OS that implements this “feature”. Either way, it broke PHP under situations where site owners were attempting to implement tight security on websites with many different users who have their own directory for PHP content.
Changes to PHP 5.2.5 that deal with this
Prior to PHP 5.2.5, PHP would would fail for relative includes in these situations: [include("../dir/")]. Since PHP 5.2.5, we added proper handling for this Solaris quirk to the code base so that relative paths in include()/require() still work under these conditions. In addition, a patch was added to safe_mode that used to cause similar behavior when the getcwd() call failed.
[1245][root@opteron:/test/testa/test]$ ls -al
total 56150
d--x--x--x 2 root root 512 Nov 11 12:45 .
d--x--x--x 4 root root 512 Nov 11 12:42 ..
-rwxr-xr-x 1 root root 16719040 Nov 11 12:44 php-5.2.0
-rwxr-xr-x 1 root root 12007004 Nov 11 12:45 php-5.2.5
-rw-r--r-- 1 root root 35 Nov 11 12:43 test.php
[1245][root@opteron:/test/testa/test]$ cat ./test.php
<?php
include('../b/file.php');
?>
[1245][root@opteron:/test/testa/test]$ cat ../b/file.php
<?php
echo "I am a file in directory b.\n";
?>
[1245][root@opteron:/test/testa/test]$ su rob
[1246][rob@opteron:/test/testa/test]$ uname -a
SunOS opteron 5.10 Generic_118855-14 i86pc i386 i86pc
[1246][rob@opteron:/test/testa/test]$ ./php-5.2.0 ./test.php
Warning: include(../b/file.php): failed to open stream: No such file or directory in /test.php on line 2
Warning: include(): Failed opening '../b/file.php' for inclusion (include_path='.:/usr/local/lib/php') in /test.php on line 2
[1246][rob@opteron:/test/testa/test]$ ./php-5.2.5 ./test.php
I am a file in directory b.
[1246][rob@opteron:/test/testa/test]$NFS Mounts can hide the problem
The issue can initially be perplexing to pin down. I originally ran into this problem on a machine with an NFS mounted directory like: /a/b/NFS/webstuff/, where ‘NFS’ was an external FS. Looking at the directory path, all permissions looked ok (r-x), so I at first dismissed it as not being the culprit of the broken include() functionality. Upon looking into the problem, it was found that the local directory called ‘NFS’, before the mount happens had the restrictive permissions that triggered the quirky Solaris behavior (–x). This was not immediately evident since after the mount happens, the /NFS path is (to the eye) replaced with the inode of the remotely mounted directory, which was (r-x). None-the-less, the OS must walk this directory tree, including the /NFS directory inode on the local FS to get to the remotely mounted content.
Directory: /a/b/NFS/webstuff
inode(a) --> inode(b) --> inode(NFS) --> inode(NFS) --> inode(webstuff)
[local] [local] [local] [remote NFS] [remote NFS]
^
^
hidden after mountUpgrade your PHP!
So, long story short, I strongly encourage anyone using Solaris to upgrade to PHP 5.2.5 as you are likely to run into this situation if you have multiple users, NFS mounted web dirs, safe_mode=on, or another reason to implement strict directory permission in your installation.
Leave a Reply