Re: BUG #19598: pg_waldump: -s/-e accept out-of-range WAL locations and silently use the low 32 bits

From: Zexin Li <lizi(dot)openmind(at)gmail(dot)com>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: malis(at)pgrust(dot)com
Subject: Re: BUG #19598: pg_waldump: -s/-e accept out-of-range WAL locations and silently use the low 32 bits
Date: 2026-08-04 08:54:03
Message-ID: CAAP6ZkTzff9LQ3Qja1edo5wuKjGiYLq3WmODouuHfA3oHcOpPA@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

On Sun, Aug 2, 2026, Michael Malis wrote:
> Note the echoed value: the 9-hex-digit input 123456789 was silently
> reduced to 23456789.

Thanks for the report -- reproduced on current master (03f420c37f).
Patch attached.

The != 2 guard only fires when fewer than two conversions complete,
i.e. when %X or the '/' literal fails outright, as with "ZZZ/0". It
cannot reject inputs where both conversions succeed on the wrong
bytes, which happens through three properties of sscanf():

1. The first %X carries no field width, so a component wider than 32
bits overflows the uint32 argument -- undefined behavior per C99
7.19.6.2p10; glibc keeps the low-order 32 bits, which is the mangling
you observed. A component wider than 64 bits saturates to ULONG_MAX
at the strtoul() level first, so FFFFFFFFFFFFFFFFFFFF/0 runs as
FFFFFFFF/0.

2. sscanf() succeeds without consuming the whole string. On master
the low component is %08X, so "0/123456789" stops after eight digits
and runs as 0/12345678 ("1/2/3" runs as 1/2); on 18.x, with a bare %X
there, the same input instead wraps modulo 2^32.

3. %X follows strtoul()'s subject-sequence rules, accepting leading
whitespace, signs, and "0x" prefixes: "-1/0" runs as FFFFFFFF/0.

The patch replaces the two sscanf() calls with a static helper that
follows the backend's parser for this syntax, pg_lsn_in_safe() in
src/backend/utils/adt/pg_lsn.c: strspn() over the hex charset, one to
eight digits per component, separator and terminating NUL checked by
position. The patch intentionally does not change the "invalid WAL
location" error text, the treatment of any input the server considers
valid (including 8-digit and mixed-case components), or the
already-rejected cases, so scripts matching on the error text are
unaffected.

Measured on master against a real segment. After the patch, -s/-e
accept exactly what the server accepts as pg_lsn -- one to eight hex
digits, a slash, one to eight hex digits, nothing else. Each line
shows unpatched behavior first, patched behavior second:

123456789/0: ran as 23456789/00000000; now rejected
FFFFFFFFFFFFFFFFFFFF/0: ran as FFFFFFFF/00000000; now rejected
0/123456789: ran as 0/12345678; now rejected
1/2/3: ran as 1/00000002; now rejected
0x1/0: ran as 1/00000000; now rejected
-1/0: ran as FFFFFFFF/00000000; now rejected
" 1/0": ran as 1/00000000; now rejected

"Rejected" is the existing "invalid WAL location" error; all seven
inputs are already rejected by the server when cast to pg_lsn, so the
tool and the server now agree on every string. Unchanged: valid
inputs (0/1000028, 0/0, FFFFFFFF/FFFFFFFF, abcdef/ABCDEF) parse as
before, and inputs that already failed ("bad", "ZZZ/0") keep failing
the same way.

Regression tests are included next to the existing invalid-LSN checks;
without the fix the four new cases fail. The pg_waldump TAP suite and
make check pass here.

The same pattern parses user-supplied LSNs in pg_recvlogical (-I/-E)
and pg_receivewal (-E); pg_basebackup and pg_rewind only parse
server-returned strings. I kept this patch to pg_waldump to match
the report's scope, and can send a follow-up moving the helper next
to option_parse_int() in fe_utils to cover the other two if that
seems worthwhile.

Regards,
Zexin Li

On Mon, Aug 03, 2026 03:12 AM, PG Bug reporting form <noreply(at)postgresql(dot)org>
wrote:

> The following bug has been logged on the website:
>
> Bug reference: 19598
> Logged by: Michael Malis
> Email address: malis(at)pgrust(dot)com
> PostgreSQL version: 18.3
> Operating system: Debian
> Description:
>
> Both LSN-accepting options parse with sscanf(optarg, "%X/%X", &xlogid,
> &xrecoff) into two uint32s, with no length or range check. %X converts via
> strtoul: a component that overflows uint32 is truncated to its low 32 bits,
> and one that overflows uint64 saturates and then truncates. In both cases
> sscanf still returns 2, so the != 2 "invalid WAL location" guard never
> fires
> and the tool proceeds with a value the user did not ask for. PostgreSQL's
> own canonical LSN parser rejects the same input.
>
> Reproducer (runnable against stock PostgreSQL 18.3)
> ---------------------------------------------------
> $ pg_waldump -s 123456789/0 000000010000000000000040
> pg_waldump: error: start WAL location 23456789/0 is not inside file
> "000000010000000000000040"
>
> Note the echoed value: the 9-hex-digit input 123456789 was silently reduced
> to 23456789. The saturating case:
> $ pg_waldump -s FFFFFFFFFFFFFFFFFFFF/0 000000010000000000000040
> pg_waldump: error: start WAL location FFFFFFFF/0 is not inside file
> "..."
>
> Control — a genuinely malformed value is rejected, so the guard works, it
> just never sees these inputs:
> $ pg_waldump -s ZZZ/0 000000010000000000000040
> pg_waldump: error: invalid WAL location: "ZZZ/0"
>
> Contrast with the server's own parser on the identical string:
> SELECT '123456789/0'::pg_lsn;
> ERROR: invalid input syntax for type pg_lsn: "123456789/0"
>
> Expected vs. actual
> -------------------
> - Expected: pg_waldump: error: invalid WAL location: "123456789/0", as for
> any other unparseable value.
> - Actual: the value is accepted, silently mangled to 23456789/0, and used.
> The error text the user eventually sees reports the mangled location,
> which actively misleads: it reads as "the location you asked for isn't in
> this file" when the location asked for was never used.
>
>
>
>
>

Attachment Content-Type Size
0001-Reject-invalid-WAL-locations-in-pg_waldump-s-s-e-opt.patch application/x-patch 5.1 KB

In response to

Browse pgsql-bugs by date

  From Date Subject
Next Message Álvaro Herrera 2026-08-04 09:29:29 Re: BUG #19491: Segmentation fault triggered by IS NULL
Previous Message Andrey Rachitskiy 2026-08-04 08:05:42 Re: BUG #19593: area(circle) silently returns Infinity instead of raising "value out of range: overflow"