aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/utils/adt/date.c9
-rw-r--r--src/backend/utils/adt/formatting.c100
-rw-r--r--src/backend/utils/adt/timestamp.c4
-rw-r--r--src/include/common/int.h48
-rw-r--r--src/test/regress/expected/date.out2
-rw-r--r--src/test/regress/expected/horology.out16
-rw-r--r--src/test/regress/sql/date.sql1
-rw-r--r--src/test/regress/sql/horology.sql8
8 files changed, 179 insertions, 9 deletions
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 8130f3e8ac0..da61ac0e867 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -256,8 +256,15 @@ make_date(PG_FUNCTION_ARGS)
/* Handle negative years as BC */
if (tm.tm_year < 0)
{
+ int year = tm.tm_year;
+
bc = true;
- tm.tm_year = -tm.tm_year;
+ if (pg_neg_s32_overflow(year, &year))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
+ errmsg("date field value out of range: %d-%02d-%02d",
+ tm.tm_year, tm.tm_mon, tm.tm_mday)));
+ tm.tm_year = year;
}
dterr = ValidateDate(DTK_DATE_M, false, false, bc, &tm);
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 9ec8769eb9d..0dcb5515119 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -77,6 +77,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
+#include "common/int.h"
#include "common/unicode_case.h"
#include "common/unicode_category.h"
#include "mb/pg_wchar.h"
@@ -3826,7 +3827,14 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out,
ereturn(escontext,,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input string for \"Y,YYY\"")));
- years += (millennia * 1000);
+
+ /* years += (millennia * 1000); */
+ if (pg_mul_s32_overflow(millennia, 1000, &millennia) ||
+ pg_add_s32_overflow(years, millennia, &years))
+ ereturn(escontext,,
+ (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
+ errmsg("value for \"Y,YYY\" in source string is out of range")));
+
if (!from_char_set_int(&out->year, years, n, escontext))
return;
out->yysz = 4;
@@ -4785,10 +4793,35 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
tm->tm_year = tmfc.year % 100;
if (tm->tm_year)
{
+ int tmp;
+
if (tmfc.cc >= 0)
- tm->tm_year += (tmfc.cc - 1) * 100;
+ {
+ /* tm->tm_year += (tmfc.cc - 1) * 100; */
+ tmp = tmfc.cc - 1;
+ if (pg_mul_s32_overflow(tmp, 100, &tmp) ||
+ pg_add_s32_overflow(tm->tm_year, tmp, &tm->tm_year))
+ {
+ DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
+ text_to_cstring(date_txt), "timestamp",
+ escontext);
+ goto fail;
+ }
+ }
else
- tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1;
+ {
+ /* tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1; */
+ tmp = tmfc.cc + 1;
+ if (pg_mul_s32_overflow(tmp, 100, &tmp) ||
+ pg_sub_s32_overflow(tmp, tm->tm_year, &tmp) ||
+ pg_add_s32_overflow(tmp, 1, &tm->tm_year))
+ {
+ DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
+ text_to_cstring(date_txt), "timestamp",
+ escontext);
+ goto fail;
+ }
+ }
}
else
{
@@ -4814,11 +4847,31 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
if (tmfc.bc)
tmfc.cc = -tmfc.cc;
if (tmfc.cc >= 0)
+ {
/* +1 because 21st century started in 2001 */
- tm->tm_year = (tmfc.cc - 1) * 100 + 1;
+ /* tm->tm_year = (tmfc.cc - 1) * 100 + 1; */
+ if (pg_mul_s32_overflow(tmfc.cc - 1, 100, &tm->tm_year) ||
+ pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year))
+ {
+ DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
+ text_to_cstring(date_txt), "timestamp",
+ escontext);
+ goto fail;
+ }
+ }
else
+ {
/* +1 because year == 599 is 600 BC */
- tm->tm_year = tmfc.cc * 100 + 1;
+ /* tm->tm_year = tmfc.cc * 100 + 1; */
+ if (pg_mul_s32_overflow(tmfc.cc, 100, &tm->tm_year) ||
+ pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year))
+ {
+ DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
+ text_to_cstring(date_txt), "timestamp",
+ escontext);
+ goto fail;
+ }
+ }
fmask |= DTK_M(YEAR);
}
@@ -4843,11 +4896,31 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
fmask |= DTK_DATE_M;
}
else
- tmfc.ddd = (tmfc.ww - 1) * 7 + 1;
+ {
+ /* tmfc.ddd = (tmfc.ww - 1) * 7 + 1; */
+ if (pg_sub_s32_overflow(tmfc.ww, 1, &tmfc.ddd) ||
+ pg_mul_s32_overflow(tmfc.ddd, 7, &tmfc.ddd) ||
+ pg_add_s32_overflow(tmfc.ddd, 1, &tmfc.ddd))
+ {
+ DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
+ date_str, "timestamp", escontext);
+ goto fail;
+ }
+ }
}
if (tmfc.w)
- tmfc.dd = (tmfc.w - 1) * 7 + 1;
+ {
+ /* tmfc.dd = (tmfc.w - 1) * 7 + 1; */
+ if (pg_sub_s32_overflow(tmfc.w, 1, &tmfc.dd) ||
+ pg_mul_s32_overflow(tmfc.dd, 7, &tmfc.dd) ||
+ pg_add_s32_overflow(tmfc.dd, 1, &tmfc.dd))
+ {
+ DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
+ date_str, "timestamp", escontext);
+ goto fail;
+ }
+ }
if (tmfc.dd)
{
tm->tm_mday = tmfc.dd;
@@ -4912,7 +4985,18 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
}
if (tmfc.ms)
- *fsec += tmfc.ms * 1000;
+ {
+ int tmp = 0;
+
+ /* *fsec += tmfc.ms * 1000; */
+ if (pg_mul_s32_overflow(tmfc.ms, 1000, &tmp) ||
+ pg_add_s32_overflow(*fsec, tmp, fsec))
+ {
+ DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
+ date_str, "timestamp", escontext);
+ goto fail;
+ }
+ }
if (tmfc.us)
*fsec += tmfc.us;
if (fprec)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 57fcfefdaf2..18d7d8a108a 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -5104,6 +5104,10 @@ interval_trunc(PG_FUNCTION_ARGS)
*
* Return the Julian day which corresponds to the first day (Monday) of the given ISO 8601 year and week.
* Julian days are used to convert between ISO week dates and Gregorian dates.
+ *
+ * XXX: This function has integer overflow hazards, but restructuring it to
+ * work with the soft-error handling that its callers do is likely more
+ * trouble than it's worth.
*/
int
isoweek2j(int year, int week)
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 3b1590d676f..6b50aa67b91 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -117,6 +117,22 @@ pg_mul_s16_overflow(int16 a, int16 b, int16 *result)
#endif
}
+static inline bool
+pg_neg_s16_overflow(int16 a, int16 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_sub_overflow(0, a, result);
+#else
+ if (unlikely(a == PG_INT16_MIN))
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = -a;
+ return false;
+#endif
+}
+
static inline uint16
pg_abs_s16(int16 a)
{
@@ -185,6 +201,22 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+static inline bool
+pg_neg_s32_overflow(int32 a, int32 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_sub_overflow(0, a, result);
+#else
+ if (unlikely(a == PG_INT32_MIN))
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = -a;
+ return false;
+#endif
+}
+
static inline uint32
pg_abs_s32(int32 a)
{
@@ -300,6 +332,22 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+static inline bool
+pg_neg_s64_overflow(int64 a, int64 *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+ return __builtin_sub_overflow(0, a, result);
+#else
+ if (unlikely(a == PG_INT64_MIN))
+ {
+ *result = 0x5EED; /* to avoid spurious warnings */
+ return true;
+ }
+ *result = -a;
+ return false;
+#endif
+}
+
static inline uint64
pg_abs_s64(int64 a)
{
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index c9cec70c38d..dcab9e76f45 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1528,6 +1528,8 @@ select make_date(2013, 13, 1);
ERROR: date field value out of range: 2013-13-01
select make_date(2013, 11, -1);
ERROR: date field value out of range: 2013-11--1
+SELECT make_date(-2147483648, 1, 1);
+ERROR: date field value out of range: -2147483648-01-01
select make_time(10, 55, 100.1);
ERROR: time field value out of range: 10:55:100.1
select make_time(24, 0, 2.1);
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 8a68379578b..cb28dfbaee7 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -3754,6 +3754,14 @@ SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS'); -- ok
SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS');
ERROR: date/time field value out of range: "2015-02-11 86400"
+SELECT to_timestamp('1000000000,999', 'Y,YYY');
+ERROR: value for "Y,YYY" in source string is out of range
+SELECT to_timestamp('0.-2147483648', 'SS.MS');
+ERROR: date/time field value out of range: "0.-2147483648"
+SELECT to_timestamp('613566758', 'W');
+ERROR: date/time field value out of range: "613566758"
+SELECT to_timestamp('2024 613566758 1', 'YYYY WW D');
+ERROR: date/time field value out of range: "2024 613566758 1"
SELECT to_date('2016-13-10', 'YYYY-MM-DD');
ERROR: date/time field value out of range: "2016-13-10"
SELECT to_date('2016-02-30', 'YYYY-MM-DD');
@@ -3794,6 +3802,14 @@ SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be
02-01-0001 BC
(1 row)
+SELECT to_date('100000000', 'CC');
+ERROR: date/time field value out of range: "100000000"
+SELECT to_date('-100000000', 'CC');
+ERROR: date/time field value out of range: "-100000000"
+SELECT to_date('-2147483648 01', 'CC YY');
+ERROR: date/time field value out of range: "-2147483648 01"
+SELECT to_date('2147483647 01', 'CC YY');
+ERROR: date/time field value out of range: "2147483647 01"
-- to_char's TZ format code produces zone abbrev if known
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
to_char
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 1c58ff6966d..805aec706cc 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -371,5 +371,6 @@ select make_date(0, 7, 15);
select make_date(2013, 2, 30);
select make_date(2013, 13, 1);
select make_date(2013, 11, -1);
+SELECT make_date(-2147483648, 1, 1);
select make_time(10, 55, 100.1);
select make_time(24, 0, 2.1);
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 86481637223..4aa88b4ba9a 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -651,6 +651,10 @@ SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSS'); -- ok
SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSS');
SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS'); -- ok
SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS');
+SELECT to_timestamp('1000000000,999', 'Y,YYY');
+SELECT to_timestamp('0.-2147483648', 'SS.MS');
+SELECT to_timestamp('613566758', 'W');
+SELECT to_timestamp('2024 613566758 1', 'YYYY WW D');
SELECT to_date('2016-13-10', 'YYYY-MM-DD');
SELECT to_date('2016-02-30', 'YYYY-MM-DD');
SELECT to_date('2016-02-29', 'YYYY-MM-DD'); -- ok
@@ -661,6 +665,10 @@ SELECT to_date('2016 365', 'YYYY DDD'); -- ok
SELECT to_date('2016 366', 'YYYY DDD'); -- ok
SELECT to_date('2016 367', 'YYYY DDD');
SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be
+SELECT to_date('100000000', 'CC');
+SELECT to_date('-100000000', 'CC');
+SELECT to_date('-2147483648 01', 'CC YY');
+SELECT to_date('2147483647 01', 'CC YY');
-- to_char's TZ format code produces zone abbrev if known
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');