Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us> writes:
> Joseph Koshakow <koshy44(at)gmail(dot)com> writes:
> > The attached patch fixes an overflow bug in DecodeInterval when applying
> > the units week, decade, century, and millennium. The overflow check logic
> > was modelled after the overflow check at the beginning of `int
> > tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);` in timestamp.c.
>
>
> Good catch, but I don't think that tm2interval code is best practice
> anymore.  Rather than bringing "double" arithmetic into the mix,
> you should use the overflow-detecting arithmetic functions in
> src/include/common/int.h.  The existing code here is also pretty
> faulty in that it doesn't notice addition overflow when combining
> multiple units.  So for example, instead of
>
>
>     tm->tm_mday += val * 7;
>
>
> I think we should write something like
>
>
>     if (pg_mul_s32_overflow(val, 7, &tmp))
>         return DTERR_FIELD_OVERFLOW;
>     if (pg_add_s32_overflow(tm->tm_mday, tmp, &tm->tm_mday))
>         return DTERR_FIELD_OVERFLOW;
>
>
> Perhaps some macros could be used to make this more legible?
>
>
>             regards, tom lane
>
>
>     @postgresql

Thanks for the feedback Tom, I've attached an updated patch with
your suggestions. Feel free to rename the horribly named macro.

Also while fixing this I noticed that fractional intervals can also
cause an overflow issue.
postgres=# SELECT INTERVAL '0.1 months 2147483647 days';
     interval
------------------
 -2147483646 days
(1 row)
I haven't looked into it, but it's probably a similar cause.
From 49b5beb4132a0c9e793e1fd7b91a4da0c96a6196 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <kosh...@gmail.com>
Date: Fri, 11 Feb 2022 14:18:32 -0500
Subject: [PATCH] Check for overflow when decoding an interval

When decoding an interval there are various date units which are
aliases for some multiple of another unit. For example a week is 7 days
and a decade is 10 years. When decoding these specific units, there is
no check for overflow, allowing the interval to overflow. This commit
adds an overflow check for all of these units.
---
 src/backend/utils/adt/datetime.c       | 22 ++++++++++++++----
 src/test/regress/expected/interval.out | 32 ++++++++++++++++++++++++++
 src/test/regress/sql/interval.sql      |  8 +++++++
 3 files changed, 58 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 7926258c06..59d391adda 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -3106,6 +3107,19 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 	int			val;
 	double		fval;
 
+	/*
+	 * Multiply src by mult and add it to dest while checking for overflow.
+	 */
+#define SAFE_MUL_ADD(src, mult, dst)					\
+	do								\
+	{								\
+		int tmp;						\
+		if (pg_mul_s32_overflow(src, mult, &tmp))		\
+			return DTERR_FIELD_OVERFLOW;			\
+		if (pg_add_s32_overflow(dst, tmp, &(dst)))		\
+			return DTERR_FIELD_OVERFLOW;			\
+	} while (0)
+
 	*dtype = DTK_DELTA;
 	type = IGNORE_DTF;
 	ClearPgTm(tm, fsec);
@@ -3293,7 +3307,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_WEEK:
-						tm->tm_mday += val * 7;
+						SAFE_MUL_ADD(val, 7, tm->tm_mday);
 						AdjustFractDays(fval, tm, fsec, 7);
 						tmask = DTK_M(WEEK);
 						break;
@@ -3311,19 +3325,19 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_DECADE:
-						tm->tm_year += val * 10;
+						SAFE_MUL_ADD(val, 10, tm->tm_year);
 						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
 						tmask = DTK_M(DECADE);
 						break;
 
 					case DTK_CENTURY:
-						tm->tm_year += val * 100;
+						SAFE_MUL_ADD(val, 100, tm->tm_year);
 						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
 						tmask = DTK_M(CENTURY);
 						break;
 
 					case DTK_MILLENNIUM:
-						tm->tm_year += val * 1000;
+						SAFE_MUL_ADD(val, 1000, tm->tm_year);
 						tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
 						tmask = DTK_M(MILLENNIUM);
 						break;
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index accd4a7d90..88865483fa 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -232,6 +232,38 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
 ERROR:  interval out of range
 LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'...
                                                  ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+ERROR:  interval field value out of range: "2147483647 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+ERROR:  interval field value out of range: "-2147483648 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks'...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+ERROR:  interval field value out of range: "2147483647 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+ERROR:  interval field value out of range: "-2147483648 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decade...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+ERROR:  interval field value out of range: "2147483647 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuri...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+ERROR:  interval field value out of range: "-2147483648 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centur...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+ERROR:  interval field value out of range: "2147483647 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millenn...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+ERROR:  interval field value out of range: "-2147483648 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millen...
+                                                 ^
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
 ERROR:  interval out of range
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 6d532398bd..70b642cef2 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -72,6 +72,14 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
 
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
-- 
2.25.1

Reply via email to