Bug #63413 [Com]: Intermittent warning and fatal error on require() statement

From: Date: Fri, 29 Apr 2016 17:29:07 +0000
Subject: Bug #63413 [Com]: Intermittent warning and fatal error on require() statement
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-200835@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=63413&edit=1

 ID:                 63413
 Comment by:         jan dot bouvrie at gmail dot com
 Reported by:        ben at indietorrent dot org
 Summary:            Intermittent warning and fatal error on require()
                     statement
 Status:             Open
 Type:               Bug
 Package:            Scripting Engine problem
 Operating System:   Windows 7 x64
 PHP Version:        5.4.8
 Block user comment: N
 Private report:     N

 New Comment:

I think I bumped into this one too, this time on PHP 5.6.20 running on Windows 8.1 x64. However, in
my case I've observed a Windows NTFS Directory Junction on a remote filesystem, mapped over a
Samba Share to resolve to the 'wrong' realpath(), potentially mapping a remote file to the
local machine's filesystem. This could perhaps be a vulnerability.

Since this case relies heavily on a client-server architecture, I created a .cmd script to reproduce
this testcase. Essence of the test is that PHP's realpath() will resolve links to remote
filesystem to the equivalent location on the local filesystem instead.

-----------------------
Steps to reproduce:
-----------------------
0. Have seperate Windows Client and Server machines on the same LAN with Samba share enabled.

1. On each PC, Create dir C:\JUNC_test
2. On each PC, Create attached files debug.cmd and debug.php in C:\JUNC_test dir.

3. On PC 1 (server), start elevated commandprompt, CD C:\JUNC_test and run "debug.cmd".
These tests will pass as all references point to the local machine.

4. On PC 2 (client), start elevated commandprompt, CD C:\JUNC_test. This time, run "debug.cmd
<SERVERNAME>", where <SERVERNAME> is PC1's hostname.

The reason for the debug.cmd and elevated privileges, is that it will create the test environment
with symlinks, create share, map drive, and remove it as well. Afterwards, just remove the
C:\JUNC_test dir.

-----------------------------
What to look for (on PC2):
-----------------------------

TESTING: NORMAL_DIR will work fine on both PC 2 (both point to their respective local or mapped
regular files)
TESTING: DIRSYMLINK will probably not work on PC 2, as Windows by default has remote symlink
disabled. Observe however that even though file_exists returns false, file_get_contents of the file
_does_ work. The shell 'type' command doesn't yield anything.
TESTING: DIRJUNCTION ON J will conflict on PC 2, its realpath() will instead follow the Junction to
PC 2' local filesystem, and open its file instead. Note that opening the dirjunction's
file in other Windows applications correctly resolve the remote file as intended behavior.


-----------------------
The testcase files:
-----------------------

