PHP: When is /tmp not /tmp?
Until a few years ago you could use PHP, or any other application, to write a file to /tmp and see it appear immediately in the server /tmp directory. This is no longer the case.
So what's happening?
On our servers, running Linux, Apache and PHP, there are at least three factors at work:
- systemd: PrivateTmp;
- PAM: libpam-tmpdir;
- PHP: sys_get_temp_dir();
In the systemd service configuration files for Apache2 and some other programs you will see PrivateTmp=yes or PrivateTmp=true as the default setting.
This means that the affected programs will not have access to the system /tmp directory. Instead any read/write requests for /tmp silently redirect to a location assigned by systemd. Something like:
So if you have a PHP script run by Apache, any files written to /tmp will only be accessible by Apache and not from the command-line or another application as there is no safe and reliable way (that we know of) to determine the actual file location.
Similarly from the command-line the libpam-tmpdir package creates separate /tmp directories for different users based on their User ID:
Only it's not as strict as the PrivateTmp as you can still write to /tmp if you want as shown below.
What's actually happening is that the $TMP and $TMPDIR constants are updated at login for each user.
When writing a file to /tmp using PHP there are (at least) two different ways to specify the the path as shown in this simple script:
<?PHP $file = new \SplFileObject("/tmp/tmp-test.txt", "a+"); $file->fwrite("written to /tmp/ by " . posix_getpwuid(posix_geteuid())['name'] . "\n"); $file = NULL; $file = new \SplFileObject(sys_get_temp_dir() . "/tmp-test.txt", "a+"); $file->fwrite("written to sys_get_temp_dir() by " . posix_getpwuid(posix_geteuid())['name'] . "\n"); $file = NULL; ?>
In the first case we simply use the literal /tmp while in the second case we rely on the sys_get_temp_dir function, which sometimes returns a different value.
Using the above script, called from Apache both using a web browser and using cURL from the command-line (which is essentially the same thing) the file is consistently written to:
/tmp/systemd-private-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-apache2.service-XXXXXX/tmp/tmp-test.txt written to /tmp/ by www-data written to sys_get_temp_dir() by www-data written to /tmp/ by www-data written to sys_get_temp_dir() by www-data
Restarting the Apache process will likely change this to a new directory, but until then we can use /tmp to share files between scripts as long as they're run from Apache.
When running PHP from the command-line as different users:
root:~# php /path/to/script.php root:~# su - www-data -s /bin/sh -c "php /path/to/script.php" chirp:~$ php /path/to/script.php
If we specify /tmp then the file appears where expected:
/tmp/tmp-test.txt written to /tmp/ by root written to /tmp/ by www-data written to /tmp/ by chirp
But using sys_get_temp_dir() results in separate files based on who is running the script:
/tmp/user/0/tmp-test.txt written to sys_get_temp_dir() by root /tmp/user/1001/tmp-test.txt written to sys_get_temp_dir() by chirp /tmp/user/XX/tmp-test.txt written to sys_get_temp_dir() by www-data
If you want to share PHP-generated files between Apache and command-line scripts then you will need to create your own PHP-writable directory for the scripts to use, and implement your own garbage collection and security.
And sharing between different command-line (CLI) users is only possible if you specify /tmp rather than relying on sys_get_temp_dir().
But if each user only needs access to their own files then sys_get_temp_dir() is the way to go.
Note that we haven't changed the default sys_temp_dir setting in php.ini which could further complicate things.