php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #36555 realpath_cache never updated with symlinks (removed or renamed)
Submitted: 2006-02-28 11:11 UTC Modified: 2012-09-07 17:16 UTC
From: p at damezin dot com Assigned:
Status: Not a bug Package: Filesystem function related
PHP Version: 5.2.0 OS: Linux OpenBSD
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If this is not your bug, you can add a comment by following this link.
If this is your bug, but you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: p at damezin dot com
New email:
PHP Version: OS:

 

 [2006-02-28 11:11 UTC] p at damezin dot com
Description:
------------
readfile() and file_get_contents() returns the old contents from an internal php cache when a symlink is modified. Or it will return the contents of an unlinked symlink().

This problem does not accured when realpath_cache_size is 0.

With php5 on apache2, the problem also exist from 1 page to another. (more disturbing)

Reproduced on php 5.1.1 and 5.1.2 using Ubuntu or Gentoo Linux. Linux Kernels 2.6.11, 2.6.15...

Reproduce code:
---------------
<?php
define('TEST_NUMBER', 2); // 1 ou 2
define('TEST_FILE1', 'testfile1');
define('TEST_FILE2', 'testfile2');
define('TEST_LINK', 'testlink');

// inexistant function in PHP4
if(!function_exists('file_put_contents')) {
    function file_put_contents($file, $content) {
        $fp = fopen($file,'w');
        fputs($fp, $content);
        fclose($fp);
    }
}

file_put_contents(TEST_FILE1, 42);
file_put_contents(TEST_FILE2, 43);

// creating the TEST_LINK, using a temp. TEST_LINK NEEDED
$uid = uniqid(time());
symlink(TEST_FILE1, TEST_LINK . $uid);
rename(TEST_LINK . $uid, TEST_LINK);

// reading this file one time NEEDED
file_get_contents(TEST_LINK);

if(TEST_NUMBER === 1) {
    unlink(TEST_LINK);
    $good_result = '';
} 
elseif(TEST_NUMBER === 2) {
    $uid = uniqid(time());
    symlink(TEST_FILE2, TEST_LINK . $uid);
    rename(TEST_LINK . $uid, TEST_LINK);
    $good_result = file_get_contents(TEST_FILE2);
}

// sleep(2);
// clearstatcache();

$result = @file_get_contents(TEST_LINK);

echo "TEST " . TEST_NUMBER . " : " . (($result == $good_result) ? 'SUCCEED' : 'FAILED') . "\n";
echo "result : [" . $result . "] | ";
echo "expected : [" . $good_result . "]\n";
echo "php version : " . PHP_VERSION."\n";
echo "realpath_cache_size (if available) : " . @ini_get('realpath_cache_size') . "\n";

// cleaning
@unlink(TEST_LINK);
unlink(TEST_FILE1);
unlink(TEST_FILE2);

?>

Expected result:
----------------
dynphp2 root # php testcasephp.php
TEST 1 : SUCCEED
result : [] | expected : []
php version : 4.4.0
realpath_cache_size (if available) :

dynphp2 root # php testcasephp.php
TEST 2 : SUCCEED
result : [43] | expected : [43]
php version : 4.4.0
realpath_cache_size (if available) :

---

TEST 1 : SUCCEED
result : [] | expected : []
php version : 5.0.5
realpath_cache_size (if available) :

TEST 2 : SUCCEED
result : [43] | expected : [43]
php version : 5.0.5
realpath_cache_size (if available) :

Actual result:
--------------
lore ~ # php testcasephp.php
TEST 1 : FAILED
result : [42] | expected : []
php version : 5.1.2
realpath_cache_size (if available) : 16384

lore ~ # php testcasephp.php
TEST 2 : FAILED
result : [42] | expected : [43]
php version : 5.1.2
realpath_cache_size (if available) : 16384

---

TEST 1 : FAILED
result : [42] | expected : []
php version : 5.1.1-gentoo
realpath_cache_size (if available) : 16K

TEST 2 : FAILED
result : [42] | expected : [43]
php version : 5.1.1-gentoo
realpath_cache_size (if available) : 16K


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2006-02-28 11:15 UTC] tony2001@php.net
Use clearstatcache() when you need to reset stat cache.
 [2006-02-28 11:22 UTC] p at damezin dot com
It's the same with clearstatcache(), this is why you can see it comment in the testcase. It have no influence.
 [2006-10-12 21:57 UTC] p at damezin dot com
Still have this problem on 5.1.4
 [2006-10-12 22:00 UTC] p at damezin dot com
still have this problem on 5.1.6
 [2006-11-03 12:00 UTC] p at damezin dot com
still have this problem on 5.2.
 [2006-11-03 12:07 UTC] p at damezin dot com
