MariaDB · Report #3896671
Connector/C Out-of-bounds read in `unpack_fields()` from short metadata field
How it was found
Summary
unpack_fields() in libmariadb/mariadb_lib.c parses 12 bytes of fixed-header metadata (charsetnr, length, type, flags, decimals) from each column descriptor without checking that the field data is present or long enough. A malicious server can send a truncated metadata field (0-11 bytes instead of the required 12) and trigger out-of-bounds reads in the client process.
Originally reported by: Aisle Research
Environment
- Component: MariaDB Connector/C (
mariadb-connector-c) - Affected versions: All versions through current
3.4branch (verified on commita4a04c52, 82 commits after v3.4.9 tag). The v3.4.9 release is affected. - Platform: All platforms — platform-independent code.
Vulnerability Details
In unpack_fields(), the fixed-header metadata parsing assumes each field's data buffer is at least 12 bytes:
p= (char *)row->data[i];
/* filler */
field->charsetnr= uint2korr(p);
p+= 2;
field->length= (uint) uint4korr(p);
p+= 4;
field->type= (enum enum_field_types)uint1korr(p);
p++;
field->flags= uint2korr(p);
p+= 2;
field->decimals= (uint) p[0];
p++;
/* filler */
p+= 2;
The row loader in mthd_my_read_rows() accepts any non-NULL_LENGTH field length and copies exactly that many bytes:
if ((len=(ulong) net_field_length(&cp)) == NULL_LENGTH) {
cur->data[field] = 0;
} else {
cur->data[field] = to;
if (len > (ulong)(end_to - to) || to > end_to) {
free_rows(result);
...
return(0);
}
memcpy(to,(char*) cp,len); to[len]=0;
...
}
The existing check protects the overall row buffer during the copy, but does not enforce the 12-byte minimum that unpack_fields() assumes. A server that sends a
non-NULL_LENGTH value of 0-11 for the fixed-header field causes unpack_fields() to read past the copied data.
unpack_fields() is called from 3 sites: mysql_read_query_result() (line 3058), mysql_list_fields() (line 3443), and mysql_list_processes() (line 3475) — all reading directly from server TCP packets via ma_net_safe_read().
The bounded-parsing pattern is already established in the codebase: ma_check_buffer_boundaries() (line 162) is a dedicated helper used 8 times in session tracking. CONC-835 (3edd223c) added similar server-data validation for packet reassembly.
Steps to Reproduce
-
Build Connector/C with AddressSanitizer.
-
Set up a protocol shim or modified server that returns otherwise normal column-definition metadata but encodes the fixed-header field with a length of 0 (or any value from 1-11) instead of the expected 12+ bytes.
-
Connect a client and issue any query that triggers metadata parsing.
-
Observe ASan reporting a heap-buffer-overflow read in
unpack_fields()at the fixed-header parsing block.
Suggested Fix
Validate the field data length before the fixed-header parse:
diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c
index 292f1db6..935a1280 100644
--- a/libmariadb/mariadb_lib.c
+++ b/libmariadb/mariadb_lib.c
@@ -1203,6 +1203,18 @@ unpack_fields(const MYSQL *mysql,
}
p= (char *)row->data[i];
+ /* fixed-header field must hold at least 12 bytes */
+ if (!p)
+ goto error;
+ {
+ size_t field_len;
+ if (i + 1 <= data->fields && row->data[i + 1])
+ field_len= (char *)row->data[i + 1] - p - 1;
+ else
+ field_len= 0;
+ if (field_len < 12)
+ goto error;
+ }
/* filler */
field->charsetnr= uint2korr(p);
p+= 2;
Originally reported by: Aisle Research to Red Hat via Embargo process. Aisle research should be attributed for the finding. To my knowledge this report was not shared outside of Aisle Research and Red Hat.
Impact
Impact
- Client-side out-of-bounds read: limited disclosure of adjacent process memory
- Client process crash (segfault or ASan abort)
The attack requires a malicious or compromised server (or MitM position to tamper with protocol traffic). All query types trigger metadata parsing, so any successful connection to a hostile endpoint is exploitable.