MariaDB · Report #3871740
r2dbc-mariadb Parsec auth: server-controlled PBKDF2 iteration count causes CPU-exhaustion DoS ( regression from b7766de )
How it was found
In mariadb-connector-r2dbc 1.4.1 (HEAD a82d265, all released versions), the Parsec authentication plugin derives its key with PBKDF2 using an iteration count of 1024 << iterations, where iterations is a single byte read directly from the server's ext-salt response and bounded only by the check iterations > 20. A malicious or man-in-the-middle MariaDB server that selects the parsec plugin can set iterations = 20, forcing 1024 << 20 = 1,073,741,824 (~1.07e9) rounds of PBKDF2-HMAC-SHA512 during the handshake, pinning a client CPU core for ~25 minutes per connection attempt before authentication completes. This is a regression introduced by commit b7766de, which loosened the cap from > 3 (old maximum 1024 << 3 = 8192 rounds) to > 20.
Root cause
ParsecPasswordPlugin.java:
64 if (buf.readableBytes() > 2) {
65 firstByte = buf.readByte();
66 iterations = buf.readByte(); // fully server-controlled
67 }
68
69 if (firstByte != 0x50 || iterations > 20) { // only upper bound
70 throw new R2dbcNonTransientResourceException(...);
71 }
...
104 PBEKeySpec spec = new PBEKeySpec(password, salt, 1024 << iterations, 256);
105 SecretKey key = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512").generateSecret(spec);
iterations is taken from the server's ext-salt reply (authMoreData) and is fully attacker-controlled. The parsec plugin is registered in META-INF/services, and the server chooses the authentication plugin during the handshake. restrictedAuth defaults to null (no client-side allow-list), so the driver runs parsec whenever the server asks. The exchange happens before authentication is established. The driver also defaults to sslMode=DISABLE (plaintext), exposing it to an active MITM.
Steps To Reproduce
Environment: JDK 21, the project's own build (mvn -o -DskipTests package producing r2dbc-mariadb-1.4.1.jar).
Proof 1 - End-to-end rogue server (RogueParsecServer.java attached):
A rogue MariaDB server speaks the wire protocol through the parsec ext-salt exchange, then sends a malicious iterations byte. It measures server-side how long the real MariadbConnectionFactory.create() is stuck computing PBKDF2. The same server differs only in that one byte:
- Compile and run the rogue server:
javac -cp r2dbc-mariadb-1.4.1.jar:reactor-core.jar:r2dbc-spi.jar RogueParsecServer.java
java -Djava.security.egd=file:/dev/urandom -cp .:r2dbc-mariadb-1.4.1.jar:reactor-core.jar:r2dbc-spi.jar:reactive-streams.jar:netty-*.jar RogueParsecServer
- Observe the output:
[neg control] ext-salt iterations=3 (1024<<3 = 8192 rounds):
server saw client stuck 160 ms ; client create() returned in 1148 ms
[attack] ext-salt iterations=15 (1024<<15 = 33554432 rounds):
server saw client stuck 47286 ms ; client create() returned in 47604 ms
at the maximum allowed iterations=20 this is 1024<<20 = 1073741824 rounds -> ~25 min.
(real driver logs "Authentication success" -- the full handshake completes after the delay)
A single attacker-chosen byte pinned the real driver's connect for ~47 seconds versus ~160 ms for the iterations=3 control (296x).
Proof 2 - Direct scaling measurement (ParsecPoc.java attached):
Measures the exact lines 104-105 computation across iteration exponents, confirming the 2x-per-increment law:
iterations rounds wall_ms
3 8192 231 <- negative control (old cap > 3)
10 1048576 1518
13 8388608 11745
15 33554432 45113
extrapolation to iterations=20 (rounds=1073741824): ~1503360 ms (~25.1 min)
Real ParsecPasswordPlugin.next() at iterations=13: 11753 ms (81x the iterations=3 control of 145 ms).
Supporting Material/References
- Vulnerable file:
src/main/java/org/mariadb/r2dbc/authentication/standard/ParsecPasswordPlugin.java:104 - Regression commit: b7766de (loosened cap from
> 3to> 20) - Accepted sibling: H1 #3835450 (mariadb-connector-nodejs DoS, Medium)
- Attachments:
RogueParsecServer.java,ParsecPoc.java,poc_evidence.txt
Impact
A malicious or MITM MariaDB server pins a client CPU core for minutes per connection attempt, unauthenticated and repeatable. Behind a connection pool that retries failed connections, this becomes sustained whole-application CPU exhaustion and denial of service. The asymmetric amplification is extreme: a single attacker-chosen byte forces ~1.07 billion PBKDF2-HMAC-SHA512 rounds (~25 minutes of CPU). No valid credentials are needed, and the default sslMode=DISABLE exposes the handshake to network-level MITM. The fix is to restore a small hard cap on the iteration exponent (e.g. iterations > 3), matching the pre-b7766de behavior.