From c6c5bf80a4988af23caaf4e8f620cb65c119b3b5 Mon Sep 17 00:00:00 2001 From: Baji Shaik Date: Wed, 24 Jun 2026 18:07:03 -0500 Subject: [PATCH v2 2/3] Reject pre-epoch timestamps in uuidv7(interval) uuidv7() with a large negative interval silently produced UUIDs whose timestamp wrapped around to the far future, since UUID version 7 uses an unsigned 48-bit millisecond field that cannot represent dates before the Unix epoch. Fix by using pg_add_s64_overflow() for the epoch conversion (so that very large intervals that overflow int64 produce a clear error instead of wrapping to the wrong sign), then rejecting negative results. Also document the valid timestamp range for the shift parameter. Author: Baji Shaik Discussion: https://postgr.es/m/18F007D6-1A33-48C8-BA51-E7A858DE0C89%40thebuild.com --- doc/src/sgml/func/func-uuid.sgml | 6 ++++++ src/backend/utils/adt/uuid.c | 22 ++++++++++++++++++++-- src/test/regress/expected/uuid.out | 8 ++++++++ src/test/regress/sql/uuid.sql | 6 ++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/func/func-uuid.sgml b/doc/src/sgml/func/func-uuid.sgml index cfd42433a95..41fc5739c31 100644 --- a/doc/src/sgml/func/func-uuid.sgml +++ b/doc/src/sgml/func/func-uuid.sgml @@ -83,6 +83,12 @@ parameter shift will shift the computed timestamp by the given interval. Infinite interval values are not accepted. + The shifted timestamp must fall between the Unix epoch + (1970-01-01 00:00:00 UTC) and approximately + 10889-08-02 05:31:50.655 UTC, which is the range representable + by UUID version 7's 48-bit millisecond timestamp field. + An error is raised if the resulting timestamp is outside this + range. uuidv7() diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c index 1197cb6c027..ca5dbca8ac3 100644 --- a/src/backend/utils/adt/uuid.c +++ b/src/backend/utils/adt/uuid.c @@ -17,6 +17,7 @@ #include /* for clock_gettime() */ #include "common/hashfn.h" +#include "common/int.h" #include "lib/hyperloglog.h" #include "libpq/pqformat.h" #include "port/pg_bswap.h" @@ -694,8 +695,25 @@ uuidv7_interval(PG_FUNCTION_ARGS) TimestampTzGetDatum(ts), IntervalPGetDatum(shift))); - /* Convert a TimestampTz value back to an UNIX epoch timestamp */ - us = ts + (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC; + /* + * Convert a TimestampTz value back to a UNIX epoch timestamp. Use + * overflow-safe addition since large intervals can exceed int64 range. + */ + if (pg_add_s64_overflow(ts, + (int64) (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * + SECS_PER_DAY * USECS_PER_SEC, + &us)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range for UUID version 7"), + errdetail("The shifted timestamp overflows the representable range."))); + + /* UUID v7 uses an unsigned 48-bit millisecond field; reject pre-epoch */ + if (us < 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range for UUID version 7"), + errdetail("UUID version 7 does not support timestamps before the Unix epoch (1970-01-01 00:00:00 UTC)."))); /* Generate an UUIDv7 */ uuid = generate_uuidv7(us / US_PER_MS, (us % US_PER_MS) * NS_PER_US + ns % NS_PER_US); diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out index 28706d77abc..72730825cda 100644 --- a/src/test/regress/expected/uuid.out +++ b/src/test/regress/expected/uuid.out @@ -268,6 +268,14 @@ DETAIL: UUID version 7 does not support infinite intervals. SELECT uuidv7('-infinity'::interval); ERROR: interval out of range for UUID version 7 DETAIL: UUID version 7 does not support infinite intervals. +-- uuidv7: timestamps before Unix epoch are rejected +SELECT uuidv7('-1000 years'::interval); +ERROR: timestamp out of range for UUID version 7 +DETAIL: UUID version 7 does not support timestamps before the Unix epoch (1970-01-01 00:00:00 UTC). +-- uuidv7: large future intervals that overflow epoch conversion are rejected +SELECT uuidv7('292230 years'::interval); +ERROR: timestamp out of range for UUID version 7 +DETAIL: The shifted timestamp overflows the representable range. -- extract functions -- version SELECT uuid_extract_version('11111111-1111-5111-8111-111111111111'); -- 5 diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql index 01bbc8558fc..eb1a12949c5 100644 --- a/src/test/regress/sql/uuid.sql +++ b/src/test/regress/sql/uuid.sql @@ -144,6 +144,12 @@ SELECT y, ts, prev_ts FROM uuidts WHERE ts < prev_ts; SELECT uuidv7('infinity'::interval); SELECT uuidv7('-infinity'::interval); +-- uuidv7: timestamps before Unix epoch are rejected +SELECT uuidv7('-1000 years'::interval); + +-- uuidv7: large future intervals that overflow epoch conversion are rejected +SELECT uuidv7('292230 years'::interval); + -- extract functions -- version -- 2.50.1 (Apple Git-155)