public inbox for [email protected]
help / color / mirror / Atom feedFrom: Christophe Pettus <[email protected]>
To: [email protected]
Subject: uuidv7 improperly accepts dates before 1970-01-01
Date: Fri, 24 Apr 2026 17:19:44 -0700
Message-ID: <[email protected]> (raw)
Hii,
When playing around with UUIDv7s, I discovered that it accepts this:
xof=# SELECT uuidv7(INTERVAL '-1000 years');
uuidv7
--------------------------------------
e4ea52a0-bda1-7121-8f1f-3d9bb3d9a76e
(1 row)
But RFC 9562 defines the time field as an unsigned number of milliseconds since Unix epoch, so timestamps earlier than that should be rejected. "Don't do that" is one answer, but for good hygiene, here's a patch that adds a < 0 check and a regression test. Applies cleanly to HEAD, make check passes.
Attachments:
[application/octet-stream] 0001-uuidv7-fix-negative-shift.diff (2.5K, 2-0001-uuidv7-fix-negative-shift.diff)
download | inline diff:
diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c
index 6ee3752ac78..42ea29aafe2 100644
--- a/src/backend/utils/adt/uuid.c
+++ b/src/backend/utils/adt/uuid.c
@@ -690,6 +690,19 @@ 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;
+ /*
+ * Per RFC 9562 the UUIDv7 timestamp is an unsigned count of milliseconds
+ * since 1970-01-01 UTC; a shift that lands before the Unix epoch has no
+ * valid encoding. Reject it explicitly -- otherwise the negative int64
+ * would be silently converted to a huge uint64 at the uint64 parameter
+ * of generate_uuidv7() and packed into the timestamp bytes as garbage.
+ */
+ 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 timestamps must not be before 1970-01-01 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 9c5dda9e9ab..a29634ba5e6 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -261,6 +261,17 @@ SELECT y, ts, prev_ts FROM uuidts WHERE ts < prev_ts;
---+----+---------
(0 rows)
+-- A negative shift that stays on or after 1970-01-01 must still succeed.
+SELECT uuid_extract_version(uuidv7(INTERVAL '-1 day')); -- 7
+ uuid_extract_version
+----------------------
+ 7
+(1 row)
+
+-- A shift before 1970-01-01 has no RFC 9562 encoding and must be rejected.
+SELECT uuidv7(INTERVAL '-1000 years');
+ERROR: timestamp out of range for UUID version 7
+DETAIL: UUID version 7 timestamps must not be before 1970-01-01 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 8cc2ad40614..0720a910ece 100644
--- a/src/test/regress/sql/uuid.sql
+++ b/src/test/regress/sql/uuid.sql
@@ -140,6 +140,12 @@ WITH uuidts AS (
)
SELECT y, ts, prev_ts FROM uuidts WHERE ts < prev_ts;
+-- A negative shift that stays on or after 1970-01-01 must still succeed.
+SELECT uuid_extract_version(uuidv7(INTERVAL '-1 day')); -- 7
+
+-- A shift before 1970-01-01 has no RFC 9562 encoding and must be rejected.
+SELECT uuidv7(INTERVAL '-1000 years');
+
-- extract functions
-- version
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected]
Subject: Re: uuidv7 improperly accepts dates before 1970-01-01
In-Reply-To: <[email protected]>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox