-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Description
Description
Problem
I use Apache + PHP-FPM to serve PHP files sitting in the server's document root. Some PHP files have spaces and UTF-8 encoded special characters (umlauts, accents) in their file names. Files without spaces or umlauts are served correctly, but files with any of those lead to a 404 with PHP-FPM responding 'Primary script unknown'.
This appears to be due to the way Apache passes the SCRIPT_FILENAME variable: it seems to prefix the physical file name by proxy:fcgi:// and then URL encodes the file name following. So the correctly resolved file name "/docroot/my file.php" gets passed as "proxy:fcgi://my.server/docroot/my%20file.php". PHP-FPM removes the prefix and host name, but then tries to find the file "/docroot/my%20file.php", which doesn't exist.
This seems to me to be related to #12996: the fix to that bug was to URL decode PATH_INFO, but it does not cover the case where the physical script file (SCRIPT_FILENAME) is also URL encoded. So it just URL decodes the bit after the file name.
Symptoms
Using the following PHP-FPM access log format
access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{ORIG_SCRIPT_FILENAME}e"
when requesting the URL
https://my.server/files/test%20me.php
I get logs like this
- - 04/Aug/2024:21:35:48 +0200 "GET /files/test me.php" 404 - /var/htdocs/files/test%20me.php
Note the request (%r) is URL decoded, but the ORIG_SCRIPT_FILENAME is not. The file "/var/htdocs/files/test me.php" exists and is readable by the php-fpm user. Files without the space work fine. I use ORIG_SCRIPT_FILENAME because PHP-FPM unsets SCRIPT_FILENAME when reporting a 404 error (hence the empty %f), but saves the value in ORIG_SCRIPT_FILENAME.
Setup
I'm using PHP 8.3.10 with Apache/2.4.62 (FreeBSD) OpenSSL/3.2.2 on FreeBSD 14.1-STABLE (both built from ports).
Relevant Apache config:
<FilesMatch "\.php$">
SetHandler "proxy:unix:/var/run/php-fpm.sock|fcgi://php-fpm"
</FilesMatch>
<Proxy "fcgi://php-fpm" enablereuse=off max=18>
</Proxy>
# PHP Apache FCGI hack to pass HTTP AUTH info
ProxyFCGISetEnvIf "%{HTTP:Authorization} =~ /(.+)/" HTTP_AUTHORIZATION "$1"
ProxyFCGISetEnvIf "%{REMOTE_USER} =~ /(.+)/" PHP_AUTH_USER "$1"
My PHP-FPM setup is stock with UNIX socket at /var/run/php-fpm.sock and access logging as per above.
I have had this setup unchanged for several years and PHP and Apache versions. I'm pretty sure this worked about half a year ago, but can't say when precisely this broke (and if it was a change on the Apache or PHP side).
Workaround
As a workaround, I can make everything run just fine by forcing Apache to unescape the SCRIPT_FILENAME variable before passing it to PHP-FPM:
ProxyFCGISetEnvIf "true" SCRIPT_FILENAME "%{unescape:%{SCRIPT_FILENAME}}"
I don't know what that does to PATH_INFO stuff as I don't use that.
Solution(?)
Since fpm_main.c already does a lot of gymnastics to fix Apache's botched inputs, it may be better to unescape the SCRIPT_FILENAME when removing the "proxy:fcgi://" prefix in fpm_main.c.
Unfortunately, I don't fully understand all the logic there, and am not familiar enough with PHP sources to confidently propose a fix.
PHP Version
PHP 8.3.10
Operating System
FreeBSD 14.1-STABLE