Change 'COM_STMT_EXECUTE' params length decoding

According to MySQL protocol, variable length strings are encoded using
length encoded integers. For reference, see:

- https://dev.mysql.com/doc/dev/mysql-server/9.4.0/page_protocol_com_stmt_execute.html
- https://dev.mysql.com/doc/dev/mysql-server/9.4.0/page_protocol_basic_dt_integers.html#a_protocol_type_int2

The protocol specifies that values greater than 2^24 (16777216) should
be encoded using '0xFE + 8-byte integer'. Yet, in reality MySQL ignores
the upper section of these 8-byte integers, treating them effectively
like '4-bytes'. For the sake of compatibility this commit changes the
decoding behavior for 'COM_STMT_EXECUTE' to match MySQL one. This
different is subtle but important, since in practice MySQL itself
doesn't use the '8 bytes' from the field. This means that connectors
that are compatible with MySQL could find issues when sending these
packets through ProxySQL (like NodeJS 'mysql2' connector which writes
the 8-bytes as a 4-bytes duplication, motivating these changes),
situation that could result in rejection due to malformed packet
detection (or crashes/invalid handling in the worse case scenario).

The previous decoding function is now renamed into
'mysql_decode_length_ll' to honor MySQL naming 'net_field_length_ll'.
For now, this protocol change is limited to 'COM_STMT_EXECUTE'.
v3.0-stmt_exec_bounds_check
Javier Jaramago Fernández 4 months ago
parent e35973b226
commit 64b16172c7

@ -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

@ -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) \

@ -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))) {
@ -2901,7 +2901,7 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(
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();

@ -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<unsigned char*>(it), &c_count);
int c_count_len = mysql_decode_length_ll(reinterpret_cast<unsigned char*>(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<unsigned char*>(it), &c_count);
int c_count_len = mysql_decode_length_ll(const_cast<unsigned char*>(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);

@ -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; }

Loading…
Cancel
Save