summaryrefslogtreecommitdiffstats
path: root/php-cve-2025-1734.patch
diff options
context:
space:
mode:
Diffstat (limited to 'php-cve-2025-1734.patch')
-rw-r--r--php-cve-2025-1734.patch300
1 files changed, 300 insertions, 0 deletions
diff --git a/php-cve-2025-1734.patch b/php-cve-2025-1734.patch
new file mode 100644
index 0000000..b7705f2
--- /dev/null
+++ b/php-cve-2025-1734.patch
@@ -0,0 +1,300 @@
+From e81d0cd14bfeb17e899c73e3aece4991bbda76af Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Sun, 19 Jan 2025 17:49:53 +0100
+Subject: [PATCH 02/11] Fix GHSA-pcmh-g36c-qc44: http headers without colon
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The header line must contain colon otherwise it is invalid and it needs
+to fail.
+
+Reviewed-by: Tim Düsterhus <tim@tideways-gmbh.com>
+(cherry picked from commit 0548c4c1756724a89ef8310709419b08aadb2b3b)
+---
+ ext/standard/http_fopen_wrapper.c | 51 ++++++++++++++-----
+ ext/standard/tests/http/bug47021.phpt | 22 ++++----
+ ext/standard/tests/http/bug75535.phpt | 4 +-
+ .../tests/http/ghsa-pcmh-g36c-qc44-001.phpt | 51 +++++++++++++++++++
+ .../tests/http/ghsa-pcmh-g36c-qc44-002.phpt | 51 +++++++++++++++++++
+ 5 files changed, 154 insertions(+), 25 deletions(-)
+ create mode 100644 ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+
+diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
+index bfc88a74545..7ee22b85f88 100644
+--- a/ext/standard/http_fopen_wrapper.c
++++ b/ext/standard/http_fopen_wrapper.c
+@@ -117,6 +117,7 @@ static zend_bool check_has_header(const char *headers, const char *header) {
+ typedef struct _php_stream_http_response_header_info {
+ php_stream_filter *transfer_encoding;
+ size_t file_size;
++ bool error;
+ bool follow_location;
+ char location[HTTP_HEADER_BLOCK_SIZE];
+ } php_stream_http_response_header_info;
+@@ -126,6 +127,7 @@ static void php_stream_http_response_header_info_init(
+ {
+ header_info->transfer_encoding = NULL;
+ header_info->file_size = 0;
++ header_info->error = false;
+ header_info->follow_location = 1;
+ header_info->location[0] = '\0';
+ }
+@@ -163,10 +165,11 @@ static bool php_stream_http_response_header_trim(char *http_header_line,
+ /* Process folding headers of the current line and if there are none, parse last full response
+ * header line. It returns NULL if the last header is finished, otherwise it returns updated
+ * last header line. */
+-static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+- php_stream_context *context, int options, zend_string *last_header_line_str,
+- char *header_line, size_t *header_line_length, int response_code,
+- zval *response_header, php_stream_http_response_header_info *header_info)
++static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *wrapper,
++ php_stream *stream, php_stream_context *context, int options,
++ zend_string *last_header_line_str, char *header_line, size_t *header_line_length,
++ int response_code, zval *response_header,
++ php_stream_http_response_header_info *header_info)
+ {
+ char *last_header_line = ZSTR_VAL(last_header_line_str);
+ size_t last_header_line_length = ZSTR_LEN(last_header_line_str);
+@@ -208,6 +211,19 @@ static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+ /* Find header separator position. */
+ char *last_header_value = memchr(last_header_line, ':', last_header_line_length);
+ if (last_header_value) {
++ /* Verify there is no space in header name */
++ char *last_header_name = last_header_line + 1;
++ while (last_header_name < last_header_value) {
++ if (*last_header_name == ' ' || *last_header_name == '\t') {
++ header_info->error = true;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP invalid response format (space in header name)!");
++ zend_string_efree(last_header_line_str);
++ return NULL;
++ }
++ ++last_header_name;
++ }
++
+ last_header_value++; /* Skip ':'. */
+
+ /* Strip leading whitespace. */
+@@ -216,9 +232,12 @@ static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+ last_header_value++;
+ }
+ } else {
+- /* There is no colon. Set the value to the end of the header line, which is effectively
+- * an empty string. */
+- last_header_value = last_header_line_end;
++ /* There is no colon which means invalid response so error. */
++ header_info->error = true;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP invalid response format (no colon in header line)!");
++ zend_string_efree(last_header_line_str);
++ return NULL;
+ }
+
+ bool store_header = true;
+@@ -928,10 +947,16 @@ finish:
+
+ if (last_header_line_str != NULL) {
+ /* Parse last header line. */
+- last_header_line_str = php_stream_http_response_headers_parse(stream, context,
+- options, last_header_line_str, http_header_line, &http_header_line_length,
+- response_code, response_header, &header_info);
+- if (last_header_line_str != NULL) {
++ last_header_line_str = php_stream_http_response_headers_parse(wrapper, stream,
++ context, options, last_header_line_str, http_header_line,
++ &http_header_line_length, response_code, response_header, &header_info);
++ if (EXPECTED(last_header_line_str == NULL)) {
++ if (UNEXPECTED(header_info.error)) {
++ php_stream_close(stream);
++ stream = NULL;
++ goto out;
++ }
++ } else {
+ /* Folding header present so continue. */
+ continue;
+ }
+@@ -961,8 +986,8 @@ finish:
+
+ /* If the stream was closed early, we still want to process the last line to keep BC. */
+ if (last_header_line_str != NULL) {
+- php_stream_http_response_headers_parse(stream, context, options, last_header_line_str,
+- NULL, NULL, response_code, response_header, &header_info);
++ php_stream_http_response_headers_parse(wrapper, stream, context, options,
++ last_header_line_str, NULL, NULL, response_code, response_header, &header_info);
+ }
+
+ if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
+diff --git a/ext/standard/tests/http/bug47021.phpt b/ext/standard/tests/http/bug47021.phpt
+index 326eceb687a..168721f4ec1 100644
+--- a/ext/standard/tests/http/bug47021.phpt
++++ b/ext/standard/tests/http/bug47021.phpt
+@@ -70,23 +70,27 @@ do_test(1, true);
+ echo "\n";
+
+ ?>
+---EXPECT--
++--EXPECTF--
++
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++
+
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++
+
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++
+
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
+diff --git a/ext/standard/tests/http/bug75535.phpt b/ext/standard/tests/http/bug75535.phpt
+index 7b015890d2f..94348d1a027 100644
+--- a/ext/standard/tests/http/bug75535.phpt
++++ b/ext/standard/tests/http/bug75535.phpt
+@@ -21,9 +21,7 @@ http_server_kill($pid);
+
+ --EXPECT--
+ string(0) ""
+-array(2) {
++array(1) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+- [1]=>
+- string(14) "Content-Length"
+ }
+diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+new file mode 100644
+index 00000000000..bb7945ce62d
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+@@ -0,0 +1,51 @@
++--TEST--
++GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (colon)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header\r\nGood-Header: test\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html
++
++Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++bool(false)
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(23) "Content-Type: text/html"
++}
+diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+new file mode 100644
+index 00000000000..1d0e4fa70a2
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+@@ -0,0 +1,51 @@
++--TEST--
++GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (name)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header : test\r\nGood-Header: test\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html
++
++Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (space in header name)! in %s
++bool(false)
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(23) "Content-Type: text/html"
++}
+--
+2.48.1
+