We were silently allowing underflowing the generated timestamp for the
UUIDv7 value:
# SELECT uuid_extract_timestamp(uuidv7('-57 years'::interval));
uuid_extract_timestamp
-----------------------------
10889-01-23 04:02:36.375+00
(1 row)
RFC 9562[0] doesn't seem to specify that this should be allowed, so
generate an error and forbid the underflow.
I checked the Rust uuid crate[1], and it also errors out with
a timestamp earlier than the Unix epoch.
[0]: https://www.rfc-editor.org/rfc/rfc9562.html
[1]:
https://github.com/uuid-rs/uuid/blob/1e6a9669e30d53bae50fd52f16b7a1961fda236b/src/timestamp.rs#L198-L220
--
Tristan Partin
PostgreSQL Contributors Team
AWS (https://aws.amazon.com)
From 7cc95fddc5b3d6124c5a340a0fb85d1dbf297d5d Mon Sep 17 00:00:00 2001
From: Tristan Partin <[email protected]>
Date: Tue, 23 Jun 2026 23:19:25 +0000
Subject: [PATCH v1] Protect against timestamp underflow in uuidv7(interval)
We were silently allowing underflowing the generated timestamp for the
UUIDv7 value:
# SELECT uuid_extract_timestamp(uuidv7('-57 years'::interval));
uuid_extract_timestamp
-----------------------------
10889-01-23 04:02:36.375+00
(1 row)
RFC 9562[0] doesn't seem to specify that this should be allowed, so
generate an error and forbid the underflow.
Link: https://www.rfc-editor.org/rfc/rfc9562.html [0]
Signed-off-by: Tristan Partin <[email protected]>
---
src/backend/utils/adt/uuid.c | 5 +++++
src/test/regress/expected/uuid.out | 5 +++++
src/test/regress/sql/uuid.sql | 4 ++++
3 files changed, 14 insertions(+)
diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c
index 6ee3752ac7..0c6ff59c06 100644
--- a/src/backend/utils/adt/uuid.c
+++ b/src/backend/utils/adt/uuid.c
@@ -689,6 +689,11 @@ uuidv7_interval(PG_FUNCTION_ARGS)
/* Convert a TimestampTz value back to an UNIX epoch timestamp */
us = ts + (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY * USECS_PER_SEC;
+ 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 9c5dda9e9a..3750bc4814 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -261,6 +261,11 @@ SELECT y, ts, prev_ts FROM uuidts WHERE ts < prev_ts;
---+----+---------
(0 rows)
+-- Check that we don't underflow when an interval pointing to before the Unix
+-- epoch is used to create a UUIDv7 value
+SELECT uuidv7('1969-12-31 23:59:59+00'::timestamptz - (now() AT TIME ZONE 'UTC'));
+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).
-- 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 8cc2ad4061..b820f0f170 100644
--- a/src/test/regress/sql/uuid.sql
+++ b/src/test/regress/sql/uuid.sql
@@ -140,6 +140,10 @@ WITH uuidts AS (
)
SELECT y, ts, prev_ts FROM uuidts WHERE ts < prev_ts;
+-- Check that we don't underflow when an interval pointing to before the Unix
+-- epoch is used to create a UUIDv7 value
+SELECT uuidv7('1969-12-31 23:59:59+00'::timestamptz - (now() AT TIME ZONE 'UTC'));
+
-- extract functions
-- version
--
Tristan Partin
https://tristan.partin.io