Merge commit from fork

Fix unterminated HandshakeResponse username parsing
infra-mysql57-binlog
René Cannaò 3 days ago committed by GitHub
commit 49b60b24a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1623,8 +1623,11 @@ int MySQL_Protocol::PPHR_1(unsigned char *pkt, unsigned int len, bool& ret, MyPr
// this function was inline in process_pkt_handshake_response() , split for readibility
bool MySQL_Protocol::PPHR_2(unsigned char *pkt, unsigned int len, bool& ret, MyProt_tmp_auth_vars& vars1) { // process_pkt_handshake_response inner 2
// if packet length is less than 4, it's a malformed packet.
if ((len - sizeof(mysql_hdr)) < 4) return false;
// HandshakeResponse41 requires a 32-byte fixed header before any variable-length fields.
const unsigned int handshake_response_header_len = sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t) + 23;
if (len < sizeof(mysql_hdr) + handshake_response_header_len) return false;
unsigned char* packet_end = vars1._ptr + len;
vars1.capabilities = CPY4(pkt);
// see bug #2916. If CLIENT_MULTI_STATEMENTS is set by the client
@ -1680,12 +1683,28 @@ bool MySQL_Protocol::PPHR_2(unsigned char *pkt, unsigned int len, bool& ret, MyP
// (*myds)->encrypted=true;
// use_ssl=true;
// } else {
unsigned char* user_end = (unsigned char*)memchr(pkt, 0, packet_end - pkt);
if (user_end == NULL) {
ret = false;
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p . malformed username in handshake response\n", (*myds), (*myds)->sess);
return false;
}
vars1.user = pkt;
pkt += strlen((char *)vars1.user) + 1;
pkt = user_end + 1;
if (vars1.capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) {
if (packet_end <= pkt) {
ret = false;
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . missing auth response length in handshake response\n", (*myds), (*myds)->sess, vars1.user);
return false;
}
uint64_t passlen64;
int pass_len_enc=mysql_decode_length_ll(pkt,&passlen64);
if (pass_len_enc <= 0 || static_cast<size_t>(packet_end - pkt) < static_cast<size_t>(pass_len_enc)) {
ret = false;
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . malformed auth response length in handshake response\n", (*myds), (*myds)->sess, vars1.user);
return false;
}
vars1.pass_len = passlen64;
pkt += pass_len_enc;
if (vars1.pass_len > (len - (pkt - vars1._ptr))) {
@ -1694,7 +1713,22 @@ bool MySQL_Protocol::PPHR_2(unsigned char *pkt, unsigned int len, bool& ret, MyP
return false;
}
} else {
vars1.pass_len = (vars1.capabilities & CLIENT_SECURE_CONNECTION ? *pkt++ : strlen((char *)pkt));
if (vars1.capabilities & CLIENT_SECURE_CONNECTION) {
if (packet_end <= pkt) {
ret = false;
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . missing auth response length in handshake response\n", (*myds), (*myds)->sess, vars1.user);
return false;
}
vars1.pass_len = *pkt++;
} else {
unsigned char* pass_end = (unsigned char*)memchr(pkt, 0, packet_end - pkt);
if (pass_end == NULL) {
ret = false;
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . malformed auth response in handshake response\n", (*myds), (*myds)->sess, vars1.user);
return false;
}
vars1.pass_len = pass_end - pkt;
}
if (vars1.pass_len > (len - (pkt - vars1._ptr))) {
ret = false;
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . goto __exit_process_pkt_handshake_response\n", (*myds), (*myds)->sess, vars1.user);
@ -1707,14 +1741,18 @@ bool MySQL_Protocol::PPHR_2(unsigned char *pkt, unsigned int len, bool& ret, MyP
pkt += vars1.pass_len;
if (vars1.capabilities & CLIENT_CONNECT_WITH_DB) {
unsigned int remaining = len - (pkt - vars1._ptr);
vars1.db_tmp = strndup((const char *)pkt, remaining);
unsigned char* db_end = (unsigned char*)memchr(pkt, 0, packet_end - pkt);
if (db_end == NULL) {
ret = false;
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . malformed default schema in handshake response\n", (*myds), (*myds)->sess, vars1.user);
return false;
}
vars1.db_tmp = strndup((const char *)pkt, db_end - pkt);
if (vars1.db_tmp) {
vars1.db = vars1.db_tmp;
}
pkt++;
pkt = db_end + 1;
if (vars1.db) {
pkt+=strlen(vars1.db);
// TODO: Not ideal, but the flow is currently complex. Resource management should be simplified in
// a future rework, so we can 'centralize' the update to the session state with auth results.
userinfo->set_schemaname(vars1.db, strlen(vars1.db));
@ -1728,9 +1766,8 @@ bool MySQL_Protocol::PPHR_2(unsigned char *pkt, unsigned int len, bool& ret, MyP
}
}
unsigned char *extra_pkt = pkt;
if (vars1._ptr+len > extra_pkt) {
if (packet_end > extra_pkt) {
if (vars1.capabilities & CLIENT_PLUGIN_AUTH) {
unsigned char *packet_end = vars1._ptr + len;
if (extra_pkt >= packet_end) {
ret = false;
proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s' . malformed auth plugin offset in handshake response\n", (*myds), (*myds)->sess, vars1.user);

@ -187,6 +187,7 @@
"reg_test_3434-text_stmt_mix-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_3493-USE_with_comment-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_3504-change_user-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_handshake_response_unterminated_username-t" : [ "mysql84-g1" ],
"reg_test_com_change_user_malformed_packet-t" : [ "mysql84-g1" ],
"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" ],

@ -0,0 +1,239 @@
#include <cerrno>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include "mysql.h"
#include "command_line.h"
#include "tap.h"
namespace {
constexpr unsigned char MYSQL_ERR_PACKET = 0xFF;
constexpr uint32_t MALFORMED_USERNAME_LEN = 32;
constexpr uint32_t MYSQL_MAX_PACKET_SIZE = 0x00ffffff;
constexpr uint8_t MYSQL_DEFAULT_CHARSET = 33;
constexpr int SOCKET_TIMEOUT_SEC = 3;
enum class malformed_result_t {
connection_closed,
error_packet,
unexpected_response,
send_failed,
};
bool connect_client(MYSQL* conn, const CommandLine& cl, const char* label) {
if (mysql_real_connect(conn, cl.host, cl.username, cl.password, nullptr, cl.port, nullptr, 0)) {
return true;
}
diag(
"Failed to connect %s host='%s' port=%d user='%s' error='%s'",
label, cl.host, cl.port, cl.username, mysql_error(conn)
);
return false;
}
bool run_select_one(MYSQL* conn) {
if (mysql_query(conn, "SELECT 1")) {
return false;
}
MYSQL_RES* result = mysql_store_result(conn);
if (result == nullptr) {
return false;
}
bool ok_result = false;
if (mysql_num_rows(result) == 1) {
MYSQL_ROW row = mysql_fetch_row(result);
ok_result = (row != nullptr && row[0] != nullptr && strcmp(row[0], "1") == 0);
}
mysql_free_result(result);
return ok_result;
}
int connect_raw_socket(const CommandLine& cl) {
struct addrinfo hints {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo* result = nullptr;
const std::string port_str = std::to_string(cl.port);
const int gai_rc = getaddrinfo(cl.host, port_str.c_str(), &hints, &result);
if (gai_rc != 0) {
diag("Failed to resolve host '%s': %s", cl.host, gai_strerror(gai_rc));
return -1;
}
int sock = -1;
for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sock < 0) {
continue;
}
timeval timeout {};
timeout.tv_sec = SOCKET_TIMEOUT_SEC;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
if (connect(sock, rp->ai_addr, rp->ai_addrlen) == 0) {
break;
}
close(sock);
sock = -1;
}
freeaddrinfo(result);
return sock;
}
bool send_all(int sock, const unsigned char* data, size_t len) {
size_t sent = 0;
while (sent < len) {
const ssize_t rc = send(sock, data + sent, len - sent, 0);
if (rc <= 0) {
return false;
}
sent += rc;
}
return true;
}
std::vector<unsigned char> build_unterminated_username_handshake_response() {
std::vector<unsigned char> payload {};
const uint32_t client_capabilities = CLIENT_PROTOCOL_41 | CLIENT_PLUGIN_AUTH;
payload.push_back(client_capabilities & 0xFF);
payload.push_back((client_capabilities >> 8) & 0xFF);
payload.push_back((client_capabilities >> 16) & 0xFF);
payload.push_back((client_capabilities >> 24) & 0xFF);
payload.push_back(MYSQL_MAX_PACKET_SIZE & 0xFF);
payload.push_back((MYSQL_MAX_PACKET_SIZE >> 8) & 0xFF);
payload.push_back((MYSQL_MAX_PACKET_SIZE >> 16) & 0xFF);
payload.push_back((MYSQL_MAX_PACKET_SIZE >> 24) & 0xFF);
payload.push_back(MYSQL_DEFAULT_CHARSET);
payload.insert(payload.end(), 23, 0);
payload.insert(payload.end(), MALFORMED_USERNAME_LEN, 0xFF);
std::vector<unsigned char> packet {};
const size_t payload_len = payload.size();
packet.reserve(payload_len + 4);
packet.push_back(payload_len & 0xFF);
packet.push_back((payload_len >> 8) & 0xFF);
packet.push_back((payload_len >> 16) & 0xFF);
packet.push_back(1);
packet.insert(packet.end(), payload.begin(), payload.end());
return packet;
}
const char* malformed_result_str(malformed_result_t result) {
switch (result) {
case malformed_result_t::connection_closed:
return "connection_closed";
case malformed_result_t::error_packet:
return "error_packet";
case malformed_result_t::unexpected_response:
return "unexpected_response";
case malformed_result_t::send_failed:
return "send_failed";
}
return "unknown";
}
malformed_result_t send_malformed_handshake_response(const CommandLine& cl, bool& greeting_received) {
greeting_received = false;
const int sock = connect_raw_socket(cl);
if (sock < 0) {
return malformed_result_t::send_failed;
}
unsigned char greeting[512] {};
const ssize_t greeting_len = recv(sock, greeting, sizeof(greeting), 0);
if (greeting_len > 0) {
greeting_received = true;
} else {
close(sock);
return malformed_result_t::send_failed;
}
const std::vector<unsigned char> packet = build_unterminated_username_handshake_response();
if (!send_all(sock, packet.data(), packet.size())) {
close(sock);
return malformed_result_t::send_failed;
}
unsigned char response[256] {};
const ssize_t received = recv(sock, response, sizeof(response), 0);
close(sock);
if (received == 0) {
return malformed_result_t::connection_closed;
}
if (received < 0) {
diag("recv() after malformed handshake failed: errno=%d (%s)", errno, strerror(errno));
return malformed_result_t::unexpected_response;
}
if (received >= 5 && response[4] == MYSQL_ERR_PACKET) {
return malformed_result_t::error_packet;
}
return malformed_result_t::unexpected_response;
}
} // namespace
int main() {
plan(4);
CommandLine cl {};
if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
return EXIT_FAILURE;
}
bool greeting_received = false;
const malformed_result_t malformed_result = send_malformed_handshake_response(cl, greeting_received);
ok(greeting_received, "Received frontend greeting before sending malformed HandshakeResponse41");
ok(
malformed_result == malformed_result_t::connection_closed ||
malformed_result == malformed_result_t::error_packet,
"Malformed HandshakeResponse41 without username terminator is rejected result='%s'",
malformed_result_str(malformed_result)
);
MYSQL* probe = mysql_init(nullptr);
ok(probe != nullptr, "Created probe connection handle after malformed handshake");
bool proxysql_alive = false;
if (probe != nullptr && connect_client(probe, cl, "after malformed HandshakeResponse41")) {
proxysql_alive = run_select_one(probe);
}
ok(proxysql_alive, "ProxySQL remains usable after malformed HandshakeResponse41");
if (probe) {
mysql_close(probe);
}
return exit_status();
}
Loading…
Cancel
Save