Fix integer overflow in MySQL client when processing malformed packets

When the MySQL client receives a packet with the maximum possible length
(0xFFFFFF), adding PacketHeader.size (4 bytes) causes an integer overflow.
This was causing a panic with "integer overflow" when fuzzing the MySQL
client with malformed packets.

The fix adds a check before buffer allocation to ensure the packet length
is within safe bounds. If a packet exceeds the maximum safe size
(0xFFFFFF - 4), the connection is forcefully closed with an InvalidEncodedLength
error instead of panicking.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2025-09-10 23:11:08 +00:00
parent 73f0594704
commit 46dd722761
2 changed files with 123 additions and 0 deletions

View File

@@ -1144,6 +1144,17 @@ pub fn processPackets(this: *MySQLConnection, comptime Context: type, reader: Ne
const header = PacketHeader.decode(reader.peek()) orelse return AnyMySQLError.Error.ShortRead;
const header_length = header.length;
debug("sequence_id: {d} header: {d}", .{ this.sequence_id, header_length });
// Check for potential integer overflow and invalid packet sizes
// MySQL packets have a max size of 16MB (0xFFFFFF), but we need to prevent overflow
// when adding PacketHeader.size (4 bytes)
const max_safe_packet_size = 0xFFFFFF - PacketHeader.size;
if (header_length > max_safe_packet_size) {
// Malformed packet with impossible length - forcefully close connection
this.fail("Malformed packet: invalid length", AnyMySQLError.Error.InvalidEncodedLength);
return AnyMySQLError.Error.InvalidEncodedLength;
}
// Ensure we have the full packet
reader.ensureCapacity(header_length + PacketHeader.size) catch return AnyMySQLError.Error.ShortRead;
// always skip the full packet, we dont care about padding or unreaded bytes

View File

@@ -0,0 +1,112 @@
import { test, expect } from "bun:test";
import { bunEnv, bunExe, tempDir, normalizeBunSnapshot } from "harness";
import net from "net";
test("MySQL client handles malformed packets with max length without integer overflow", async () => {
// Create a fake MySQL server that sends malformed packets
const server = net.createServer();
await new Promise<void>((resolve) => {
server.listen(0, "127.0.0.1", () => {
resolve();
});
});
const port = (server.address() as net.AddressInfo).port;
let connectionClosed = false;
server.on("connection", (socket) => {
// Send initial MySQL handshake packet
const handshake = Buffer.alloc(78);
handshake[0] = 74; // packet length low
handshake[1] = 0; // packet length mid
handshake[2] = 0; // packet length high
handshake[3] = 0; // sequence id
handshake[4] = 10; // protocol version
handshake.write("5.7.0\x00", 5); // server version
handshake[13] = 0x01; // connection id
// Fill some auth data
for (let i = 14; i < 34; i++) {
handshake[i] = i;
}
handshake[34] = 0x00; // filler
handshake[35] = 0xff; // capability flags
handshake[36] = 0xff;
handshake[37] = 33; // character set
handshake[38] = 0x00; // status flags
handshake[39] = 0x00;
socket.write(handshake);
// After handshake, send a malformed packet with max length
setTimeout(() => {
if (!connectionClosed) {
const malformed = Buffer.alloc(4);
malformed[0] = 0xFF; // packet length low
malformed[1] = 0xFF; // packet length mid
malformed[2] = 0xFF; // packet length high (max 24-bit value = 16777215)
malformed[3] = 1; // sequence id
socket.write(malformed);
}
}, 50);
socket.on("close", () => {
connectionClosed = true;
});
socket.on("error", () => {
// Expected if client closes connection
});
});
// Test script that attempts to connect using Bun's SQL
const testScript = `
import { SQL } from "bun";
const sql = new SQL({
url: "mysql://root:test@127.0.0.1:${port}/test",
max: 1,
connection_timeout: 1000,
});
try {
// Try to execute a query - this should fail due to malformed packet
await sql\`SELECT 1\`;
console.log("Query unexpectedly succeeded");
process.exit(1);
} catch (err) {
// We expect an error due to the malformed packet
console.log("Expected error:", err.code || err.message || "Connection failed");
process.exit(0);
}
`;
using dir = tempDir("mysql-overflow", {});
await Bun.write(dir + "/test.js", testScript);
// Run with the debug build
await using proc = Bun.spawn({
cmd: [bunExe(), "test.js"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
proc.stdout.text(),
proc.stderr.text(),
proc.exited,
]);
server.close();
// The process should NOT panic with integer overflow
expect(stderr).not.toContain("panic");
expect(stderr).not.toContain("integer overflow");
// It should handle the error gracefully
expect(exitCode).toBe(0);
expect(stdout).toContain("Expected error:");
}, 10000);