[php-src] master: Merge branch 'PHP-8.5'

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-08-30T11:27:21-04:00

Commit: https://github.com/php/php-src/commit/90ebbf71e7536b252ac95c9152e59c846be66f65
Raw diff: https://github.com/php/php-src/commit/90ebbf71e7536b252ac95c9152e59c846be66f65.diff

Merge branch 'PHP-8.5'

* PHP-8.5:
  [mysqlnd] Fix result set field metadata length buffer over-read

Changed paths:
  A  ext/mysqli/tests/mysqlnd_rset_field_len_over_read.phpt
  A  ext/mysqli/tests/mysqlnd_rset_field_len_past_packet.phpt
  M  ext/mysqli/tests/fake_server.inc
  M  ext/mysqlnd/mysqlnd_wireprotocol.c


Diff:

diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc
index 4056b9fb78cf..3af5c7459159 100644
--- a/ext/mysqli/tests/fake_server.inc
+++ b/ext/mysqli/tests/fake_server.inc
@@ -829,6 +829,50 @@ function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_
     }
 }
 
+function my_mysqli_test_rset_field_metadata_len_over_read(my_mysqli_fake_server_conn $conn): void
+{
+    $rh = $conn->packet_generator->server_tabular_query_response();
+
+    $qr2 = new my_mysqli_fake_packet();
+    $qr2->packet_length = "0c0000";
+    $qr2->packet_number = "02";
+    $qr2->catalog_length_plus_name = "0161";
+    $qr2->db_length_plus_name = "0162";
+    $qr2->table_length_plus_name = "0163";
+    $qr2->original_t = "0164";
+    $qr2->name_length_plus_name = "0165";
+    $qr2->original_n = "fcff";
+
+    $conn->send_server_greetings();
+    $conn->read_packets(1);
+    $conn->send_server_ok();
+    $conn->read_packets(1);
+    $conn->send($conn->packets_to_bytes([$rh[0], $qr2]), "Malicious Tabular Response [metadata string length past the packet size]");
+    $conn->read();
+}
+
+function my_mysqli_test_rset_field_metadata_len_past_packet(my_mysqli_fake_server_conn $conn): void
+{
+    $rh = $conn->packet_generator->server_tabular_query_response();
+
+    $qr2 = new my_mysqli_fake_packet();
+    $qr2->packet_length = "0c0000";
+    $qr2->packet_number = "02";
+    $qr2->catalog_length_plus_name = "0161";
+    $qr2->db_length_plus_name = "0162";
+    $qr2->table_length_plus_name = "0163";
+    $qr2->original_t = "0164";
+    $qr2->name_length_plus_name = "0165";
+    $qr2->original_n = "0561";
+
+    $conn->send_server_greetings();
+    $conn->read_packets(1);
+    $conn->send_server_ok();
+    $conn->read_packets(1);
+    $conn->send($conn->packets_to_bytes([$rh[0], $qr2]), "Malicious Tabular Response [metadata string length past the packet size]");
+    $conn->read();
+}
+
 function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn): void
 {
     $rh = $conn->packet_generator->server_query_execute_data_response('strval');
diff --git a/ext/mysqli/tests/mysqlnd_rset_field_len_over_read.phpt b/ext/mysqli/tests/mysqlnd_rset_field_len_over_read.phpt
new file mode 100644
index 000000000000..274468ded63d
--- /dev/null
+++ b/ext/mysqli/tests/mysqlnd_rset_field_len_over_read.phpt
@@ -0,0 +1,42 @@
+--TEST--
+mysqlnd result set field metadata string length buffer over-read (len clamped to packet size)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('rset_field_metadata_len_over_read');
+$process->wait();
+
+try {
+    $conn = new mysqli( $servername, $username, $password, "", $process->getPort());
+    var_dump($conn->query("SELECT * from users"));
+} catch (Exception $e) {
+    echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+$conn->close();
+
+$process->terminate();
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started on 127.0.0.1:%d
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: %s
+[*] Sending - Server OK: 0700000200000002000000
+[*] Received: %s
+[*] Sending - Malicious Tabular Response [metadata string length past the packet size]: 01000001010c00000201610162016301640165fcff
+
+Warning: mysqli::query(): Premature end of data (mysqlnd_wireprotocol.c:%d) in %s on line %d
+
+Warning: mysqli::query(): Result set field packet %d bytes shorter than expected in %s on line %d
+bool(false)
+done!
diff --git a/ext/mysqli/tests/mysqlnd_rset_field_len_past_packet.phpt b/ext/mysqli/tests/mysqlnd_rset_field_len_past_packet.phpt
new file mode 100644
index 000000000000..020d28d95b25
--- /dev/null
+++ b/ext/mysqli/tests/mysqlnd_rset_field_len_past_packet.phpt
@@ -0,0 +1,40 @@
+--TEST--
+mysqlnd result set field metadata string length exceeds remaining packet bytes
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('rset_field_metadata_len_past_packet');
+$process->wait();
+
+try {
+    $conn = new mysqli( $servername, $username, $password, "", $process->getPort());
+    var_dump($conn->query("SELECT * from users"));
+} catch (Exception $e) {
+    echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+$conn->close();
+
+$process->terminate();
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started on 127.0.0.1:%d
+[*] Connection established
+[*] Sending - Server Greeting: %s
+[*] Received: %s
+[*] Sending - Server OK: %s
+[*] Received: %s
+[*] Sending - Malicious Tabular Response [metadata string length past the packet size]: %s
+
+Warning: mysqli::query(): Result set field metadata string length is past the packet size in %s on line %d
+bool(false)
+done!
diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c
index 5827ddeb06d6..094daa939aa4 100644
--- a/ext/mysqlnd/mysqlnd_wireprotocol.c
+++ b/ext/mysqlnd/mysqlnd_wireprotocol.c
@@ -1174,10 +1174,17 @@ void php_mysqlnd_rset_header_free_mem(void * _packet)
 /* }}} */
 
 #define READ_RSET_FIELD(field_name) do { \
+		BAIL_IF_NO_MORE_DATA; \
 		len = php_mysqlnd_net_field_length(&p); \
 		if (UNEXPECTED(len == MYSQLND_NULL_LENGTH)) { \
 			goto faulty_or_fake; \
 		} else if (len != 0) { \
+			BAIL_IF_NO_MORE_DATA; \
+			if (UNEXPECTED((p - begin) > packet->header.size || packet->header.size - (p - begin) < len)) { \
+				DBG_ERR_FMT("Result set field metadata string length is past the packet size"); \
+				php_error_docref(NULL, E_WARNING, "Result set field metadata string length is past the packet size"); \
+				DBG_RETURN(FAIL); \
+			} \
 			meta->field_name = (const char *)p; \
 			meta->field_name ## _length = len; \
 			p += len; \
@@ -1246,7 +1253,7 @@ php_mysqlnd_rset_field_read(MYSQLND_CONN_DATA * conn, void * _packet)
 	READ_RSET_FIELD(name);
 	READ_RSET_FIELD(org_name);
 
-	/* 1 byte length */
+	BAIL_IF_NO_MORE_DATA;
 	if (UNEXPECTED(12 != *p)) {
 		DBG_ERR_FMT("Protocol error. Server sent false length. Expected 12 got %d", (int) *p);
 		php_error_docref(NULL, E_WARNING, "Protocol error. Server sent false length. Expected 12");
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.