MariaDB · Report #3871977
Connector/ODBC: malicious server's long version string wraps size_t PrefixLen via negative _snprintf return, causing heap out-of-bounds write
How it was found
MariaDB Connector/ODBC formats every diagnostic message with a prefix that embeds the server version string. The prefix is built in MADB_PutErrorPrefix (driver/ma_error.cpp:165) with the driver's custom _snprintf, which returns a NEGATIVE value when the output is truncated (driver/ma_platform_posix.cpp:41). The return value is added to error->PrefixLen, a size_t (unsigned, driver/ma_c_stuff.h:70). A malicious or MITM MariaDB server that sends a handshake server_version string longer than ~500 bytes causes _snprintf to truncate and return negative, wrapping PrefixLen to ~2^64. Every subsequent use of PrefixLen as an offset into the fixed 513-byte SqlErrorMsg buffer then writes out of bounds at a server-chosen backward offset.
This is an unauthenticated, remotely triggered client-side memory corruption that fires as soon as the application allocates a statement on the poisoned connection. Confirmed end-to-end with ASAN against the real driver built from HEAD 4e175d9 (v3.2.10).
Root cause
- Custom
_snprintfreturns negative on truncation (driver/ma_platform_posix.cpp:41):
int _snprintf(char *buffer, size_t count, const char *format, ...) {
int result = vsnprintf(buffer, count, format, list);
if (result > count) { return count - result - 1; } // NEGATIVE
return result;
}
MADB_PutErrorPrefixadds the negative return to asize_t(driver/ma_error.cpp:155-170):
error->PrefixLen = strlen(MARIADB_ODBC_ERR_PREFIX); // "[ma-3.2.10]" ~ 11
strcpy_s(error->SqlErrorMsg, SQL_MAX_MESSAGE_LENGTH + 1, MARIADB_ODBC_ERR_PREFIX);
if (dbc != NULL && dbc->mariadb != NULL) {
error->PrefixLen += _snprintf(error->SqlErrorMsg + error->PrefixLen,
SQL_MAX_MESSAGE_LENGTH + 1 - error->PrefixLen,
"[%s]", mysql_get_server_info(dbc->mariadb));
// ^^ attacker-controlled, uncapped version string from handshake
}
With a version string of length L > ~500, the _snprintf return is negative, and PrefixLen += <negative> wraps to approximately 2^64 - (L - 510). The backward distance is chosen by the attacker via the version-string length.
- Two OOB write sinks use the corrupted
PrefixLen:
MADB_CLEAR_ERROR/MA_ClearError(driver/ma_error.h:168, driver/ma_api_internal.cpp:58):SqlErrorMsg[PrefixLen] = 0;- one-byte OOB writeMADB_SetNativeError(driver/ma_error.cpp:231,235):strcpy_s(SqlErrorMsg + PrefixLen, 513 - PrefixLen, msg)- multi-byte OOB write with server-controlled error text
Steps To Reproduce
Environment: Linux x86-64, unixODBC 2.3.14, driver built from HEAD 4e175d9 with AddressSanitizer (cmake -DWITH_ASAN=ON).
Step 0 - Confirm libmariadb exposes the long version (direct_version_check.c attached):
$ ./direct_version_check 3402 # rogue server version_len=2000
server_info_len=2000 first16=AAAAAAAAAAAAAAAA
mysql_get_server_info() returns the full 2000-byte handshake version uncapped.
Step 1 - Start the rogue server (rogue_server.py attached):
python3 rogue_server.py 3404 4000 & # port 3404, version string = 4000 bytes
The rogue server speaks enough MariaDB wire protocol for the connection to succeed and advertises a 4000-byte server_version in the handshake.
Step 2 - POSITIVE (attack) - real ASAN-built driver + real unixODBC (odbc_client.c attached):
./odbc_client "DRIVER=.../build_asan/driver/libmaodbc.so;SERVER=127.0.0.1;PORT=3404;UID=u;PWD=p;SSLVERIFY=0;"
Output:
SQLDriverConnect r=0 <- connection SUCCEEDS
allocating statement (MADB_StmtInit -> PutErrorPrefix)
==ERROR: AddressSanitizer: heap-buffer-overflow ... WRITE of size 1
#0 MA_ClearError(short, void*) driver/ma_api_internal.cpp:58
#1 SQLGetStmtAttr driver/odbc_3_api.cpp:997
#2 libodbc.so.2 (unixODBC Driver Manager)
#3 main odbc_client.c:32
The OOB write fires at statement allocation. No application query needed.
Step 3 - NEGATIVE CONTROL - identical path, normal version string (20 bytes):
python3 rogue_server.py 3405 20 &
./odbc_client "DRIVER=...;SERVER=127.0.0.1;PORT=3405;UID=u;PWD=p;SSLVERIFY=0;"
Output:
SQLDriverConnect r=0
SQLAllocHandle(STMT) r=0
SQLExecDirect r=0 (survived)
clean exit EXIT=0
Normal-length version string causes no corruption. The long version is the sole difference.
Automated reproduction: bash run_poc.sh (attached) builds the driver with ASAN, starts the rogue server, runs both positive and negative cases.
Supporting Material/References
- Vulnerable function:
MADB_PutErrorPrefixat driver/ma_error.cpp:165 - Negative-return source:
_snprintfat driver/ma_platform_posix.cpp:41 PrefixLentype:size_tat driver/ma_c_stuff.h:70- OOB sink 1:
MA_ClearErrorat driver/ma_api_internal.cpp:58 - OOB sink 2:
MADB_SetNativeErrorat driver/ma_error.cpp:231 - Trigger path:
MADB_StmtInitat driver/ma_statement.cpp:4113 - The same
_snprintfnegative-return footgun exists inMADB_DsnToString(Windows prompt path) and catalog-query builders - should be reviewed as part of the same fix - Attachments:
rogue_server.py,odbc_client.c,direct_version_check.c,harness.cpp,run_poc.sh,poc_evidence.txt
Impact
A malicious or MITM MariaDB/MySQL server corrupts client-process memory (out-of-bounds write) in any application using MariaDB Connector/ODBC, the moment it opens a connection and allocates a statement. The demonstrated effect is a reliable crash (denial of service) via an AddressSanitizer-confirmed heap out-of-bounds write.
The write offset is chosen by the attacker through the version-string length, and the strcpy_s sink (MADB_SetNativeError) copies attacker-influenced content (server error text) to that attacker-chosen backward offset, so corruption of adjacent heap objects - and potentially impact beyond DoS - is plausible on non-hardened builds. Only the crash is demonstrated here.
Blast radius: every application (BI tools, ETL jobs, application servers, isql, etc.) that connects through MariaDB Connector/ODBC. Preconditions: (a) client connects to a server the attacker controls or can MITM (driver default is plaintext - no TLS), and (b) the application allocates a statement, which every ODBC workflow does. No credentials, privileges, or human interaction beyond initiating the connection are required.