diff --git a/include/MySQL_Protocol.h b/include/MySQL_Protocol.h index 7b74c6839..946fcc2d5 100644 --- a/include/MySQL_Protocol.h +++ b/include/MySQL_Protocol.h @@ -75,7 +75,8 @@ class MySQL_ResultSet { }; -uint8_t mysql_decode_length(unsigned char *ptr, uint64_t *len); +uint8_t mysql_decode_length(unsigned char *ptr, uint32_t *len); +uint8_t mysql_decode_length_ll(unsigned char *ptr, uint64_t *len); /** * @brief ProxySQL replacement function for 'mysql_stmt_close'. Closes a @@ -232,7 +233,9 @@ class MySQL_Protocol { bool generate_STMT_PREPARE_RESPONSE(uint8_t sequence_id, MySQL_STMT_Global_info *stmt_info, uint32_t _stmt_id=0); void generate_STMT_PREPARE_RESPONSE_OK(uint8_t sequence_id, uint32_t stmt_id); - stmt_execute_metadata_t * get_binds_from_pkt(void *ptr, unsigned int size, MySQL_STMT_Global_info *stmt_info, stmt_execute_metadata_t **stmt_meta); + stmt_execute_metadata_t * get_binds_from_pkt( + PtrSize_t& pkt, MySQL_STMT_Global_info *stmt_info, stmt_execute_metadata_t **stmt_meta + ); bool generate_COM_QUERY_from_COM_FIELD_LIST(PtrSize_t *pkt); diff --git a/include/proxysql_macros.h b/include/proxysql_macros.h index f83cca736..4d1e4f376 100644 --- a/include/proxysql_macros.h +++ b/include/proxysql_macros.h @@ -41,7 +41,7 @@ */ // copy 4 bytes -#define CPY4(x) *((uint32_t *)x) +#define CPY4(x) *((uint32_t *)(x)) // (un)set blocking mode on a file descriptor #define ioctl_FIONBIO(fd, mode) \ diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp index 94cb980f2..f83ff0700 100644 --- a/lib/MySQL_Protocol.cpp +++ b/lib/MySQL_Protocol.cpp @@ -1640,7 +1640,7 @@ bool MySQL_Protocol::PPHR_2(unsigned char *pkt, unsigned int len, bool& ret, MyP if (vars1.capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) { uint64_t passlen64; - int pass_len_enc=mysql_decode_length(pkt,&passlen64); + int pass_len_enc=mysql_decode_length_ll(pkt,&passlen64); vars1.pass_len = passlen64; pkt += pass_len_enc; if (vars1.pass_len > (len - (pkt - vars1._ptr))) { @@ -2672,9 +2672,11 @@ void * MySQL_Protocol::Query_String_to_packet(uint8_t sid, std::string *s, unsig // // returns stmt_meta, or a new one // See https://dev.mysql.com/doc/internals/en/com-stmt-execute.html for reference -stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned int size, MySQL_STMT_Global_info *stmt_info, stmt_execute_metadata_t **stmt_meta) { +stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt( + PtrSize_t& pkt, MySQL_STMT_Global_info *stmt_info, stmt_execute_metadata_t **stmt_meta +) { stmt_execute_metadata_t *ret=NULL; //return NULL in case of failure - if (size<14) { + if (pkt.size < 14) { // some error! return ret; } @@ -2682,7 +2684,7 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned if (num_params==2) { PROXY_TRACE(); } - char *p=(char *)ptr+5; + char *p=(char *)pkt.ptr+5; if (*stmt_meta) { // this PS was executed at least once, and we already have metadata ret=*stmt_meta; } else { // this is the first time that this PS is executed @@ -2700,12 +2702,12 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned // * binds[X].buffer does NOT point to a new allocated buffer // * binds[X].buffer points to offset inside the original packet // FIXME: there is still no free for pkt, so that will be a memory leak that needs to be fixed - ret->pkt=ptr; + ret->pkt=pkt.ptr; uint8_t new_params_bound_flag; if (num_params) { uint16_t i; size_t null_bitmap_length=(num_params+7)/8; - if (size < (14+1+null_bitmap_length)) { + if (pkt.size < (14+1+null_bitmap_length)) { // some data missing? delete ret; return NULL; @@ -2790,6 +2792,14 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned } for (i=0;i static_cast(pkt.ptr) + pkt.size) { + // Required to prevent double-free in dtor + if (ret->pkt) { ret->pkt = NULL; } + // Only free when metadata not obtained from cache (i.e. first execute) + if (!*stmt_meta) { delete ret; } + + return NULL; + } unsigned long *_l = 0; my_bool * _is_null; void *_data = (*myds)->sess->SLDH->get(ret->stmt_id, i, &_l, &_is_null); @@ -2891,11 +2901,21 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned case MYSQL_TYPE_GEOMETRY: { uint8_t l=0; - uint64_t len; + uint32_t len { 0 }; l=mysql_decode_length((unsigned char *)p, &len); if (l>1) { PROXY_TRACE(); } + + if (p + l > static_cast(pkt.ptr) + pkt.size || len > pkt.size) { + // Required to prevent double-free in dtor + if (ret->pkt) { ret->pkt = NULL; } + // Only free when metadata not obtained from cache (i.e. first execute) + if (!*stmt_meta) { delete ret; } + + return NULL; + } + p+=l; binds[i].buffer=p; p+=len; @@ -2926,7 +2946,8 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned #endif */ if (ret) - ret->size=size; + ret->size=pkt.size; + return ret; } diff --git a/lib/MySQL_Query_Cache.cpp b/lib/MySQL_Query_Cache.cpp index 94334e130..81d8d7368 100644 --- a/lib/MySQL_Query_Cache.cpp +++ b/lib/MySQL_Query_Cache.cpp @@ -117,7 +117,7 @@ unsigned char* ok_to_eof_packet(const MySQL_QC_entry_t* entry) { // Find the spot in which the first EOF needs to be placed it += sizeof(mysql_hdr); uint64_t c_count = 0; - int c_count_len = mysql_decode_length(reinterpret_cast(it), &c_count); + int c_count_len = mysql_decode_length_ll(reinterpret_cast(it), &c_count); it += c_count_len; mysql_hdr column_hdr; @@ -196,7 +196,7 @@ bool MySQL_Query_Cache::set(uint64_t user_hash, const unsigned char* kp, uint32_ unsigned char* it = vp; it += sizeof(mysql_hdr); uint64_t c_count = 0; - int c_count_len = mysql_decode_length(const_cast(it), &c_count); + int c_count_len = mysql_decode_length_ll(const_cast(it), &c_count); it += c_count_len; for (uint64_t i = 0; i < c_count; i++) { @@ -230,10 +230,10 @@ bool MySQL_Query_Cache::set(uint64_t user_hash, const unsigned char* kp, uint32_ unsigned char* payload_temp = payload + 1; // skip affected_rows - payload_temp += mysql_decode_length(payload_temp, nullptr); + payload_temp += mysql_decode_length_ll(payload_temp, nullptr); // skip last_insert_id - payload_temp += mysql_decode_length(payload_temp, nullptr); + payload_temp += mysql_decode_length_ll(payload_temp, nullptr); // skip stats_flags payload_temp += sizeof(uint16_t); diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 22b8f19dc..1d328f33f 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -3445,7 +3445,7 @@ void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C if (stmt_meta==NULL) { // we couldn't find any metadata stmt_meta_found=false; } - stmt_meta=client_myds->myprot.get_binds_from_pkt(pkt.ptr,pkt.size,stmt_info, &stmt_meta); + stmt_meta=client_myds->myprot.get_binds_from_pkt(pkt,stmt_info, &stmt_meta); if (stmt_meta==NULL) { l_free(pkt.size,pkt.ptr); client_myds->setDSS_STATE_QUERY_SENT_NET(); diff --git a/lib/MySQL_encode.cpp b/lib/MySQL_encode.cpp index 384a415c2..601824f53 100644 --- a/lib/MySQL_encode.cpp +++ b/lib/MySQL_encode.cpp @@ -221,7 +221,15 @@ uint64_t CPY8(unsigned char *ptr) { * poiter to the variable to store the length * returns the bytes length of th field */ -uint8_t mysql_decode_length(unsigned char *ptr, uint64_t *len) { +uint8_t mysql_decode_length(unsigned char *ptr, uint32_t *len) { + if (*ptr <= 0xfb) { if (len) { *len = CPY1(ptr); }; return 1; } + if (*ptr == 0xfc) { if (len) { *len = CPY2(ptr+1); }; return 3; } + if (*ptr == 0xfd) { if (len) { *len = CPY3(ptr+1); }; return 4; } + if (*ptr == 0xfe) { if (len) { *len = CPY4(ptr+1); }; return 9; } + return 0; // never reaches here +} + +uint8_t mysql_decode_length_ll(unsigned char *ptr, uint64_t *len) { if (*ptr <= 0xfb) { if (len) { *len = CPY1(ptr); }; return 1; } if (*ptr == 0xfc) { if (len) { *len = CPY2(ptr+1); }; return 3; } if (*ptr == 0xfd) { if (len) { *len = CPY3(ptr+1); }; return 4; } diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index 9bc24803b..f972143b2 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -104,6 +104,7 @@ "reg_test_sql_calc_found_rows-t" : [ "default-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ], "reg_test__ssl_client_busy_wait-t" : [ "default-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ], "reg_test_stmt_resultset_err_no_rows_libmysql-t" : [ "default-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ], + "reg_test_stmt_inv_param_offset-t" : [ "default-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ], "reg_test_stmt_resultset_err_no_rows_php-t" : [ "default-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ], "reg_test_stmt_resultset_err_no_rows-t" : [ "default-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ], "savepoint-3749-t" : [ "default-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ], diff --git a/test/tap/tests/reg_test_proclist_use_after_free-t.cpp b/test/tap/tests/reg_test_proclist_use_after_free-t.cpp index ff675c0cd..37e656cdb 100644 --- a/test/tap/tests/reg_test_proclist_use_after_free-t.cpp +++ b/test/tap/tests/reg_test_proclist_use_after_free-t.cpp @@ -35,6 +35,7 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } + MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test"); MYSQL_QUERY_T(proxy, "CREATE TABLE IF NOT EXISTS test.auto_inc_multiplex " "(c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY, c2 VARCHAR(100), c3 VARCHAR(100))" diff --git a/test/tap/tests/reg_test_stmt_inv_param_offset-t.cpp b/test/tap/tests/reg_test_stmt_inv_param_offset-t.cpp new file mode 100644 index 000000000..72307a81b --- /dev/null +++ b/test/tap/tests/reg_test_stmt_inv_param_offset-t.cpp @@ -0,0 +1,426 @@ +/** + * @file reg_test_stmt_inv_param_offset-t.cpp + * @brief Ensures valid handling of malformed COM_STMT_EXECUTE packets. + * @details Checks that the following flows are covered: + * + * - Malformed STMT_EXECUTE (1-param) with mangled UPPER 4-bytes for param-length are executed normally. + * - Malformed STMT_EXECUTE (2-params) with mangled UPPER 4-bytes for param-length are executed normally. + * - Malformed STMT_EXECUTE (1-param) with mangled LOWER 4-bytes for param-length are detected as malformed. + * - Malformed STMT_EXECUTE (2-params) with mangled LOWER 4-bytes for param-length are detected as malformed. + * + * Packets with 1 and 2 params are tested to cover different regression flows: + * + * - Invalid allocation attempt due to big param length (1 param). + * - Invalid memory access due to invalid offset-jump (length) during packet processing (2 >= params). + */ + +#include +#include +#include +#include +#include + +#include "mysql.h" + +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using std::vector; +using std::string; +using std::pair; + +/** + * @brief SUCCESS (Invalid 4 upper-bytes) - Hardcoded STMT_EXECUTE packet header. + */ +unsigned char SUCCESS_FAKE_EXEC_HEADER_1[] = { + 0xff,0xff,0xff,0x00,0x17,0x01,0x00,0x00, 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x01, + 0xfd,0x00,0xfe,0x00,0x00,0x10,0x01,0x00, 0x00,0x10,0x01 // 0x00, 0x00,0x00,0x00 +}; + +/** + * @brief Hardcoded STMT_EXECUTE packet header - continuation. + */ +unsigned char SUCCESS_FAKE_EXEC_HEADER_1_2[] = { + 0x18,0x00,0x10,0x01 +}; + +/** + * @brief SUCCESS (Invalid 4 upper-bytes) - Hardcoded STMT_EXECUTE packet header. + */ +unsigned char SUCCESS_FAKE_EXEC_HEADER_2[] = { + 0xff,0xff,0xff,0x00,0x17,0x02,0x00,0x00, 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x01, + 0xfd,0x00,0x03,0x00,0xfe,0x00,0x00,0x10, 0x01,0x00,0x00,0x10,0x01, // 0x00,0x00,0x00,0x00 +}; + +/** + * @brief Hardcoded STMT_EXECUTE packet header - continuation. + */ +unsigned char SUCCESS_FAKE_EXEC_HEADER_2_1[] = { + 0x1e,0x00,0x10,0x01 +}; + +/** + * @brief FAIL (Invalid 4 upper-bytes) - Hardcoded STMT_EXECUTE packet header. + */ +unsigned char FAIL_FAKE_EXEC_HEADER_1[] = { + 0xff,0xff,0xff,0x00,0x17,0x01,0x00,0x00, 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x01, + 0xfd,0x00,0xfe,0x00,0x00,0x10,0x02,0x00, 0x00,0x10,0x01 +}; + +/** + * @brief Hardcoded STMT_EXECUTE packet header - continuation. + */ +unsigned char FAIL_FAKE_EXEC_HEADER_1_2[] = { + 0x18,0x00,0x10,0x01 +}; + +/** + * @brief FAIL (Invalid 4 upper-bytes) - Hardcoded STMT_EXECUTE packet header. + */ +unsigned char FAIL_FAKE_EXEC_HEADER_2[] = { + 0xff,0xff,0xff,0x00,0x17,0x02,0x00,0x00, 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x01, + 0xfd,0x00,0x03,0x00,0xfe,0x00,0x00,0x10, 0x02,0x00,0x00,0x10,0x01 +}; + +/** + * @brief Hardcoded STMT_EXECUTE packet header - continuation. + */ +unsigned char FAIL_FAKE_EXEC_HEADER_2_1[] = { + 0x1e,0x00,0x10,0x01 +}; + +struct hdr_t { + unsigned char* hdr_1 { nullptr }; + size_t size_1 { 0 }; + unsigned char* hdr_2 { nullptr }; + size_t size_2 { 0 }; +}; + +struct q_params_t { + const char* q { nullptr }; + const vector p {}; + hdr_t hdrs {}; + const vector d {}; +}; + +struct stmt_params_t { + MYSQL_STMT* stmt; + vector p; + hdr_t hdrs {}; + const vector d {}; +}; + +rc_t prepare_stmt(MYSQL* conn, const q_params_t& qp) { + MYSQL_STMT* stmt = mysql_stmt_init(conn); + if (!stmt) { + diag("'mysql_stmt_init' failed error=\"out of memory\""); + return { EXIT_FAILURE, {} }; + } + + diag("Issuing STMT PREPARE query=\"%s\"", qp.q); + int my_err = mysql_stmt_prepare(stmt, qp.q, strlen(qp.q)); + if (my_err) { + diag( + "'mysql_stmt_prepare' failed query=\"%s\" err=\"%d\" msg=\"%s\"", + qp.q, mysql_stmt_errno(stmt), mysql_stmt_error(stmt) + ); + return { EXIT_FAILURE, { stmt, qp.p, qp.hdrs, qp.d } }; + } + + return { EXIT_SUCCESS, { stmt, qp.p, qp.hdrs, qp.d } }; +} + +uint32_t perform_real_execute(const stmt_params_t& sp) { + int myerr = 0; + + if ((myerr=mysql_stmt_bind_param(sp.stmt, const_cast(sp.p.data())))) { + diag("'mysql_stmt_bind_param' failed error=\"%s\"", mysql_stmt_error(sp.stmt)); + return EXIT_FAILURE; + } + + if (mysql_stmt_execute(sp.stmt)) { + diag("'mysql_stmt_execute' failed error=\"%s\"", mysql_stmt_error(sp.stmt)); + return EXIT_FAILURE; + } else { + return EXIT_SUCCESS; + } +} + + +int perform_fake_execute(MYSQL* mysql, const hdr_t& hdr, const vector& data) { + size_t pkt_size_1 { 16 * 1024 * 1024 - 1 + 4 }; + + int rc = write(mysql->net.fd, hdr.hdr_1, hdr.size_1); + diag("1 - Header written to socket len=%d", rc); + if (rc == -1) { return rc; } + + size_t body_size_1 { pkt_size_1 - hdr.size_1 }; + rc = write(mysql->net.fd, data.data(), body_size_1); + diag("1 - Body written to socket len=%d", rc); + if (rc == -1) { return rc; } + + rc = write(mysql->net.fd, hdr.hdr_2, hdr.size_2); + diag("2 - Header written to socket len=%d", rc); + + size_t body_size_2 { data.size() - body_size_1 }; + rc = write(mysql->net.fd, data.data() + body_size_1, body_size_2); + diag("2 - Body written to socket len=%d body_size_2=%ld", rc, body_size_2); + + return rc; +} + +int prepare_server_defaults(const CommandLine& cl) { + MYSQL* proxy = mysql_init(NULL); + + if (!mysql_real_connect(proxy, cl.host, cl.root_username, cl.root_password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy)); + return EXIT_FAILURE; + } + + int rc = 0; + if ((rc=mysql_query_t(proxy, "/* hostgroup=0 */ SET GLOBAL max_allowed_packet=20971520"))) { + diag("'mysql_query' failed error=\"%s\"", mysql_error(proxy)); + } + + mysql_close(proxy); + + return rc; +} + +void check_execute(MYSQL* proxy, const vector& stmts, bool exp_scs) { + size_t exec_count { 0 }; + + for (const stmt_params_t& sp : stmts) { + if (getenv("REG_TEST_STMT_INV_PARAM_OFFSET___REAL_EXECUTE")) { + uint32_t rc = perform_real_execute(sp); + + ok( + rc == EXIT_SUCCESS, + "Real execute for packet capture should succeed error=\"%s\"", + mysql_error(proxy) + ); + } else { + errno = 0; + + int rc = perform_fake_execute(proxy, sp.hdrs, sp.d); + ok ( + rc > 0, + "%ld - Fake EXECUTE written correctly error=\"%s\"", + exec_count, strerror(errno) + ); + + char res_buf[200] { 0 }; // OK ~= 11 bytes || ERR ~= 50 bytes + rc = read(proxy->net.fd, &res_buf, sizeof(res_buf)); + ok ( + rc > 0, + "%ld - Fake EXECUTE response read error=\"%s\" packet_str=\"%s\"", + exec_count, strerror(errno), res_buf + 7 + ); + + bool act_scs = strncasecmp(res_buf + 7, "#28000", strlen("#28000")) != 0; + ok( + act_scs == exp_scs, + "%ld - Fake EXECUTE success should match expected exp_scs=%d act_scs=%d", + exec_count, exp_scs, act_scs + ); + exec_count += 1; + } + } +} + +int main(int argc, char** argv) { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + if (prepare_server_defaults(cl)) { + diag("Failed to prepare server defaults, exiting..."); + return EXIT_FAILURE; + } + + plan(24); + + MYSQL* proxy_1 = mysql_init(NULL); + MYSQL* proxy_2 = mysql_init(NULL); + MYSQL* admin = mysql_init(NULL); + + diag( + "Creating MySQL connection host=\"%s\" port=%d user=\"%s\" pass=\"%s\"", + cl.host, cl.port, cl.username, cl.password + ); + if (!mysql_real_connect(proxy_1, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_1)); + return EXIT_FAILURE; + } + diag( + "Creating MySQL connection host=\"%s\" port=%d user=\"%s\" pass=\"%s\"", + cl.host, cl.port, cl.username, cl.password + ); + if (!mysql_real_connect(proxy_2, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_2)); + return EXIT_FAILURE; + } + + diag( + "Creating Admin MySQL connection host=\"%s\" port=%d user=\"%s\" pass=\"%s\"", + cl.host, cl.admin_port, cl.admin_username, cl.admin_password + ); + if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); + return EXIT_FAILURE; + } + + MYSQL_QUERY_T(proxy_1, "CREATE DATABASE IF NOT EXISTS test"); + + diag("Start_Ting trx in new connection; required for 'max_allowed_packet' conn=%p", proxy_1); + MYSQL_QUERY_T(proxy_1, "/* create_new_connection=1 */ BEGIN"); + + MYSQL_QUERY_T(proxy_2, "USE test"); + diag("Start_Ting trx in new connection; required for 'max_allowed_packet' conn=%p", proxy_2); + MYSQL_QUERY_T(proxy_2, "/* create_new_connection=1 */ BEGIN"); + + MYSQL_QUERY_T(proxy_1, + "CREATE TABLE IF NOT EXISTS test.test_stmt_inv__large_col_1 (" + "id INT AUTO_INCREMENT PRIMARY KEY," + "data_column LONGTEXT" + ")" + ); + + MYSQL_QUERY_T(proxy_1, + "CREATE TABLE IF NOT EXISTS test.test_stmt_inv__large_col_2 (" + "id INT AUTO_INCREMENT PRIMARY KEY," + "data_column LONGTEXT," + "data_int INT" + ")" + ); + + const char* q1 { "INSERT INTO test.test_stmt_inv__large_col_1 (data_column) VALUES (?)" }; + const char* q2 { "INSERT INTO test.test_stmt_inv__large_col_2 (data_column,data_int) VALUES (?,?)" }; + + MYSQL_BIND b1 {}; + vector p1(17 * 1024 * 1024, 'a'); + uint64_t p1_len { p1.size() }; + b1.buffer_type = MYSQL_TYPE_VAR_STRING; + b1.buffer = p1.data(); + b1.buffer_length = p1_len; + b1.is_null = 0; + b1.length = &p1_len; + + MYSQL_BIND b2 {}; + uint64_t p2 { 7 }; + b2.buffer_type = MYSQL_TYPE_LONG; + b2.buffer = reinterpret_cast(&p2); + b2.is_null = 0; + b2.length = 0; + + const hdr_t scs_hdr_1 { + SUCCESS_FAKE_EXEC_HEADER_1, sizeof(SUCCESS_FAKE_EXEC_HEADER_1), + SUCCESS_FAKE_EXEC_HEADER_1_2, sizeof(SUCCESS_FAKE_EXEC_HEADER_1_2) + }; + const hdr_t scs_hdr_2 { + SUCCESS_FAKE_EXEC_HEADER_2, sizeof(SUCCESS_FAKE_EXEC_HEADER_2), + SUCCESS_FAKE_EXEC_HEADER_2_1, sizeof(SUCCESS_FAKE_EXEC_HEADER_2_1) + }; + const hdr_t fail_hdr_1 { + FAIL_FAKE_EXEC_HEADER_1, sizeof(FAIL_FAKE_EXEC_HEADER_1), + FAIL_FAKE_EXEC_HEADER_1_2, sizeof(FAIL_FAKE_EXEC_HEADER_1_2) + }; + const hdr_t fail_hdr_2 { + FAIL_FAKE_EXEC_HEADER_2, sizeof(FAIL_FAKE_EXEC_HEADER_2), + FAIL_FAKE_EXEC_HEADER_2_1, sizeof(FAIL_FAKE_EXEC_HEADER_2_1) + }; + + vector p1_2 {}; + std::copy(std::begin(p1), std::end(p1), std::back_inserter(p1_2)); + vector vp2 { static_cast(p2), 0, 0, 0 }; + std::copy(std::begin(vp2), std::end(vp2), std::back_inserter(p1_2)); + + const vector scs_q_params { + { q1, { b1 }, scs_hdr_1, p1 }, + { q2, { b1, b2 }, scs_hdr_2, p1_2 } + }; + const vector fail_hdrs { fail_hdr_1, fail_hdr_2 }; + vector> scs_stmt_params {}; + + std::transform(scs_q_params.begin(), scs_q_params.end(), std::back_inserter(scs_stmt_params), + [&proxy_1] (const q_params_t& qp) -> rc_t { + return prepare_stmt(proxy_1, qp); + } + ); + + vector> scs_stmt_params_2 {}; + vector> fail_stmt_params_2 {}; + + std::transform(scs_q_params.begin(), scs_q_params.end(), std::back_inserter(scs_stmt_params_2), + [&proxy_2] (const q_params_t& qp) -> rc_t { + return prepare_stmt(proxy_2, qp); + } + ); + + vector v_stmts {}; + vector v_stmts_2 {}; + + for (const auto& sp : scs_stmt_params) { + if (sp.first) { + diag("Exiting due to failed prepare error=\"%s\"", mysql_stmt_error(sp.second.stmt)); + goto cleanup; + } else { + v_stmts.push_back(sp.second); + } + } + for (const auto& sp : scs_stmt_params_2) { + if (sp.first) { + diag("Exiting due to failed prepare error=\"%s\"", mysql_stmt_error(sp.second.stmt)); + goto cleanup; + } else { + v_stmts_2.push_back(sp.second); + } + } + + { + // Check for SUCCESS for payloads with invalid UPPER 4-bytes in string - CONN 1 + check_execute(proxy_1, v_stmts, true); + + for (size_t i = 0; i < scs_stmt_params.size(); i++) { + v_stmts[i].hdrs = fail_hdrs[i]; + } + + // Check for FAILURE for payloads with invalid LOWER 4-bytes in string - CONN 1 + check_execute(proxy_1, v_stmts, false); + + vector v_stmts_2_inv { v_stmts_2 }; + + for (size_t i = 0; i < scs_stmt_params_2.size(); i++) { + v_stmts_2_inv[i].hdrs = fail_hdrs[i]; + } + + // Check for FAILURE for payloads with invalid LOWER 4-bytes in string - CONN 2 + check_execute(proxy_2, v_stmts_2_inv, false); + // Check for SUCCESS for payloads with invalid UPPER 4-bytes in string - CONN 2 + check_execute(proxy_2, v_stmts_2, true); + } + +cleanup: + MYSQL_QUERY_T(proxy_1, "ROLLBACK"); + MYSQL_QUERY_T(proxy_2, "ROLLBACK"); + + MYSQL_QUERY_T(proxy_1, "DELETE FROM test.test_stmt_inv__large_col_1"); + MYSQL_QUERY_T(proxy_1, "DELETE FROM test.test_stmt_inv__large_col_2"); + + for (const auto& sp : scs_stmt_params) { + mysql_stmt_close(sp.second.stmt); + } + for (const auto& sp : scs_stmt_params_2) { + mysql_stmt_close(sp.second.stmt); + } + + mysql_close(proxy_1); + mysql_close(proxy_2); + mysql_close(admin); + + return exit_status(); +}