Commit 9e5cf96095c for php.net
commit 9e5cf96095ca54543c68787092cc28fcc4bc534d
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Sat Aug 29 08:00:20 2026 -0400
[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
diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc
index dad8bc52ddd..4056b9fb78c 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 00000000000..8364251e8df
--- /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 64c2c796961..80b4b37591a 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;