mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 06:12:08 +00:00
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:
@@ -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
|
||||
|
||||
112
test/regression/issue/mysql-packet-overflow.test.ts
Normal file
112
test/regression/issue/mysql-packet-overflow.test.ts
Normal 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);
|
||||
Reference in New Issue
Block a user