From 905397e5642dfbdd61c6c795ed6e8ef15ce31be4 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH v27 1/8] Check for overflow in make_interval

---
 src/backend/utils/adt/timestamp.c      | 39 ++++++++++++++++++--------
 src/include/datatype/timestamp.h       |  1 +
 src/test/regress/expected/interval.out | 27 ++++++++++++++++++
 src/test/regress/sql/interval.sql      | 15 ++++++++++
 4 files changed, 71 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e172e90614..647b97aca6 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1509,24 +1509,41 @@ make_interval(PG_FUNCTION_ARGS)
 	Interval   *result;
 
 	/*
-	 * Reject out-of-range inputs.  We really ought to check the integer
-	 * inputs as well, but it's not entirely clear what limits to apply.
+	 * Reject out-of-range inputs.  We reject any input values that cause
+	 * integer overflow of the corresponding interval fields.
 	 */
 	if (isinf(secs) || isnan(secs))
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("interval out of range")));
+		goto out_of_range;
 
 	result = (Interval *) palloc(sizeof(Interval));
-	result->month = years * MONTHS_PER_YEAR + months;
-	result->day = weeks * 7 + days;
 
-	secs = rint(secs * USECS_PER_SEC);
-	result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
-		mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
-		(int64) secs;
+	/* years and months -> months */
+	if (pg_mul_s32_overflow(years, MONTHS_PER_YEAR, &result->month) ||
+		pg_add_s32_overflow(result->month, months, &result->month))
+		goto out_of_range;
+
+	/* weeks and days -> days */
+	if (pg_mul_s32_overflow(weeks, DAYS_PER_WEEK, &result->day) ||
+		pg_add_s32_overflow(result->day, days, &result->day))
+		goto out_of_range;
+
+	/* hours and mins -> usecs (cannot overflow 64-bit) */
+	result->time = hours * USECS_PER_HOUR + mins * USECS_PER_MINUTE;
+
+	/* secs -> usecs */
+	secs = rint(float8_mul(secs, USECS_PER_SEC));
+	if (!FLOAT8_FITS_IN_INT64(secs) ||
+		pg_add_s64_overflow(result->time, (int64) secs, &result->time))
+		goto out_of_range;
 
 	PG_RETURN_INTERVAL_P(result);
+
+out_of_range:
+	ereport(ERROR,
+			errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+			errmsg("interval out of range"));
+
+	PG_RETURN_NULL();			/* keep compiler quiet */
 }
 
 /* EncodeSpecialTimestamp()
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
  *	30 days.
  */
 #define DAYS_PER_MONTH	30		/* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK	7
 #define HOURS_PER_DAY	24		/* assume no daylight savings time changes */
 
 /*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c0ca8e041b..75d19d6594 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,33 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
 ERROR:  interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
 LINE 1: select interval '-2147483648 months -2147483648 days -922337...
                         ^
+-- overflowing using make_interval
+select make_interval(years := 178956971);
+ERROR:  interval out of range
+select make_interval(years := -178956971);
+ERROR:  interval out of range
+select make_interval(years := 1, months := 2147483647);
+ERROR:  interval out of range
+select make_interval(years := -1, months := -2147483648);
+ERROR:  interval out of range
+select make_interval(weeks := 306783379);
+ERROR:  interval out of range
+select make_interval(weeks := -306783379);
+ERROR:  interval out of range
+select make_interval(weeks := 1, days := 2147483647);
+ERROR:  interval out of range
+select make_interval(weeks := -1, days := -2147483648);
+ERROR:  interval out of range
+select make_interval(secs := 1e308);
+ERROR:  value out of range: overflow
+select make_interval(secs := 1e18);
+ERROR:  interval out of range
+select make_interval(secs := -1e18);
+ERROR:  interval out of range
+select make_interval(mins := 1, secs := 9223372036800.0);
+ERROR:  interval out of range
+select make_interval(mins := -1, secs := -9223372036800.0);
+ERROR:  interval out of range
 -- test that INT_MIN number is formatted properly
 SET IntervalStyle to postgres;
 select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 038fc508d0..a0a373f08b 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,21 @@ select interval '-2147483648 days ago';
 select interval '-9223372036854775808 microseconds ago';
 select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
 
+-- overflowing using make_interval
+select make_interval(years := 178956971);
+select make_interval(years := -178956971);
+select make_interval(years := 1, months := 2147483647);
+select make_interval(years := -1, months := -2147483648);
+select make_interval(weeks := 306783379);
+select make_interval(weeks := -306783379);
+select make_interval(weeks := 1, days := 2147483647);
+select make_interval(weeks := -1, days := -2147483648);
+select make_interval(secs := 1e308);
+select make_interval(secs := 1e18);
+select make_interval(secs := -1e18);
+select make_interval(mins := 1, secs := 9223372036800.0);
+select make_interval(mins := -1, secs := -9223372036800.0);
+
 -- test that INT_MIN number is formatted properly
 SET IntervalStyle to postgres;
 select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
-- 
2.35.3

