[php-src] PHP-8.4: [mysqlnd] Fix OK packet message length buffer over-read

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

Commit: https://github.com/php/php-src/commit/9e5cf96095ca54543c68787092cc28fcc4bc534d
Raw diff: https://github.com/php/php-src/commit/9e5cf96095ca54543c68787092cc28fcc4bc534d.diff

[mysqlnd] Fix OK packet message length buffer over-read

The OK packet message-length varint is read after the last bounds check,
so a length-encoded integer at the end of a packet can advance p past
header.size and even past the end of the 4096-byte command buffer. The
old MIN(net_len, buf_len - (p - begin)) clamp then underflows and passes
an unclamped attacker-controlled length to mnd_pestrndup(), reading heap
memory beyond both the packet and its allocation. Reject a message length
that extends past the payload, matching php_mysqlnd_auth_response_read()
from GHSA-h35g-vwh6-m678; an audit found no further readers using the
vulnerable buf_len clamp.

Closes GH-23497

Changed paths:
  A  ext/mysqli/tests/mysqlnd_ok_packet_message_over_read.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 dad8bc52ddd1..4056b9fb78cf 100644
--- a/ext/mysqli/tests/fake_server.inc
+++ b/ext/mysqli/tests/fake_server.inc
@@ -721,6 +721,19 @@ function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_co
     $conn->read();
 }
 
+function my_mysqli_test_ok_packet_message_over_read(my_mysqli_fake_server_conn $conn): void
+{
+    $p = new my_mysqli_fake_packet();
+    $p->full = "08000001" . "00" . "00" . "00" . "0200" . "0000" . "fa";
+
+    $conn->send_server_greetings();
+    $conn->read_packets(1);
+    $conn->send_server_ok();
+    $conn->read_packets(1);
+    $conn->send($p->to_bytes(), "Malicious OK Packet [message length past the packet size]");
+    $conn->read();
+}
+
 function my_mysqli_test_stmt_response_row_over_read_string(my_mysqli_fake_server_conn $conn): void
 {
     $rh = $conn->packet_generator->server_stmt_execute_items_response();
diff --git a/ext/mysqli/tests/mysqlnd_ok_packet_message_over_read.phpt b/ext/mysqli/tests/mysqlnd_ok_packet_message_over_read.phpt
new file mode 100644
index 000000000000..8364251e8df4
--- /dev/null
+++ b/ext/mysqli/tests/mysqlnd_ok_packet_message_over_read.phpt
@@ -0,0 +1,40 @@
+--TEST--
+mysqlnd OK packet message length buffer over-read
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('ok_packet_message_over_read');
+$process->wait();
+
+try {
+    $conn = new mysqli( $servername, $username, $password, "", $process->getPort());
+    var_dump($conn->select_db("test"));
+} catch (Exception $e) {
+    echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+$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 OK Packet [message length past the packet size]: %s
+
+Warning: mysqli::select_db(): OK packet message length is past the packet size in %s on line %d
+
+Warning: mysqli::select_db(): Error while reading INIT_DB's response packet. PID=%d in %s on line %d
+mysqli_sql_exception: Malformed packet
+done!
diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c
index 64c2c7969619..80b4b37591ab 100644
--- a/ext/mysqlnd/mysqlnd_wireprotocol.c
+++ b/ext/mysqlnd/mysqlnd_wireprotocol.c
@@ -878,7 +878,12 @@ php_mysqlnd_ok_read(MYSQLND_CONN_DATA * conn, void * _packet)
 
 	/* There is a message */
 	if (packet->header.size > (size_t) (p - buf) && (net_len = php_mysqlnd_net_field_length(&p))) {
-		packet->message_len = MIN(net_len, buf_len - (p - begin));
+		if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < net_len) {
+			DBG_ERR_FMT("OK packet message length is past the packet size");
+			php_error_docref(NULL, E_WARNING, "OK packet message length is past the packet size");
+			DBG_RETURN(FAIL);
+		}
+		packet->message_len = net_len;
 		packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE);
 	} else {
 		packet->message = NULL;
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.