From c9104016c4f51c5125c7dd494cac418ca893f084 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Tue, 16 Jun 2026 16:17:16 -0400 Subject: [PATCH] Guard against short server scramble in sha256_password auth mysqlnd_sha256_auth_get_auth_data() XORs SCRAMBLE_LENGTH bytes of the server-supplied scramble into the password without checking the scramble is at least that long, unlike the native and caching_sha2 plugins which reject a short scramble with CR_MALFORMED_PACKET. A server reporting a scramble shorter than 20 bytes shrinks the heap buffer the scramble is copied into, so the XOR reads past it. Add the same length guard the sibling plugins use. --- ext/mysqli/tests/fake_server.inc | 14 ++++++++++ ...mysqli_sha256_password_short_scramble.phpt | 26 +++++++++++++++++++ ext/mysqlnd/mysqlnd_auth.c | 5 ++++ 3 files changed, 45 insertions(+) create mode 100644 ext/mysqli/tests/mysqli_sha256_password_short_scramble.phpt 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");