From 0d3ccf4cc54d3844bc9d1c8f6bdcd36180752a2c Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Tue, 8 Oct 2024 16:17:53 +0100
Subject: [PATCH 1/6] Fix GHSA-h35g-vwh6-m678: Mysqlnd - various heap buffer
 over-reads

This fixes issues causing buffer over-read that leak heap content:
- RESP packet field default left over for COM_LIST
- RESP packet upsert filename
- OK packet message
- RESP packet for stmt row data
  - ps_fetch_from_1_to_8_bytes
  - ps_fetch_float
  - ps_fetch_double
  - ps_fetch_time
  - ps_fetch_date
  - ps_fetch_datetime
  - ps_fetch_string
  - ps_fetch_bit
- RESP packet for query row data (just possible overflow on 32bit)

It also adds various protocol tests using a new fake server.

(cherry picked from commit 2f5aa9f9d150ca56e356f3ca9acf9d530108cb08)
---
 ext/mysqli/tests/fake_server.inc              | 856 ++++++++++++++++++
 .../ghsa-h35g-vwh6-m678-auth-message.phpt     |  38 +
 ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt |  47 +
 .../tests/ghsa-h35g-vwh6-m678-filename.phpt   |  43 +
 ...hsa-h35g-vwh6-m678-query-len-overflow.phpt |  48 +
 .../ghsa-h35g-vwh6-m678-stmt-row-bit.phpt     |  53 ++
 .../ghsa-h35g-vwh6-m678-stmt-row-date.phpt    |  53 ++
 ...ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt |  53 ++
 .../ghsa-h35g-vwh6-m678-stmt-row-double.phpt  |  53 ++
 .../ghsa-h35g-vwh6-m678-stmt-row-float.phpt   |  53 ++
 .../ghsa-h35g-vwh6-m678-stmt-row-int.phpt     |  53 ++
 ...ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt |  53 ++
 .../ghsa-h35g-vwh6-m678-stmt-row-string.phpt  |  53 ++
 .../ghsa-h35g-vwh6-m678-stmt-row-time.phpt    |  53 ++
 .../tests/protocol_query_row_fetch_data.phpt  |  74 ++
 .../tests/protocol_stmt_row_fetch_data.phpt   |  91 ++
 ext/mysqlnd/mysqlnd_ps_codec.c                |  69 ++
 ext/mysqlnd/mysqlnd_result.c                  |   2 +-
 ext/mysqlnd/mysqlnd_wireprotocol.c            |  71 +-
 19 files changed, 1794 insertions(+), 22 deletions(-)
 create mode 100644 ext/mysqli/tests/fake_server.inc
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt
 create mode 100644 ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt
 create mode 100644 ext/mysqli/tests/protocol_query_row_fetch_data.phpt
 create mode 100644 ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt

diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc
new file mode 100644
index 00000000000..b02fabc584c
--- /dev/null
+++ b/ext/mysqli/tests/fake_server.inc
@@ -0,0 +1,856 @@
+<?php
+
+function my_mysqli_data_fields(): array
+{
+    return [
+        'intval' => [
+            'type' => '03',
+            'charset' => '3f00',
+            'length' => '0b000000',
+            'flags' => '0110',
+            'decimal' => '00',
+            'query_data_packet_length' => '080000',
+            'query_data_value' => '023134',
+            'stmt_data_packet_length' => '0b0000',
+            'stmt_data_value' => '0e000000'
+        ],
+        'fltval' => [
+            'type' => '04',
+            'charset' => '3f00',
+            'length' => '0c000000',
+            'flags' => '0110',
+            'decimal' => '1f',
+            'query_data_packet_length' => '090000',
+            'query_data_value' => '03322e33',
+            'stmt_data_packet_length' => '0b0000',
+            'stmt_data_value' => '33331340',
+        ],
+        'dblval' => [
+            'type' => '05',
+            'charset' => '3f00',
+            'length' => '16000000',
+            'flags' => '0110',
+            'decimal' => '1f',
+            'query_data_packet_length' => '090000',
+            'query_data_value' => '03312e32',
+            'stmt_data_packet_length' => '0f0000',
+            'stmt_data_value' => '333333333333f33f'
+        ],
+        'datval' => [
+            'type' => '0a',
+            'charset' => '3f00',
+            'length' => '0a000000',
+            'flags' => '8110',
+            'decimal' => '00',
+            'query_data_packet_length' => '100000',
+            'query_data_value' => '0a323031342d31322d3135',
+            'stmt_data_packet_length' => '0c0000',
+            'stmt_data_value' => '04de070c0f'
+        ],
+        'timval' => [
+            'type' => '0b',
+            'charset' => '3f00',
+            'length' => '0a000000',
+            'flags' => '8110',
+            'decimal' => '00',
+            'query_data_packet_length' => '0e0000',
+            'query_data_value' => '0831333a30303a3032',
+            'stmt_data_packet_length' => '100000',
+            'stmt_data_value' => '080000000000150801'
+        ],
+        'dtival' => [
+            'type' => '0c',
+            'charset' => '3f00',
+            'length' => '13000000',
+            'flags' => '8110',
+            'decimal' => '00',
+            'query_data_packet_length' => '190000',
+            'query_data_value' => '13323031342d31322d31362031333a30303a3031',
+            'stmt_data_packet_length' => '0f0000',
+            'stmt_data_value' => '07de070c100d0001'
+        ],
+        'bitval' => [
+            'type' => '10',
+            'charset' => '3f00',
+            'length' => '40000000',
+            'flags' => '2110',
+            'decimal' => '00',
+            'query_data_packet_length' => '0e0000',
+            'query_data_value' => '080808080808080808',
+            'stmt_data_packet_length' => '100000',
+            'stmt_data_value' => '080808080808080808'
+        ],
+        'strval' => [
+            'type' => 'fd',
+            'charset' => 'e000',
+            'length' => 'c8000000',
+            'flags' => '0110',
+            'decimal' => '00',
+            'query_data_packet_length' => '0a0000',
+            'query_data_value' => '0474657374',
+            'stmt_data_packet_length' => '0c0000',
+            'stmt_data_value' => '0474657374'
+        ],
+    ];
+}
+
+function my_mysqli_data_field(string $field): array
+{
+    $fields = my_mysqli_data_fields();
+    if (!isset($fields[$field])) {
+        throw new Exception("Unknown field $field");
+    }
+    return $fields[$field];
+}
+
+
+
+class my_mysqli_fake_packet_item
+{
+    public function __construct(public string|null $name, public string $value, public bool $is_hex = true)
+    {
+    }
+}
+
+class my_mysqli_fake_packet
+{
+    private array $data = array();
+
+    public function __get(string $name)
+    {
+        foreach ($this->data as $item) {
+            if ($item->name === $name) {
+                return $item->value;
+            }
+        }
+        return null;
+    }
+
+    public function __set(string $name, string|my_mysqli_fake_packet_item $value)
+    {
+        if ($value instanceof my_mysqli_fake_packet_item) {
+            if ($value->name === null) {
+                $value->name = $name;
+            }
+        } else {
+            $value = new my_mysqli_fake_packet_item($name, $value, true);
+        }
+
+        for ($i = 0; $i < count($this->data); $i++) {
+            if ($this->data[$i]->name === $name) {
+                $this->data[$i] = $value;
+                return;
+            }
+        }
+
+        $this->data[] = $value;
+    }
+
+    public function to_bytes(): string
+    {
+        $bytes = '';
+        foreach ($this->data as $item) {
+            $bytes .= $item->is_hex ? hex2bin($item->value) : $item->value;
+        }
+        return $bytes;
+    }
+}
+
+class my_mysqli_fake_packet_generator
+{
+    public static function create_packet_item(int|string $value, bool $is_hex = false, string $format = 'v'): my_mysqli_fake_packet_item
+    {
+        if (is_string($value)) {
+            $packed_value = $value;
+        } else {
+            $packed_value = pack($format, $value);
+        }
+        return new my_mysqli_fake_packet_item(null, $packed_value, $is_hex);
+    }
+
+    public function server_ok(): my_mysqli_fake_packet
+    {
+        $packet = new my_mysqli_fake_packet();
+        $packet->packet_length = "070000";
+        $packet->packet_number = "02";
+        $packet->header = "00"; // OK
+        $packet->affected_rows = "00";
+        $packet->last_insert_id = "00";
+        $packet->server_status = "0200";
+        $packet->warning_count = "0000";
+        return $packet;
+    }
+
+    public function server_greetings(): my_mysqli_fake_packet
+    {
+        $packet = new my_mysqli_fake_packet();
+        $packet->packet_length = "580000";
+        $packet->packet_number = "00";
+        $packet->proto_version = "0a";
+        $packet->version = self::create_packet_item('5.5.5-10.5.18-MariaDB' . chr(0));
+        $packet->thread_id = "03000000";
+        $packet->salt = "473e3f6047257c67";
+        $packet->filler = "00";
+        $packet->server_capabilities = self::create_packet_item(0b1111011111111110);
+        $packet->server_character_set = "08";
+        $packet->server_status = self::create_packet_item(0b000000000000010);
+        $packet->extended_server_capabilities = self::create_packet_item(0b1000000111111111);
+        $packet->auth_plugin = "15";
+        $packet->unused = "000000000000";
+        $packet->mariadb_extended_server_capabilities = self::create_packet_item(0b1111, false, 'V');
+        $packet->mariadb_extended_server_capabilities_salt = "6c6b55463f49335f686c643100";
+        $packet->mariadb_extended_server_capabilities_auth_plugin = self::create_packet_item('mysql_native_password');
+
+        return $packet;
+    }
+
+    public function server_tabular_query_response(): array
+    {
+        $qr1 = new my_mysqli_fake_packet();
+        $qr1->packet_length = "010000";
+        $qr1->packet_number = "01";
+        $qr1->field_count = "01";
+
+        $qr2 = new my_mysqli_fake_packet();
+        $qr2->packet_length = "190000";
+        $qr2->packet_number = "02";
+        $qr2->catalog_length_plus_name = "0164";
+        $qr2->db_length_plus_name = "0164";
+        $qr2->table_length_plus_name = "0164";
+        $qr2->original_t = "0164";
+        $qr2->name_length_plus_name = "0164";
+        $qr2->original_n = "0164";
+        $qr2->canary = "0c";
+        $qr2->charset = "3f00";
+        $qr2->length = "0b000000";
+        $qr2->type = "03";
+        $qr2->flags = "0350";
+        $qr2->decimals = "000000";
+
+        $qr3 = new my_mysqli_fake_packet();
+        $qr3->full = "05000003fe00002200";
+
+        $qr4 = new my_mysqli_fake_packet();
+        $qr4->full = "0400000401350174";
+
+        $qr5 = new my_mysqli_fake_packet();
+        $qr5->full = "05000005fe00002200";
+
+        return [$qr1, $qr2, $qr3, $qr4, $qr5];
+    }
+
+    public function server_upsert_query_response(): array
+    {
+        $qr1 = new my_mysqli_fake_packet();
+        $qr1->packet_length = "010000";
+        $qr1->packet_number = "01";
+        $qr1->field_count = "00"; // UPSERT
+        $qr1->affected_rows = "00";
+        $qr1->affected_rows = "00";
+        $qr1->last_insert_id = "00";
+        $qr1->server_status = "0000";
+        $qr1->warning_count = "0000";
+        $qr1->len = "01";
+        $qr1->filename = "65";
+        $qr1->packet_length = sprintf("%02x0000", strlen($qr1->to_bytes())-4);
+
+        return [$qr1];
+    }
+
+    public function server_stmt_prepare_response_start($num_field): my_mysqli_fake_packet
+    {
+        $pr1 = new my_mysqli_fake_packet();
+        $pr1->packet_length = "0c0000";
+        $pr1->packet_number = "01";
+        $pr1->response_code = '00'; // OK
+        $pr1->statement_id = '01000000';
+        $pr1->num_fields = $num_field;
+        $pr1->num_params = '0000';
+        $pr1->filler = '00';
+        $pr1->warnings = '0000';
+
+        return $pr1;
+    }
+
+    public function server_stmt_prepare_response_end($packer_number): my_mysqli_fake_packet
+    {
+        $pr3 = new my_mysqli_fake_packet();
+        $pr3->packet_length = "050000";
+        $pr3->packet_number = $packer_number;
+        $pr3->packet_type = 'fe'; // EOF
+        $pr3->warnings = '0000';
+        $pr3->server_status = '0200';
+
+        return $pr3;
+    }
+
+    public function server_stmt_prepare_items_response(): array
+    {
+        $pr1 = $this->server_stmt_prepare_response_start('0100');
+
+        $pr2 = new my_mysqli_fake_packet();
+        $pr2->packet_length = "300000";
+        $pr2->packet_number = "02";
+        $pr2->catalogue_len = '03';
+        $pr2->catalogue = '646566'; // def
+        $pr2->db_len = '08';
+        $pr2->db = '7068705f74657374'; // php_test
+        $pr2->table_len = '05';
+        $pr2->table = '6974656d73'; // items
+        $pr2->orig_table_len = '05';
+        $pr2->orig_table = '6974656d73'; // items
+        $pr2->name_len = '04';
+        $pr2->name = '6974656d';
+        $pr2->orig_name_len = '04';
+        $pr2->orig_name = '6974656d';
+        $pr2->something = '0c';
+        $pr2->charset = 'e000';
+        $pr2->length = 'c8000000';
+        $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING
+        $pr2->flags = '0110';
+        $pr2->decimal = '00';
+        $pr2->padding = '0000';
+
+        $pr3 = $this->server_stmt_prepare_response_end('03');
+
+        return [$pr1, $pr2, $pr3];
+    }
+
+    public function server_stmt_prepare_data_response_field($packet_number, $field_name): my_mysqli_fake_packet
+    {
+        if (strlen($field_name) != 6) {
+            throw new Exception("Invalid field length - only 6 is allowed");
+        }
+
+        $field = my_mysqli_data_field($field_name);
+
+        $pr = new my_mysqli_fake_packet();
+        $pr->packet_length = "320000";
+        $pr->packet_number = $packet_number;
+        $pr->catalogue_len = '03';
+        $pr->catalogue = bin2hex('def');
+        $pr->db_len = '08';
+        $pr->db = bin2hex('php_test');
+        $pr->table_len = '04';
+        $pr->table = bin2hex('data');
+        $pr->orig_table_len = '04';
+        $pr->orig_table = bin2hex('data');
+        $pr->name_len = '06';
+        $pr->name = bin2hex($field_name);
+        $pr->orig_name_len = '06';
+        $pr->orig_name =  bin2hex($field_name);
+        $pr->something = '0c';
+        $pr->charset = $field['charset'];
+        $pr->length = $field['length'];
+        $pr->field_type = $field['type'];
+        $pr->flags = $field['flags'];
+        $pr->decimal = $field['decimal'];
+        $pr->padding = '0000';
+
+        return $pr;
+    }
+
+    public function server_stmt_prepare_data_response(string $field_name): array
+    {
+        $pr1 = $this->server_stmt_prepare_response_start('0200');
+
+        $pr2 = $this->server_stmt_prepare_data_response_field('02', 'strval');
+        $pr3 = $this->server_stmt_prepare_data_response_field('03', $field_name);
+
+        $pr4 = $this->server_stmt_prepare_response_end('04');
+
+        return [$pr1, $pr2, $pr3, $pr4];
+    }
+
+    public function server_stmt_execute_items_response(): array
+    {
+        $pr1 = new my_mysqli_fake_packet();
+        $pr1->packet_length = "010000";
+        $pr1->packet_number = "01";
+        $pr1->num_fields = '01';
+
+        $pr2 = new my_mysqli_fake_packet();
+        $pr2->packet_length = "300000";
+        $pr2->packet_number = "02";
+        $pr2->catalogue_len = '03';
+        $pr2->catalogue = '646566'; // def
+        $pr2->db_len = '08';
+        $pr2->db = '7068705f74657374'; // php_test
+        $pr2->table_len = '05';
+        $pr2->table = '6974656d73'; // items
+        $pr2->orig_table_len = '05';
+        $pr2->orig_table = '6974656d73'; // items
+        $pr2->name_len = '04';
+        $pr2->name = '6974656d';
+        $pr2->orig_name_len = '04';
+        $pr2->orig_name = '6974656d';
+        $pr2->something = '0c';
+        $pr2->charset = 'e000';
+        $pr2->length = 'c8000000';
+        $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING
+        $pr2->flags = '0110';
+        $pr2->decimal = '00';
+        $pr2->padding = '0000';
+
+        $pr3 = new my_mysqli_fake_packet();
+        $pr3->packet_length = "050000";
+        $pr3->packet_number = "03";
+        $pr3->packet_type = 'fe'; // EOF
+        $pr3->warnings = '0000';
+        $pr3->server_status = '2200';
+
+        $pr4 = new my_mysqli_fake_packet();
+        $pr4->packet_length = "070000";
+        $pr4->packet_number = "04";
+        $pr4->packet_type = '00'; // OK
+        $pr4->affected_rows = '00';
+        $pr4->row_data_len = '04';
+        $pr4->row_data = '74657374'; // item
+
+        $pr5 = new my_mysqli_fake_packet();
+        $pr5->full = '05000005fe00002200';
+
+        return [$pr1, $pr2, $pr3, $pr4, $pr5];
+    }
+
+    private function server_execute_data_response_start(string $field_name): array
+    {
+        $pr1 = new my_mysqli_fake_packet();
+        $pr1->packet_length = "010000";
+        $pr1->packet_number = "01";
+        $pr1->num_fields = '02';
+
+        $pr2 = new my_mysqli_fake_packet();
+        $pr2->packet_length = "320000";
+        $pr2->packet_number = "02";
+        $pr2->catalogue_len = '03';
+        $pr2->catalogue = '646566'; // def
+        $pr2->db_len = '08';
+        $pr2->db = '7068705f74657374'; // php_test
+        $pr2->table_len = '04';
+        $pr2->table = bin2hex('data');
+        $pr2->orig_table_len = '04';
+        $pr2->orig_table = bin2hex('data');
+        $pr2->name_len = '06';
+        $pr2->name = bin2hex('strval');
+        $pr2->orig_name_len = '06';
+        $pr2->orig_name =  bin2hex('strval');
+        $pr2->something = '0c';
+        $pr2->charset = 'e000';
+        $pr2->length = 'c8000000';
+        $pr2->field_type = 'fd'; // FIELD_TYPE_VAR_STRING
+        $pr2->flags = '0110';
+        $pr2->decimal = '00';
+        $pr2->padding = '0000';
+
+        $field = my_mysqli_data_field($field_name);
+
+        $pr3 = new my_mysqli_fake_packet();
+        $pr3->packet_length = "320000";
+        $pr3->packet_number = "03";
+        $pr3->catalogue_len = '03';
+        $pr3->catalogue = '646566'; // def
+        $pr3->db_len = '08';
+        $pr3->db = '7068705f74657374'; // php_test
+        $pr3->table_len = '04';
+        $pr3->table = bin2hex('data');
+        $pr3->orig_table_len = '04';
+        $pr3->orig_table = bin2hex('data');
+        $pr3->name_len = '06';
+        $pr3->name = bin2hex($field_name);
+        $pr3->orig_name_len = '06';
+        $pr3->orig_name =  bin2hex($field_name);
+        $pr3->something = '0c';
+        $pr3->charset = $field['charset'];
+        $pr3->length = $field['length'];
+        $pr3->field_type = $field['type'];
+        $pr3->flags = $field['flags'];
+        $pr3->decimal = $field['decimal'];
+        $pr3->padding = '0000';
+
+        $pr4 = new my_mysqli_fake_packet();
+        $pr4->packet_length = "050000";
+        $pr4->packet_number = "04";
+        $pr4->packet_type = 'fe'; // EOF
+        $pr4->warnings = '0000';
+        $pr4->server_status = '2200';
+
+        return [$field, $pr1, $pr2, $pr3, $pr4];
+    }
+
+    private function server_execute_data_response_end(): my_mysqli_fake_packet
+    {
+        $pr6 = new my_mysqli_fake_packet();
+        $pr6->packet_length = '050000';
+        $pr6->packet_number = "06";
+        $pr6->packet_type = 'fe'; // EOF
+        $pr6->warnings = '0000';
+        $pr6->server_status = '2200';
+
+        return $pr6;
+    }
+
+    public function server_stmt_execute_data_response(string $field_name): array
+    {
+        [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name);
+
+        $pr5 = new my_mysqli_fake_packet();
+        $pr5->packet_length = $field['stmt_data_packet_length'];
+        $pr5->packet_number = "05";
+        $pr5->packet_type = '00'; // OK
+        $pr5->affected_rows = '00';
+        $pr5->row_field1_len = '04';
+        $pr5->row_field1_data = '74657374'; // test
+        $pr5->row_field2 = $field['stmt_data_value'];
+
+        return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()];
+    }
+
+    public function server_query_execute_data_response(string $field_name): array
+    {
+        [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name);
+
+        $pr5 = new my_mysqli_fake_packet();
+        $pr5->packet_length = $field['query_data_packet_length'];
+        $pr5->packet_number = "05";
+        $pr5->row_field1_len = '04';
+        $pr5->row_field1_data = '74657374'; // test
+        $pr5->row_field2 = $field['query_data_value'];
+
+        return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()];
+    }
+}
+
+class my_mysqli_fake_server_conn
+{
+    private $conn;
+    public $packet_generator;
+
+    public function __construct($socket)
+    {
+        $this->packet_generator = new my_mysqli_fake_packet_generator();
+        $this->conn = stream_socket_accept($socket);
+        if ($this->conn) {
+            fprintf(STDERR, "[*] Connection established\n");
+        } else {
+            fprintf(STDERR, "[*] Failed to establish connection\n");
+        }
+    }
+
+    public function packets_to_bytes(array $packets): string
+    {
+        return implode('', array_map(fn($s) => $s->to_bytes(), $packets));
+    }
+
+    public function send($payload, $message = null): void
+    {
+        if ($message) {
+            fprintf(STDERR, "[*] Sending - %s: %s\n", $message, bin2hex($payload));
+        }
+        fwrite($this->conn, $payload);
+    }
+
+    public function read($bytes_len = 1024)
+    {
+        // wait 10ms to fill the buffer
+        usleep(10000);
+        $data = fread($this->conn, $bytes_len);
+        if ($data) {
+            fprintf(STDERR, "[*] Received: %s\n", bin2hex($data));
+        }
+    }
+
+    public function close()
+    {
+        fclose($this->conn);
+    }
+
+    public function send_server_greetings()
+    {
+        $this->send($this->packet_generator->server_greetings()->to_bytes(), "Server Greeting");
+    }
+
+    public function send_server_ok()
+    {
+        $this->send($this->packet_generator->server_ok()->to_bytes(), "Server OK");
+    }
+
+    public function send_server_tabular_query_response(): void
+    {
+        $packets = $this->packet_generator->server_tabular_query_response();
+        $this->send($this->packets_to_bytes($packets), "Tabular response");
+    }
+
+    public function send_server_stmt_prepare_items_response(): void
+    {
+        $packets = $this->packet_generator->server_stmt_prepare_items_response();
+        $this->send($this->packets_to_bytes($packets), "Stmt prepare items");
+    }
+
+
+    public function send_server_stmt_prepare_data_response(string $field_name): void
+    {
+        $packets = $this->packet_generator->server_stmt_prepare_data_response($field_name);
+        $this->send($this->packets_to_bytes($packets), "Stmt prepare data $field_name");
+    }
+
+    public function send_server_stmt_execute_items_response(): void
+    {
+        $packets = $this->packet_generator->server_stmt_execute_items_response();
+        $this->send($this->packets_to_bytes($packets), "Stmt execute items");
+    }
+
+    public function send_server_stmt_execute_data_response(string $field_name): void
+    {
+        $packets = $this->packet_generator->server_stmt_execute_data_response($field_name);
+        $this->send($this->packets_to_bytes($packets), "Stmt execute data $field_name");
+    }
+
+    public function send_server_query_execute_data_response(string $field_name): void
+    {
+        $packets = $this->packet_generator->server_query_execute_data_response($field_name);
+        $this->send($this->packets_to_bytes($packets), "Query execute data $field_name");
+    }
+}
+
+class my_mysqli_fake_server_process
+{
+    public function __construct(private $process, private array $pipes) {}
+
+    public function terminate(bool $wait = false)
+    {
+        if ($wait) {
+            $this->wait();
+        }
+        proc_terminate($this->process);
+    }
+
+    public function wait()
+    {
+        echo fgets($this->pipes[1]);
+    }
+}
+
+function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_conn $conn): void
+{
+    $rh = $conn->packet_generator->server_tabular_query_response();
+
+    // Length of the packet is modified to include the next added data
+    $rh[1]->packet_length = "1e0000";
+
+    // We add a length field encoded on 4 bytes which evaluates to 65536. If the process crashes because
+    // the heap has been overread, lower this value.
+    $rh[1]->extra_def_size = "fd000001";  # 65536
+
+    // Filler
+    $rh[1]->extra_def_data = "aa";
+
+    $trrh = $conn->packets_to_bytes($rh);
+
+    $conn->send_server_greetings();
+    $conn->read();
+    $conn->send_server_ok();
+    $conn->read();
+    $conn->send($trrh, "Malicious Tabular Response [Extract heap through buffer over-read]");
+    $conn->read(65536);
+}
+
+function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server_conn $conn): void
+{
+    $rh = $conn->packet_generator->server_upsert_query_response();
+
+    // Set extra length to overread
+    $rh[0]->len = "fa";
+
+    $trrh = $conn->packets_to_bytes($rh);
+
+    $conn->send_server_greetings();
+    $conn->read();
+    $conn->send_server_ok();
+    $conn->read();
+    $conn->send($trrh, "Malicious Tabular Response [Extract heap through buffer over-read]");
+    $conn->read(65536);
+}
+
+function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_conn $conn): void
+{
+    $p = $conn->packet_generator->server_ok();
+    $p->packet_length = "090000";
+    $p->message_len = "fcff";
+
+    $conn->send_server_greetings();
+    $conn->read();
+    $conn->send($p->to_bytes(), "Malicious OK Auth Response [Extract heap through buffer over-read]");
+    $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();
+
+    // Set extra length to overread
+    $rh[3]->row_data_len = "fa";
+
+    $conn->send_server_greetings();
+    $conn->read();
+    $conn->send_server_ok();
+    $conn->read();
+    $conn->send_server_stmt_prepare_items_response();
+    $conn->read();
+    $conn->send($conn->packets_to_bytes($rh), "Malicious Stmt Response for items [Extract heap through buffer over-read]");
+    $conn->read(65536);
+}
+
+function my_mysqli_test_stmt_response_row_over_read_two_fields(
+    my_mysqli_fake_server_conn $conn,
+    string $field_name,
+    string $row_field1_len = '06'
+): void {
+    $rh = $conn->packet_generator->server_stmt_execute_data_response($field_name);
+
+    // Set extra length to overread by two bytes
+    $rh[4]->row_field1_len = $row_field1_len;
+
+    $conn->send_server_greetings();
+    $conn->read();
+    $conn->send_server_ok();
+    $conn->read();
+    $conn->send_server_stmt_prepare_data_response($field_name);
+    $conn->read();
+    $conn->send(
+        $conn->packets_to_bytes($rh),
+        "Malicious Stmt Response for data $field_name [Extract heap through buffer over-read]"
+    );
+    $conn->read(65536);
+}
+
+function my_mysqli_test_stmt_response_row_over_read_int(my_mysqli_fake_server_conn $conn): void
+{
+    my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'intval');
+}
+
+function my_mysqli_test_stmt_response_row_over_read_float(my_mysqli_fake_server_conn $conn): void
+{
+    my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'fltval');
+}
+
+function my_mysqli_test_stmt_response_row_over_read_double(my_mysqli_fake_server_conn $conn): void
+{
+    my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dblval');
+}
+
+function my_mysqli_test_stmt_response_row_over_read_date(my_mysqli_fake_server_conn $conn): void
+{
+    my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'datval');
+}
+
+function my_mysqli_test_stmt_response_row_over_read_time(my_mysqli_fake_server_conn $conn): void
+{
+    my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'timval', '0c');
+}
+
+function my_mysqli_test_stmt_response_row_over_read_datetime(my_mysqli_fake_server_conn $conn): void
+{
+    my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'dtival');
+}
+
+function my_mysqli_test_stmt_response_row_no_space(my_mysqli_fake_server_conn $conn): void
+{
+    my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'strval', '09');
+}
+
+function my_mysqli_test_stmt_response_row_over_read_bit(my_mysqli_fake_server_conn $conn): void
+{
+    my_mysqli_test_stmt_response_row_over_read_two_fields($conn, 'bitval');
+}
+
+function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void
+{
+    $conn->send_server_greetings();
+    $conn->read();
+    $conn->send_server_ok();
+    $conn->read();
+    $field_names = array_keys(my_mysqli_data_fields());
+    foreach ($field_names as $field_name) {
+        $conn->send_server_stmt_prepare_data_response($field_name);
+        $conn->read(65536);
+        $conn->send_server_stmt_execute_data_response($field_name);
+        $conn->read(65536);
+    }
+}
+
+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');
+
+    // Set extra length to overread by two bytes
+    $rh[4]->row_field2 = 'fefefefefe';
+
+    $conn->send_server_greetings();
+    $conn->read();
+    $conn->send_server_ok();
+    $conn->read();
+    $conn->send($conn->packets_to_bytes($rh), "Malicious Query Response for data strval field [length overflow]");
+    $conn->read(65536);
+}
+
+function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void
+{
+    $conn->send_server_greetings();
+    $conn->read();
+    $conn->send_server_ok();
+    $conn->read();
+    $field_names = array_keys(my_mysqli_data_fields());
+    foreach ($field_names as $field_name) {
+        $conn->send_server_query_execute_data_response($field_name);
+        $conn->read();
+    }
+}
+
+function run_fake_server(string $test_function, $port = 33305): void
+{
+    $address = '127.0.0.1';
+
+    $socket = @stream_socket_server("tcp://$address:$port", $errno, $errstr);
+    if (!$socket) {
+        die("Failed to create socket: $errstr ($errno)\n");
+    }
+    echo "[*] Server started\n";
+
+    try {
+        $conn = new my_mysqli_fake_server_conn($socket);
+        $test_function_name = 'my_mysqli_test_' . $test_function;
+        call_user_func($test_function_name, $conn);
+        $conn->close();
+    } catch (Exception $e) {
+        fprintf(STDERR, "[!] Exception: " . $e->getMessage() . "\n");
+    }
+
+    fclose($socket);
+
+    echo "[*] Server finished\n";
+}
+
+
+function run_fake_server_in_background($test_function, $port = 33305): my_mysqli_fake_server_process
+{
+    $command = [PHP_BINARY, '-n', __FILE__, 'mysqli_fake_server', $test_function, $port];
+
+    $descriptorspec = array(
+        0 => array("pipe", "r"),
+        1 => array("pipe", "w"),
+        2 => STDERR,
+    );
+
+    $process = proc_open($command, $descriptorspec, $pipes);
+
+    if (is_resource($process)) {
+        return new my_mysqli_fake_server_process($process, $pipes);
+    } else {
+        throw new Exception("Failed to start server process");
+    }
+}
+
+if (isset($argv) && $argc > 2 && $argv[1] == 'mysqli_fake_server') {
+    run_fake_server($argv[2], $argv[3] ?? '33305');
+}
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
new file mode 100644
index 00000000000..db54a6c0177
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
@@ -0,0 +1,38 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - auth message buffer over-read)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 50001;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('auth_response_message_over_read', $port);
+$process->wait();
+
+try {
+    $conn = new mysqli( $servername, $username, $password, "", $port );
+    $info = mysqli_info($conn);
+    var_dump($info);
+} catch (Exception $e) {
+    echo $e->getMessage() . PHP_EOL;
+}
+
+$process->terminate();
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Malicious OK Auth Response [Extract heap through buffer over-read]: 0900000200000002000000fcff
+
+Warning: mysqli::__construct(): OK packet message length is past the packet size in %s on line %d
+Unknown error while trying to connect via tcp://127.0.0.1:50001
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt
new file mode 100644
index 00000000000..77f2232eca6
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-def.phpt
@@ -0,0 +1,47 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - tabular default)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('tabular_response_def_over_read', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Running query on the fake server...\n";
+
+$result = $conn->query("SELECT * from users");
+
+if ($result) {
+    $all_fields = $result->fetch_fields();
+    var_dump($result->fetch_all(MYSQLI_ASSOC));
+    var_dump(get_object_vars($all_fields[0])["def"]);
+}
+
+$conn->close();
+
+$process->terminate();
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Running query on the fake server...
+[*] Received: 140000000353454c454354202a2066726f6d207573657273
+[*] Sending - Malicious Tabular Response [Extract heap through buffer over-read]: 01000001011e0000020164016401640164016401640c3f000b000000030350000000fd000001aa05000003fe00002200040000040135017405000005fe00002200
+
+Warning: mysqli::query(): Protocol error. Server sent default for unsupported field list (mysqlnd_wireprotocol.c:%d) in %s on line %d
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt
new file mode 100644
index 00000000000..0b4db8ccece
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-filename.phpt
@@ -0,0 +1,43 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - upsert filename buffer over-read)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('upsert_response_filename_over_read', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+echo "[*] Running query on the fake server...\n";
+
+$result = $conn->query("SELECT * from users");
+$info = mysqli_info($conn);
+
+var_dump($info);
+
+$process->terminate();
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Running query on the fake server...
+[*] Received: 140000000353454c454354202a2066726f6d207573657273
+[*] Sending - Malicious Tabular Response [Extract heap through buffer over-read]: 0900000100000000000000fa65
+
+Warning: mysqli::query(): RSET_HEADER packet additional data length is past 249 bytes the packet size in %s on line %d
+
+Warning: mysqli::query(): Error reading result set's header in %s on line %d
+NULL
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt
new file mode 100644
index 00000000000..f141a79bdaa
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-query-len-overflow.phpt
@@ -0,0 +1,48 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row no space for the field)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('query_response_row_length_overflow', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Query the fake server...\n";
+$sql = "SELECT strval, strval FROM data";
+
+$result = $conn->query($sql);
+
+if ($result->num_rows > 0) {
+    while ($row = $result->fetch_assoc()) {
+        var_dump($row['strval']);
+    }
+}
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Query the fake server...
+[*] Received: 200000000353454c4543542073747276616c2c2073747276616c2046524f4d2064617461
+[*] Sending - Malicious Query Response for data strval field [length overflow]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000a0000050474657374fefefefefe05000006fe00002200
+
+Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after end of packet in %s on line %d
+[*] Received: 0100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt
new file mode 100644
index 00000000000..e43518217eb
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-bit.phpt
@@ -0,0 +1,53 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row bit buffer over-read)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('stmt_response_row_over_read_bit', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Preparing statement on the fake server...\n";
+$stmt = $conn->prepare("SELECT bitval, timval FROM data");
+
+$stmt->execute();
+$result = $stmt->get_result();
+
+// Fetch and display the results
+if ($result->num_rows > 0) {
+    while ($row = $result->fetch_assoc()) {
+        var_dump($row["bitval"]);
+    }
+}
+$stmt->close();
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Preparing statement on the fake server...
+[*] Received: 200000001653454c4543542062697476616c2c2074696d76616c2046524f4d2064617461
+[*] Sending - Stmt prepare data bitval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Malicious Stmt Response for data bitval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00002200100000050000067465737408080808080808080805000006fe00002200
+
+Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
+[*] Received: 0500000019010000000100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt
new file mode 100644
index 00000000000..76158e940d0
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-date.phpt
@@ -0,0 +1,53 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row date buffer over-read)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('stmt_response_row_over_read_date', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Preparing statement on the fake server...\n";
+$stmt = $conn->prepare("SELECT strval, datval FROM data");
+
+$stmt->execute();
+$result = $stmt->get_result();
+
+// Fetch and display the results
+if ($result->num_rows > 0) {
+    while ($row = $result->fetch_assoc()) {
+        var_dump($row["datval"]);
+    }
+}
+$stmt->close();
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Preparing statement on the fake server...
+[*] Received: 200000001653454c4543542073747276616c2c2064617476616c2046524f4d2064617461
+[*] Sending - Stmt prepare data datval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Malicious Stmt Response for data datval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022000c0000050000067465737404de070c0f05000006fe00002200
+
+Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
+[*] Received: 0500000019010000000100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt
new file mode 100644
index 00000000000..f53d5b83bd4
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-datetime.phpt
@@ -0,0 +1,53 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row datetime buffer over-read)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('stmt_response_row_over_read_datetime', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Preparing statement on the fake server...\n";
+$stmt = $conn->prepare("SELECT strval, dtival FROM data");
+
+$stmt->execute();
+$result = $stmt->get_result();
+
+// Fetch and display the results
+if ($result->num_rows > 0) {
+    while ($row = $result->fetch_assoc()) {
+        var_dump($row["dtival"]);
+    }
+}
+$stmt->close();
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Preparing statement on the fake server...
+[*] Received: 200000001653454c4543542073747276616c2c2064746976616c2046524f4d2064617461
+[*] Sending - Stmt prepare data dtival: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Malicious Stmt Response for data dtival [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe000022000f0000050000067465737407de070c100d000105000006fe00002200
+
+Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
+[*] Received: 0500000019010000000100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt
new file mode 100644
index 00000000000..03c9b045d73
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-double.phpt
@@ -0,0 +1,53 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row double buffer over-read)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('stmt_response_row_over_read_double', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Preparing statement on the fake server...\n";
+$stmt = $conn->prepare("SELECT strval, dblval FROM data");
+
+$stmt->execute();
+$result = $stmt->get_result();
+
+// Fetch and display the results
+if ($result->num_rows > 0) {
+    while ($row = $result->fetch_assoc()) {
+        var_dump($row["dblval"]);
+    }
+}
+$stmt->close();
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Preparing statement on the fake server...
+[*] Received: 200000001653454c4543542073747276616c2c2064626c76616c2046524f4d2064617461
+[*] Sending - Stmt prepare data dblval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Malicious Stmt Response for data dblval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe000022000f00000500000674657374333333333333f33f05000006fe00002200
+
+Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
+[*] Received: 0500000019010000000100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt
new file mode 100644
index 00000000000..b1ec9aa51ec
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-float.phpt
@@ -0,0 +1,53 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row int buffer over-read)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('stmt_response_row_over_read_float', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Preparing statement on the fake server...\n";
+$stmt = $conn->prepare("SELECT strval, fltval FROM data");
+
+$stmt->execute();
+$result = $stmt->get_result();
+
+// Fetch and display the results
+if ($result->num_rows > 0) {
+    while ($row = $result->fetch_assoc()) {
+        var_dump($row["fltval"]);
+    }
+}
+$stmt->close();
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Preparing statement on the fake server...
+[*] Received: 200000001653454c4543542073747276616c2c20666c7476616c2046524f4d2064617461
+[*] Sending - Stmt prepare data fltval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Malicious Stmt Response for data fltval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe000022000b000005000006746573743333134005000006fe00002200
+
+Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
+[*] Received: 0500000019010000000100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt
new file mode 100644
index 00000000000..426d9ea7b3f
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-int.phpt
@@ -0,0 +1,53 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row int buffer over-read)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('stmt_response_row_over_read_int', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Preparing statement on the fake server...\n";
+$stmt = $conn->prepare("SELECT strval, intval FROM data");
+
+$stmt->execute();
+$result = $stmt->get_result();
+
+// Fetch and display the results
+if ($result->num_rows > 0) {
+    while ($row = $result->fetch_assoc()) {
+        var_dump($row["intval"]);
+    }
+}
+$stmt->close();
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Preparing statement on the fake server...
+[*] Received: 200000001653454c4543542073747276616c2c20696e7476616c2046524f4d2064617461
+[*] Sending - Stmt prepare data intval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Malicious Stmt Response for data intval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe000022000b000005000006746573740e00000005000006fe00002200
+
+Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
+[*] Received: 0500000019010000000100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt
new file mode 100644
index 00000000000..6db6952d42a
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-no-space.phpt
@@ -0,0 +1,53 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row no space for the field)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('stmt_response_row_no_space', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Preparing statement on the fake server...\n";
+$stmt = $conn->prepare("SELECT strval, strval FROM data");
+
+$stmt->execute();
+$result = $stmt->get_result();
+
+// Fetch and display the results
+if ($result->num_rows > 0) {
+    while ($row = $result->fetch_assoc()) {
+        var_dump($row["strval"]);
+    }
+}
+$stmt->close();
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Preparing statement on the fake server...
+[*] Received: 200000001653454c4543542073747276616c2c2073747276616c2046524f4d2064617461
+[*] Sending - Stmt prepare data strval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Malicious Stmt Response for data strval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000c00000500000974657374047465737405000006fe00002200
+
+Warning: mysqli_result::fetch_assoc(): Malformed server packet. No packet space left for the field in %s on line %d
+[*] Received: 0500000019010000000100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt
new file mode 100644
index 00000000000..55bad4cc544
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-string.phpt
@@ -0,0 +1,53 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row string buffer over-read)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('stmt_response_row_over_read_string', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Preparing statement on the fake server...\n";
+$stmt = $conn->prepare("SELECT item FROM items");
+
+$stmt->execute();
+$result = $stmt->get_result();
+
+// Fetch and display the results
+if ($result->num_rows > 0) {
+    while ($row = $result->fetch_assoc()) {
+        var_dump($row["item"]);
+    }
+}
+$stmt->close();
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Preparing statement on the fake server...
+[*] Received: 170000001653454c454354206974656d2046524f4d206974656d73
+[*] Sending - Stmt prepare items: 0c0000010001000000010000000000003000000203646566087068705f74657374056974656d73056974656d73046974656d046974656d0ce000c8000000fd011000000005000003fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Malicious Stmt Response for items [Extract heap through buffer over-read]: 01000001013000000203646566087068705f74657374056974656d73056974656d73046974656d046974656d0ce000c8000000fd011000000005000003fe00002200070000040000fa7465737405000005fe00002200
+
+Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
+[*] Received: 0500000019010000000100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt
new file mode 100644
index 00000000000..06918c375f3
--- /dev/null
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-stmt-row-time.phpt
@@ -0,0 +1,53 @@
+--TEST--
+GHSA-h35g-vwh6-m678 (mysqlnd leaks partial content of the heap - stmt row time buffer over-read)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('stmt_response_row_over_read_time', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+echo "[*] Preparing statement on the fake server...\n";
+$stmt = $conn->prepare("SELECT strval, timval FROM data");
+
+$stmt->execute();
+$result = $stmt->get_result();
+
+// Fetch and display the results
+if ($result->num_rows > 0) {
+    while ($row = $result->fetch_assoc()) {
+        var_dump($row["timval"]);
+    }
+}
+$stmt->close();
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECTF--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Preparing statement on the fake server...
+[*] Received: 200000001653454c4543542073747276616c2c2074696d76616c2046524f4d2064617461
+[*] Sending - Stmt prepare data timval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Malicious Stmt Response for data timval [Extract heap through buffer over-read]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe000022001000000500000c7465737408000000000015080105000006fe00002200
+
+Warning: mysqli_result::fetch_assoc(): Malformed server packet. Field length pointing after the end of packet in %s on line %d
+[*] Received: 0500000019010000000100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/protocol_query_row_fetch_data.phpt b/ext/mysqli/tests/protocol_query_row_fetch_data.phpt
new file mode 100644
index 00000000000..524fe5e587c
--- /dev/null
+++ b/ext/mysqli/tests/protocol_query_row_fetch_data.phpt
@@ -0,0 +1,74 @@
+--TEST--
+MySQL protocol - statement row data fetch)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('query_response_row_read_two_fields', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+function my_query($conn, $field)
+{
+    $sql = "SELECT strval, $field FROM data";
+
+    $result = $conn->query($sql);
+
+    if ($result->num_rows > 0) {
+        while ($row = $result->fetch_assoc()) {
+            var_dump($row[$field]);
+        }
+    }
+}
+
+foreach (my_mysqli_data_fields() as $field_name => $field) {
+    my_query($conn, $field_name);
+}
+
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECT--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Received: 200000000353454c4543542073747276616c2c20696e7476616c2046524f4d2064617461
+[*] Sending - Query execute data intval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe0000220008000005047465737402313405000006fe00002200
+string(2) "14"
+[*] Received: 200000000353454c4543542073747276616c2c20666c7476616c2046524f4d2064617461
+[*] Sending - Query execute data fltval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe0000220009000005047465737403322e3305000006fe00002200
+string(3) "2.3"
+[*] Received: 200000000353454c4543542073747276616c2c2064626c76616c2046524f4d2064617461
+[*] Sending - Query execute data dblval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe0000220009000005047465737403312e3205000006fe00002200
+string(3) "1.2"
+[*] Received: 200000000353454c4543542073747276616c2c2064617476616c2046524f4d2064617461
+[*] Sending - Query execute data datval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022001000000504746573740a323031342d31322d313505000006fe00002200
+string(10) "2014-12-15"
+[*] Received: 200000000353454c4543542073747276616c2c2074696d76616c2046524f4d2064617461
+[*] Sending - Query execute data timval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe000022000e00000504746573740831333a30303a303205000006fe00002200
+string(8) "13:00:02"
+[*] Received: 200000000353454c4543542073747276616c2c2064746976616c2046524f4d2064617461
+[*] Sending - Query execute data dtival: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe0000220019000005047465737413323031342d31322d31362031333a30303a303105000006fe00002200
+string(19) "2014-12-16 13:00:01"
+[*] Received: 200000000353454c4543542073747276616c2c2062697476616c2046524f4d2064617461
+[*] Sending - Query execute data bitval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe000022000e000005047465737408080808080808080805000006fe00002200
+string(18) "578721382704613384"
+[*] Received: 200000000353454c4543542073747276616c2c2073747276616c2046524f4d2064617461
+[*] Sending - Query execute data strval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000a0000050474657374047465737405000006fe00002200
+string(4) "test"
+[*] Received: 0100000001
+[*] Server finished
+done!
diff --git a/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt
new file mode 100644
index 00000000000..d461ec24b8c
--- /dev/null
+++ b/ext/mysqli/tests/protocol_stmt_row_fetch_data.phpt
@@ -0,0 +1,91 @@
+--TEST--
+MySQL protocol - statement row data fetch)
+--EXTENSIONS--
+mysqli
+--FILE--
+<?php
+require_once 'fake_server.inc';
+
+$port = 33305;
+$servername = "127.0.0.1";
+$username = "root";
+$password = "";
+
+$process = run_fake_server_in_background('stmt_response_row_read_two_fields', $port);
+$process->wait();
+
+$conn = new mysqli($servername, $username, $password, "", $port);
+
+function my_query($conn, $field)
+{
+    $stmt = $conn->prepare("SELECT strval, $field FROM data");
+
+    $stmt->execute();
+    $result = $stmt->get_result();
+
+    if ($result->num_rows > 0) {
+        while ($row = $result->fetch_assoc()) {
+            var_dump($row[$field]);
+        }
+    }
+}
+
+foreach (my_mysqli_data_fields() as $field_name => $field) {
+    my_query($conn, $field_name);
+}
+
+$conn->close();
+
+$process->terminate(true);
+
+print "done!";
+?>
+--EXPECT--
+[*] Server started
+[*] Connection established
+[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
+[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
+[*] Sending - Server OK: 0700000200000002000000
+[*] Received: 200000001653454c4543542073747276616c2c20696e7476616c2046524f4d2064617461
+[*] Sending - Stmt prepare data intval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Stmt execute data intval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe000022000b000005000004746573740e00000005000006fe00002200
+int(14)
+[*] Received: 050000001901000000200000001653454c4543542073747276616c2c20666c7476616c2046524f4d2064617461
+[*] Sending - Stmt prepare data fltval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Stmt execute data fltval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106666c7476616c06666c7476616c0c3f000c0000000401101f000005000004fe000022000b000005000004746573743333134005000006fe00002200
+float(2.3)
+[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064626c76616c2046524f4d2064617461
+[*] Sending - Stmt prepare data dblval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Stmt execute data dblval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664626c76616c0664626c76616c0c3f00160000000501101f000005000004fe000022000f00000500000474657374333333333333f33f05000006fe00002200
+float(1.2)
+[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064617476616c2046524f4d2064617461
+[*] Sending - Stmt prepare data datval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Stmt execute data datval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664617476616c0664617476616c0c3f000a0000000a811000000005000004fe000022000c0000050000047465737404de070c0f05000006fe00002200
+string(10) "2014-12-15"
+[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2074696d76616c2046524f4d2064617461
+[*] Sending - Stmt prepare data timval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Stmt execute data timval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610674696d76616c0674696d76616c0c3f000a0000000b811000000005000004fe00002200100000050000047465737408000000000015080105000006fe00002200
+string(8) "21:08:01"
+[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2064746976616c2046524f4d2064617461
+[*] Sending - Stmt prepare data dtival: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Stmt execute data dtival: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610664746976616c0664746976616c0c3f00130000000c811000000005000004fe000022000f0000050000047465737407de070c100d000105000006fe00002200
+string(19) "2014-12-16 13:00:01"
+[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2062697476616c2046524f4d2064617461
+[*] Sending - Stmt prepare data bitval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Stmt execute data bitval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610662697476616c0662697476616c0c3f004000000010211000000005000004fe00002200100000050000047465737408080808080808080805000006fe00002200
+int(578721382704613384)
+[*] Received: 050000001901000000200000001653454c4543542073747276616c2c2073747276616c2046524f4d2064617461
+[*] Sending - Stmt prepare data strval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe00000200
+[*] Received: 0a00000017010000000001000000
+[*] Sending - Stmt execute data strval: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd011000000005000004fe000022000c00000500000474657374047465737405000006fe00002200
+string(4) "test"
+[*] Received: 0500000019010000000100000001
+[*] Server finished
+done!
diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c
index e942f10faca..736d777b43a 100644
--- a/ext/mysqlnd/mysqlnd_ps_codec.c
+++ b/ext/mysqlnd/mysqlnd_ps_codec.c
@@ -50,6 +50,37 @@ struct st_mysqlnd_perm_bind mysqlnd_ps_fetch_functions[MYSQL_TYPE_LAST + 1];
 #define MYSQLND_PS_SKIP_RESULT_W_LEN	-1
 #define MYSQLND_PS_SKIP_RESULT_STR		-2
 
+static inline void ps_fetch_over_read_error(const zend_uchar ** row)
+{
+	php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing after the end of packet");
+	*row = NULL;
+}
+
+static inline bool ps_fetch_is_packet_over_read_with_variable_length(const unsigned int pack_len,
+		const zend_uchar ** row, const zend_uchar *p, unsigned int length)
+{
+	if (pack_len == 0) {
+		return false;
+	}
+	size_t length_len = *row - p;
+	if (length_len > pack_len || length > pack_len - length_len) {
+		ps_fetch_over_read_error(row);
+		return true;
+	}
+	return false;
+}
+
+static inline bool ps_fetch_is_packet_over_read_with_static_length(const unsigned int pack_len,
+		const zend_uchar ** row, unsigned int length)
+{
+	if (pack_len > 0 && length > pack_len) {
+		ps_fetch_over_read_error(row);
+		return true;
+	}
+	return false;
+}
+
+
 /* {{{ ps_fetch_from_1_to_8_bytes */
 void
 ps_fetch_from_1_to_8_bytes(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len,
@@ -58,6 +89,11 @@ ps_fetch_from_1_to_8_bytes(zval * zv, const MYSQLND_FIELD * const field, const u
 	char tmp[22];
 	size_t tmp_len = 0;
 	zend_bool is_bit = field->type == MYSQL_TYPE_BIT;
+
+	if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, byte_count))) {
+		return;
+	}
+
 	DBG_ENTER("ps_fetch_from_1_to_8_bytes");
 	DBG_INF_FMT("zv=%p byte_count=%u", zv, byte_count);
 	if (field->flags & UNSIGNED_FLAG) {
@@ -176,6 +212,11 @@ ps_fetch_float(zval * zv, const MYSQLND_FIELD * const field, const unsigned int
 	float fval;
 	double dval;
 	DBG_ENTER("ps_fetch_float");
+
+	if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, 4))) {
+		return;
+	}
+
 	float4get(fval, *row);
 	(*row)+= 4;
 	DBG_INF_FMT("value=%f", fval);
@@ -198,6 +239,11 @@ ps_fetch_double(zval * zv, const MYSQLND_FIELD * const field, const unsigned int
 {
 	double value;
 	DBG_ENTER("ps_fetch_double");
+
+	if (UNEXPECTED(ps_fetch_is_packet_over_read_with_static_length(pack_len, row, 8))) {
+		return;
+	}
+
 	float8get(value, *row);
 	ZVAL_DOUBLE(zv, value);
 	(*row)+= 8;
@@ -214,9 +260,14 @@ ps_fetch_time(zval * zv, const MYSQLND_FIELD * const field, const unsigned int p
 	struct st_mysqlnd_time t;
 	zend_ulong length; /* First byte encodes the length*/
 	char * value;
+	const zend_uchar *p = *row;
 	DBG_ENTER("ps_fetch_time");
 
 	if ((length = php_mysqlnd_net_field_length(row))) {
+		if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
+			return;
+		}
+
 		const zend_uchar * to = *row;
 
 		t.time_type = MYSQLND_TIMESTAMP_TIME;
@@ -271,9 +322,14 @@ ps_fetch_date(zval * zv, const MYSQLND_FIELD * const field, const unsigned int p
 	struct st_mysqlnd_time t = {0};
 	zend_ulong length; /* First byte encodes the length*/
 	char * value;
+	const zend_uchar *p = *row;
 	DBG_ENTER("ps_fetch_date");
 
 	if ((length = php_mysqlnd_net_field_length(row))) {
+		if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
+			return;
+		}
+
 		const zend_uchar * to = *row;
 
 		t.time_type = MYSQLND_TIMESTAMP_DATE;
@@ -308,9 +364,14 @@ ps_fetch_datetime(zval * zv, const MYSQLND_FIELD * const field, const unsigned i
 	struct st_mysqlnd_time t;
 	zend_ulong length; /* First byte encodes the length*/
 	char * value;
+	const zend_uchar *p = *row;
 	DBG_ENTER("ps_fetch_datetime");
 
 	if ((length = php_mysqlnd_net_field_length(row))) {
+		if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
+			return;
+		}
+
 		const zend_uchar * to = *row;
 
 		t.time_type = MYSQLND_TIMESTAMP_DATETIME;
@@ -369,7 +430,11 @@ ps_fetch_string(zval * zv, const MYSQLND_FIELD * const field, const unsigned int
 	  For now just copy, before we make it possible
 	  to write \0 to the row buffer
 	*/
+	const zend_uchar *p = *row;
 	const zend_ulong length = php_mysqlnd_net_field_length(row);
+	if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
+		return;
+	}
 	DBG_ENTER("ps_fetch_string");
 	DBG_INF_FMT("len = %lu", length);
 	DBG_INF("copying from the row buffer");
@@ -385,7 +450,11 @@ ps_fetch_string(zval * zv, const MYSQLND_FIELD * const field, const unsigned int
 static void
 ps_fetch_bit(zval * zv, const MYSQLND_FIELD * const field, const unsigned int pack_len, const zend_uchar ** row)
 {
+	const zend_uchar *p = *row;
 	const zend_ulong length = php_mysqlnd_net_field_length(row);
+	if (UNEXPECTED(ps_fetch_is_packet_over_read_with_variable_length(pack_len, row, p, length))) {
+		return;
+	}
 	ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, length);
 }
 /* }}} */
diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c
index 5b63e09f71e..011ba34cb5e 100644
--- a/ext/mysqlnd/mysqlnd_result.c
+++ b/ext/mysqlnd/mysqlnd_result.c
@@ -503,7 +503,7 @@ mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s)
 				if (FAIL == (ret = result->m.read_result_metadata(result, conn))) {
 					/* For PS, we leave them in Prepared state */
 					if (!stmt && conn->current_result) {
-						mnd_efree(conn->current_result);
+						conn->current_result->m.free_result(conn->current_result, TRUE);
 						conn->current_result = NULL;
 					}
 					DBG_ERR("Error occurred while reading metadata");
diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c
index 664a993322d..929ea422924 100644
--- a/ext/mysqlnd/mysqlnd_wireprotocol.c
+++ b/ext/mysqlnd/mysqlnd_wireprotocol.c
@@ -721,7 +721,14 @@ php_mysqlnd_auth_response_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));
+			/* p can get past packet size when getting field length so it needs to be checked first
+			 * and after that it can be checked that the net_len is not greater than the packet size */
+			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;
@@ -1110,6 +1117,17 @@ php_mysqlnd_rset_header_read(MYSQLND_CONN_DATA * conn, void * _packet)
 			BAIL_IF_NO_MORE_DATA;
 			/* Check for additional textual data */
 			if (packet->header.size  > (size_t) (p - buf) && (len = php_mysqlnd_net_field_length(&p))) {
+				/* p can get past packet size when getting field length so it needs to be checked first
+				 * and after that it can be checked that the len is not greater than the packet size */
+				if ((p - buf) > packet->header.size || packet->header.size - (p - buf) < len) {
+					size_t local_file_name_over_read = ((p - buf) - packet->header.size) + len;
+					DBG_ERR_FMT("RSET_HEADER packet additional data length is past %zu bytes the packet size",
+						local_file_name_over_read);
+					php_error_docref(NULL, E_WARNING,
+						"RSET_HEADER packet additional data length is past %zu bytes the packet size",
+						local_file_name_over_read);
+					DBG_RETURN(FAIL);
+				}
 				packet->info_or_local_file.s = mnd_emalloc(len + 1);
 				if (packet->info_or_local_file.s) {
 					memcpy(packet->info_or_local_file.s, p, len);
@@ -1268,23 +1286,16 @@ php_mysqlnd_rset_field_read(MYSQLND_CONN_DATA * conn, void * _packet)
 		meta->flags |= NUM_FLAG;
 	}
 
-
-	/*
-	  def could be empty, thus don't allocate on the root.
-	  NULL_LENGTH (0xFB) comes from COM_FIELD_LIST when the default value is NULL.
-	  Otherwise the string is length encoded.
-	*/
+	/* COM_FIELD_LIST is no longer supported so def should not be present */
 	if (packet->header.size > (size_t) (p - buf) &&
 		(len = php_mysqlnd_net_field_length(&p)) &&
 		len != MYSQLND_NULL_LENGTH)
 	{
-		BAIL_IF_NO_MORE_DATA;
-		DBG_INF_FMT("Def found, length %lu", len);
-		meta->def = packet->memory_pool->get_chunk(packet->memory_pool, len + 1);
-		memcpy(meta->def, p, len);
-		meta->def[len] = '\0';
-		meta->def_length = len;
-		p += len;
+		DBG_ERR_FMT("Protocol error. Server sent default for unsupported field list");
+		php_error_docref(NULL, E_WARNING,
+			"Protocol error. Server sent default for unsupported field list (mysqlnd_wireprotocol.c:%u)",
+			__LINE__);
+		DBG_RETURN(FAIL);
 	}
 
 	root_ptr = meta->root = packet->memory_pool->get_chunk(packet->memory_pool, total_len);
@@ -1451,8 +1462,10 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fi
 									  const unsigned int field_count, const MYSQLND_FIELD * const fields_metadata,
 									  const zend_bool as_int_or_float, MYSQLND_STATS * const stats)
 {
-	unsigned int i;
-	const zend_uchar * p = row_buffer->ptr;
+	unsigned int i, j;
+	size_t rbs = row_buffer->size;
+	const zend_uchar * rbp = row_buffer->ptr;
+	const zend_uchar * p = rbp;
 	const zend_uchar * null_ptr;
 	zend_uchar bit;
 	zval *current_field, *end_field, *start_field;
@@ -1485,7 +1498,21 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fi
 			statistic = STAT_BINARY_TYPE_FETCHED_NULL;
 		} else {
 			enum_mysqlnd_field_types type = fields_metadata[i].type;
-			mysqlnd_ps_fetch_functions[type].func(current_field, &fields_metadata[i], 0, &p);
+			size_t row_position = p - rbp;
+			if (rbs <= row_position) {
+				for (j = 0, current_field = start_field; j < i; current_field++, j++) {
+					zval_ptr_dtor(current_field);
+				}
+				php_error_docref(NULL, E_WARNING, "Malformed server packet. No packet space left for the field");
+				DBG_RETURN(FAIL);
+			}
+			mysqlnd_ps_fetch_functions[type].func(current_field, &fields_metadata[i], rbs - row_position, &p);
+			if (p == NULL) {
+				for (j = 0, current_field = start_field; j < i; current_field++, j++) {
+					zval_ptr_dtor(current_field);
+				}
+				DBG_RETURN(FAIL);
+			}
 
 			if (MYSQLND_G(collect_statistics)) {
 				switch (fields_metadata[i].type) {
@@ -1542,7 +1569,7 @@ php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_ROW_BUFFER * row_buffer, zval *
 									unsigned int field_count, const MYSQLND_FIELD * fields_metadata,
 									zend_bool as_int_or_float, MYSQLND_STATS * stats)
 {
-	unsigned int i;
+	unsigned int i, j;
 	zval *current_field, *end_field, *start_field;
 	zend_uchar * p = row_buffer->ptr;
 	const size_t data_size = row_buffer->size;
@@ -1563,9 +1590,11 @@ php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_ROW_BUFFER * row_buffer, zval *
 		/* NULL or NOT NULL, this is the question! */
 		if (len == MYSQLND_NULL_LENGTH) {
 			ZVAL_NULL(current_field);
-		} else if ((p + len) > packet_end) {
-			php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing "MYSQLND_SZ_T_SPEC
-											  " bytes after end of packet", (p + len) - packet_end - 1);
+		} else if (p > packet_end || len > packet_end - p) {
+			php_error_docref(NULL, E_WARNING, "Malformed server packet. Field length pointing after end of packet");
+			for (j = 0, current_field = start_field; j < i; current_field++, j++) {
+				zval_ptr_dtor(current_field);
+			}
 			DBG_RETURN(FAIL);
 		} else {
 #ifdef MYSQLND_STRING_TO_INT_CONVERSION
-- 
2.47.0

From d5f9da0d6af72ae21b0a9f4c94c59dfdd409e3e2 Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Mon, 18 Nov 2024 15:54:30 +0100
Subject: [PATCH 2/6] Fix MySQLnd possible buffer over read in auth_protocol

(cherry picked from commit 32f905f1d689aaa8eacd6331a18c0dd45972c3c1)
---
 ext/mysqlnd/mysqlnd_wireprotocol.c | 27 +++++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)

diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c
index 929ea422924..5499ebac967 100644
--- a/ext/mysqlnd/mysqlnd_wireprotocol.c
+++ b/ext/mysqlnd/mysqlnd_wireprotocol.c
@@ -447,8 +447,31 @@ php_mysqlnd_greet_read(MYSQLND_CONN_DATA * conn, void * _packet)
 	if (packet->server_capabilities & CLIENT_PLUGIN_AUTH) {
 		BAIL_IF_NO_MORE_DATA;
 		/* The server is 5.5.x and supports authentication plugins */
-		packet->auth_protocol = estrdup((char *)p);
-		p+= strlen(packet->auth_protocol) + 1; /* eat the '\0' */
+		size_t remaining_size = packet->header.size - (size_t)(p - buf);
+		if (remaining_size == 0) {
+			/* Might be better to fail but this will fail anyway */
+			packet->auth_protocol = estrdup("");
+		} else {
+			/* Check if NUL present */
+			char *null_terminator = memchr(p, '\0', remaining_size);
+			size_t auth_protocol_len;
+			if (null_terminator) {
+				/* If present, do basically estrdup */
+				auth_protocol_len = null_terminator - (char *)p;
+			} else {
+				/* If not present, copy the rest of the buffer */
+				auth_protocol_len = remaining_size;
+			}
+			char *auth_protocol = emalloc(auth_protocol_len + 1);
+			memcpy(auth_protocol, p, auth_protocol_len);
+			auth_protocol[auth_protocol_len] = '\0';
+			packet->auth_protocol = auth_protocol;
+
+			p += auth_protocol_len;
+			if (null_terminator) {
+				p++;
+			}
+		}
 	}
 
 	DBG_INF_FMT("proto=%u server=%s thread_id=%u",
-- 
2.47.0

From 7e6af9c78d84d15880cfbc7867501f25ab982f5f Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Sun, 24 Nov 2024 20:13:47 +0100
Subject: [PATCH 3/6] Change port for mysqli fake server auth message test

(cherry picked from commit 51f5539914ae62ef8568ea1ed302dceda897c439)
---
 ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
index db54a6c0177..279aec6a2cb 100644
--- a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
@@ -6,7 +6,7 @@ mysqli
 <?php
 require_once 'fake_server.inc';
 
-$port = 50001;
+$port = 33305;
 $servername = "127.0.0.1";
 $username = "root";
 $password = "";
@@ -34,5 +34,5 @@ print "done!";
 [*] Sending - Malicious OK Auth Response [Extract heap through buffer over-read]: 0900000200000002000000fcff
 
 Warning: mysqli::__construct(): OK packet message length is past the packet size in %s on line %d
-Unknown error while trying to connect via tcp://127.0.0.1:50001
+Unknown error while trying to connect via tcp://127.0.0.1:33305
 done!
-- 
2.47.0

From cae38b1c749d27dc3a65f7d65fdf238439e2676c Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Sun, 24 Nov 2024 23:48:27 +0100
Subject: [PATCH 4/6] Increase MySQLi fake server read timeout for ASAN job

(cherry picked from commit eb951b3d11109aa16982a2132f8d1fd5129edc9e)
---
 ext/mysqli/tests/fake_server.inc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc
index b02fabc584c..1127f6c00e3 100644
--- a/ext/mysqli/tests/fake_server.inc
+++ b/ext/mysqli/tests/fake_server.inc
@@ -552,8 +552,8 @@ class my_mysqli_fake_server_conn
 
     public function read($bytes_len = 1024)
     {
-        // wait 10ms to fill the buffer
-        usleep(10000);
+        // wait 20ms to fill the buffer
+        usleep(20000);
         $data = fread($this->conn, $bytes_len);
         if ($data) {
             fprintf(STDERR, "[*] Received: %s\n", bin2hex($data));
-- 
2.47.0

From 69853e12b73a989e2383452356cdc07172427ae3 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Wed, 27 Nov 2024 10:54:10 +0100
Subject: [PATCH 5/6] Avoid using uninitialised struct

    (cherry picked from commit 7e7817bc2f82570bbc510a2bf5e4e0ec09dbc774)
---
 ext/mysqlnd/mysqlnd_result.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c
index 011ba34cb5e..9dcbd046a43 100644
--- a/ext/mysqlnd/mysqlnd_result.c
+++ b/ext/mysqlnd/mysqlnd_result.c
@@ -547,8 +547,8 @@ mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s)
 					}
 					MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic);
 				}
+				PACKET_FREE(&fields_eof);
 			} while (0);
-			PACKET_FREE(&fields_eof);
 			break; /* switch break */
 		}
 	} while (0);
-- 
2.47.0

From 4c5f0e9541f675033aff30be6d08f629c8da01d5 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Wed, 27 Nov 2024 11:17:48 +0100
Subject: [PATCH 6/6] adapt test + NEWS

---
 ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt | 3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)

diff --git a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
index 279aec6a2cb..161c9a5b8e6 100644
--- a/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
+++ b/ext/mysqli/tests/ghsa-h35g-vwh6-m678-auth-message.phpt
@@ -34,5 +34,4 @@ print "done!";
 [*] Sending - Malicious OK Auth Response [Extract heap through buffer over-read]: 0900000200000002000000fcff
 
 Warning: mysqli::__construct(): OK packet message length is past the packet size in %s on line %d
-Unknown error while trying to connect via tcp://127.0.0.1:33305
-done!
+%A
-- 
2.47.0

