php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #41899 Can't open files with leading relative path of '..' and '..' is not readable
Submitted: 2007-07-05 06:34 UTC Modified: 2007-10-10 23:55 UTC
From: geoffwa at cs dot rmit dot edu dot au Assigned: ab5602 (profile)
Status: Closed Package: Streams related
PHP Version: 5.2.3 OS: Solaris 10
Private report: No CVE-ID: None
 [2007-07-05 06:34 UTC] geoffwa at cs dot rmit dot edu dot au
Description:
------------
(possibly related to bug #39953 or bug #39351)

If a relative path to a file has '..' as a leading path component,
and the directory referred to by '..' is not readable by the user,
then opening a file using the relative path fails.

Using an absolute path or a path with a non-'..' leading component
opens said file just fine.



Reproduce code:
---------------
See http://goanna.cs.rmit.edu.au/~geoffwa/relative_path_bug.php
for a large test script.

Expected result:
----------------
(Using PHP 5.1.4)
Current working directory is: /home/g/geoffwa/test
Opened /home/g/geoffwa/test/a/b/file
Opened ./a/b/file from ./a using ./b/file
Opened ./a/b/file from ./a using ./b/c/../file
Opened ./a/b/file from ./a/b/c using ../file
Opened ../file from ./a/b/c using ./../file
Opened ./a/b/file from ./a/b using ./file
Opened ./a/file from ./a/b using ./c/../../file
Opened ./a/b/c/file from ./a/b/c using ../c/file


Actual result:
--------------
(Using PHP 5.2.3 + suhosin patch)
Opened /home/g/geoffwa/test/a/b/file
Opened ./a/b/file from ./a using ./b/file
Opened ./a/b/file from ./a using ./b/c/../file
Failed to open ./a/b/file from ./a/b/c using ../file
Failed to open ./a/b/file from ./a/b/c using ./../file
Opened ./a/b/file from ./a/b using ./file
Opened ./a/file from ./a/b using ./c/../../file
Failed to open ./a/b/c/file from ./a/b/c using ../c/file


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2007-07-06 01:03 UTC] geoffwa at cs dot rmit dot edu dot au
Is also broken in 5.2.0 and 5.2.2.

Appears to be fixed in latest snapshot:
> ./php-5.2-200707060030 -v
PHP 5.2.4-dev (cli) (built: Jul  6 2007 10:59:53) 
Copyright (c) 1997-2007 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies

> ./php-5.2-200707060030 -f test.php
Current working directory is: /home/g/geoffwa/test
Opened /home/g/geoffwa/test/a/b/file
Opened ./a/b/file from ./a using ./b/file
Opened ./a/b/file from ./a using ./b/c/../file
Opened ./a/b/file from ./a/b/c using ../file
Opened ../file from ./a/b/c using ./../file
Opened ./a/b/file from ./a/b using ./file
Opened ./a/file from ./a/b using ./c/../../file
Opened ./a/b/c/file from ./a/b/c using ../c/file
 [2007-07-06 07:54 UTC] sniper@php.net
Since it's fixed in CVS -> closed.
 [2007-07-06 09:26 UTC] tony2001@php.net
I'm still able to reproduce it, though I'm not able to debug it since both GDB and GCC fail to compile on Solaris.
I'm working on that atm..
 [2007-07-06 13:56 UTC] geoffwa at cs dot rmit dot edu dot au
I've updated http://goanna.cs.rmit.edu.au/~geoffwa/relative_path_bug.php
with a shorter test script and two syscall traces of 5.2.3 and
5.2-200707060030.

Looking through these the main difference is that 5.2.3 makes a stat()
call with an empty path, whereas 5.2-snap makes a stat() call using the
relative path supplied to fopen() in the PHP script.

We use Sun Studio's C compiler, so I can use dbx for debugging
(gdb crashes on most Sun-compiled binaries I give it). Is there
anything I can trace for you?
 [2007-07-06 14:02 UTC] tony2001@php.net
Which configure options did you use?
As I said, I don't see any difference between 5.2.3 and the snapshot and there should not be any difference since I don't remember any changes that could affect it.

>We use Sun Studio's C compiler, so I can use dbx for debugging
I can use that too, but I prefer GDB.

>Is there anything I can trace for you?
No, I'm still hoping to get GCC & GDB working there.
 [2007-07-06 15:00 UTC] geoffwa at cs dot rmit dot edu dot au
