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:
PrivateTmp
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.
libpam-tmpdir
Similarly from the command-line the libpam-tmpdir package creates separate /tmp directories for different users based on their User ID:
root /tmp/user/0 www-data /tmp/user/XX chirp /tmp/user/1001
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.
PHP sys_get_temp_dir
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:
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.
Test Results
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
Conclusions
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.
Php set sys temp directory
Поведение этих функций зависит от установок в php.ini .
Имя | По умолчанию | Место изменения | Список изменений |
---|---|---|---|
allow_url_fopen | «1» | PHP_INI_SYSTEM | |
allow_url_include | «0» | PHP_INI_SYSTEM | Объявлена устаревшей с версии PHP 7.4.0. |
user_agent | NULL | PHP_INI_ALL | |
default_socket_timeout | «60» | PHP_INI_ALL | |
from | «» | PHP_INI_ALL | |
auto_detect_line_endings | «0» | PHP_INI_ALL | Объявлена устаревшей с версии PHP 8.1.0. |
sys_temp_dir | «» | PHP_INI_SYSTEM |
Краткое разъяснение конфигурационных директив.
Данная директива включает поддержку обёрток URL (URL wrappers), которые позволяют работать с объектами URL как с обычными файлами. Обёртки, доступные по умолчанию, служат для работы с удалёнными файлами с использованием ftp или http протокола. Некоторые модули, например, zlib, могут регистрировать собственные обёртки.
Эта опция позволяет использование обёрток fopen, которые поддерживают работу с URL, в функциях include , include_once , require , require_once .
Замечание:
Эта опция требует включения опции allow_url_fopen.
Устанавливает отсылаемую PHP строку «User-Agent».
Значение времени ожидания по умолчанию (в секундах) для потоков, использующих сокеты. Отрицательное значения означает бесконечное время ожидания.
Адрес email, используемый в соединениях FTP без авторизации, а также в качестве значения заголовка From в HTTP соединениях при использовании ftp и http обёрток, соответственно.
Когда данная директива включена, PHP проверяет данные, получаемые функциями fgets() и file() с тем, чтобы определить способ завершения строк (Unix, MS-Dos или Macintosh).
Данная директива позволяет PHP взаимодействовать с системами Macintosh, однако, по умолчанию эта директива выключена, поскольку при её использовании возникает (несущественная) потребность в дополнительных ресурсах для определения символа окончания первой строки, а также потому, что программисты, использующие в системах Unix символы возврата каретки в качестве разделителей, столкнутся с обратно-несовместимым поведением PHP.
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:
PrivateTmp
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.
libpam-tmpdir
Similarly from the command-line the libpam-tmpdir package creates separate /tmp directories for different users based on their User ID:
root /tmp/user/0 www-data /tmp/user/XX chirp /tmp/user/1001
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.
PHP sys_get_temp_dir
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:
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.
Test Results
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
Conclusions
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.