Skip to content

Fix GH-17645: FPM with httpd ProxyPass does not decode script path #17896

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions php.ini-development
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,12 @@ enable_dl = Off
; https://php.net/fastcgi.impersonate
;fastcgi.impersonate = 1

; Prevent decoding of SCRIPT_FILENAME when using Apache ProxyPass or
; ProxyPassMatch. This should only be used if script file paths are already
; stored in an encoded format on the file system.
; Default is 0.
;fastcgi.script_path_encoded = 1

; Disable logging through FastCGI connection. PHP's default behavior is to enable
; this feature.
;fastcgi.logging = 0
Expand Down
6 changes: 6 additions & 0 deletions php.ini-production
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,12 @@ enable_dl = Off
; https://php.net/fastcgi.impersonate
;fastcgi.impersonate = 1

; Prevent decoding of SCRIPT_FILENAME when using Apache ProxyPass or
; ProxyPassMatch. This should only be used if script file paths are already
; stored in an encoded format on the file system.
; Default is 0.
;fastcgi.script_path_encoded = 1

; Disable logging through FastCGI connection. PHP's default behavior is to enable
; this feature.
;fastcgi.logging = 0
Expand Down
23 changes: 15 additions & 8 deletions sapi/fpm/fpm/fpm_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ typedef struct _php_cgi_globals_struct {
bool nph;
bool fix_pathinfo;
bool discard_path;
bool fcgi_script_path_encoded;
bool fcgi_logging;
bool fcgi_logging_request_started;
HashTable user_config_cache;
Expand Down Expand Up @@ -1066,6 +1067,10 @@ static void init_request_info(void)
}
}

if (apache_was_here && !CGIG(fcgi_script_path_encoded)) {
php_raw_url_decode(env_script_filename, strlen(env_script_filename));
}

