If you’re building Go apps with PostgreSQL, you’ve probably used pgx, one of the most popular drivers out there. But did you know that, until early 2024, a single overlooked bug meant a huge SQL injection risk—thanks to how pgx handled gigantic queries?

Let’s break down CVE-2024-27304, see some code, understand how to exploit it, and (most importantly) how to keep your code safe.

What is CVE-2024-27304?

In short, if an attacker could get your app to send a single query (or bind message) over 4GB in size, they could trigger an integer overflow in the pgx driver. This bug means that instead of the message being sent as one big chunk, it’s split into smaller chunks of the attacker’s choice. That gives the attacker control over message boundaries—crucial for SQL injection attacks!

pgx v5.5.4

Original advisory:
- GitHub Security Advisory GHSA-m7rv-6wwj-97f5
- NVD Entry for CVE-2024-27304

How Did It Happen?

When pgx prepares to send a query, it calculates the message size using a 32-bit integer (which maxes out at 4,294,967,295 bytes—just under 4GB). If your message is bigger, adding more data causes that value to wrap around to zero and start again. Suddenly, pgx thinks your 4.1GB message is only 100MB!

Imagine pgx is packing your query...

// Pseudocode
size := len(query) + len(args) + ... // All in bytes, as int32

if size > MAX_SIZE {
    // supposed to throw error, but doesn't catch overflow
}

If len(query) is huge, size goes negative (or wraps), but there’s no proper check—the driver just keeps going.

Say, you take user input and run it through pgx

// Dangerous! Do not use in production
userInput := r.FormValue("username")
query := fmt.Sprintf("SELECT * FROM users WHERE username='%s';", userInput)
row := pgxConn.QueryRow(context.Background(), query)

Normal injection is an issue here, but what if the input is *so big* it busts the 4GB mark? Now, with CVE-2024-27304, the message size wraps, and the attacker can slip in additional protocol messages—not just SQL commands. That means they could change the logic, inject totally new SQL statements, or even impersonate other users. It’s like smuggling dangerous packages past the guards because your labeled box’s weight “overflowed” the scale!

Proof-of-Concept (Simplified)

Due to memory constraints, actually sending a 4GB payload is tricky in demos, but here’s what it might look like:

// Warning: For educational purposes ONLY

hugePayload := strings.Repeat("a", x100000000) // 4GB
maliciousSQL := hugePayload + "'); DROP TABLE users; --"

query := "SELECT * FROM users WHERE username='" + maliciousSQL + "';"
_, err := conn.Exec(context.Background(), query)
if err != nil {
    log.Fatal(err)
}

If your app builds queries like this, the driver’s integer overflow could split this up in ways that let the attacker inject actual new protocol messages—the technical details here get deep, but the core idea is, they control more than they should.

How to Stay Safe

Upgrade

Upgrade to v5.5.4 or later (for v5.x users)

go get github.com/jackc/pgx/v4@v4.18.2
# or for v5
go get github.com/jackc/pgx/v5@v5.5.4

Workaround

If for some reason you can’t upgrade, validate input sizes before they reach pgx

const MaxQuerySize = 4 * 1024 * 1024 * 1024 // 4GB

func isSafeQuery(query string) bool {
    return len(query) < MaxQuerySize
}

Best Practice

Always use parameterized queries!

userInput := r.FormValue("username")
row := pgxConn.QueryRow(context.Background(), "SELECT * FROM users WHERE username=$1;", userInput)

Final Words

CVE-2024-27304 is a classic reminder that even robust libraries can suffer from “big number” bugs. Attackers love edge cases like integer overflows and boundary wrapping—so keep your dependencies up to date, and always code defensively.

Further Reading

- pgx GitHub repo
- CVE-2024-27304 security advisory
- PostgreSQL protocol basics

Timeline

Published on: 03/06/2024 19:15:08 UTC
Last modified on: 03/06/2024 21:42:48 UTC