From fbf3fbe5dad8cbeea6d44eadf5d3deaec4c632d4 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Mon, 24 Aug 2026 01:59:59 +0500 Subject: [PATCH 1/4] sapi/cli: check php_cli_server_client_send_through() return value --- NEWS | 4 ++++ sapi/cli/php_cli_server.c | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index d25d441ca8b5..1f009ffef87f 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,10 @@ PHP NEWS . Fixed bug GH-23301 (Nested "yield from" yields a value twice when the middle generator delegates again). (Lazizbek Ergashev) +- CLI: + . Fixed bug GH-23425 (sapi_cli_server_send_headers() does not check the + return value of php_cli_server_client_send_through()). (Lazizbek Ergashev) + - DOM: . Fixed a use-after-free when cloning a DOMNameSpaceNode after DOMDocument::xinclude(). (iliaal) diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index f4d29ce56855..d8ed4525304b 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -578,10 +578,11 @@ static int sapi_cli_server_send_headers(sapi_headers_struct *sapi_headers) /* {{ } smart_str_appendl(&buffer, "\r\n", 2); - php_cli_server_client_send_through(client, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s)); + size_t buffer_len = ZSTR_LEN(buffer.s); + bool sent = php_cli_server_client_send_through(client, ZSTR_VAL(buffer.s), buffer_len) == buffer_len; smart_str_free(&buffer); - return SAPI_HEADER_SENT_SUCCESSFULLY; + return sent ? SAPI_HEADER_SENT_SUCCESSFULLY : SAPI_HEADER_SEND_FAILED; } /* }}} */ From e2f8bcd0930b5e3f4a436f668efce56d3821b872 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Mon, 24 Aug 2026 02:49:44 +0500 Subject: [PATCH 2/4] sapi/cli: report bytes actually sent on a failed send_through() --- sapi/cli/php_cli_server.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index d8ed4525304b..9fb88d3ec734 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -1921,11 +1921,11 @@ static size_t php_cli_server_client_send_through(php_cli_server_client *client, } else { /* error or timeout */ php_handle_aborted_connection(); - return nbytes_left; + return str_len - nbytes_left; } } else { php_handle_aborted_connection(); - return nbytes_left; + return str_len - nbytes_left; } } nbytes_left -= nbytes_sent; From 683e87b358d3207f95d23a1104193d27c4a38b62 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Mon, 24 Aug 2026 14:33:51 +0500 Subject: [PATCH 3/4] sapi/cli: add regression test for GH-23425 --- sapi/cli/tests/gh23425.phpt | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 sapi/cli/tests/gh23425.phpt diff --git a/sapi/cli/tests/gh23425.phpt b/sapi/cli/tests/gh23425.phpt new file mode 100644 index 000000000000..e0de0982f7cc --- /dev/null +++ b/sapi/cli/tests/gh23425.phpt @@ -0,0 +1,39 @@ +--TEST-- +GH-23425 (sapi_cli_server_send_headers() does not check the return value of php_cli_server_client_send_through()) +--EXTENSIONS-- +sockets +--SKIPIF-- + +--FILE-- + 1, 'l_linger' => 0]); +socket_close($sock); + +$result_file = $info->docRoot . '/result.txt'; +for ($i = 0; $i < 40 && !file_exists($result_file); $i++) { + usleep(50000); +} + +echo file_get_contents($result_file), "\n"; +?> +--EXPECT-- +not-sent From f502d8a6092bcaf1773ae44611e8e6b5dc1a4828 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Mon, 24 Aug 2026 18:22:03 +0500 Subject: [PATCH 4/4] sapi/cli: guard against sending response headers twice --- sapi/cli/php_cli_server.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 9fb88d3ec734..36187aaeb035 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -177,6 +177,7 @@ typedef struct php_cli_server_client { zend_string *addr_str; php_http_parser parser; bool request_read; + bool headers_written; zend_string *current_header_name; zend_string *current_header_value; enum { HEADER_NONE=0, HEADER_FIELD, HEADER_VALUE } last_header_element; @@ -555,7 +556,7 @@ static int sapi_cli_server_send_headers(sapi_headers_struct *sapi_headers) /* {{ sapi_header_struct *h; zend_llist_position pos; - if (client == NULL || SG(request_info).no_headers) { + if (client == NULL || SG(request_info).no_headers || client->headers_written) { return SAPI_HEADER_SENT_SUCCESSFULLY; } @@ -581,6 +582,7 @@ static int sapi_cli_server_send_headers(sapi_headers_struct *sapi_headers) /* {{ size_t buffer_len = ZSTR_LEN(buffer.s); bool sent = php_cli_server_client_send_through(client, ZSTR_VAL(buffer.s), buffer_len) == buffer_len; + client->headers_written = true; smart_str_free(&buffer); return sent ? SAPI_HEADER_SENT_SUCCESSFULLY : SAPI_HEADER_SEND_FAILED; } @@ -1974,6 +1976,7 @@ static void php_cli_server_client_ctor(php_cli_server_client *client, php_cli_se php_http_parser_init(&client->parser, PHP_HTTP_REQUEST); client->request_read = false; + client->headers_written = false; client->last_header_element = HEADER_NONE; client->current_header_name = NULL;