-
-
Notifications
You must be signed in to change notification settings - Fork 1k
pgtype: NUMERIC binary decode hangs/infinite loop for large-weight values (misuses dscale) #2415
Copy link
Copy link
Closed
Labels
Description
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:
- Use pgtype to decode the following binary NUMERIC payload.
- 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) —dscaleis display-only; no rescale during decode. - Minimal fix in pgx decoder:
- Do not rescale by
dscalein binary decode. - Guard trailing-zero removal with
accum != 0. - If rescaling is needed elsewhere, use
big.Int.Expinstead of per-digit loops.
- Do not rescale by
Reactions are currently unavailable