Commit 1ac68e7b079 for php.net
commit 1ac68e7b079b33b012c9f69b986b31852370f2ee
Author: Jakub Zelenka <bukka@php.net>
Date: Thu Jul 31 13:42:54 2025 +0200
Fix GH-8157: post_max_size evaluates .user.ini too late in php-fpm
This introduces new SAPI callback that runs before post read
Closes GH-19333
diff --git a/NEWS b/NEWS
index 7adc874c818..42846ab7c52 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,10 @@ PHP NEWS
. Fix OSS-Fuzz #442954659 (Crash in exif_scan_HEIF_header). (nielsdos)
. Various hardening fixes to HEIF parsing. (nielsdos)
+- FPM:
+ . Fixed GH-8157 (post_max_size evaluates .user.ini too late in php-fpm).
+ (Jakub Zelenka)
+
- Opcache:
. Fixed bug GH-19669 (assertion failure in zend_jit_trace_type_to_info_ex).
(Arnaud)
diff --git a/main/SAPI.c b/main/SAPI.c
index 169ae572fa9..6709d467e34 100644
--- a/main/SAPI.c
+++ b/main/SAPI.c
@@ -458,6 +458,10 @@ SAPI_API void sapi_activate(void)
SG(request_parse_body_context).throw_exceptions = false;
memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache));
+ if (sapi_module.pre_request_init) {
+ sapi_module.pre_request_init();
+ }
+
/* Handle request method */
if (SG(server_context)) {
if (PG(enable_post_data_reading)
diff --git a/main/SAPI.h b/main/SAPI.h
index f7a64f24310..9196982f549 100644
--- a/main/SAPI.h
+++ b/main/SAPI.h
@@ -290,6 +290,8 @@ struct _sapi_module_struct {
const char *ini_entries;
const zend_function_entry *additional_functions;
unsigned int (*input_filter_init)(void);
+
+ int (*pre_request_init)(void); /* called before activate and before the post data read - used for .user.ini */
};
struct _sapi_post_entry {
@@ -340,6 +342,7 @@ END_EXTERN_C()
0, /* phpinfo_as_text; */ \
NULL, /* ini_entries; */ \
NULL, /* additional_functions */ \
- NULL /* input_filter_init */
+ NULL, /* input_filter_init */ \
+ NULL /* pre_request_init */
#endif /* SAPI_H */
diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c
index 8af1e51d512..fa2417122e7 100644
--- a/sapi/fpm/fpm/fpm_main.c
+++ b/sapi/fpm/fpm/fpm_main.c
@@ -702,7 +702,7 @@ static void php_cgi_ini_activate_user_config(char *path, int path_len, const cha
}
/* }}} */
-static int sapi_cgi_activate(void) /* {{{ */
+static int sapi_cgi_pre_request_init(void)
{
fcgi_request *request = (fcgi_request*) SG(server_context);
char *path, *doc_root, *server_name;
@@ -766,6 +766,11 @@ static int sapi_cgi_activate(void) /* {{{ */
return SUCCESS;
}
+
+static int sapi_cgi_activate(void) /* {{{ */
+{
+ return SUCCESS;
+}
/* }}} */
static int sapi_cgi_deactivate(void) /* {{{ */
@@ -1600,6 +1605,7 @@ int main(int argc, char *argv[])
sapi_startup(&cgi_sapi_module);
cgi_sapi_module.php_ini_path_override = NULL;
cgi_sapi_module.php_ini_ignore_cwd = 1;
+ cgi_sapi_module.pre_request_init = sapi_cgi_pre_request_init;
#ifndef HAVE_ATTRIBUTE_WEAK
fcgi_set_logger(fpm_fcgi_log);
diff --git a/sapi/fpm/tests/gh8157-user-ini-post.phpt b/sapi/fpm/tests/gh8157-user-ini-post.phpt
new file mode 100644
index 00000000000..db291f76d24
--- /dev/null
+++ b/sapi/fpm/tests/gh8157-user-ini-post.phpt
@@ -0,0 +1,58 @@
+--TEST--
+FPM: gh8157 - post related INI settings not applied for .user.ini
+--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
+EOT;
+
+$code = <<<EOT
+<?php
+var_dump(\$_POST);
+EOT;
+
+$ini = <<<EOT
+post_max_size=10K
+html_errors=off
+EOT;
+
+$tester = new FPM\Tester($cfg, $code);
+$tester->setUserIni($ini);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester
+ ->request(
+ headers: [ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded'],
+ stdin: 'foo=' . str_repeat('a', 20000),
+ method: 'POST',
+ )
+ ->expectBody([
+ 'Warning: PHP Request Startup: POST Content-Length of 20004 bytes exceeds the limit of 10240 bytes in Unknown on line 0',
+ 'array(0) {',
+ '}',
+ ], skipHeadersCheck: true);
+$tester->terminate();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/response.inc b/sapi/fpm/tests/response.inc
index aefd2e027c6..5e6400e1b4c 100644
--- a/sapi/fpm/tests/response.inc
+++ b/sapi/fpm/tests/response.inc
@@ -119,10 +119,11 @@ class Response extends BaseResponse
/**
* @param mixed $body
* @param string $contentType
+ * @param bool $skipHeadersCheck
*
* @return Response
*/
- public function expectBody($body, $contentType = 'text/html')
+ public function expectBody($body, $contentType = 'text/html', bool $skipHeadersCheck = false)
{
if ($multiLine = is_array($body)) {
$body = implode("\n", $body);
@@ -130,7 +131,7 @@ class Response extends BaseResponse
if ( ! $this->checkIfValid()) {
$this->error('Response is invalid');
- } elseif ( ! $this->checkDefaultHeaders($contentType)) {
+ } elseif ( ! $skipHeadersCheck && ! $this->checkDefaultHeaders($contentType)) {
$this->error('Response default headers not found');
} elseif ($body !== $this->rawBody) {
if ($multiLine) {