From c18f9c6bd7cf055f1d161b78b2809a18eb866d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 10 Feb 2020 12:09:05 +0200 Subject: [PATCH] MXS-2883: Handle AuthSwitchRequest packets The backend didn't expect AuthSwitchRequest packets in response to the handshake response packets. This is allowed by the protocol and appears to happen with at least MySQL 8.0. --- include/maxscale/protocol/mysql.h | 2 +- .../MySQLBackendAuth/mysql_backend_auth.cc | 10 ++++++ .../MySQL/mariadbbackend/mysql_backend.cc | 9 +----- server/modules/protocol/MySQL/mysql_common.cc | 31 +++++++++++-------- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/include/maxscale/protocol/mysql.h b/include/maxscale/protocol/mysql.h index 25959a0b4..0afe8a542 100644 --- a/include/maxscale/protocol/mysql.h +++ b/include/maxscale/protocol/mysql.h @@ -510,7 +510,7 @@ bool gw_read_backend_handshake(DCB* dcb, GWBUF* buffer); mxs_auth_state_t gw_send_backend_auth(DCB* dcb); /** Sends a response for an AuthSwitchRequest to the default auth plugin */ -int send_mysql_native_password_response(DCB* dcb); +int send_mysql_native_password_response(DCB* dcb, GWBUF* buffer); /** Sends an AuthSwitchRequest packet with the default auth plugin to the DCB */ bool send_auth_switch_request_packet(DCB* dcb); diff --git a/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.cc b/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.cc index 679bf7a64..4e94b7915 100644 --- a/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.cc +++ b/server/modules/authenticator/MySQLBackendAuth/mysql_backend_auth.cc @@ -92,6 +92,11 @@ static bool auth_backend_extract(DCB* dcb, GWBUF* buf) rval = true; mba->state = MBA_AUTH_OK; } + else if (mxs_mysql_get_command(buf) == MYSQL_REPLY_AUTHSWITCHREQUEST + && send_mysql_native_password_response(dcb, buf)) + { + rval = true; + } else { mba->state = MBA_AUTH_FAILED; @@ -124,6 +129,11 @@ static int auth_backend_authenticate(DCB* dcb) /** Authentication completed successfully */ rval = MXS_AUTH_SUCCEEDED; } + else if (mba->state == MBA_NEED_OK) + { + /** Sent AuthSwitchRequest response */ + rval = MXS_AUTH_INCOMPLETE; + } return rval; } diff --git a/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.cc b/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.cc index 7b8f4259a..315e14d5d 100644 --- a/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.cc +++ b/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.cc @@ -784,14 +784,7 @@ static bool handle_auth_change_response(GWBUF* reply, MySQLProtocol* proto, DCB* * a new scramble for the re-authentication process. */ - // Load the new scramble into the protocol... - gwbuf_copy_data(reply, - 5 + strlen(DEFAULT_MYSQL_AUTH_PLUGIN) + 1, - GW_MYSQL_SCRAMBLE_SIZE, - proto->scramble); - - /// ... and use it to send the encrypted password to the server - rval = send_mysql_native_password_response(dcb); + rval = send_mysql_native_password_response(dcb, reply); } return rval; diff --git a/server/modules/protocol/MySQL/mysql_common.cc b/server/modules/protocol/MySQL/mysql_common.cc index ede003adc..d87791482 100644 --- a/server/modules/protocol/MySQL/mysql_common.cc +++ b/server/modules/protocol/MySQL/mysql_common.cc @@ -1009,19 +1009,30 @@ mxs_auth_state_t gw_send_backend_auth(DCB* dcb) return rval; } -int send_mysql_native_password_response(DCB* dcb) +int send_mysql_native_password_response(DCB* dcb, GWBUF* reply) { MySQLProtocol* proto = (MySQLProtocol*) dcb->protocol; MYSQL_session local_session; gw_get_shared_session_auth_info(dcb, &local_session); + const char default_plugin_name[] = DEFAULT_MYSQL_AUTH_PLUGIN; + + // Calculate the next sequence number + uint8_t seqno = 0; + gwbuf_copy_data(reply, 3, 1, &seqno); + ++seqno; + + // Copy the new scramble. Skip packet header, command byte and null-terminated plugin name. + gwbuf_copy_data(reply, MYSQL_HEADER_LEN + 1 + sizeof(default_plugin_name), + sizeof(proto->scramble), proto->scramble); + uint8_t* curr_passwd = memcmp(local_session.client_sha1, null_client_sha1, MYSQL_SCRAMBLE_LEN) ? local_session.client_sha1 : null_client_sha1; GWBUF* buffer = gwbuf_alloc(MYSQL_HEADER_LEN + GW_MYSQL_SCRAMBLE_SIZE); uint8_t* data = GWBUF_DATA(buffer); gw_mysql_set_byte3(data, GW_MYSQL_SCRAMBLE_SIZE); - data[3] = 2; // This is the third packet after the COM_CHANGE_USER + data[3] = seqno; calculate_hash(proto->scramble, curr_passwd, data + MYSQL_HEADER_LEN); return dcb_write(dcb, buffer); @@ -1060,7 +1071,7 @@ int gw_decode_mysql_server_handshake(MySQLProtocol* conn, uint8_t* payload) uint8_t scramble_data_1[GW_SCRAMBLE_LENGTH_323] = ""; uint8_t scramble_data_2[GW_MYSQL_SCRAMBLE_SIZE - GW_SCRAMBLE_LENGTH_323] = ""; uint8_t capab_ptr[4] = ""; - int scramble_len = 0; + uint8_t scramble_len = 0; uint8_t mxs_scramble[GW_MYSQL_SCRAMBLE_SIZE] = ""; int protocol_version = 0; @@ -1116,21 +1127,15 @@ int gw_decode_mysql_server_handshake(MySQLProtocol* conn, uint8_t* payload) // get scramble len if (payload[0] > 0) { - scramble_len = payload[0] - 1; - mxb_assert(scramble_len > GW_SCRAMBLE_LENGTH_323); - mxb_assert(scramble_len <= GW_MYSQL_SCRAMBLE_SIZE); - - if ((scramble_len < GW_SCRAMBLE_LENGTH_323) - || scramble_len > GW_MYSQL_SCRAMBLE_SIZE) - { - /* log this */ - return -2; - } + scramble_len = std::min(payload[0] - 1, GW_MYSQL_SCRAMBLE_SIZE); } else { scramble_len = GW_MYSQL_SCRAMBLE_SIZE; } + + mxb_assert(scramble_len > GW_SCRAMBLE_LENGTH_323); + // skip 10 zero bytes payload += 11;