From 6225137b4a51da4550f01aafbe8bc39655aedc23 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 27 Dec 2019 12:37:51 +0100 Subject: [PATCH 1/5] Support auth switch request during caching sha2 auth --- ext/mysqlnd/mysqlnd_auth.c | 32 +++++++++++++++++++++--------- ext/mysqlnd/mysqlnd_structs.h | 7 +++++-- ext/mysqlnd/mysqlnd_wireprotocol.c | 29 ++++++++++++++++++++++++++- ext/mysqlnd/mysqlnd_wireprotocol.h | 4 ++++ 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c index b916e40b71c..3ceaaa457e3 100644 --- a/ext/mysqlnd/mysqlnd_auth.c +++ b/ext/mysqlnd/mysqlnd_auth.c @@ -320,8 +320,12 @@ mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn, } if (auth_plugin && auth_plugin->methods.handle_server_response) { - auth_plugin->methods.handle_server_response(auth_plugin, conn, - orig_auth_plugin_data, orig_auth_plugin_data_len, passwd, passwd_len); + if (FAIL == auth_plugin->methods.handle_server_response(auth_plugin, conn, + orig_auth_plugin_data, orig_auth_plugin_data_len, passwd, passwd_len, + switch_to_auth_protocol, switch_to_auth_protocol_len, + switch_to_auth_protocol_data, switch_to_auth_protocol_data_len)) { + goto end; + } } if (FAIL == PACKET_READ(conn, &auth_resp_packet) || auth_resp_packet.response_code >= 0xFE) { @@ -1028,26 +1032,36 @@ mysqlnd_caching_sha2_get_and_use_key(MYSQLND_CONN_DATA *conn, } /* }}} */ -/* {{{ mysqlnd_native_auth_get_auth_data */ -static void +/* {{{ mysqlnd_caching_sha2_handle_server_response */ +static enum_func_status mysqlnd_caching_sha2_handle_server_response(struct st_mysqlnd_authentication_plugin *self, MYSQLND_CONN_DATA * conn, const zend_uchar * auth_plugin_data, const size_t auth_plugin_data_len, const char * const passwd, - const size_t passwd_len) + const size_t passwd_len, + char **new_auth_protocol, size_t *new_auth_protocol_len, + zend_uchar **new_auth_protocol_data, size_t *new_auth_protocol_data_len + ) { DBG_ENTER("mysqlnd_caching_sha2_handle_server_response"); MYSQLND_PACKET_CACHED_SHA2_RESULT result_packet; conn->payload_decoder_factory->m.init_cached_sha2_result_packet(&result_packet); if (FAIL == PACKET_READ(conn, &result_packet)) { - DBG_VOID_RETURN; + DBG_RETURN(PASS); } switch (result_packet.response_code) { + case 0xFE: + DBG_INF("auth switch response"); + *new_auth_protocol = result_packet.new_auth_protocol; + *new_auth_protocol_len = result_packet.new_auth_protocol_len; + *new_auth_protocol_data = result_packet.new_auth_protocol_data; + *new_auth_protocol_data_len = result_packet.new_auth_protocol_data_len; + DBG_RETURN(FAIL); case 3: DBG_INF("fast path succeeded"); - DBG_VOID_RETURN; + DBG_RETURN(PASS); case 4: if (conn->vio->data->ssl || conn->unix_socket.s) { DBG_INF("fast path failed, doing full auth via SSL"); @@ -1060,7 +1074,7 @@ mysqlnd_caching_sha2_handle_server_response(struct st_mysqlnd_authentication_plu PACKET_WRITE(conn, &result_packet); efree(result_packet.password); } - DBG_VOID_RETURN; + DBG_RETURN(PASS); case 2: // The server tried to send a key, which we didn't expect // fall-through @@ -1068,7 +1082,7 @@ mysqlnd_caching_sha2_handle_server_response(struct st_mysqlnd_authentication_plu php_error_docref(NULL, E_WARNING, "Unexpected server response while doing caching_sha2 auth: %i", result_packet.response_code); } - DBG_VOID_RETURN; + DBG_RETURN(PASS); } /* }}} */ diff --git a/ext/mysqlnd/mysqlnd_structs.h b/ext/mysqlnd/mysqlnd_structs.h index 9880d1643d6..fcd3bc2f39f 100644 --- a/ext/mysqlnd/mysqlnd_structs.h +++ b/ext/mysqlnd/mysqlnd_structs.h @@ -1383,11 +1383,14 @@ typedef zend_uchar * (*func_auth_plugin__get_auth_data)(struct st_mysqlnd_authen const MYSQLND_PFC_DATA * const pfc_data, const zend_ulong mysql_flags ); -typedef void (*func_auth_plugin__handle_server_response)(struct st_mysqlnd_authentication_plugin * self, +typedef enum_func_status (*func_auth_plugin__handle_server_response)(struct st_mysqlnd_authentication_plugin * self, MYSQLND_CONN_DATA * conn, const zend_uchar * auth_plugin_data, size_t auth_plugin_data_len, const char * const passwd, - const size_t passwd_len); + const size_t passwd_len, + char **new_auth_protocol, size_t *new_auth_protocol_len, + zend_uchar **new_auth_protocol_data, size_t *new_auth_protocol_data_len + ); struct st_mysqlnd_authentication_plugin { diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index d0f1312f558..928e4f42012 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -2178,6 +2178,7 @@ php_mysqlnd_cached_sha2_result_read(MYSQLND_CONN_DATA * conn, void * _packet) zend_uchar buf[SHA256_PK_REQUEST_RESP_BUFFER_SIZE]; zend_uchar *p = buf; const zend_uchar * const begin = buf; + uint8_t main_response_code; DBG_ENTER("php_mysqlnd_cached_sha2_result_read"); if (FAIL == mysqlnd_read_packet_header_and_body(&(packet->header), pfc, vio, stats, error_info, connection_state, buf, sizeof(buf), "PROT_CACHED_SHA2_RESULT_PACKET", PROT_CACHED_SHA2_RESULT_PACKET)) { @@ -2185,11 +2186,37 @@ php_mysqlnd_cached_sha2_result_read(MYSQLND_CONN_DATA * conn, void * _packet) } BAIL_IF_NO_MORE_DATA; + main_response_code = uint1korr(p); p++; - packet->response_code = uint1korr(p); BAIL_IF_NO_MORE_DATA; + if (0xFE == main_response_code) { + packet->response_code = main_response_code; + /* Authentication Switch Response */ + if (packet->header.size > (size_t) (p - buf)) { + packet->new_auth_protocol = mnd_pestrdup((char *)p, FALSE); + packet->new_auth_protocol_len = strlen(packet->new_auth_protocol); + p+= packet->new_auth_protocol_len + 1; /* +1 for the \0 */ + + packet->new_auth_protocol_data_len = packet->header.size - (size_t) (p - buf); + if (packet->new_auth_protocol_data_len) { + packet->new_auth_protocol_data = mnd_emalloc(packet->new_auth_protocol_data_len); + memcpy(packet->new_auth_protocol_data, p, packet->new_auth_protocol_data_len); + } + DBG_INF_FMT("The server requested switching auth plugin to : %s", packet->new_auth_protocol); + DBG_INF_FMT("Server salt : [%d][%.*s]", packet->new_auth_protocol_data_len, packet->new_auth_protocol_data_len, packet->new_auth_protocol_data); + } + DBG_RETURN(PASS); + } + + if (0x1 != main_response_code) { + DBG_ERR_FMT("Unexpected response code %d", main_response_code); + } + + packet->response_code = uint1korr(p); p++; + BAIL_IF_NO_MORE_DATA; + packet->result = uint1korr(p); BAIL_IF_NO_MORE_DATA; diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.h b/ext/mysqlnd/mysqlnd_wireprotocol.h index 4b63e76a359..83ea5557dd5 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.h +++ b/ext/mysqlnd/mysqlnd_wireprotocol.h @@ -288,6 +288,10 @@ typedef struct st_mysqlnd_packet_cached_sha2_result { uint8_t request; zend_uchar * password; size_t password_len; + char *new_auth_protocol; + size_t new_auth_protocol_len; + zend_uchar *new_auth_protocol_data; + size_t new_auth_protocol_data_len; } MYSQLND_PACKET_CACHED_SHA2_RESULT; From 03ee36d1c526b402e1e5f283ee6f1631f3f61982 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 27 Dec 2019 13:27:10 +0100 Subject: [PATCH 2/5] Fix unix socket check during caching_sha2_password The fact that conn->unix_socket is set does not mean that a Unix socket is actually in use -- this member is set in a default configuration. Instead check whether a unix_socket stream ops is used. --- ext/mysqlnd/mysqlnd_auth.c | 14 +++++++++++--- ext/mysqlnd/mysqlnd_connection.c | 4 ---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c index 3ceaaa457e3..a1aaebd9dab 100644 --- a/ext/mysqlnd/mysqlnd_auth.c +++ b/ext/mysqlnd/mysqlnd_auth.c @@ -1032,6 +1032,14 @@ mysqlnd_caching_sha2_get_and_use_key(MYSQLND_CONN_DATA *conn, } /* }}} */ +static int is_secure_transport(MYSQLND_CONN_DATA *conn) { + if (conn->vio->data->ssl) { + return 1; + } + + return strcmp(conn->vio->data->stream->ops->label, "unix_socket") == 0; +} + /* {{{ mysqlnd_caching_sha2_handle_server_response */ static enum_func_status mysqlnd_caching_sha2_handle_server_response(struct st_mysqlnd_authentication_plugin *self, @@ -1063,13 +1071,13 @@ mysqlnd_caching_sha2_handle_server_response(struct st_mysqlnd_authentication_plu DBG_INF("fast path succeeded"); DBG_RETURN(PASS); case 4: - if (conn->vio->data->ssl || conn->unix_socket.s) { - DBG_INF("fast path failed, doing full auth via SSL"); + if (is_secure_transport(conn)) { + DBG_INF("fast path failed, doing full auth via secure transport"); result_packet.password = (zend_uchar *)passwd; result_packet.password_len = passwd_len + 1; PACKET_WRITE(conn, &result_packet); } else { - DBG_INF("fast path failed, doing full auth without SSL"); + DBG_INF("fast path failed, doing full auth via insecure transport"); result_packet.password_len = mysqlnd_caching_sha2_get_and_use_key(conn, auth_plugin_data, auth_plugin_data_len, &result_packet.password, passwd, passwd_len); PACKET_WRITE(conn, &result_packet); efree(result_packet.password); diff --git a/ext/mysqlnd/mysqlnd_connection.c b/ext/mysqlnd/mysqlnd_connection.c index 9154731be5c..2822672da5f 100644 --- a/ext/mysqlnd/mysqlnd_connection.c +++ b/ext/mysqlnd/mysqlnd_connection.c @@ -671,13 +671,9 @@ MYSQLND_METHOD(mysqlnd_conn_data, connect)(MYSQLND_CONN_DATA * conn, { const MYSQLND_CSTRING scheme = { transport.s, transport.l }; - /* This will be overwritten below with a copy, but we can use it during authentication */ - conn->unix_socket.s = (char *)socket_or_pipe.s; if (FAIL == conn->m->connect_handshake(conn, &scheme, &username, &password, &database, mysql_flags)) { - conn->unix_socket.s = NULL; goto err; } - conn->unix_socket.s = NULL; } { From e7e1254f3e41db8f92b85b25658f393f7f5bce66 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 27 Dec 2019 14:40:54 +0100 Subject: [PATCH 3/5] Add support for caching_sha2_password in change user authentication Same as for connection handshakes. --- ext/mysqlnd/mysqlnd_auth.c | 13 +++++++++++++ ext/mysqlnd/mysqlnd_auth.h | 3 +++ 2 files changed, 16 insertions(+) diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c index a1aaebd9dab..c4bd53d8c32 100644 --- a/ext/mysqlnd/mysqlnd_auth.c +++ b/ext/mysqlnd/mysqlnd_auth.c @@ -138,6 +138,7 @@ mysqlnd_run_authentication( ret = mysqlnd_auth_change_user(conn, user, strlen(user), passwd, passwd_len, db, db_len, silent, first_call, requested_protocol, + auth_plugin, plugin_data, plugin_data_len, scrambled_data, scrambled_data_len, &switch_to_auth_protocol, &switch_to_auth_protocol_len, &switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len @@ -377,6 +378,9 @@ mysqlnd_auth_change_user(MYSQLND_CONN_DATA * const conn, const zend_bool silent, const zend_bool use_full_blown_auth_packet, const char * const auth_protocol, + struct st_mysqlnd_authentication_plugin * auth_plugin, + const zend_uchar * const orig_auth_plugin_data, + const size_t orig_auth_plugin_data_len, const zend_uchar * const auth_plugin_data, const size_t auth_plugin_data_len, char ** switch_to_auth_protocol, @@ -442,6 +446,15 @@ mysqlnd_auth_change_user(MYSQLND_CONN_DATA * const conn, PACKET_FREE(&auth_packet); } + if (auth_plugin && auth_plugin->methods.handle_server_response) { + if (FAIL == auth_plugin->methods.handle_server_response(auth_plugin, conn, + orig_auth_plugin_data, orig_auth_plugin_data_len, passwd, passwd_len, + switch_to_auth_protocol, switch_to_auth_protocol_len, + switch_to_auth_protocol_data, switch_to_auth_protocol_data_len)) { + goto end; + } + } + ret = PACKET_READ(conn, &chg_user_resp); COPY_CLIENT_ERROR(conn->error_info, chg_user_resp.error_info); diff --git a/ext/mysqlnd/mysqlnd_auth.h b/ext/mysqlnd/mysqlnd_auth.h index e6673754764..54fac21bdec 100644 --- a/ext/mysqlnd/mysqlnd_auth.h +++ b/ext/mysqlnd/mysqlnd_auth.h @@ -53,6 +53,9 @@ mysqlnd_auth_change_user(MYSQLND_CONN_DATA * const conn, const zend_bool silent, const zend_bool use_full_blown_auth_packet, const char * const auth_protocol, + struct st_mysqlnd_authentication_plugin * auth_plugin, + const zend_uchar * const orig_auth_plugin_data, + const size_t orig_auth_plugin_data_len, const zend_uchar * auth_plugin_data, const size_t auth_plugin_data_len, char ** switch_to_auth_protocol, From 813d4a00b4ce912e58c090f9bb1d0bdd74baf68c Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 27 Dec 2019 16:07:28 +0100 Subject: [PATCH 4/5] Handle error response during caching_sha2_password auth In particular, this fixes handling of expired passwords. --- ext/mysqlnd/mysqlnd_auth.c | 7 +++++++ ext/mysqlnd/mysqlnd_wireprotocol.c | 18 ++++++++++++------ ext/mysqlnd/mysqlnd_wireprotocol.h | 5 +++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c index c4bd53d8c32..2a5924a531d 100644 --- a/ext/mysqlnd/mysqlnd_auth.c +++ b/ext/mysqlnd/mysqlnd_auth.c @@ -1073,6 +1073,13 @@ mysqlnd_caching_sha2_handle_server_response(struct st_mysqlnd_authentication_plu } switch (result_packet.response_code) { + case 0xFF: + if (result_packet.sqlstate[0]) { + strlcpy(conn->error_info->sqlstate, result_packet.sqlstate, sizeof(conn->error_info->sqlstate)); + DBG_ERR_FMT("ERROR:%u [SQLSTATE:%s] %s", result_packet.error_no, result_packet.sqlstate, result_packet.error); + } + SET_CLIENT_ERROR(conn->error_info, result_packet.error_no, UNKNOWN_SQLSTATE, result_packet.error); + DBG_RETURN(FAIL); case 0xFE: DBG_INF("auth switch response"); *new_auth_protocol = result_packet.new_auth_protocol; diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index 928e4f42012..ba289a6fce9 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -2178,7 +2178,6 @@ php_mysqlnd_cached_sha2_result_read(MYSQLND_CONN_DATA * conn, void * _packet) zend_uchar buf[SHA256_PK_REQUEST_RESP_BUFFER_SIZE]; zend_uchar *p = buf; const zend_uchar * const begin = buf; - uint8_t main_response_code; DBG_ENTER("php_mysqlnd_cached_sha2_result_read"); if (FAIL == mysqlnd_read_packet_header_and_body(&(packet->header), pfc, vio, stats, error_info, connection_state, buf, sizeof(buf), "PROT_CACHED_SHA2_RESULT_PACKET", PROT_CACHED_SHA2_RESULT_PACKET)) { @@ -2186,12 +2185,18 @@ php_mysqlnd_cached_sha2_result_read(MYSQLND_CONN_DATA * conn, void * _packet) } BAIL_IF_NO_MORE_DATA; - main_response_code = uint1korr(p); + packet->response_code = uint1korr(p); p++; BAIL_IF_NO_MORE_DATA; - if (0xFE == main_response_code) { - packet->response_code = main_response_code; + if (ERROR_MARKER == packet->response_code) { + php_mysqlnd_read_error_from_line(p, packet->header.size - 1, + packet->error, sizeof(packet->error), + &packet->error_no, packet->sqlstate + ); + DBG_RETURN(PASS); + } + if (0xFE == packet->response_code) { /* Authentication Switch Response */ if (packet->header.size > (size_t) (p - buf)) { packet->new_auth_protocol = mnd_pestrdup((char *)p, FALSE); @@ -2209,10 +2214,11 @@ php_mysqlnd_cached_sha2_result_read(MYSQLND_CONN_DATA * conn, void * _packet) DBG_RETURN(PASS); } - if (0x1 != main_response_code) { - DBG_ERR_FMT("Unexpected response code %d", main_response_code); + if (0x1 != packet->response_code) { + DBG_ERR_FMT("Unexpected response code %d", packet->response_code); } + /* This is not really the response code, but we reuse the field. */ packet->response_code = uint1korr(p); p++; BAIL_IF_NO_MORE_DATA; diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.h b/ext/mysqlnd/mysqlnd_wireprotocol.h index 83ea5557dd5..c4ef4c3dade 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.h +++ b/ext/mysqlnd/mysqlnd_wireprotocol.h @@ -288,10 +288,15 @@ typedef struct st_mysqlnd_packet_cached_sha2_result { uint8_t request; zend_uchar * password; size_t password_len; + /* Used for auth switch request */ char *new_auth_protocol; size_t new_auth_protocol_len; zend_uchar *new_auth_protocol_data; size_t new_auth_protocol_data_len; + /* Used for error result */ + char error[MYSQLND_ERRMSG_SIZE+1]; + char sqlstate[MYSQLND_SQLSTATE_LENGTH + 1]; + unsigned int error_no; } MYSQLND_PACKET_CACHED_SHA2_RESULT; From 32cd373dfd4e5b5710929c534f960694853a02e0 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 27 Dec 2019 16:17:10 +0100 Subject: [PATCH 5/5] Handle empty password fast path in caching_sha2_password If an empty password is used, no additional packets are exchanged during caching_sha2_password auth. We're only looking for an OK/ERR response. --- ext/mysqlnd/mysqlnd_auth.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c index 2a5924a531d..55bdb1d3feb 100644 --- a/ext/mysqlnd/mysqlnd_auth.c +++ b/ext/mysqlnd/mysqlnd_auth.c @@ -1066,8 +1066,13 @@ mysqlnd_caching_sha2_handle_server_response(struct st_mysqlnd_authentication_plu { DBG_ENTER("mysqlnd_caching_sha2_handle_server_response"); MYSQLND_PACKET_CACHED_SHA2_RESULT result_packet; - conn->payload_decoder_factory->m.init_cached_sha2_result_packet(&result_packet); + if (passwd_len == 0) { + DBG_INF("empty password fast path"); + DBG_RETURN(PASS); + } + + conn->payload_decoder_factory->m.init_cached_sha2_result_packet(&result_packet); if (FAIL == PACKET_READ(conn, &result_packet)) { DBG_RETURN(PASS); }