[php-src] PR #24541: Fix FPM truncating output of 2 GiB or more
| From: | Marti2203 | Date: | Thu, 22 Oct 2026 14:23:20 +0000 |
| Subject: | [php-src] PR #24541: Fix FPM truncating output of 2 GiB or more | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-39705@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24541
Author: Marti2203
In FPM,
sapi_cgibin_ub_write() stores the remaining length in a uint32_t,
and sapi_cgibin_single_write() passes it on to fcgi_write(), which takes
an int:
```c
static size_t sapi_cgibin_ub_write(const char *str, size_t str_length)
{
uint32_t remaining = str_length;
...
ret = sapi_cgibin_single_write(ptr, remaining); /* -> fcgi_write(..., int len) */
```
A single write of 2 GiB or more is therefore sent truncated or not at all:
- From 2 GiB to just under 4 GiB, the length becomes negative as an int.
fcgi_write() returns 0, sapi_cgibin_ub_write() treats that as an aborted
connection, and no body is sent.
- At 4 GiB or more, only the low 32 bits of the length are written.
In both cases the request ends normally (END_REQUEST with status 0) and nothing is logged, so the
client just gets a short response. This happens for any script that echoes a single string of that
size with output buffering off, or when the output buffer is flushed with that much data in it.
The CGI SAPI already handles this: sapi_fcgi_ub_write() in
sapi/cgi/cgi_main.c uses size_t remaining and caps each
fcgi_write() call at INT_MAX.
### Reproducer
big.php:
```php
<?php
echo str_repeat('A', (int)$_GET['n']);
```
The pool runs with pm = static, pm.max_children = 1,
php_admin_value[memory_limit] = -1 and php_admin_value[output_buffering] =
0. A small FastCGI client sends GET big.php?n=... and counts the FCGI_STDOUT
bytes (the 72 header bytes are not counted below).
| n | body bytes before | body bytes after |
|---|---|---|
| 104857600 (100 MiB) | 104857600 | 104857600 |
| 3221225472 (3 GiB) | 0 | 3221225472 |
| 4294967306 (4 GiB + 10) | 10 | 4294967306 |
### Fix
Make the remaining length and the sapi_cgibin_single_write() parameter
size_t, and cap each fcgi_write() call at INT_MAX, as
cgi_main.c does. fcgi_write() and main/fastcgi.h are
unchanged, so the CGI SAPI is not affected. The other fcgi_write() caller in FPM
(sapi_cgi_log_fastcgi()) writes log messages and is left alone.
### Test
sapi/fpm/tests/fcgi-write-over-4gib.phpt echoes 4 GiB + 10 bytes through FPM and checks
the body length. This needs about 4 GB of memory in each of the FPM worker and the test client, so
like the other large-memory tests it only runs with RUN_RESOURCE_HEAVY_TESTS set, on
64-bit platforms, and not with SKIP_SLOW_TESTS.
- Without the fix: fails with int(10) instead of int(4294967306).
- With the fix: passes (about 17 s); without RUN_RESOURCE_HEAVY_TESTS it is skipped.
- sapi/fpm/tests: 118 passed, 18 skipped, 1 warned. Two tests failed
(bug68423-multi-pool-all-pms.phpt and bug68391-conf-include-order.phpt)
because another process on my shared test machine was already using ports 9010-9019. The failures
are bind(): Address already in use and are unrelated to this change.
This issue was found by an LLM-Enhanced Static Analysis Engine and verified manually.
🤖 Generated with [Claude Code](https://claude.com/claude-code)