Doing a bit of tracing of expand_filepath nets:

(from PHP-5.2.3)
expand_filepath(filepath = 0xffbff5f4 "test2.php",
                real_path = 0xffbfee20 "")
                called from function php_execute_script
expand_filepath returns 0xffbfee20 "/home/g/geoffwa/test/test2.php"
expand_filepath(filepath = 0xffbfdec0 "./a",
                real_path = 0xffbfe2c4 "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe2c4 "/home/g/geoffwa/test/a"
expand_filepath(filepath = 0xffbfe108 "./a/b/file",
                real_path = 0xffbfe50c "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe50c "/home/g/geoffwa/test/a/b/file"
expand_filepath(filepath = 0xffbfe098 "./a",
                real_path = 0xffbfe49c "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe49c "/home/g/geoffwa/test/a"
expand_filepath(filepath = 0xffbfe028 "./a",
                real_path = 0xffbfe42c "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe42c "/home/g/geoffwa/test/a"
expand_filepath(filepath = 0xffbfe120 "./a/b",
                real_path = 0xffbfe524 "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe524 "/home/g/geoffwa/test/a/b"
expand_filepath(filepath = 0xffbfdfd8 "../b/file",
                real_path = 0xffbfe3dc "")
                called from function php_checkuid_ex
expand_filepath returns (nil)

(from PHP 5.2 snap 200707060030)
expand_filepath(filepath = 0xffbff5ef "test2.php",
                real_path = 0xffbfee18 "")
                called from function php_execute_script
expand_filepath returns 0xffbfee18 "/home/g/geoffwa/test/test2.php"
expand_filepath(filepath = 0xffbfdeb8 "./a",
                real_path = 0xffbfe2bc "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe2bc "/home/g/geoffwa/test/a"
expand_filepath(filepath = 0xffbfe100 "./a/b/file",
                real_path = 0xffbfe504 "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe504 "/home/g/geoffwa/test/a/b/file"
expand_filepath(filepath = 0xffbfe090 "./a",
                real_path = 0xffbfe494 "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe494 "/home/g/geoffwa/test/a"
expand_filepath(filepath = 0xffbfe020 "./a",
                real_path = 0xffbfe424 "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe424 "/home/g/geoffwa/test/a"
expand_filepath(filepath = 0xffbfe118 "./a/b",
                real_path = 0xffbfe51c "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe51c "/home/g/geoffwa/test/a/b"
expand_filepath(filepath = 0xffbfdfd0 "../b/file",
                real_path = 0xffbfe3d4 "")
                called from function php_checkuid_ex
expand_filepath returns 0xffbfe3d4 "../b/file"
expand_filepath(filepath = 0x53d1c0 "../b/file",
                real_path = (nil))
                called from function _php_stream_fopen
expand_filepath returns 0x53d398 "../b/file"
 [2007-07-06 15:16 UTC] tony2001@php.net
Thanks, but I need to do it myself in order to understand it.

I know quite well that realpath() on Solaris is badly broken, that's known issue and Sun is not going to do anything about as far as I understand.
The problem is that we need to invent a workaround for it so that we don't break working realpath() implementations.
And to do that I need to reproduce it myself and investigate it using GDB.
 [2007-07-06 16:04 UTC] geoffwa at cs dot rmit dot edu dot au
It's still broken in CVS (my bad - forgot to remove the workaround
patch we had).

virtual_file_ex() get called several times, with the last
invocation being:

virtual_file_ex(state = 0xffbfdf9c,
                path = 0xffbfe018 "../b/file",
                verify_path = (nil),
                use_realpath = 1)
                called from function expand_filepath
                virtual_file_ex returns 1

Having written a rather grandoise summary of stepping through
virtual_file_ex() I think the problem might be in php_checkuid_ex().
 [2007-07-07 02:04 UTC] geoffwa at cs dot rmit dot edu dot au
No idea if this is correct but it fixes it:
diff -ur ./php5.2-200707060030/main/safe_mode.c ./php-5.2-snap/main/safe_mode.c
--- ./php5.2-200707060030/main/safe_mode.c      2007-01-13 00:30:58.000000000 +1100
+++ ./php-5.2-snap/main/safe_mode.c     2007-07-07 11:42:10.804129000 +1000
@@ -86,7 +86,8 @@
         * If that fails, passthrough and check directory...
         */
        if (mode != CHECKUID_ALLOW_ONLY_DIR) {
-               expand_filepath(filename, path TSRMLS_CC);
+               // VCWD_STAT() can handle relative paths right?
+               strlcpy(path, filename, MAXPATHLEN);
                ret = VCWD_STAT(path, &sb);
                if (ret < 0) {
                        if (mode == CHECKUID_DISALLOW_FILE_NOT_EXISTS) {
diff -ur ./php5.2-200707060030/main/streams/plain_wrapper.c ./php-5.2-snap/main/streams/plain_wrapper.c
--- ./php5.2-200707060030/main/streams/plain_wrapper.c  2007-04-19 00:31:35.000000000 +1000
+++ ./php-5.2-snap/main/streams/plain_wrapper.c 2007-07-07 11:58:57.673891000 +1000
@@ -888,9 +888,10 @@
                return NULL;
        }

-       if ((realpath = expand_filepath(filename, NULL TSRMLS_CC)) == NULL) {
-               return NULL;
-       }
+       //if ((realpath = expand_filepath(filename, NULL TSRMLS_CC)) == NULL) {
+       //      return NULL;
+       //}
+       realpath = estrndup(filename, strlen(filename));

        if (persistent) {
                spprintf(&persistent_id, 0, "streams_stdio_%d_%s", open_flags, realpath);
 [2007-08-14 15:21 UTC] wdierkes at 5dollarwhitebox dot org
I have verified that this is *NOT* fixed in the latest CVS snapshot.  Tested on Redhat Enterprise Linux 4 i386.  Can we can an ETA on an official patch?
 [2007-09-12 10:38 UTC] ian at onlineloop dot com
Verified that this is still not working in 5.2.4.

We made a system available on a Sun E3500, partially for the purposes of fixing this bug.  The last login from anyone from the PHP team was on 5 July 2007.

Is there any time plan to fix this bug?  We are running on Solaris 10 and are stuck on PHP 5.1.6 because of this problem, so the situation for us is critical.
 [2007-09-12 11:53 UTC] ian at onlineloop dot com
I've tried the patch offered by Geoff.  It seems to work just fine for us too in the cvs version from today (php5.2-200709121030).
 [2007-09-13 01:56 UTC] geoffwa at cs dot rmit dot edu dot au
I'll stress again that while the patch may work, I'm not sure if it's 'correct' or not, mainly because I have no idaa what php_checkuid_ex()
is supposed to return, safe_mode-isms like open_basedir may need it.

I just traced the execution of the offending PHP script repeatedly for the failure case, and deduced that the expand_filepath() call in php_checkuid_ex() that I've removed in the patch was returning an empty
path under similar conditions to where a getcwd() call would fail.

The actual path blatting appeared to occur in virtual_file_ex(), and we produced a separate patch which completely short-circuited this function and also made the all test conditions work.

Given that PHP6 is removing safe_mode completely, I imagine this problem will hopefully be fixed then :)
 [2007-09-17 09:48 UTC] ian at onlineloop dot com
Unfortunately we are not in a position to wither wait for PHP6 nor 
immediately migrate to it when it does come out.  We have too many 
users with too many scripts on our server, and telling over 800 
people that they have to adjust their scripts in less than 6 months 
just doesn't work here :-(  

With the continuing failure to fix this bug, we are left in a very 
uncomfortable situation, either continue with the security hole 
loaded 5.1.6, or apply the patch you offered.  No one from the PHP project has logged into the system we set up for them on an E3500 since 5 July 2007 either, so I'm really left wondering about the seriousness there is to actually fix this bug.

Anyway, I have seen from the source for PHP that realpath is 
definately not a function from Sun.  realplath is all from the PHP 
project itself, so the attempt to shovel off the blame to Sun (post 
from 6 Jul 3:16pm) is not justifiable.  Besides, this all worked just 
fine up until PHP 5.2.0 came out...
 [2007-10-08 03:04 UTC] ab5602@php.net
Please try using this CVS snapshot:

  http://snaps.php.net/php5.2-latest.tar.gz
 
For Windows (zip):
 
  http://snaps.php.net/win32/php5.2-win32-latest.zip

For Windows (installer):

  http://snaps.php.net/win32/php5.2-win32-installer-latest.msi

Try the latest CVS snapshot.  Fixed the 41822 bug, which I feel is probably the same.
 [2007-10-08 05:31 UTC] ab5602@php.net
With php5.2-200710080430 the problem is worse, not better.

I can't even mkdir() in my test script any more!

<?php
ini_set('display_errors', '1');
mkdir("./a/b", 0700, true) or die('mkdir failed');
touch("./a/b/file") or die('touch failed');
chmod("./a", 0300) or die('chmod failed');
chdir("./a/b") or die('chdir failed');
$fp = fopen('../b/file', 'r');
if ($fp) print "SUCCESS\n";
else print "FAILURE\n";
?>

produces:
Warning: mkdir(): Unable to access ./a in /home/g/geoffwa/work/test/test2.php on line 3
mkdir failed

Looking at truss, the last four syscalls are:
getcwd("/home/g/geoffwa/work/test", 1024)       = 0
resolvepath("./a", 0xFFBFD238, 1024)            Err#2 ENOENT
stat("a", 0xFFBFDF20)                           Err#2 ENOENT
stat("a", 0xFFBFDF20)                           Err#2 ENOENT

I'd also like to point out that the Solaris getcwd() works fine:
(from the man page)

The getcwd() function may fail if:

EACCES          A parent directory cannot be read to get its
                name.
 [2007-10-08 13:42 UTC] ab5602@php.net
Geoffwa or ian, could you please send me a .tar with the directory structure and permissions you are using along with instructions on how to reproduce the error?

The script works just fine for me in Solaris with the most recent CVS.

-------

d--x--x--x   2 rob      rob          512 Oct  8 09:24 .
drwxr-xr-x  20 rob      rob         1536 Oct  8 09:15 ..
-rwxr-xr-x   1 rob      rob      10620420 Oct  8 09:17 php-cvs
-rw-r--r--   1 rob      rob          301 Oct  8 09:17 test.php

-------

[0925][rob@opteron:~/mkdirtest]$ ./php-cvs ./test.php                                                                                               

Warning: mkdir(): Permission denied in /export/home/rob/mkdirtest/test.php on line 3
mkdir failed
                                                                                                                   
[0925][rob@opteron:~/mkdirtest]$ chmod u+w .
[0925][rob@opteron:~/mkdirtest]$ ./php-cvs ./test.php                                                                                               
SUCCESS
[0925][rob@opteron:~/mkdirtest]$
 [2007-10-08 17:39 UTC] ab5602@php.net
Also, tested the longer script posted in this thread.  That appears to work for me as well.

[1339][rob@opteron:/test/abc]$ uname -a
SunOS opteron 5.10 Generic_118855-14 i86pc i386 i86pc
[1339][rob@opteron:/test/abc]$ ./php-cvs -v
PHP 5.2.5-dev (cli) (built: Oct  7 2007 11:26:15) (DEBUG)
Copyright (c) 1997-2007 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies
[1339][rob@opteron:/test/abc]$ ./php-cvs ./test.php
Current working directory is: /test/abc
Opened /test/abc/a/b/file
Opened ./a/b/file from ./a using ./b/file
Opened ./a/b/file from ./a using ./b/c/../file
Opened ./a/b/file from ./a/b/c using ../file
Opened ../file from ./a/b/c using ./../file
Opened ./a/b/file from ./a/b using ./file
Opened ./a/file from ./a/b using ./c/../../file
Opened ./a/b/c/file from ./a/b/c using ../c/file


[1339][rob@opteron:/test/abc]$ 
 [2007-10-09 06:56 UTC] geoffwa at cs dot rmit dot edu dot au
I can send you a tar, but I doubt that'll help matters. Here's the directory:

-------
drwxrwxrwx 2 geoffwa staff  512 Oct  9 16:44 .
drwx------ 8 geoffwa staff  512 Oct  8 15:14 ..
-rwx------ 1 geoffwa staff 3.7M Oct  9 16:23 php5.2-200710080430
-rwx------ 1 geoffwa staff  11M Oct  9 16:44 php5.2-200710080430-debug
-rw------- 1 geoffwa staff 2.7K Oct  8 15:17 test.php
-rw------- 1 geoffwa staff  301 Oct  8 15:21 test2.php
-------
(set . to 0777 for demonstration purposes)

(run the smaller of the two example scripts)
$ ./php5.2-200710080430-debug test2.php
Warning: mkdir(): Unable to access ./a in /homedir/test2.php on line 3
mkdir failed
 [2007-10-09 06:58 UTC] geoffwa at cs dot rmit dot edu dot au
Err are you running with safe mode on?

$ ./php5.2-200710080430-debug -dsafe_mode=0 test2.php
SUCCESS
 [2007-10-09 20:34 UTC] ab5602@php.net
Thanks, that was it.  I am able to reproduce the situation now.

Have tracked the issue down to safe_mode.c, php_checkuid_ex(), as is shown above in the thread and am working with it.

 [2007-10-10 01:01 UTC] ab5602@php.net
Geoffwa, when you get a chance, please let me know if applying the patch below to the current snapshot fixes the issue for you.

[2014][rob@opteron:~/mkdirtest]$ ./php-solfix -dsafe_mode=1 ./test2.php
Current working directory is: /export/home/rob/mkdirtest
Opened /export/home/rob/mkdirtest/a/b/file
Opened ./a/b/file from ./a using ./b/file
Opened ./a/b/file from ./a using ./b/c/../file
Opened ./a/b/file from ./a/b/c using ../file
Opened ../file from ./a/b/c using ./../file
Opened ./a/b/file from ./a/b using ./file
Opened ./a/file from ./a/b using ./c/../../file
Opened ./a/b/c/file from ./a/b/c using ../c/file
[2014][rob@opteron:~/mkdirtest]$ ./php-solfix -dsafe_mode=1 ./test.php
SUCCESS



--- ./safe_mode.c.old   2007-09-23 10:19:21.000000000 -0500
+++ ./safe_mode.c       2007-10-09 19:39:44.000000000 -0500
@@ -86,7 +86,15 @@
         * If that fails, passthrough and check directory...
         */
        if (mode != CHECKUID_ALLOW_ONLY_DIR) {
-               expand_filepath(filename, path TSRMLS_CC);
+
+                char filename_test[MAXPATHLEN];
+                strcpy(filename_test,filename);
+                if (VCWD_GETCWD(filename_test, sizeof(filename)) == NULL) {
+                        strcpy(path,filename);
+                } else {
+                        expand_filepath(filename, path TSRMLS_CC);
+                        }
+
                ret = VCWD_STAT(path, &sb);
                if (ret < 0) {
                        if (mode == CHECKUID_DISALLOW_FILE_NOT_EXISTS) {

 [2007-10-10 02:42 UTC] geoffwa at cs dot rmit dot edu dot au
PHP5.2-200710080430 + your patch still doesn't work for either test case (also, shouldn't sizeof(filename) be sizeof(filename_test)?).

$ ./php5.2-200710080430-fixed -dsafe_mode=1 test2.php

Warning: mkdir(): Unable to access ./a in /pathto/test2.php on line 3
mkdir failed

(so the initial mkdir("./a/b", 0700, true) call is failing)

Stepping through the non-patched PHP5.2-200710080430 the error message is being generated from:

if (mode != CHECKUID_ALLOW_ONLY_FILE) {
   /* check directory */
   ret = VCWD_STAT(path, &sb);
   if (ret < 0) {
      if ((flags & CHECKUID_NO_ERRORS) == 0) {
         php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to access %s", filename);
      }
      return 0;
   }

(line 147 in safe_mode.c)
 [2007-10-10 21:21 UTC] ab5602@php.net
Verified w/ Geoffrey that the above patch fixes the problem.  I am currently working on a final patch to apply to CVS and testing to make sure it does not break safe_mode.

------

> 1) Please try with the most recent snapshot (200710100230) there has
> > been recent patches to code (main/fopen_wrappers.c) that effects this,
> > as recent as today.  Noticed that binary you are using is 10-08 4:30.
> > 

The 200710100230 snapshot + your patch appears to work:

$ ./php5.2-200710100430-patched -n -dsafe_mode-1 test.php
Current working directory is: /home/g/geoffwa/work/test
Opened /home/g/geoffwa/work/test/a/b/file
Opened ./a/b/file from ./a using ./b/file
Opened ./a/b/file from ./a using ./b/c/../file
Opened ./a/b/file from ./a/b/c using ../file
Opened ../file from ./a/b/c using ./../file
Opened ./a/b/file from ./a/b using ./file
Opened ./a/file from ./a/b using ./c/../../file
Opened ./a/b/c/file from ./a/b/c using ../c/file

 [2007-10-10 23:55 UTC] ab5602@php.net
This bug has been fixed in CVS.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.
 
Thank you for the report, and for helping us make PHP better.


 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Mar 28 17:01:29 2024 UTC