From 004207b39a95c42ae4bd2780bad6eaf0198d6e07 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 17 Sep 2026 17:27:12 -0700 Subject: [PATCH 1/2] CLI server: make all heap chunks self contained For chunks that were pointing to heap data outside of the chunk itself, ASAN would report some memory leaks. Rather than trying to figure out exactly where the right place to free that data is, just copy the data into a self-contained chunk. While this might not be the best option from a performance perspective, the CLI server is meant only as a development tool and accordingly performance is not a major concern. The static `php_cli_server_chunk_heap_new()` function was removed, additional cleanup will be done in subsequent commits. --- sapi/cli/php_cli_server.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index c9ed8ff53c63..83013c9363f8 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -1013,18 +1013,6 @@ static php_cli_server_chunk *php_cli_server_chunk_immortal_new(const char *buf, return chunk; } /* }}} */ -static php_cli_server_chunk *php_cli_server_chunk_heap_new(void *block, char *buf, size_t len) /* {{{ */ -{ - php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk), 1); - - chunk->type = PHP_CLI_SERVER_CHUNK_HEAP; - chunk->next = NULL; - chunk->data.heap.block = block; - chunk->data.heap.p = buf; - chunk->data.heap.len = len; - return chunk; -} /* }}} */ - static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len) /* {{{ */ { php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk) + len, 1); @@ -2146,9 +2134,12 @@ static zend_result php_cli_server_send_error_page(php_cli_server *server, php_cl } smart_str_appendl_ex(&buffer, "\r\n", 2, 1); - chunk = php_cli_server_chunk_heap_new(buffer.s, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s)); + chunk = php_cli_server_chunk_heap_new_self_contained(ZSTR_LEN(buffer.s)); + if (chunk) { + memcpy(chunk->data.heap.p, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s)); + } + smart_str_free_ex(&buffer, 1); if (!chunk) { - smart_str_free_ex(&buffer, 1); goto fail; } php_cli_server_buffer_prepend(&client->content_sender.buffer, chunk); @@ -2264,9 +2255,13 @@ static zend_result php_cli_server_begin_send_static(php_cli_server *server, php_ smart_str_append_unsigned_ex(&buffer, client->request.sb.st_size, 1); smart_str_appendl_ex(&buffer, "\r\n", 2, 1); smart_str_appendl_ex(&buffer, "\r\n", 2, 1); - chunk = php_cli_server_chunk_heap_new(buffer.s, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s)); + + chunk = php_cli_server_chunk_heap_new_self_contained(ZSTR_LEN(buffer.s)); + if (chunk) { + memcpy(chunk->data.heap.p, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s)); + } + smart_str_free_ex(&buffer, 1); if (!chunk) { - smart_str_free_ex(&buffer, 1); php_cli_server_log_response(client, 500, NULL); return FAILURE; } From d6b8f45831c8966ae2aa84cccff5461bd3b65b29 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 17 Sep 2026 17:32:50 -0700 Subject: [PATCH 2/2] CLI server: remove `php_cli_server_chunk.data.heap.block` pointer Now that all heap allocations are self contained, the block is always the same as the overall chunk; remove the unneeded field. Additionally, `php_cli_server_chunk_dtor()`, which only acts for heap allocations for which the block was different than the chunk, is no longer needed and is removed. --- sapi/cli/php_cli_server.c | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 83013c9363f8..5a454adc2f89 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -152,7 +152,7 @@ typedef struct php_cli_server_chunk { PHP_CLI_SERVER_CHUNK_IMMORTAL } type; union { - struct { void *block; char *p; size_t len; } heap; + struct { char *p; size_t len; } heap; struct { const char *p; size_t len; } immortal; } data; } php_cli_server_chunk; @@ -940,25 +940,11 @@ static size_t php_cli_server_chunk_size(const php_cli_server_chunk *chunk) /* {{ return 0; } /* }}} */ -static void php_cli_server_chunk_dtor(php_cli_server_chunk *chunk) /* {{{ */ -{ - switch (chunk->type) { - case PHP_CLI_SERVER_CHUNK_HEAP: - if (chunk->data.heap.block != chunk) { - pefree(chunk->data.heap.block, 1); - } - break; - case PHP_CLI_SERVER_CHUNK_IMMORTAL: - break; - } -} /* }}} */ - static void php_cli_server_buffer_dtor(php_cli_server_buffer *buffer) /* {{{ */ { php_cli_server_chunk *chunk, *next; for (chunk = buffer->first; chunk; chunk = next) { next = chunk->next; - php_cli_server_chunk_dtor(chunk); pefree(chunk, 1); } } /* }}} */ @@ -1019,7 +1005,6 @@ static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t chunk->type = PHP_CLI_SERVER_CHUNK_HEAP; chunk->next = NULL; - chunk->data.heap.block = chunk; chunk->data.heap.p = (char *)(chunk + 1); chunk->data.heap.len = len; return chunk; @@ -1063,7 +1048,6 @@ static int php_cli_server_content_sender_send(php_cli_server_content_sender *sen #else } else if (nbytes_sent == (ssize_t)chunk->data.heap.len) { #endif - php_cli_server_chunk_dtor(chunk); pefree(chunk, 1); sender->buffer.first = next; if (!next) { @@ -1091,7 +1075,6 @@ static int php_cli_server_content_sender_send(php_cli_server_content_sender *sen #else } else if (nbytes_sent == (ssize_t)chunk->data.immortal.len) { #endif - php_cli_server_chunk_dtor(chunk); pefree(chunk, 1); sender->buffer.first = next; if (!next) { @@ -1130,7 +1113,6 @@ static bool php_cli_server_content_sender_pull(php_cli_server_content_sender *se php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "%s", errstr); pefree(errstr, 1); } - php_cli_server_chunk_dtor(chunk); pefree(chunk, 1); return false; }