new summary (perhaps it would make this bug more interesting...)

Bug on 5.2.0 is verified with default configuration (without php.ini) and sources provided from php.net (no patch from a portage).
 [2006-11-03 12:37 UTC] p at damezin dot com
New simplified testcase :

<?php
define('TEST_FILE1', '/tmp/testfile1');
define('TEST_LINK', '/tmp/testlink');

file_put_contents(TEST_FILE1, '42');

// create TEST_LINK pointing on TEST_FILE1 using a temporary filename (needed)
$tmp_link_name = TEST_LINK . uniqid(time());
symlink(TEST_FILE1, $tmp_link_name);
rename($tmp_link_name, TEST_LINK);

// reading this link/file
file_get_contents(TEST_LINK);

// remove the link (we have the same bug making this link pointing somewhere else)
unlink(TEST_LINK);
$good_result = '';

// clearstatcache have no effect
clearstatcache();

// fetching result, must be empty (link does not exist)
$result = @file_get_contents(TEST_LINK);
// on bogus php it return TEST_FILE1 content

echo "TEST : " . (($result == $good_result) ?
        'SUCCEED' : 'FAILED') . "\n";
echo "result : '" . $result . "' | ";
echo "expected : '" . $good_result . "'\n";
echo "php version : " . PHP_VERSION."\n";
echo "realpath_cache_size (if available) : " .
        @ini_get('realpath_cache_size') . "\n";

// cleaning
@unlink(TEST_LINK);
unlink(TEST_FILE1);
?>
 [2012-09-07 16:11 UTC] webmaster at wininup dot com
Why is this bug report not considered like a real bug ? It is because it's not at 
all the behavior expected : file_get_contents() randomly the content delivered is 
the the new symlink or from the old symlink content !!

Still occurs on PHP 5.4.6 (Linux Fedora 17).
 [2012-09-07 17:16 UTC] laruence@php.net
I can not reproduce this. 

could you explain your test steps?
 [2012-09-08 15:51 UTC] webmaster at wininup dot com
Well, i'm afraid I can only reproduce this bug when it's used through Apache.
Testing is hard because it does not appear at the first time.

Download the file "test.php" from this URL : 
http://dl.dropbox.com/u/62007491/test.php

To init the test, in the same directory that "test.php" please create a 
directory "writable", writable by Apache ("mkdir writable && chmod 777 writable" 
for testing should be okay).

1. Run the script with the $flag = true. This should create 2 files in the 
writable directory, the "content" file, plus the symlink file.
2. Execute the script multiple times (this is important) by pressing F5. No 
error should occur.
3. Now Run the same script with the $flag = false. This should delete this 
"content" and "symlink" file.
4. Execute the script multiple times (this is important)
5. Then Run the same process from the step 1 to 4. Try again until you see that 
the step 1 ($flag = true) fails with :

----------------------------------------
Warning: symlink(): File exists in /var/www/html/test/test.php on line 37
islink: 

Warning: linkinfo(): No such file or directory in /var/www/html/test/test.php on 
line 41
linkinfo: -1 

Warning: readlink(): No such file or directory in /var/www/html/test/test.php on 
line 42
readlink: 
php version : 5.4.6 realpath_cache_size (if available) : 16K 
Array ( [0] => /var/www/html/test/writable/content_42 )
----------------------------------------

=> As you can see in the above output, the symlink() fails with "File exists" 
but the symlink actually does not exist !!

Hope this helps.
 [2012-09-08 16:04 UTC] webmaster at wininup dot com
Same problem for this user : http://sixohthree.com/1517/php-and-the-realpath-
cache

He seems right telling that the cache is not thread safe.
 [2012-09-09 01:06 UTC] webmaster at wininup dot com
Well this basic workaround is to replace file_get_contents($symlink) with : 

$fh = fopen($symlink, 'r');
$raw = fread($fh, filesize($symlink));
fclose($symlink);
 [2012-09-09 01:20 UTC] webmaster at wininup dot com
Sorry, the last workaround with fopen() is not working better. Same bug.
 [2013-01-10 16:56 UTC] v dot rutkunas at pccsuk dot com
XAMPP - Apache 2.4, PHP 5.4.7 here, bug still exists...
 [2014-06-26 19:37 UTC] alexandru dot ort at gmail dot com
Same bug on ubuntu 12.04 lts with php 5.3.10.
 [2017-01-07 18:42 UTC] spam2 at rhsoft dot net
the problem with "clearstatcache" is that each php-worker maintains it's own 'realpath_cache' and so your request touching the file may correctly reset it, but every request hit another php-process until TTL is reached will continue to work with the still existing stale data

https://bugs.php.net/bug.php?id=73888
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Wed Apr 24 01:01:31 2024 UTC