diff --git a/ext/mysqli/tests/fake_server.inc b/ext/mysqli/tests/fake_server.inc index d6afbb049773..d8333a470d7c 100644 --- a/ext/mysqli/tests/fake_server.inc +++ b/ext/mysqli/tests/fake_server.inc @@ -818,6 +818,20 @@ function my_mysqli_test_query_response_row_read_two_fields(my_mysqli_fake_server } } +function my_mysqli_test_sha256_password_short_scramble(my_mysqli_fake_server_conn $conn): void +{ + $conn->send_server_greetings(); + $conn->read(); + // AuthSwitchRequest (0xfe) to sha256_password carrying a scramble shorter + // than SCRAMBLE_LENGTH (20). mnd_emalloc sizes the heap copy to this + // length, so the sha256 plugin's 20-byte XOR over-reads it without the + // length guard. + $payload = "\xfe" . "sha256_password\x00" . str_repeat("\x41", 8); + $header = substr(pack('V', strlen($payload)), 0, 3) . "\x02"; + $conn->send($header . $payload, "Auth Switch Request [short sha256 scramble]"); + $conn->read(); +} + function run_fake_server(string $test_function, int|string $port = 0): int { $host = '127.0.0.1'; diff --git a/ext/mysqli/tests/mysqli_sha256_password_short_scramble.phpt b/ext/mysqli/tests/mysqli_sha256_password_short_scramble.phpt new file mode 100644 index 000000000000..27de8e765087 --- /dev/null +++ b/ext/mysqli/tests/mysqli_sha256_password_short_scramble.phpt @@ -0,0 +1,26 @@ +--TEST-- +mysqlnd sha256_password: reject server scramble shorter than 20 bytes +--EXTENSIONS-- +mysqli +mysqlnd +openssl +--FILE-- +wait(); + +$link = @new mysqli("127.0.0.1", "root", "", "", $process->getPort()); +printf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()); + +$process->terminate(); +print "done!\n"; +?> +--EXPECTF-- +[*] Server started on 127.0.0.1:%d +%A +[2027] The server sent wrong length for scramble +done! diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c index ea9755c982ed..f5502cc99ed0 100644 --- a/ext/mysqlnd/mysqlnd_auth.c +++ b/ext/mysqlnd/mysqlnd_auth.c @@ -908,6 +908,11 @@ mysqlnd_sha256_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self DBG_ENTER("mysqlnd_sha256_auth_get_auth_data"); DBG_INF_FMT("salt(%zu)=[%.*s]", auth_plugin_data_len, (int) auth_plugin_data_len, auth_plugin_data); + if (auth_plugin_data_len < SCRAMBLE_LENGTH) { + SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "The server sent wrong length for scramble"); + DBG_ERR_FMT("The server sent wrong length for scramble %zu. Expected %u", auth_plugin_data_len, SCRAMBLE_LENGTH); + DBG_RETURN(NULL); + } if (conn->vio->data->ssl) { DBG_INF("simple clear text under SSL");