mirror of https://github.com/sysown/proxysql
Merge pull request #5188 from sysown/v3.0-stmt_exec_bounds_check
Improve handling of malformed `COM_STMT_EXECUTE` packetsv3.0_add_delay_tap_test v3.0.3
commit
cce161bc74
@ -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 <cstring>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <utility>
|
||||
|
||||
#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<MYSQL_BIND> p {};
|
||||
hdr_t hdrs {};
|
||||
const vector<char> d {};
|
||||
};
|
||||
|
||||
struct stmt_params_t {
|
||||
MYSQL_STMT* stmt;
|
||||
vector<MYSQL_BIND> p;
|
||||
hdr_t hdrs {};
|
||||
const vector<char> d {};
|
||||
};
|
||||
|
||||
rc_t<stmt_params_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<MYSQL_BIND*>(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<char>& 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<stmt_params_t>& 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<char> 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<char*>(&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<char> p1_2 {};
|
||||
std::copy(std::begin(p1), std::end(p1), std::back_inserter(p1_2));
|
||||
vector<char> vp2 { static_cast<char>(p2), 0, 0, 0 };
|
||||
std::copy(std::begin(vp2), std::end(vp2), std::back_inserter(p1_2));
|
||||
|
||||
const vector<q_params_t> scs_q_params {
|
||||
{ q1, { b1 }, scs_hdr_1, p1 },
|
||||
{ q2, { b1, b2 }, scs_hdr_2, p1_2 }
|
||||
};
|
||||
const vector<hdr_t> fail_hdrs { fail_hdr_1, fail_hdr_2 };
|
||||
vector<rc_t<stmt_params_t>> 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<stmt_params_t> {
|
||||
return prepare_stmt(proxy_1, qp);
|
||||
}
|
||||
);
|
||||
|
||||
vector<rc_t<stmt_params_t>> scs_stmt_params_2 {};
|
||||
vector<rc_t<stmt_params_t>> 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<stmt_params_t> {
|
||||
return prepare_stmt(proxy_2, qp);
|
||||
}
|
||||
);
|
||||
|
||||
vector<stmt_params_t> v_stmts {};
|
||||
vector<stmt_params_t> 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<var> - 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<var> - CONN 1
|
||||
check_execute(proxy_1, v_stmts, false);
|
||||
|
||||
vector<stmt_params_t> 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<var> - CONN 2
|
||||
check_execute(proxy_2, v_stmts_2_inv, false);
|
||||
// Check for SUCCESS for payloads with invalid UPPER 4-bytes in string<var> - 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();
|
||||
}
|
||||
Loading…
Reference in new issue