Skip to content

pgtype: NUMERIC binary decode hangs/infinite loop for large-weight values (misuses dscale) #2415

@iskakaushik

Description

@iskakaushik

Describe the bug

Decoding certain valid PostgreSQL NUMERIC values in binary format can hang or do excessive work (and infinite loop). Inputs with very large weight and small dscale trigger:

  • int16 overflow in an intermediate calculation
  • huge loops rescaling by powers of 10
  • a possible infinite loop when the accumulated integer becomes zero

Root cause: the decoder attempts to rescale to the wire dscale and uses int16 math. PostgreSQL’s numeric_recv does not rescale to dscale (it’s display-only).

To Reproduce

Steps:

  1. Use pgtype to decode the following binary NUMERIC payload.
  2. It represents ndigits=2, weight=11134, sign=0, dscale=1, digits=[6,7000].

Runnable example (no DB needed):

package main

import (
    "encoding/base64"
    "fmt"
    "time"

    "github.com/jackc/pgx/v5"
    "github.com/jackc/pgx/v5/pgtype"
)

func main() {
    const b64 = "AAIrfgAAAAEABhtY" // ndigits=2, weight=11134, sign=0, dscale=1, digits=[6,7000]
    payload, _ := base64.StdEncoding.DecodeString(b64)

    m := pgtype.NewMap()
    var n pgtype.Numeric

    done := make(chan error, 1)
    go func() { done <- m.Scan(pgtype.NumericOID, pgx.BinaryFormatCode, payload, &n) }()

    select {
    case err := <-done:
        fmt.Println("done", err, n.Valid)
    case <-time.After(5000 * time.Millisecond):
        fmt.Println("hang / excessive work")
    }
}

Please run with the race detector: go run -race main.go.

Expected behavior

Numeric decodes immediately to a valid value without rescaling to dscale, matching PostgreSQL numeric_recv.

Actual behavior

  • Decoder performs ~21k sequential big.Int div/mul operations, often making the value 0.
  • Trailing-zero loop can spin on zero (infinite loop).
  • CPU spikes; program appears to hang.

Version

  • Go: go version -> e.g. go1.25.x darwin/arm64
  • PostgreSQL: psql -c 'select version()'
  • pgx: grep 'github.com/jackc/pgx/v[0-9]' go.mod -> e.g. v5.7.6

Additional context

  • PostgreSQL reference: src/backend/utils/adt/numeric.c (numeric_recv) — dscale is display-only; no rescale during decode.
  • Minimal fix in pgx decoder:
    • Do not rescale by dscale in binary decode.
    • Guard trailing-zero removal with accum != 0.
    • If rescaling is needed elsewhere, use big.Int.Exp instead of per-digit loops.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions