FunctionCall.Decode Broken Null Sentinel on 64-bit Platforms in pgx/v5 (CWE-697)
Summary
github.com/jackc/pgx/v5 v5.8.0 contains a memory-safety vulnerability in the FunctionCall.Decode function of the inlined pgproto3 package. A missing int32() cast makes the null sentinel dead code on every 64-bit machine, meaning any legitimate PostgreSQL server that sends a null function argument will crash the client process immediately.
| Package | github.com/jackc/pgx/v5 |
| File | pgproto3/function_call.go |
| Affected Version | v5.8.0 (current stable) |
| CWE | CWE-697 — Incorrect Comparison |
| CVSS 3.1 | 7.5 High — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
Vulnerability Details
Every other length-field read in the pgproto3 package uses this pattern to preserve the sign of the value:
// Correct pattern used everywhere else:
foo := int(int32(binary.BigEndian.Uint32(src[rp:])))
In FunctionCall.Decode, the intermediate int32() cast is absent:
// Vulnerable line in function_call.go:
argumentLength := int(binary.BigEndian.Uint32(src[rp:]))
On a 64-bit platform, int is 64 bits wide. The conversion chain is:
uint32(0xFFFFFFFF) = 4,294,967,295
int(uint32(0xFFFFFFFF)) = 4,294,967,295 // 64-bit: positive, NOT -1
int(int32(uint32(0xFFFFFFFF))) = -1 // correct interpretation
The PostgreSQL wire protocol uses 0xFFFFFFFF as the standard null sentinel for length fields. The null check that follows is therefore dead code on every 64-bit machine:
if argumentLength == -1 {
// treat as SQL NULL -- NEVER reached on 64-bit
src = append(src, 0)
}
Execution falls through to the slice operation with a massive upper bound:
argumentValue := src[rp : rp+argumentLength]
// rp+4294967295 vastly exceeds capacity -- immediate panic
The resulting panic:
panic: runtime error: slice bounds out of range [:4294967307] with capacity 14
Why It Matters Beyond Security
Unlike Bug 1 (Bind.Decode), this vulnerability does not require a malicious server. Any legitimate PostgreSQL server that sends a null argument in a FunctionCall response will trigger this panic. This makes it a correctness bug as well as a security vulnerability:
- Production databases that use stored procedures or functions with nullable parameters will crash any pgx/v5 client on the first call that returns a null argument.
- The bug is silent — it only manifests at runtime when a null is actually returned, not at compile time or during testing with non-null values.
- All 64-bit deployments (the vast majority of production Go services) are affected. 32-bit platforms are not affected because
intis 32 bits and the cast is a no-op.
Proof of Concept
Connect to a real PostgreSQL server and call any function that returns a null argument. For example:
-- PostgreSQL server side: create a function with a null argument
CREATE OR REPLACE FUNCTION test_null_arg(x INT DEFAULT NULL)
RETURNS INT AS $$ SELECT x $$ LANGUAGE sql;
-- Calling this function via the FunctionCall protocol path causes
-- the pgx client to panic immediately on 64-bit platforms.
Alternatively, a malicious server can craft a FunctionCallResponse message with argument length 0xFFFFFFFF. No authentication with the server is needed if the client is misconfigured to connect to an attacker-controlled endpoint.
Fix
Restore the missing cast — a one-token change:
// Before (broken on 64-bit):
argumentLength := int(binary.BigEndian.Uint32(src[rp:]))
// After (correct, consistent with rest of package):
argumentLength := int(int32(binary.BigEndian.Uint32(src[rp:])))
Adding the int32() intermediate cast restores sign extension, making 0xFFFFFFFF evaluate to -1 as intended, and the null sentinel check becomes reachable again.
Impact
- Correctness: Any call to a PostgreSQL stored function that passes or returns a null argument crashes the client on 64-bit platforms. This is a regression for any application using pgx/v5 with nullable function arguments.
- Security: An attacker-controlled server can reliably crash any pgx/v5 client process. Combined with an SSRF or DNS rebinding attack, this enables unauthenticated remote denial-of-service against Go services using pgx/v5.
- Scope: All 64-bit deployments running pgx/v5 v5.8.0 are affected. This covers the overwhelming majority of production Go services.
- No workaround: There is no application-level workaround short of patching the library. The panic occurs deep inside the wire protocol decoder before the application has a chance to handle errors.