###### debug.php START: #######
<?php
foreach (array('C:\\JUNC_test\\','J:\\') as $dir)
if (is_dir($dir))
foreach (scandir($dir)as $entry)
{
	if
(!in_array($entry,array('.','..','debug.cmd','debug.php'))&&
$entry=$dir.$entry)
	{
		$file=$entry.'\\file.php';
		echo 'TESTING: '.basename($entry).' on '.$dir[0].":\n";

		$result=array(	'path'=>$file,
						'path_file_exists'=>file_exists($file),
						'path_shell_exec'=>exec('type "'.$file.'"'),
						'path_get_contents'=>trim(file_get_contents($file)),
						'realpath'=>realpath($file),
						'realpath_file_exists'=>file_exists(realpath($file)),
						'realpath_shell_exec'=>exec('type
"'.realpath($file).'"'),
						'realpath_get_contents'=>trim(file_get_contents(realpath($file))),
			);
		var_dump($result);
		echo 'include '.$file.' result:';
		include($file);
		echo PHP_EOL.PHP_EOL;
	}
}
die();
?>
###### debug.php END. #######


###### debug.cmd START: #######
@echo off
SETLOCAL ENABLEDELAYEDEXPANSION
cls
SET PHP_EXE=PHP.EXE
WHERE php.exe 2>nul 1>&2
if not %errorLevel% == 0 (	SET PHP_EXE=C:\XAMPP\PHP\PHP.EXE
				if not exist !PHP_EXE! goto ERROR
			)
if not exist C:\JUNC_test\debug.php goto ERROR

rem check for admin rights 
net session >nul 2>&1
if not %errorLevel% == 0 goto ERROR
if not exist C:\JUNC_test\NORMAL_DIR MD C:\JUNC_test\NORMAL_DIR
rem 1. prepare filesystem for test in C:\JUNC_test folder
rem Creating linked directory and file.
echo ^<php echo '%COMPUTERNAME% file OK';^>> C:\JUNC_test\NORMAL_DIR\file.php

if not "%1"=="" (
	echo %COMPUTERNAME% connecting as client to server %~1...
	echo Mapping share to J: letter
	@NET USE J: /D 2>NUL 1>&2
	NET USE J: \\%~1\JUNC_test_Share
	call :RUNTEST
	echo Test complete. 

	echo Unmapping J drive on client:
	NET USE J: /D 2>NUL 1>&2
) else (
	echo Using %COMPUTERNAME% as server for the tests...

	echo Creating Directory Junction:
	mklink /J C:\JUNC_test\DIRJUNCTION C:\JUNC_test\NORMAL_DIR
	echo Creating symbolic link:
	mklink /D C:\JUNC_test\DIRSYMLINK C:\JUNC_test\NORMAL_DIR


	rem 2. Share C:\JUNC_test folder on the network and map to J: letter
	rem
	echo Sharing C:\JUNC_test dir:
	NET SHARE JUNC_test_Share=C:\JUNC_test /UNLIMITED

	echo Mapping share to letter J:
	@NET USE J: /D 2>NUL 1>&2
	NET USE J: \\%COMPUTERNAME%\JUNC_test_Share
	echo.
	echo Press any key to start test on this ^(server^) PC...
	@pause>nul
	call :RUNTEST
	echo.
	echo See the results above. All files should be acessible ^(true^) and reference
"%COMPUTERNAME%". To finish the testcase, please use a second PC and run the following
	echo command: debug.cmd "%COMPUTERNAME%" 
	echo.
	echo After that, press any key to remove the share and mapped J:...
	@pause
	NET USE J: /DELETE
	NET SHARE JUNC_test_Share /DELETE
	RD C:\JUNC_test\DIRSYMLINK
	RD C:\JUNC_test\DIRJUNCTION
)
goto END

:RUNTEST
	echo Windows host SymlinkEvaluation Settings:
	echo ---------------------------------------
	fsutil behavior query SymlinkEvaluation
	echo.
	echo Show the folders:
	echo -----------------
	DIR C:\JUNC_test /ad
	DIR J:\ /ad
	echo.
	echo Run PHP testcode:
	echo -----------------
        call !PHP_EXE! -v
        call !PHP_EXE! -n debug.php
goto :EOF

:ERROR
	echo ERROR:
	echo This script should be run from commandline, placed alongside debug.php in C:\JUNC_test
	echo Also, you must run this script with admin rights in order to perform the test!
	echo Finally, make sure PHP.exe is installed and in your path, or edit the PHP_EXE variable in
debug.cmd
	echo.
	@pause
goto END

:END
if exist C:\JUNC_test\NORMAL_DIR\file.php DEL /F/Q C:\JUNC_test\NORMAL_DIR\file.php
if exist C:\JUNC_test\NORMAL_DIR RD C:\JUNC_test\NORMAL_DIR
ENDLOCAL
###### debug.cmd START: #######


Previous Comments:
------------------------------------------------------------------------
[2013-02-18 15:51:17] ben at indietorrent dot org

@giunta dot gaetano at gmail dot com:

I can't thank you enough; your insightful comments reveal the "missing link": NTFS
junction points. I didn't think they were relevant until reading your comments.

In my comment dated [2012-11-16 16:29 UTC], I stated that I setup a virtual machine with the same
WAMP stack components that are installed on the "problem" machines, but that I was unable
to reproduce the problem. Well, I forgot to configure the site-root as an NTFS junction point in
those tests.

As soon as I changed the site-root from a regular directory to an NTFS function point, I was able to
reproduce the problem almost immediately.

As stated in my previous comments, concurrency seems to be a factor in how likely the failures are
to occur. Unfortunately, this fact may point to a race-condition.

It seems safe to say that include() and require() do not perform reliably across NTFS junction
points.

Whether or not the same root cause is behind the NFS problems on Linux remains to be seen.

------------------------------------------------------------------------
[2013-02-14 10:11:41] giunta dot gaetano at gmail dot com

Btw, did some testing on my rig: win7 64bit, apache 2.4.3/vc10 from Apache Lounge, php 5.3.20/vc9.

Using the test scripts provided above, and "ab" hitting them 100 times in a row with
concurrency ranging from 1 to 64.

When no NTFS junctions in use => no sign of errors whatsoever

When an NTFS junction in use => one or two php errors do happen, across the whole test (127k
requests).

NB: just accessing the main file over the junction is ok. The problems happen then the
"require" call is for a file over in the junction-ed directory

------------------------------------------------------------------------
[2013-02-13 17:34:19] giunta dot gaetano at gmail dot com

A behaviour which has been puzzling me and that might (or not) be related: we also have some failing
code which assumes that filemtime should not be zero (for an existing file). This is generally
happening on Linux servers at customers (php in mode_prefork), at times of high load, for
nfs-mounted files

------------------------------------------------------------------------
[2012-11-16 16:29:11] ben at indietorrent dot org

Additional testing indicates that this problem is likely related to a specific piece of software
that has been installed on the affected machines, and not PHP or the manner in which it is
integrated with Apache.

I tested the steps-to-reproduce with the exact same project/code-base on a LAMP stack (Ubuntu 12.04
+ Apache 2.2.22 + MySQL 5.5.24 + PHP 5.3.10) and cannot reproduce the issue, no matter how hard I
hammer the server with requests.

As mentioned previously, I am unable to reproduce this issue with a comparable stack on Mac OS 10.8,
either.

These facts pointed to a Windows-specific cause, perhaps related to Apache's "winnt"
MPM, so I setup a VM with a pristine Windows 7 x86 installation. I installed the same stack
components as are installed on the computers on which this issue occurs. Yet, after several hours of
hammering the server with constant page-requests, not a single error has been registered in
PHP's error log.

If at any point I'm able to determine which software causes this issue, I will post my findings
here.

------------------------------------------------------------------------
[2012-11-05 15:45:02] ben at indietorrent dot org

Another update.

I began to suspect that this is a thread-safety issue, so I downloaded the latest non-thread-safe
version of PHP and configured Apache to serve PHP files via Fast-CGI (mod_fcgid).

To my surprise, this problem still occurs, and it seems to be worse with Fast-CGI than with Mod-PHP.

Also, I tried to reproduce the problem on a Mac with Mac OS 10.8 and a fairly modern MAMP
installation that runs PHP via Mod-PHP. No matter how hard I hammered on Apache, these spurious
require() failures did not occur.

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


The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at

    https://bugs.php.net/bug.php?id=63413


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


Thread (10 messages)

« previous php.bugs (#200835) next »