From 7cc95fddc5b3d6124c5a340a0fb85d1dbf297d5d Mon Sep 17 00:00:00 2001
From: Tristan Partin <tristan@partin.io>
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 <tristan@partin.io>
---
 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

