Cybersecurity News

Bind.Decode Negative Parameter Length Bypass in pgx/v5 (CWE-129)

Memory safety vulnerability in Go code
Memory safety vulnerability in Go code

Summary

github.com/jackc/pgx/v5 v5.8.0 contains a memory-safety vulnerability in the inlined pgproto3 package. A server-controlled Bind message can crash the client process immediately via an unrecoverable runtime panic.

Packagegithub.com/jackc/pgx/v5
Filepgproto3/bind.go
Affected Versionv5.8.0 (current stable)
CWECWE-129 — Improper Validation of Array Index
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

The parameter length field is read correctly as a signed integer:

msgSize := int(int32(binary.BigEndian.Uint32(src[rp:])))

The null sentinel check (msgSize == -1) is correct. However, the bounds check that follows is:

if len(src[rp:]) < msgSize {
    return &invalidMessageFormatErr{...}
}

len() always returns a non-negative value. When msgSize is any negative value other than -1, the comparison is always false and the guard is bypassed entirely. Execution continues to:

dst.Parameters[i] = src[rp : rp+msgSize]

A negative slice upper bound causes an immediate, unrecoverable runtime panic:

panic: runtime error: slice bounds out of range [:-13619142]

Root Cause

The fix for this exact class of bug is already present in DataRow.Decode within the same package:

} else if valueLen < 0 {
    return &invalidMessageFormatErr{messageType: "DataRow"}
}

The guard was applied to DataRow.Decode but not to Bind.Decode — a copy-paste omission that left the analogous code path unprotected.

Proof of Concept

A malicious or compromised PostgreSQL server sends a Bind message with a parameter length field set to any negative value other than -1 (for example 0xFFFFEB7A which decodes as int32 = -5254). The pgx client decodes the message, bypasses the bounds check, and panics immediately.

// Crafted wire bytes: 1 parameter, length = 0xFFFFEB7A
payload := []byte{
    0x00, 0x00, 0x00, 0x00, // destination portal (empty string)
    0x00, 0x00, 0x00, 0x00, // prepared statement (empty string)
    0x00, 0x00,             // format codes count = 0
    0x00, 0x01,             // parameter count = 1
    0xFF, 0xFF, 0xEB, 0x7A, // parameter length = -5254 (not -1)
    // no further bytes -- bounds check bypassed, panic triggered
}

Fix

A single additional condition in the existing bounds check:

// Before:
if len(src[rp:]) < msgSize {

// After:
if msgSize < 0 || len(src[rp:]) < msgSize {

This is directly consistent with the guard already present in DataRow.Decode.

Impact

  • Any pgx/v5 client connecting to an attacker-controlled server is subject to immediate process termination.
  • In microservice architectures where the database DSN can be influenced via environment variable injection, DNS poisoning, or SSRF to an internal endpoint, this enables unauthenticated denial-of-service.
  • The panic is unrecoverable from normal application code unless every database call is wrapped in a recover() goroutine, which is non-standard in Go applications.
  • No authentication, credentials, or prior access to the client are required — only the ability to route the client to an attacker-controlled PostgreSQL endpoint.

References