Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions ext/mysqli/tests/fake_server.inc
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,50 @@ function my_mysqli_test_stmt_response_row_read_two_fields(my_mysqli_fake_server_
}
}

function my_mysqli_test_rset_field_metadata_len_over_read(my_mysqli_fake_server_conn $conn): void
{
$rh = $conn->packet_generator->server_tabular_query_response();

$qr2 = new my_mysqli_fake_packet();
$qr2->packet_length = "0c0000";
$qr2->packet_number = "02";
$qr2->catalog_length_plus_name = "0161";
$qr2->db_length_plus_name = "0162";
$qr2->table_length_plus_name = "0163";
$qr2->original_t = "0164";
$qr2->name_length_plus_name = "0165";
$qr2->original_n = "fcff";

$conn->send_server_greetings();
$conn->read_packets(1);
$conn->send_server_ok();
$conn->read_packets(1);
$conn->send($conn->packets_to_bytes([$rh[0], $qr2]), "Malicious Tabular Response [metadata string length past the packet size]");
$conn->read();
}

function my_mysqli_test_rset_field_metadata_len_past_packet(my_mysqli_fake_server_conn $conn): void
{
$rh = $conn->packet_generator->server_tabular_query_response();

$qr2 = new my_mysqli_fake_packet();
$qr2->packet_length = "0c0000";
$qr2->packet_number = "02";
$qr2->catalog_length_plus_name = "0161";
$qr2->db_length_plus_name = "0162";
$qr2->table_length_plus_name = "0163";
$qr2->original_t = "0164";
$qr2->name_length_plus_name = "0165";
$qr2->original_n = "0561";

$conn->send_server_greetings();
$conn->read_packets(1);
$conn->send_server_ok();
$conn->read_packets(1);
$conn->send($conn->packets_to_bytes([$rh[0], $qr2]), "Malicious Tabular Response [metadata string length past the packet size]");
$conn->read();
}

function my_mysqli_test_query_response_row_length_overflow(my_mysqli_fake_server_conn $conn): void
{
$rh = $conn->packet_generator->server_query_execute_data_response('strval');
Expand Down
42 changes: 42 additions & 0 deletions ext/mysqli/tests/mysqlnd_rset_field_len_over_read.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
--TEST--
mysqlnd result set field metadata string length buffer over-read (len clamped to packet size)
--EXTENSIONS--
mysqli
--FILE--
<?php
require_once 'fake_server.inc';

$servername = "127.0.0.1";
$username = "root";
$password = "";

$process = run_fake_server_in_background('rset_field_metadata_len_over_read');
$process->wait();

try {
$conn = new mysqli( $servername, $username, $password, "", $process->getPort());
var_dump($conn->query("SELECT * from users"));
} catch (Exception $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

$conn->close();

$process->terminate();

print "done!";
?>
--EXPECTF--
[*] Server started on 127.0.0.1:%d
[*] Connection established
[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
[*] Received: %s
[*] Sending - Server OK: 0700000200000002000000
[*] Received: %s
[*] Sending - Malicious Tabular Response [metadata string length past the packet size]: 01000001010c00000201610162016301640165fcff

Warning: mysqli::query(): Premature end of data (mysqlnd_wireprotocol.c:%d) in %s on line %d

Warning: mysqli::query(): Result set field packet %d bytes shorter than expected in %s on line %d
bool(false)
done!
40 changes: 40 additions & 0 deletions ext/mysqli/tests/mysqlnd_rset_field_len_past_packet.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
mysqlnd result set field metadata string length exceeds remaining packet bytes
--EXTENSIONS--
mysqli
--FILE--
<?php
require_once 'fake_server.inc';

$servername = "127.0.0.1";
$username = "root";
$password = "";

$process = run_fake_server_in_background('rset_field_metadata_len_past_packet');
$process->wait();

try {
$conn = new mysqli( $servername, $username, $password, "", $process->getPort());
var_dump($conn->query("SELECT * from users"));
} catch (Exception $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

$conn->close();

$process->terminate();

print "done!";
?>
--EXPECTF--
[*] Server started on 127.0.0.1:%d
[*] Connection established
[*] Sending - Server Greeting: %s
[*] Received: %s
[*] Sending - Server OK: %s
[*] Received: %s
[*] Sending - Malicious Tabular Response [metadata string length past the packet size]: %s

Warning: mysqli::query(): Result set field metadata string length is past the packet size in %s on line %d
bool(false)
done!
9 changes: 8 additions & 1 deletion ext/mysqlnd/mysqlnd_wireprotocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -1171,10 +1171,17 @@ void php_mysqlnd_rset_header_free_mem(void * _packet)
/* }}} */

#define READ_RSET_FIELD(field_name) do { \
BAIL_IF_NO_MORE_DATA; \
len = php_mysqlnd_net_field_length(&p); \
if (UNEXPECTED(len == MYSQLND_NULL_LENGTH)) { \
goto faulty_or_fake; \
} else if (len != 0) { \
BAIL_IF_NO_MORE_DATA; \
if (UNEXPECTED((p - begin) > packet->header.size || packet->header.size - (p - begin) < len)) { \
DBG_ERR_FMT("Result set field metadata string length is past the packet size"); \
php_error_docref(NULL, E_WARNING, "Result set field metadata string length is past the packet size"); \
DBG_RETURN(FAIL); \
} \
Comment on lines +1179 to +1184

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
BAIL_IF_NO_MORE_DATA; \
if (UNEXPECTED(len > (zend_ulong)(packet->header.size - (size_t)(p - begin)))) { \
goto premature_end; \
} \
BAIL_IF_NO_MORE_DATA; \
if (UNEXPECTED(len > (zend_ulong)(packet->header.size - (size_t)(p - begin)))) { \
php_error_docref(NULL, E_WARNING, "Result set field metadata string length is past the packet size"); \
DBG_RETURN(FAIL); \
} \

This part is untested, but if there was a test the value in the test would underflow, so it's probably better to do it how we do it on line 740. Also, on line 740 we don't do (zend_ulong) cast so I think that's redundant too, but I am not sure.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matched the line 740 check and warning. goto premature_end underflows the reported size when p is still inside the packet; added mysqlnd_rset_field_len_past_packet.phpt for that.

meta->field_name = (const char *)p; \
meta->field_name ## _length = len; \
p += len; \
Expand Down Expand Up @@ -1243,7 +1250,7 @@ php_mysqlnd_rset_field_read(MYSQLND_CONN_DATA * conn, void * _packet)
READ_RSET_FIELD(name);
READ_RSET_FIELD(org_name);

/* 1 byte length */
BAIL_IF_NO_MORE_DATA;
if (UNEXPECTED(12 != *p)) {
DBG_ERR_FMT("Protocol error. Server sent false length. Expected 12 got %d", (int) *p);
php_error_docref(NULL, E_WARNING, "Protocol error. Server sent false length. Expected 12");
Expand Down
Loading