[php-src] PR #25038: Fix bug #61471: Incomplete POST does not timeout but is passed to PHP
| From: | bukka | Date: | Thu, 29 Oct 2026 19:09:58 +0000 |
| Subject: | [php-src] PR #25038: Fix bug #61471: Incomplete POST does not timeout but is passed to PHP | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-40065@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/25038
Author: bukka
Fixes https://bugs.php.net/bug.php?id=61471
php_apache_sapi_read_post() ignores the status of ap_get_brigade() and
returns 0 on error which PHP takes as the end of the body. So when the client sends less than the
declared Content-Length (disconnects or stalls until mod_reqtimeout or the socket timeout aborts the
read), the script runs with a partial or empty $_POST and httpd logs 200. Other
handlers report the error and httpd responds with 400 or 408.
This was already fixed twice and both fixes got reverted. #2180 compared content_length
with read_post_bytes before running the script but PHP reads the body at startup only
for POST, so every PATCH or PUT with a body was rejected. #2323 tracked the error properly but
returned from the handler before php_request_shutdown() so the temporary files of
partial uploads leaked (bug #74318).
The error status is now kept in php_struct so it is per request and works with threaded
MPMs. It is set in read_post which also keeps the data that HTTP_IN leaves in the
brigade on APR_INCOMPLETE and does not try to read again after a failure. The handler
checks it right after php_apache_request_ctor() (so after the POST data reading), skips
the script execution and returns the status from ap_map_http_request_error() to httpd
which generates the 408 or 400 response, or AP_FILTER_ERROR if the input filter already
responded. The request shutdown is always done so the uploaded files are cleaned up. Reads during
the script execution (php://input, enable_post_data_reading=0) are not
changed because the script might have already sent a response.
It means that a truncated POST now results in 400/408 instead of running the script, so for example
a cancelled upload can no longer be seen through UPLOAD_ERR_PARTIAL. FPM behind nginx
or mod_proxy_fcgi never got that reliably either.
I tested it with wst using instances labelled bug61471 in https://github.com/wstool/wst-php-apache2handler
. The request-body-incomplete-* instances cover the actual bug (stall and disconnect,
chunked and Content-Length), request-body-multipart-incomplete the temp files from bug
#74318, request-body-unread-methods the PATCH regression of #2180 and
request-body-unread-no-post-reading and upload-chunked-over-limit check
that an unread body is not reported as incomplete. All pass with prefork and event MPM.