[php-src] Issue #24685: Memory leak in php_cli_server_client_save_header when joining duplicate headers

From: Date: Sun, 25 Oct 2026 13:18:08 +0000
Subject: [php-src] Issue #24685: Memory leak in php_cli_server_client_save_header when joining duplicate headers
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-253180@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/24685 Author: m-this ### Description php_cli_server_client_save_header() in sapi/cli/php_cli_server.c leaks the value of a duplicate request header. When a request repeats a header name, the duplicate value is copied into a joined string but the original current_header_value string is never released. ### Details In the merge branch (8.4.26, lines 1699-1707), a duplicate header is joined with the value already stored in the hash table: ```c } else { zend_string *curval = Z_STR_P(entry); zend_string *newval = zend_string_safe_alloc(1, ZSTR_LEN(curval), ZSTR_LEN(client->current_header_value) + 2, /* persistent */ true); memcpy(ZSTR_VAL(newval), ZSTR_VAL(curval), ZSTR_LEN(curval)); memcpy(ZSTR_VAL(newval) + ZSTR_LEN(curval), ", ", 2); memcpy(ZSTR_VAL(newval) + ZSTR_LEN(curval) + 2, ZSTR_VAL(client->current_header_value), ZSTR_LEN(client->current_header_value) + 1); ZVAL_STR(&tmp, newval); } ``` newval is stored in the hash tables, but client->current_header_value is only copied out of and then dropped at line 1718: ```c client->current_header_value = NULL; ``` In the non-duplicate branch (entry == NULL), ZVAL_STR(&tmp, client->current_header_value) hands the string to the hash table, which owns and frees it at request teardown. The merge branch copies the bytes of current_header_value into newval but never releases current_header_value itself, so the string leaks. ### PoC Start the built-in server and send a request with two headers of the same name: ```python import socket s = socket.create_connection(("127.0.0.1", 8000)) s.sendall(b"GET / HTTP/1.1\r\nX: 1\r\nX: 2\r\n\r\n") ``` Under a LeakSanitizer build this leaks 32 bytes per connection. The leak is linear in the number of connections that carry a duplicate header. ### LeakSanitizer output ``` Direct leak of 32 byte(s) in 1 object(s) allocated from: #0 0x7f8b7e8f4c57 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69 #1 0x558100a29061 in __zend_malloc /src/Zend/zend_alloc.c:3294 #2 0x5581010b0e47 in zend_string_alloc /src/Zend/zend_string.h:176 #3 0x5581010b0e47 in zend_string_init /src/Zend/zend_string.h:198 #4 0x5581010b0e47 in php_cli_server_client_read_request_on_header_value /src/sapi/cli/php_cli_server.c:1765 #5 0x5581010a2803 in php_http_parser_execute /src/sapi/cli/php_http_parser.c:1209 #6 0x5581010b7797 in php_cli_server_client_read_request /src/sapi/cli/php_cli_server.c:1884 #7 0x5581010b856f in php_cli_server_recv_event_read_request /src/sapi/cli/php_cli_server.c:2643 ... SUMMARY: AddressSanitizer: 32 byte(s) leaked in 1 allocation(s). ``` ### Impact A small server-side leak (32 bytes) per request that sends a duplicate header. The built-in server is single-process, so a long-running instance receiving such requests grows memory slowly. No crash or memory corruption. ### Suggested fix Release the incoming value in the merge branch after it has been copied: ```c ZVAL_STR(&tmp, newval); zend_string_release_ex(client->current_header_value, /* persistent */ true); ``` ### PHP Version 8.4.26; the same code is present on PHP-8.6 and master (8.7.0-dev).

« previous php.bugs (#253180) next »