if (CGIG(fix_pathinfo)) {
struct stat st;
char *real_path = NULL;
Expand Down Expand Up @@ -1174,7 +1179,7 @@ static void init_request_info(void)
* it is probably also in SCRIPT_NAME and need to be removed
*/
size_t decoded_path_info_len = 0;
if (strchr(path_info, '%')) {
if (CGIG(fcgi_script_path_encoded) && strchr(path_info, '%')) {
decoded_path_info = estrdup(path_info);
decoded_path_info_len = php_raw_url_decode(decoded_path_info, strlen(path_info));
}
Expand Down Expand Up @@ -1421,13 +1426,14 @@ static void fastcgi_ini_parser(zval *arg1, zval *arg2, zval *arg3, int callback_
/* }}} */

PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN("cgi.rfc2616_headers", "0", PHP_INI_ALL, OnUpdateBool, rfc2616_headers, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_BOOLEAN("cgi.nph", "0", PHP_INI_ALL, OnUpdateBool, nph, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_BOOLEAN("cgi.fix_pathinfo", "1", PHP_INI_SYSTEM, OnUpdateBool, fix_pathinfo, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_BOOLEAN("cgi.discard_path", "0", PHP_INI_SYSTEM, OnUpdateBool, discard_path, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_BOOLEAN("fastcgi.logging", "1", PHP_INI_SYSTEM, OnUpdateBool, fcgi_logging, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_ENTRY("fastcgi.error_header", NULL, PHP_INI_SYSTEM, OnUpdateString, error_header, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_ENTRY("fpm.config", NULL, PHP_INI_SYSTEM, OnUpdateString, fpm_config, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_BOOLEAN("cgi.rfc2616_headers", "0", PHP_INI_ALL, OnUpdateBool, rfc2616_headers, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_BOOLEAN("cgi.nph", "0", PHP_INI_ALL, OnUpdateBool, nph, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_BOOLEAN("cgi.fix_pathinfo", "1", PHP_INI_SYSTEM, OnUpdateBool, fix_pathinfo, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_BOOLEAN("cgi.discard_path", "0", PHP_INI_SYSTEM, OnUpdateBool, discard_path, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_BOOLEAN("fastcgi.script_path_encoded", "0", PHP_INI_SYSTEM, OnUpdateBool, fcgi_script_path_encoded, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_BOOLEAN("fastcgi.logging", "1", PHP_INI_SYSTEM, OnUpdateBool, fcgi_logging, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_ENTRY("fastcgi.error_header", NULL, PHP_INI_SYSTEM, OnUpdateString, error_header, php_cgi_globals_struct, php_cgi_globals)
STD_PHP_INI_ENTRY("fpm.config", NULL, PHP_INI_SYSTEM, OnUpdateString, fpm_config, php_cgi_globals_struct, php_cgi_globals)
PHP_INI_END()

/* {{{ php_cgi_globals_ctor */
Expand All @@ -1437,6 +1443,7 @@ static void php_cgi_globals_ctor(php_cgi_globals_struct *php_cgi_globals)
php_cgi_globals->nph = 0;
php_cgi_globals->fix_pathinfo = 1;
php_cgi_globals->discard_path = 0;
php_cgi_globals->fcgi_script_path_encoded = 0;
php_cgi_globals->fcgi_logging = 1;
php_cgi_globals->fcgi_logging_request_started = false;
zend_hash_init(&php_cgi_globals->user_config_cache, 0, NULL, user_config_cache_entry_dtor, 1);
Expand Down
54 changes: 54 additions & 0 deletions sapi/fpm/tests/fcgi-env-pif-apache-pp-sfp-decoding.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
--TEST--
FPM: FastCGI change for Apache ProxyPass SCRIPT_FILENAME decoding (GH-17645)
--SKIPIF--
<?php include "skipif.inc"; ?>
--FILE--
<?php

require_once "tester.inc";

$cfg = <<<EOT
[global]
error_log = {{FILE:LOG}}
[unconfined]
listen = {{ADDR}}
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3
php_admin_value[cgi.fix_pathinfo] = yes
EOT;

$code = <<<EOT
<?php
echo \$_SERVER["SCRIPT_NAME"] . "\n";
echo \$_SERVER["ORIG_SCRIPT_NAME"] . "\n";
echo \$_SERVER["SCRIPT_FILENAME"] . "\n";
echo \$_SERVER["PATH_INFO"] . "\n";
echo \$_SERVER["PHP_SELF"];
EOT;

$tester = new FPM\Tester($cfg, $code);
[$sourceFilePath, $scriptName] = $tester->createSourceFileAndScriptName('+');
$tester->start();
$tester->expectLogStartNotices();
$tester
->request(
uri: $scriptName . '/1%202',
scriptFilename: "proxy:fcgi://" . $tester->getAddr() . str_replace('+', '%2B', $sourceFilePath) . '/1%202',
scriptName: $scriptName . '/1 2'
)
->expectBody([$scriptName, $scriptName . '/1 2', $sourceFilePath, '/1 2', $scriptName . '/1 2']);
$tester->terminate();
$tester->close();

?>
Done
--EXPECT--
Done
--CLEAN--
<?php
require_once "tester.inc";
FPM\Tester::clean();
?>
55 changes: 55 additions & 0 deletions sapi/fpm/tests/fcgi-env-pif-apache-pp-sfp-encoded.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
--TEST--
FPM: FastCGI change for Apache ProxyPass SCRIPT_FILENAME decoding - fallback (GH-17645)
--SKIPIF--
<?php include "skipif.inc"; ?>
--FILE--
<?php

require_once "tester.inc";

$cfg = <<<EOT
[global]
error_log = {{FILE:LOG}}
[unconfined]
listen = {{ADDR}}
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3
php_admin_value[cgi.fix_pathinfo] = yes
php_admin_value[fastcgi.script_path_encoded] = yes
EOT;

$code = <<<EOT
<?php
echo \$_SERVER["SCRIPT_NAME"] . "\n";
echo \$_SERVER["ORIG_SCRIPT_NAME"] . "\n";
echo \$_SERVER["SCRIPT_FILENAME"] . "\n";
echo \$_SERVER["PATH_INFO"] . "\n";
echo \$_SERVER["PHP_SELF"];
EOT;

$tester = new FPM\Tester($cfg, $code);
[$sourceFilePath, $scriptName] = $tester->createSourceFileAndScriptName('%2B');
$tester->start();
$tester->expectLogStartNotices();
$tester
->request(
uri: $scriptName . '/1%202',
scriptFilename: "proxy:fcgi://" . $tester->getAddr() . $sourceFilePath . '/1%202',
scriptName: $scriptName . '/1 2'
)
->expectBody([$scriptName, $scriptName . '/1 2', $sourceFilePath, '/1 2', $scriptName . '/1 2']);
$tester->terminate();
$tester->close();

?>
Done
--EXPECT--
Done
--CLEAN--
<?php
require_once "tester.inc";
FPM\Tester::clean();
?>
21 changes: 16 additions & 5 deletions sapi/fpm/tests/tester.inc
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class Tester
self::FILE_EXT_LOG_ERR,
self::FILE_EXT_LOG_SLOW,
self::FILE_EXT_PID,
'src.php',
'*src.php',
'ini',
'skip.ini',
'*.sock',
Expand Down Expand Up @@ -107,6 +107,11 @@ class Tester
*/
private string $fileName;

/**
* @var string
*/
private ?string $sourceFile = null;

/**
* @var resource
*/
Expand Down Expand Up @@ -1483,21 +1488,27 @@ class Tester
/**
* Create a source code file.
*
* @param string $nameSuffix
* @return string
*/
public function makeSourceFile(): string
public function makeSourceFile(string $nameSuffix = ''): string
{
return $this->makeFile('src.php', $this->code, overwrite: false);
if (is_null($this->sourceFile)) {
$this->sourceFile = $this->makeFile($nameSuffix . 'src.php', $this->code, overwrite: false);
}

return $this->sourceFile;
}

/**
* Create a source file and script name.
*
* @param string $nameSuffix
* @return string[]
*/
public function createSourceFileAndScriptName(): array
public function createSourceFileAndScriptName(string $nameSuffix = ''): array
{
$sourceFile = $this->makeFile('src.php', $this->code, overwrite: false);
$sourceFile = $this->makeSourceFile($nameSuffix);

return [$sourceFile, '/' . basename($sourceFile)];
}
Expand Down
Loading