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) {