[php-src] Issue #24509: HTTP wrapper leaks Location buffer when an empty Location header is followed by another Location header

From: Date: Wed, 21 Oct 2026 20:39:59 +0000
Subject: [php-src] Issue #24509: HTTP wrapper leaks Location buffer when an empty Location header is followed by another Location header
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-253066@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/24509 Author: IzieStratt ### Description The following code: server.php (run first, php server.php): ```php <?php $srv = stream_socket_server("tcp://127.0.0.1:18233", $errno, $errstr); $conn = stream_socket_accept($srv); while (($line = fgets($conn, 8192)) !== false) { if (trim($line) === '') break; } // empty Location value, then a real one fwrite($conn, "HTTP/1.1 302 Found\r\nLocation:\r\nLocation: /abcdefghijklmno\r\n\r\n"); fclose($conn); ``` client.php (debug build, php -d report_memleaks=1 client.php): ```php <?php @file_get_contents("http://127.0.0.1:18233/"); ``` Resulted in this output: ``` ext/standard/http_fopen_wrapper.c(341) : Freeing 0x... (16 bytes), script=.../client.php === Total 1 memory leaks detected === ``` But I expected this output instead: ``` (no leaks reported) ``` Root cause: the wrapper stores the Location value using location_len == 0 as the "nothing allocated yet" sentinel (ext/standard/http_fopen_wrapper.c:340 in 8.5.12, :327 on master): ```c if (header_info->location_len == 0) { header_info->location = emalloc(last_header_value_len + 1); } else if (header_info->location_len <= last_header_value_len) { header_info->location = erealloc(header_info->location, last_header_value_len + 1); } header_info->location_len = last_header_value_len; ``` An empty Location: header allocates a 1-byte buffer and then sets location_len = 0, restoring the sentinel state while the pointer is still held. A second Location header takes the emalloc branch again and the first buffer is leaked. Each empty-then-nonempty sequence leaks one buffer per request. Control (two non-empty Location headers): 0 leaks. Reproduces on current master (8.7.0-dev) as well. Suggested patch (verified against master: 0 leaks after patching; repeated non-empty Location headers still take the erealloc path and behave as before). header_info is zero-initialized by php_stream_http_response_header_info_init, so the pointer works as the sentinel: ```diff --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -324,7 +324,9 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w zend_string_efree(last_header_line); return NULL; } - if (header_info->location_len == 0) { + /* An empty Location value leaves location_len at 0, so the buffer + * pointer, not the length, must be the "already allocated" test. */ + if (header_info->location == NULL) { header_info->location = emalloc(last_header_value_len + 1); } else if (header_info->location_len <= last_header_value_len) { header_info->location = erealloc(header_info->location, last_header_value_len + 1); ``` ### PHP Version ```plain PHP 8.5.12 (also reproduces on master, 8.7.0-dev) ``` ### Operating System Linux x86_64

« previous php.bugs (#253066) next »