Cybersecurity News

FunctionCall.Decode Broken Null Sentinel on 64-bit Platforms in pgx/v5 (CWE-697)

64-bit integer overflow vulnerability
64-bit integer overflow vulnerability

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.

Packagegithub.com/jackc/pgx/v5
Filepgproto3/function_call.go
Affected Versionv5.8.0 (current stable)
CWECWE-697 — Incorrect Comparison
CVSS 3.17.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 int is 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.

References