From e8bc357123ea19c4e2390374f088c9d4941f19e6 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka 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) (cherry picked from commit 0d3ccf4cc54d3844bc9d1c8f6bdcd36180752a2c) adapt for 7.x --- 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 @@ + [ + '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-- +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-- +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-- +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-- +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-- +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-- +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-- +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-- +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-- +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-- +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-- +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-- +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-- +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-- +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-- +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 12edd9a2849..976143cb471 100644 --- a/ext/mysqlnd/mysqlnd_ps_codec.c +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -52,6 +52,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 zend_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 0; + } + size_t length_len = *row - p; + if (length_len > pack_len || length > pack_len - length_len) { + ps_fetch_over_read_error(row); + return 1; + } + return 0; +} + +static inline zend_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 1; + } + return 0; +} + + /* {{{ 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, @@ -60,6 +91,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) { @@ -178,6 +214,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); @@ -200,6 +241,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; @@ -216,9 +262,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; @@ -273,9 +324,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; @@ -310,9 +366,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; @@ -371,7 +432,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"); @@ -387,7 +452,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 4dcaf121fb7..c0f65f854ac 100644 --- a/ext/mysqlnd/mysqlnd_result.c +++ b/ext/mysqlnd/mysqlnd_result.c @@ -505,7 +505,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 e4a298adaea..844b90c5704 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -715,7 +715,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; @@ -1113,6 +1120,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); @@ -1271,23 +1289,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); @@ -1462,8 +1473,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; @@ -1496,7 +1509,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) { @@ -1553,7 +1580,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; @@ -1574,9 +1601,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 { #if defined(MYSQLND_STRING_TO_INT_CONVERSION) -- 2.47.0 From aaeb9549a1bdfa787fc3d3a2d499b418d09a5387 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka 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) (cherry picked from commit d5f9da0d6af72ae21b0a9f4c94c59dfdd409e3e2) --- 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 844b90c5704..e75c859bab7 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -441,8 +441,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 83a0d005d51a44bbe77a178c387e2c9f042a335d Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Wed, 27 Nov 2024 10:54:10 +0100 Subject: [PATCH 3/6] Avoid using uninitialised struct (cherry picked from commit 7e7817bc2f82570bbc510a2bf5e4e0ec09dbc774) (cherry picked from commit 69853e12b73a989e2383452356cdc07172427ae3) --- 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 c0f65f854ac..9f19adb0ce9 100644 --- a/ext/mysqlnd/mysqlnd_result.c +++ b/ext/mysqlnd/mysqlnd_result.c @@ -549,8 +549,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 606322b7f3475fb5980f7785789adfb9c381abbc Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Sun, 24 Nov 2024 20:13:47 +0100 Subject: [PATCH 4/6] Change port for mysqli fake server auth message test (cherry picked from commit 51f5539914ae62ef8568ea1ed302dceda897c439) (cherry picked from commit 7e6af9c78d84d15880cfbc7867501f25ab982f5f) --- 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 Date: Sun, 24 Nov 2024 23:48:27 +0100 Subject: [PATCH 5/6] Increase MySQLi fake server read timeout for ASAN job (cherry picked from commit eb951b3d11109aa16982a2132f8d1fd5129edc9e) (cherry picked from commit cae38b1c749d27dc3a65f7d65fdf238439e2676c) --- 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 016ffd6131a6174fe5ca5f4af3c66ad9f59ed879 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Wed, 27 Nov 2024 11:17:48 +0100 Subject: [PATCH 6/6] adapt test + NEWS --- NEWS | 4 + ext/mysqli/tests/fake_server.inc | 99 ++++++++++--------- .../ghsa-h35g-vwh6-m678-auth-message.phpt | 3 +- ext/mysqli/tests/mysqli_change_user_new.phpt | 3 + 4 files changed, 61 insertions(+), 48 deletions(-) diff --git a/NEWS b/NEWS index f600d6aea65..09cf2cfa0bb 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,10 @@ Backported from 8.1.31 . Fixed bug GHSA-g665-fm4p-vhff (OOB access in ldap_escape). (CVE-2024-8932) (nielsdos) +- MySQLnd: + . Fixed bug GHSA-h35g-vwh6-m678 (Leak partial content of the heap through + heap buffer over-read). (CVE-2024-8929) (Jakub Zelenka) + - PDO DBLIB: . Fixed bug GHSA-5hqh-c84r-qjcv (Integer overflow in the dblib quoter causing OOB writes). (CVE-2024-11236) (nielsdos) diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc index 1127f6c00e3..18b77cb1a76 100644 --- a/ext/mysqli/tests/fake_server.inc +++ b/ext/mysqli/tests/fake_server.inc @@ -1,6 +1,6 @@ [ @@ -107,8 +107,11 @@ function my_mysqli_data_field(string $field): array class my_mysqli_fake_packet_item { - public function __construct(public string|null $name, public string $value, public bool $is_hex = true) + public function __construct($name, string $value, bool $is_hex = true) { + $this->name = $name; + $this->value = $value; + $this->is_hex = $is_hex; } } @@ -126,7 +129,7 @@ class my_mysqli_fake_packet return null; } - public function __set(string $name, string|my_mysqli_fake_packet_item $value) + public function __set(string $name, $value) { if ($value instanceof my_mysqli_fake_packet_item) { if ($value->name === null) { @@ -146,7 +149,7 @@ class my_mysqli_fake_packet $this->data[] = $value; } - public function to_bytes(): string + public function to_bytes() { $bytes = ''; foreach ($this->data as $item) { @@ -158,7 +161,7 @@ class my_mysqli_fake_packet 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 + public static function create_packet_item($value, bool $is_hex = false, string $format = 'v') { if (is_string($value)) { $packed_value = $value; @@ -168,7 +171,7 @@ class my_mysqli_fake_packet_generator return new my_mysqli_fake_packet_item(null, $packed_value, $is_hex); } - public function server_ok(): my_mysqli_fake_packet + public function server_ok() { $packet = new my_mysqli_fake_packet(); $packet->packet_length = "070000"; @@ -181,7 +184,7 @@ class my_mysqli_fake_packet_generator return $packet; } - public function server_greetings(): my_mysqli_fake_packet + public function server_greetings() { $packet = new my_mysqli_fake_packet(); $packet->packet_length = "580000"; @@ -204,7 +207,7 @@ class my_mysqli_fake_packet_generator return $packet; } - public function server_tabular_query_response(): array + public function server_tabular_query_response() { $qr1 = new my_mysqli_fake_packet(); $qr1->packet_length = "010000"; @@ -239,7 +242,7 @@ class my_mysqli_fake_packet_generator return [$qr1, $qr2, $qr3, $qr4, $qr5]; } - public function server_upsert_query_response(): array + public function server_upsert_query_response() { $qr1 = new my_mysqli_fake_packet(); $qr1->packet_length = "010000"; @@ -257,7 +260,7 @@ class my_mysqli_fake_packet_generator return [$qr1]; } - public function server_stmt_prepare_response_start($num_field): my_mysqli_fake_packet + public function server_stmt_prepare_response_start($num_field) { $pr1 = new my_mysqli_fake_packet(); $pr1->packet_length = "0c0000"; @@ -272,7 +275,7 @@ class my_mysqli_fake_packet_generator return $pr1; } - public function server_stmt_prepare_response_end($packer_number): my_mysqli_fake_packet + public function server_stmt_prepare_response_end($packer_number) { $pr3 = new my_mysqli_fake_packet(); $pr3->packet_length = "050000"; @@ -284,7 +287,7 @@ class my_mysqli_fake_packet_generator return $pr3; } - public function server_stmt_prepare_items_response(): array + public function server_stmt_prepare_items_response() { $pr1 = $this->server_stmt_prepare_response_start('0100'); @@ -316,7 +319,7 @@ class my_mysqli_fake_packet_generator return [$pr1, $pr2, $pr3]; } - public function server_stmt_prepare_data_response_field($packet_number, $field_name): my_mysqli_fake_packet + public function server_stmt_prepare_data_response_field($packet_number, $field_name) { if (strlen($field_name) != 6) { throw new Exception("Invalid field length - only 6 is allowed"); @@ -350,7 +353,7 @@ class my_mysqli_fake_packet_generator return $pr; } - public function server_stmt_prepare_data_response(string $field_name): array + public function server_stmt_prepare_data_response(string $field_name) { $pr1 = $this->server_stmt_prepare_response_start('0200'); @@ -362,7 +365,7 @@ class my_mysqli_fake_packet_generator return [$pr1, $pr2, $pr3, $pr4]; } - public function server_stmt_execute_items_response(): array + public function server_stmt_execute_items_response() { $pr1 = new my_mysqli_fake_packet(); $pr1->packet_length = "010000"; @@ -413,7 +416,7 @@ class my_mysqli_fake_packet_generator return [$pr1, $pr2, $pr3, $pr4, $pr5]; } - private function server_execute_data_response_start(string $field_name): array + private function server_execute_data_response_start(string $field_name) { $pr1 = new my_mysqli_fake_packet(); $pr1->packet_length = "010000"; @@ -478,7 +481,7 @@ class my_mysqli_fake_packet_generator return [$field, $pr1, $pr2, $pr3, $pr4]; } - private function server_execute_data_response_end(): my_mysqli_fake_packet + private function server_execute_data_response_end() { $pr6 = new my_mysqli_fake_packet(); $pr6->packet_length = '050000'; @@ -490,7 +493,7 @@ class my_mysqli_fake_packet_generator return $pr6; } - public function server_stmt_execute_data_response(string $field_name): array + public function server_stmt_execute_data_response(string $field_name) { [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name); @@ -506,7 +509,7 @@ class my_mysqli_fake_packet_generator return [$pr1, $pr2, $pr3, $pr4, $pr5, $this->server_execute_data_response_end()]; } - public function server_query_execute_data_response(string $field_name): array + public function server_query_execute_data_response(string $field_name) { [$field, $pr1, $pr2, $pr3, $pr4] = $this->server_execute_data_response_start($field_name); @@ -537,12 +540,12 @@ class my_mysqli_fake_server_conn } } - public function packets_to_bytes(array $packets): string + public function packets_to_bytes(array $packets) { return implode('', array_map(fn($s) => $s->to_bytes(), $packets)); } - public function send($payload, $message = null): void + public function send($payload, $message = null) { if ($message) { fprintf(STDERR, "[*] Sending - %s: %s\n", $message, bin2hex($payload)); @@ -575,38 +578,38 @@ class my_mysqli_fake_server_conn $this->send($this->packet_generator->server_ok()->to_bytes(), "Server OK"); } - public function send_server_tabular_query_response(): void + public function send_server_tabular_query_response() { $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 + public function send_server_stmt_prepare_items_response() { $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 + public function send_server_stmt_prepare_data_response(string $field_name) { $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 + public function send_server_stmt_execute_items_response() { $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 + public function send_server_stmt_execute_data_response(string $field_name) { $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 + public function send_server_query_execute_data_response(string $field_name) { $packets = $this->packet_generator->server_query_execute_data_response($field_name); $this->send($this->packets_to_bytes($packets), "Query execute data $field_name"); @@ -615,7 +618,11 @@ class my_mysqli_fake_server_conn class my_mysqli_fake_server_process { - public function __construct(private $process, private array $pipes) {} + public function __construct($process, array $pipes) + { + $this->process = $process; + $this->pipes = $pipes; + } public function terminate(bool $wait = false) { @@ -631,7 +638,7 @@ class my_mysqli_fake_server_process } } -function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_conn $conn): void +function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_conn $conn) { $rh = $conn->packet_generator->server_tabular_query_response(); @@ -655,7 +662,7 @@ function my_mysqli_test_tabular_response_def_over_read(my_mysqli_fake_server_con $conn->read(65536); } -function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server_conn $conn): void +function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server_conn $conn) { $rh = $conn->packet_generator->server_upsert_query_response(); @@ -672,7 +679,7 @@ function my_mysqli_test_upsert_response_filename_over_read(my_mysqli_fake_server $conn->read(65536); } -function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_conn $conn): void +function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_conn $conn) { $p = $conn->packet_generator->server_ok(); $p->packet_length = "090000"; @@ -684,7 +691,7 @@ function my_mysqli_test_auth_response_message_over_read(my_mysqli_fake_server_co $conn->read(); } -function my_mysqli_test_stmt_response_row_over_read_string(my_mysqli_fake_server_conn $conn): void +function my_mysqli_test_stmt_response_row_over_read_string(my_mysqli_fake_server_conn $conn) { $rh = $conn->packet_generator->server_stmt_execute_items_response(); @@ -705,7 +712,7 @@ 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 @@ -724,47 +731,47 @@ function my_mysqli_test_stmt_response_row_over_read_two_fields( $conn->read(65536); } -function my_mysqli_test_stmt_response_row_over_read_int(my_mysqli_fake_server_conn $conn): void +function my_mysqli_test_stmt_response_row_over_read_int(my_mysqli_fake_server_conn $conn) { 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 +function my_mysqli_test_stmt_response_row_over_read_float(my_mysqli_fake_server_conn $conn) { 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 +function my_mysqli_test_stmt_response_row_over_read_double(my_mysqli_fake_server_conn $conn) { 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 +function my_mysqli_test_stmt_response_row_over_read_date(my_mysqli_fake_server_conn $conn) { 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 +function my_mysqli_test_stmt_response_row_over_read_time(my_mysqli_fake_server_conn $conn) { 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 +function my_mysqli_test_stmt_response_row_over_read_datetime(my_mysqli_fake_server_conn $conn) { 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 +function my_mysqli_test_stmt_response_row_no_space(my_mysqli_fake_server_conn $conn) { 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 +function my_mysqli_test_stmt_response_row_over_read_bit(my_mysqli_fake_server_conn $conn) { 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 +function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_conn $conn) { $conn->send_server_greetings(); $conn->read(); @@ -779,7 +786,7 @@ function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_ } } -function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn): void +function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn) { $rh = $conn->packet_generator->server_query_execute_data_response('strval'); @@ -794,7 +801,7 @@ function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server $conn->read(65536); } -function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server_conn $conn): void +function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server_conn $conn) { $conn->send_server_greetings(); $conn->read(); @@ -807,7 +814,7 @@ function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server } } -function run_fake_server(string $test_function, $port = 33305): void +function run_fake_server(string $test_function, $port = 33305) { $address = '127.0.0.1'; @@ -832,7 +839,7 @@ function run_fake_server(string $test_function, $port = 33305): void } -function run_fake_server_in_background($test_function, $port = 33305): my_mysqli_fake_server_process +function run_fake_server_in_background($test_function, $port = 33305) { $command = [PHP_BINARY, '-n', __FILE__, 'mysqli_fake_server', $test_function, $port]; 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 diff --git a/ext/mysqli/tests/mysqli_change_user_new.phpt b/ext/mysqli/tests/mysqli_change_user_new.phpt index 2b8993fc20d..d8fed6dfaee 100644 --- a/ext/mysqli/tests/mysqli_change_user_new.phpt +++ b/ext/mysqli/tests/mysqli_change_user_new.phpt @@ -12,6 +12,9 @@ if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) if (mysqli_get_server_version($link) < 50600) die("SKIP For MySQL >= 5.6.0"); + +if (mysqli_get_server_version($link) >= 10_00_00) + die("SKIP Not applicable for MariaDB"); ?> --FILE--