MariaDB · Report #3872239
Connector/J: malicious server crashes client JVM via unbounded result-set field-count allocation in ClientMessage.readPacket
How it was found
MariaDB Connector/J parses result-set responses by reading the column count as a length-encoded integer straight from the wire and immediately uses it to size a Java array (new ColumnDecoder[fieldCount]) with no upper bound. A malicious or MITM MariaDB server can reply to any query - or to the driver's own connection-setup query - with a ~10-byte packet declaring a field count of up to 2^31-1, causing the client JVM to attempt a multi-gigabyte allocation and die with java.lang.OutOfMemoryError. The crash is JVM-wide, taking down all pooled connections and application threads.
Root cause
src/main/java/org/mariadb/jdbc/message/ClientMessage.java:
default:
int fieldCount = buf.readIntLengthEncodedNotNull(); // line 282: server-controlled
ColumnDecoder[] ci;
if (context.canSkipMeta() && this.canSkipMeta()) {
...
} else {
ci = new ColumnDecoder[fieldCount]; // line 302: unbounded allocation
for (int i = 0; i < fieldCount; i++) { ... }
}
readIntLengthEncodedNotNull() (ReadableByteBuf.java:148) returns the wire value verbatim. For the 0xfe form it does return (int) readLong(), giving the server full control over the 32-bit integer:
fieldCount = 0x10000000(268M) ->OutOfMemoryError: Java heap spacefieldCount = 0x7fffffff->OutOfMemoryError: Requested array size exceeds VM limitfieldCount = 0xffffffff->NegativeArraySizeException
None are caught inside readPacket. OutOfMemoryError is JVM-wide - it destabilizes the entire client process.
The driver default is sslMode=DISABLE (plaintext, Configuration.java:309), so an on-path attacker can rewrite server responses without controlling the actual database.
Steps To Reproduce
Environment: OpenJDK 21, driver built from HEAD c9377bc (mariadb-java-client-3.5.9.jar).
Step 1 - Start the rogue server (rogue_server.py attached):
python3 rogue_server.py positive 0 &
The server completes a plaintext handshake, OKs the driver's setup query, then answers SELECT 1 with a result-set header declaring fieldCount = 0x10000000.
Step 2 - POSITIVE (attack) - run a standard JDBC client (OomClient.java attached):
java -Xmx256m -cp .:mariadb-java-client-3.5.9.jar OomClient "jdbc:mariadb://127.0.0.1:<port>/test?user=app&password=secret"
Output:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:302)
at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:1438)
at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:1377)
at org.mariadb.jdbc.Statement.executeQuery(Statement.java:137)
at OomClient.main(OomClient.java:22)
client exit code = 1
Connect-time variant (server mode connect - injects on the driver's own setup query):
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:302)
at org.mariadb.jdbc.client.impl.StandardClient.postConnectionQueries(StandardClient.java:816)
at org.mariadb.jdbc.Driver.connect(Driver.java:75)
JVM dies inside DriverManager.getConnection() - no application query, no valid credentials needed.
Step 3 - NEGATIVE CONTROL (same driver, same URL, same SELECT 1, benign 1-column result):
connected; running SELECT 1
RESULT=1
CLEAN_EXIT
client exit code = 0
The only difference is the field-count value in the server's result-set header.
Supporting Material/References
- Vulnerable line:
ClientMessage.java:302(new ColumnDecoder[fieldCount]) - Same-family siblings:
Reader.java:133(unbounded accumulator),OkPacket.java:160(new byte[readIntLengthEncodedNotNull()]) - Accepted sibling: H1 #3835450 (connector-nodejs malicious-server DoS, Medium)
- Attachments:
rogue_server.py,OomClient.java,run_poc.sh,poc_evidence.txt
Impact
A malicious or MITM MariaDB server crashes the connecting client's JVM with OutOfMemoryError using a single small packet. The crash is JVM-wide - it brings down the entire client application, every pooled connection and worker thread, not just one query.
The crash fires during DriverManager.getConnection() (connect-time variant), before any application query and without valid credentials. Any service that opens a JDBC connection to an attacker-influenced endpoint - SSRF-style connection strings, MITM on a plaintext link, a compromised database host, or a hostile replica in a failover topology - is knocked over at connect time.
Preconditions: the client must open a connection whose bytes the attacker controls. The driver default is sslMode=DISABLE (plaintext), so any network-level MITM suffices. No authentication and no application-level query are required.