BUG #19590: to_date/to_timestamp "Y,YYY" accepts out-of-range values

Started by PG Bug reporting formabout 2 months ago2 messagesbugs
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253265
psql -h localhost -U postgres

Built from patchset v2 (message #2), September 20, 2026 at 09:45 AM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253265_2 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253265_2 && git checkout t253265_2

Patchset v2 (message #2) is on t253265_2

Jump to latest
#1PG Bug reporting form
noreply@postgresql.org

The following bug has been logged on the website:

Bug reference: 19590
Logged by: Michael Malis
Email address: malis@pgrust.com
PostgreSQL version: 18.4
Operating system: Debian 18.4-1.pgdg13+1, aarch64
Description:

The Y,YYY template field parses its millennia component with a bare
sscanf(..., "%d", ...), which silently truncates values too large for int
instead of rejecting them, so out-of-range input yields a wrong year:

SELECT to_date('4294969320,024','Y,YYY'); -- 2024024-01-01 (expected:
error)
SELECT to_date('-4294965272,024','Y,YYY'); -- 2024024-01-01 (expected:
error)

4294969320 is 2^32 + 2024, so it truncates to 2024 and is read as 2024
millennia; any multiple of 2^32 works, and %d accepts a sign, so wrapped
negatives too. to_timestamp() shares the code path. Every other numeric
field rejects this:

SELECT to_date('4294969320','YYYY');
-- ERROR: value for "YYYY" in source string is out of range

Cause: DCH_Y_YYY is the only numeric field using raw sscanf; the others go
through from_char_parse_int_len(), which range-checks with strtol/ERANGE.
The existing pg_mul_s32_overflow guard in DCH_Y_YYY runs too late. %d has
already discarded the magnitude.

Suggested fix: after the sscanf, re-scan the millennia field with strtol and
reject ERANGE or out-of-int-range values, matching
from_char_parse_int_len():

errno = 0;
lval = strtol(s, &endptr, 10);
if (errno == ERANGE || lval < INT_MIN || lval > INT_MAX)
ereturn(escontext,,
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
errmsg("value for \"%s\" in source string is out of range",
"Y,YYY"),
errdetail("Value must be in the range %d to %d.", INT_MIN,
INT_MAX)));

strtol skips leading whitespace and stops at the comma exactly as %d does,
so this only adds a rejection path; the ERANGE test covers 32-bit-long
platforms where strtol saturates.

#2Andrey Rachitskiy
pl0h0yp1@gmail.com
In reply to: PG Bug reporting form (#1)
Re: BUG #19590: to_date/to_timestamp "Y,YYY" accepts out-of-range values

Hi, Michael!

Thanks for report.

DCH_Y_YYY used to parse millennia with sscanf("%d"). That silently
truncates values outside int range, so
```
SELECT to_date('4294969320,024', 'Y,YYY'); -- 2^32+2024
SELECT to_date('-4294965272,024', 'Y,YYY');
```
used to return 2024024-01-01 instead of erroring.

Other numeric fields already reject this via
from_char_parse_int_len(); the pg_mul_s32_overflow() check in
DCH_Y_YYY runs too late, after %d has discarded the magnitude.

This patch replaces the millennia %d with a single strtol(), using
the same ERANGE / INT_MIN / INT_MAX checks as from_char_parse_int_len().
The years part remains sscanf("%03d"): the field width limits the
conversion to three characters, so the value always fits in int. The
existing mul/add overflow checks are unchanged (they still catch
cases like 1000000000,999).

Y,YYY has two pieces: variable-width millennia up to a comma, then
three year digits. We parse and validate each separately.

Patch with tests, attached.

пт, 31 июл. 2026 г. в 14:47, PG Bug reporting form <noreply@postgresql.org>:

The following bug has been logged on the website:

Bug reference: 19590
Logged by: Michael Malis
Email address: malis@pgrust.com
PostgreSQL version: 18.4
Operating system: Debian 18.4-1.pgdg13+1, aarch64
Description:

The Y,YYY template field parses its millennia component with a bare
sscanf(..., "%d", ...), which silently truncates values too large for int
instead of rejecting them, so out-of-range input yields a wrong year:

SELECT to_date('4294969320,024','Y,YYY'); -- 2024024-01-01 (expected:
error)
SELECT to_date('-4294965272,024','Y,YYY'); -- 2024024-01-01 (expected:
error)

4294969320 is 2^32 + 2024, so it truncates to 2024 and is read as 2024
millennia; any multiple of 2^32 works, and %d accepts a sign, so wrapped
negatives too. to_timestamp() shares the code path. Every other numeric
field rejects this:

SELECT to_date('4294969320','YYYY');
-- ERROR: value for "YYYY" in source string is out of range

Cause: DCH_Y_YYY is the only numeric field using raw sscanf; the others go
through from_char_parse_int_len(), which range-checks with strtol/ERANGE.
The existing pg_mul_s32_overflow guard in DCH_Y_YYY runs too late. %d has
already discarded the magnitude.

Suggested fix: after the sscanf, re-scan the millennia field with strtol
and
reject ERANGE or out-of-int-range values, matching
from_char_parse_int_len():

errno = 0;
lval = strtol(s, &endptr, 10);
if (errno == ERANGE || lval < INT_MIN || lval > INT_MAX)
ereturn(escontext,,
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
errmsg("value for \"%s\" in source string is out of range",
"Y,YYY"),
errdetail("Value must be in the range %d to %d.", INT_MIN,
INT_MAX)));

strtol skips leading whitespace and stops at the comma exactly as %d does,
so this only adds a rejection path; the ERANGE test covers 32-bit-long
platforms where strtol saturates.

--
Regards,
Rachitskiy Andrey

Attachments:

t253265_2
0001-Reject-out-of-range-millennia-in-Y-YYY-parsing.patchtext/x-patch; charset=US-ASCII; name=0001-Reject-out-of-range-millennia-in-Y-YYY-parsing.patchDownload+43-5