diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 3a1bf4e09..8b5ce75b6 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -9448,10 +9448,19 @@ void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_SEND_LONG_DATA(PtrSize_t& pkt) { - // FIXME: no input validation + if (pkt.size < 11) { + proxy_warning( + "Received malformed COM_STMT_SEND_LONG_DATA packet of %lu bytes\n", + static_cast(pkt.size) + ); + client_myds->DSS=STATE_SLEEP; + status=WAITING_CLIENT_DATA; + l_free(pkt.size,pkt.ptr); + return; + } uint32_t stmt_global_id=0; memcpy(&stmt_global_id,(char *)pkt.ptr+5,sizeof(uint32_t)); - uint32_t stmt_param_id=0; + uint16_t stmt_param_id=0; memcpy(&stmt_param_id,(char *)pkt.ptr+9,sizeof(uint16_t)); SLDH->add(stmt_global_id,stmt_param_id,(char *)pkt.ptr+11,pkt.size-11); client_myds->DSS=STATE_SLEEP; diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index c0c5ab6c1..8a5948df4 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -190,6 +190,7 @@ "reg_test_3546-stmt_empty_params-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ], "reg_test_3549-autocommit_tracking-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ], "reg_test_3585-stmt_metadata-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ], + "reg_test_stmt_send_long_data_short_packet-t" : [ "mysql84-g1" ], "reg_test_3591-restapi_num_fds-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ], "reg_test_3603-stmt_metadata-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ], "reg_test_3606-mysql_warnings-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ], diff --git a/test/tap/tests/reg_test_stmt_send_long_data_short_packet-t.cpp b/test/tap/tests/reg_test_stmt_send_long_data_short_packet-t.cpp new file mode 100644 index 000000000..751667984 --- /dev/null +++ b/test/tap/tests/reg_test_stmt_send_long_data_short_packet-t.cpp @@ -0,0 +1,162 @@ +#include +#include +#include +#include + +#include +#include + +#include "mysql.h" + +#include "tap.h" +#include "command_line.h" + +namespace { + +constexpr unsigned char kComStmtSendLongData = 0x18; +constexpr unsigned char kMalformedStmtSendLongDataPacket[] = { + 0x01, 0x00, 0x00, 0x00, + kComStmtSendLongData +}; + +MYSQL* connect_proxy(const CommandLine& cl) { + MYSQL* mysql = mysql_init(nullptr); + if (mysql == nullptr) { + return nullptr; + } + + if (!mysql_real_connect(mysql, cl.host, cl.username, cl.password, nullptr, cl.port, nullptr, 0)) { + mysql_close(mysql); + return nullptr; + } + + return mysql; +} + +bool query_returns_one(MYSQL* mysql) { + if (mysql_query(mysql, "SELECT 1")) { + return false; + } + + MYSQL_RES* result = mysql_store_result(mysql); + if (result == nullptr) { + return false; + } + + MYSQL_ROW row = mysql_fetch_row(result); + const bool ok = row != nullptr && row[0] != nullptr && strcmp(row[0], "1") == 0; + mysql_free_result(result); + return ok; +} + +bool prepared_select_returns_42(MYSQL* mysql) { + MYSQL_STMT* stmt = mysql_stmt_init(mysql); + if (stmt == nullptr) { + return false; + } + + const char query[] = "SELECT 42"; + if (mysql_stmt_prepare(stmt, query, sizeof(query) - 1)) { + mysql_stmt_close(stmt); + return false; + } + + if (mysql_stmt_execute(stmt)) { + mysql_stmt_close(stmt); + return false; + } + + int value = 0; + unsigned long value_len = 0; + my_bool is_null = 0; + + MYSQL_BIND bind {}; + bind.buffer_type = MYSQL_TYPE_LONG; + bind.buffer = &value; + bind.buffer_length = sizeof(value); + bind.length = &value_len; + bind.is_null = &is_null; + + if (mysql_stmt_bind_result(stmt, &bind)) { + mysql_stmt_close(stmt); + return false; + } + + if (mysql_stmt_store_result(stmt)) { + mysql_stmt_close(stmt); + return false; + } + + const int fetch_rc = mysql_stmt_fetch(stmt); + const bool ok = fetch_rc == 0 && is_null == 0 && value == 42; + mysql_stmt_free_result(stmt); + mysql_stmt_close(stmt); + return ok; +} + +bool send_all(int fd, const unsigned char* data, size_t len) { + size_t total_sent = 0; + + while (total_sent < len) { + const ssize_t sent = send(fd, data + total_sent, len - total_sent, MSG_NOSIGNAL); + if (sent <= 0) { + return false; + } + total_sent += sent; + } + + return true; +} + +bool send_malformed_stmt_send_long_data(MYSQL* mysql) { + return send_all(mysql->net.fd, kMalformedStmtSendLongDataPacket, sizeof(kMalformedStmtSendLongDataPacket)); +} + +} // namespace + +int main(int argc, char** argv) { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + plan(6); + + MYSQL* attack = connect_proxy(cl); + ok(attack != nullptr, "Authenticated frontend connection succeeds"); + if (attack == nullptr) { + skip(5, "Cannot continue without an authenticated frontend connection"); + return exit_status(); + } + + ok(query_returns_one(attack), "Baseline query succeeds before malformed COM_STMT_SEND_LONG_DATA"); + + ok( + send_malformed_stmt_send_long_data(attack), + "Malformed COM_STMT_SEND_LONG_DATA packet is written to the live connection" + ); + + usleep(200000); + + ok( + prepared_select_returns_42(attack), + "Prepared statements still execute after malformed COM_STMT_SEND_LONG_DATA" + ); + + ok( + query_returns_one(attack), + "Same connection still serves queries after malformed COM_STMT_SEND_LONG_DATA" + ); + + MYSQL* fresh = connect_proxy(cl); + ok(fresh != nullptr && query_returns_one(fresh), "Fresh connection still succeeds after malformed COM_STMT_SEND_LONG_DATA"); + + if (fresh != nullptr) { + mysql_close(fresh); + } + mysql_close(attack); + + return exit_status(); +}