I am trying to figure out why my php script is not able to find files outside of the web-root. It is part of a larger application but this seems to be the minimum for reproducing my issue.
I’m running php version 8.1.27 and nginx version 1.24.0.
This is the script in /var/www/testing/index.php
:
# /var/www/testing/index.php
<?php
$devPath = '/dev/dri/renderD128';
if (!file_exists($devPath)) {
echo "File does not exist!";
} elseif (!is_readable($devPath)) {
echo "File is not readable!";
} else {
echo "All good!";
}
clearstatcache();
?>
will display File does not exist!
But if I create:
# touch /var/www/testing/testing
# chown http:http /var/www/testing/testing
and change devPath
to /var/www/testing/testing
, the script displays All good!
.
I was thinking that maybe the /dev/
files are special in this context for some reason so I created:
# touch /tmp/testing
# chown http:http /tmp/testing
and changed devPath
to /tmp/testing
. But I still get File does not exist!
.
my php-fpm.conf
looks like this:
[global]
error_log = log/php-fpm.error.log
[testing]
user = http
pm = dynamic
pm.max_children = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 2
listen = /run/php-fpm-legacy/testing.sock
listen.owner = http
listen.group = http
listen.mode = 0660
access.log = /var/lib/$pool/php-fpm-access.log
access.format = "%{%Y-%m-%dT%H:%M:%S%z}t %R: "%m %r%Q%q" %s %f %{milli}d %{kilo}M %C%%"
slowlog = /var/lib/$pool/php-fpm-slow.log
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp
php_admin_value[error_log] = /var/lib/$pool/php-fpm-error.log
php_admin_flag[log_errors] = on
and my php-fpm.ini
is empty.
I serve the file using nginx, I suspect that config is ok as I can navigate to the server and see the results, but in either case, the config looks like this:
user http;
worker_processes auto;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
http {
root /var/www;
types_hash_max_size 2048;
types_hash_bucket_size 128;
server {
listen 80;
server_name 123.123.0.123;
root /var/www/testing;
access_log /var/log/nginx/testing.access.log;
error_log /var/log/nginx/testing.error.log error;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ .php$ {
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass unix:/run/php-fpm-legacy/testing.sock;
fastcgi_index index.php;
include fastcgi.conf;
}
}
server {
listen 80 default_server;
server_name _;
return 444;
}
}
To the best of my abilities I have verified that:
- the file exists:
- the
http
user is in the render group - it is the
http
user running the php process - the correct
php-fpm.conf
is used
# ls -lah /dev/dri/renderD128
crw-rw-rw- 1 root render 226, 128 Feb 2 22:49 /dev/dri/renderD128
# groups http
render redis http
# ps aux | grep php | grep testing
http 116949 0.0 0.0 79256 10036 ? S 18:24 0:00 php-fpm: pool testing
# ps aux | grep php | grep master
root 116935 0.0 0.0 79404 19676 ? Ss 18:24 0:00 php-fpm: master process (/etc/php-legacy/php-fpm.conf)
I’ve also tried to add an open_basedir
directive to see if I can get a hint or if maybe it was set somewhere else, but it results in the following error:
Warning: file_exists(): open_basedir restriction in effect. File(/dev/dri/renderD128) is not within the allowed path(s): (/var/www/testing:/dev/dri/renderD128) in /var/www/testing/index.php on line 3
Somewhat expected, but it seems weird that it complains when the path is among the allowed ones.
I’ve also tried listing the files in the directory by changing the index.php
script to:
<?php
$devPath = '/dev/dri/';
print_r(scandir($devPath));
?>
which gives me the following output:
Warning: scandir(/dev/dri/): Failed to open directory: No such file or directory in /var/www/testing/index.php on line 3
Warning: scandir(): (errno 13): Permission denied in /var/www/testing/index.php on line 3
However listing at a lower level, so /dev/
gives me some output:
Array ( [0] => . [1] => .. [2] => char [3] => core [4] => fd [5] => full [6] => hugepages [7] => log [8] => mqueue [9] => null [10] => ptmx [11] => pts [12] => random [13] => shm [14] => stderr [15] => stdin [16] => stdout [17] => tty [18] => urandom [19] => zero )
But the /dev/dri
directory is nowhere to be seen in that output.
I’m all out of ideas on where to look next. What could be the cause of php not being able to find the file? Is there some default (or non default) setting that would prevent this?
EDIT: I’ve tried to make sure that the files should be readable and listable by the http
user. The /dev
and /dev/dri
directories have 755
permissions; the /dev/dri/renderD128
file has 666
permissions and is owned by a group that the http
user belongs to; the /tmp
directory has 777
permissions; and the /tmp/testing
file has 644
permissions and is owned by the http
user.
2
Answers
Turns out that the php installation, on my system, defaults to setting the
PrivateDevices=yes
setting in the systemd service definition ofphp-fpm
.The systemd docs says that the setting:
This basically mean that the process will not see any of the global
/dev
devices, this is reasonable and a safety feature. But it also means that/dev/dri/renderD128
will be hidden from the process, thus the reason for my error.Additionally the systemd service definition sets
PrivateTmp=yes
, which does the same thing but for/tmp
so me testing the process's access by creating something in/tmp
put me on the wrong track. I tried to create the external file in/home/http
instead and the script could access it fine.We don't want to remove these settings as they are a security and safety feature, preventing the process from accessing other processes' data and system files/devices it shouldn't. However, we can create exceptions by adding:
to the
[Service]
section of php-fpm's systemd service file. This will allow the process to access that specific file even if devices in general are blocked.I could also add the
open_basedir
directive back without issues.Remember that PHP must have "r" access rights on the file
renderD128
and "x" access rights on all its parent folders, e.g./dev
and/dev/dri
.The ‘x’ (‘execute’ if on files but ‘list contents’ if on directories) access right is needed for the
file_exists()
operation because it allows PHP to traverse and access directories in the given path. Without execute permission on a directory in the path, PHP won’t be able to reach and check the existence of the specified file or directory.