Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 0a27c3d

Browse files
Fix various overflow hazards in date and timestamp functions.
This commit makes use of the overflow-aware routines in int.h to fix a variety of reported overflow bugs in the date and timestamp code. It seems unlikely that this fixes all such bugs in this area, but since the problems seem limited to cases that are far beyond any realistic usage, I'm not going to worry too much. Note that for one bug, I've chosen to simply add a comment about the overflow hazard because fixing it would require quite a bit of code restructuring that doesn't seem worth the risk. Since this is a bug fix, it could be back-patched, but given the risk of conflicts with the new routines in int.h and the overall risk/reward ratio of this patch, I've opted not to do so for now. Fixes bug #18585 (except for the one case that's just commented). Reported-by: Alexander Lakhin Author: Matthew Kim, Nathan Bossart Reviewed-by: Joseph Koshakow, Jian He Discussion: https://postgr.es/m/31ad2cd1-db94-bdb3-f91a-65ffdb4bef95%40gmail.com Discussion: https://postgr.es/m/18585-db646741dd649abd%40postgresql.org
1 parent 3eea7a0 commit 0a27c3d

File tree

8 files changed

+179
-9
lines changed

8 files changed

+179
-9
lines changed

src/backend/utils/adt/date.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,15 @@ make_date(PG_FUNCTION_ARGS)
256256
/* Handle negative years as BC */
257257
if (tm.tm_year < 0)
258258
{
259+
int year = tm.tm_year;
260+
259261
bc = true;
260-
tm.tm_year = -tm.tm_year;
262+
if (pg_neg_s32_overflow(year, &year))
263+
ereport(ERROR,
264+
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
265+
errmsg("date field value out of range: %d-%02d-%02d",
266+
tm.tm_year, tm.tm_mon, tm.tm_mday)));
267+
tm.tm_year = year;
261268
}
262269

263270
dterr = ValidateDate(DTK_DATE_M, false, false, bc, &tm);

src/backend/utils/adt/formatting.c

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777

7878
#include "catalog/pg_collation.h"
7979
#include "catalog/pg_type.h"
80+
#include "common/int.h"
8081
#include "common/unicode_case.h"
8182
#include "common/unicode_category.h"
8283
#include "mb/pg_wchar.h"
@@ -3826,7 +3827,14 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out,
38263827
ereturn(escontext,,
38273828
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
38283829
errmsg("invalid input string for \"Y,YYY\"")));
3829-
years += (millennia * 1000);
3830+
3831+
/* years += (millennia * 1000); */
3832+
if (pg_mul_s32_overflow(millennia, 1000, &millennia) ||
3833+
pg_add_s32_overflow(years, millennia, &years))
3834+
ereturn(escontext,,
3835+
(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
3836+
errmsg("value for \"Y,YYY\" in source string is out of range")));
3837+
38303838
if (!from_char_set_int(&out->year, years, n, escontext))
38313839
return;
38323840
out->yysz = 4;
@@ -4785,10 +4793,35 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
47854793
tm->tm_year = tmfc.year % 100;
47864794
if (tm->tm_year)
47874795
{
4796+
int tmp;
4797+
47884798
if (tmfc.cc >= 0)
4789-
tm->tm_year += (tmfc.cc - 1) * 100;
4799+
{
4800+
/* tm->tm_year += (tmfc.cc - 1) * 100; */
4801+
tmp = tmfc.cc - 1;
4802+
if (pg_mul_s32_overflow(tmp, 100, &tmp) ||
4803+
pg_add_s32_overflow(tm->tm_year, tmp, &tm->tm_year))
4804+
{
4805+
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4806+
text_to_cstring(date_txt), "timestamp",
4807+
escontext);
4808+
goto fail;
4809+
}
4810+
}
47904811
else
4791-
tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1;
4812+
{
4813+
/* tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1; */
4814+
tmp = tmfc.cc + 1;
4815+
if (pg_mul_s32_overflow(tmp, 100, &tmp) ||
4816+
pg_sub_s32_overflow(tmp, tm->tm_year, &tmp) ||
4817+
pg_add_s32_overflow(tmp, 1, &tm->tm_year))
4818+
{
4819+
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4820+
text_to_cstring(date_txt), "timestamp",
4821+
escontext);
4822+
goto fail;
4823+
}
4824+
}
47924825
}
47934826
else
47944827
{
@@ -4814,11 +4847,31 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
48144847
if (tmfc.bc)
48154848
tmfc.cc = -tmfc.cc;
48164849
if (tmfc.cc >= 0)
4850+
{
48174851
/* +1 because 21st century started in 2001 */
4818-
tm->tm_year = (tmfc.cc - 1) * 100 + 1;
4852+
/* tm->tm_year = (tmfc.cc - 1) * 100 + 1; */
4853+
if (pg_mul_s32_overflow(tmfc.cc - 1, 100, &tm->tm_year) ||
4854+
pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year))
4855+
{
4856+
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4857+
text_to_cstring(date_txt), "timestamp",
4858+
escontext);
4859+
goto fail;
4860+
}
4861+
}
48194862
else
4863+
{
48204864
/* +1 because year == 599 is 600 BC */
4821-
tm->tm_year = tmfc.cc * 100 + 1;
4865+
/* tm->tm_year = tmfc.cc * 100 + 1; */
4866+
if (pg_mul_s32_overflow(tmfc.cc, 100, &tm->tm_year) ||
4867+
pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year))
4868+
{
4869+
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4870+
text_to_cstring(date_txt), "timestamp",
4871+
escontext);
4872+
goto fail;
4873+
}
4874+
}
48224875
fmask |= DTK_M(YEAR);
48234876
}
48244877

@@ -4843,11 +4896,31 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
48434896
fmask |= DTK_DATE_M;
48444897
}
48454898
else
4846-
tmfc.ddd = (tmfc.ww - 1) * 7 + 1;
4899+
{
4900+
/* tmfc.ddd = (tmfc.ww - 1) * 7 + 1; */
4901+
if (pg_sub_s32_overflow(tmfc.ww, 1, &tmfc.ddd) ||
4902+
pg_mul_s32_overflow(tmfc.ddd, 7, &tmfc.ddd) ||
4903+
pg_add_s32_overflow(tmfc.ddd, 1, &tmfc.ddd))
4904+
{
4905+
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4906+
date_str, "timestamp", escontext);
4907+
goto fail;
4908+
}
4909+
}
48474910
}
48484911

48494912
if (tmfc.w)
4850-
tmfc.dd = (tmfc.w - 1) * 7 + 1;
4913+
{
4914+
/* tmfc.dd = (tmfc.w - 1) * 7 + 1; */
4915+
if (pg_sub_s32_overflow(tmfc.w, 1, &tmfc.dd) ||
4916+
pg_mul_s32_overflow(tmfc.dd, 7, &tmfc.dd) ||
4917+
pg_add_s32_overflow(tmfc.dd, 1, &tmfc.dd))
4918+
{
4919+
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4920+
date_str, "timestamp", escontext);
4921+
goto fail;
4922+
}
4923+
}
48514924
if (tmfc.dd)
48524925
{
48534926
tm->tm_mday = tmfc.dd;
@@ -4912,7 +4985,18 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std,
49124985
}
49134986

49144987
if (tmfc.ms)
4915-
*fsec += tmfc.ms * 1000;
4988+
{
4989+
int tmp = 0;
4990+
4991+
/* *fsec += tmfc.ms * 1000; */
4992+
if (pg_mul_s32_overflow(tmfc.ms, 1000, &tmp) ||
4993+
pg_add_s32_overflow(*fsec, tmp, fsec))
4994+
{
4995+
DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4996+
date_str, "timestamp", escontext);
4997+
goto fail;
4998+
}
4999+
}
49165000
if (tmfc.us)
49175001
*fsec += tmfc.us;
49185002
if (fprec)

src/backend/utils/adt/timestamp.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5104,6 +5104,10 @@ interval_trunc(PG_FUNCTION_ARGS)
51045104
*
51055105
* Return the Julian day which corresponds to the first day (Monday) of the given ISO 8601 year and week.
51065106
* Julian days are used to convert between ISO week dates and Gregorian dates.
5107+
*
5108+
* XXX: This function has integer overflow hazards, but restructuring it to
5109+
* work with the soft-error handling that its callers do is likely more
5110+
* trouble than it's worth.
51075111
*/
51085112
int
51095113
isoweek2j(int year, int week)

src/include/common/int.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,22 @@ pg_mul_s16_overflow(int16 a, int16 b, int16 *result)
117117
#endif
118118
}
119119

120+
static inline bool
121+
pg_neg_s16_overflow(int16 a, int16 *result)
122+
{
123+
#if defined(HAVE__BUILTIN_OP_OVERFLOW)
124+
return __builtin_sub_overflow(0, a, result);
125+
#else
126+
if (unlikely(a == PG_INT16_MIN))
127+
{
128+
*result = 0x5EED; /* to avoid spurious warnings */
129+
return true;
130+
}
131+
*result = -a;
132+
return false;
133+
#endif
134+
}
135+
120136
static inline uint16
121137
pg_abs_s16(int16 a)
122138
{
@@ -185,6 +201,22 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
185201
#endif
186202
}
187203

204+
static inline bool
205+
pg_neg_s32_overflow(int32 a, int32 *result)
206+
{
207+
#if defined(HAVE__BUILTIN_OP_OVERFLOW)
208+
return __builtin_sub_overflow(0, a, result);
209+
#else
210+
if (unlikely(a == PG_INT32_MIN))
211+
{
212+
*result = 0x5EED; /* to avoid spurious warnings */
213+
return true;
214+
}
215+
*result = -a;
216+
return false;
217+
#endif
218+
}
219+
188220
static inline uint32
189221
pg_abs_s32(int32 a)
190222
{
@@ -300,6 +332,22 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
300332
#endif
301333
}
302334

335+
static inline bool
336+
pg_neg_s64_overflow(int64 a, int64 *result)
337+
{
338+
#if defined(HAVE__BUILTIN_OP_OVERFLOW)
339+
return __builtin_sub_overflow(0, a, result);
340+
#else
341+
if (unlikely(a == PG_INT64_MIN))
342+
{
343+
*result = 0x5EED; /* to avoid spurious warnings */
344+
return true;
345+
}
346+
*result = -a;
347+
return false;
348+
#endif
349+
}
350+
303351
static inline uint64
304352
pg_abs_s64(int64 a)
305353
{

src/test/regress/expected/date.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,6 +1528,8 @@ select make_date(2013, 13, 1);
15281528
ERROR: date field value out of range: 2013-13-01
15291529
select make_date(2013, 11, -1);
15301530
ERROR: date field value out of range: 2013-11--1
1531+
SELECT make_date(-2147483648, 1, 1);
1532+
ERROR: date field value out of range: -2147483648-01-01
15311533
select make_time(10, 55, 100.1);
15321534
ERROR: time field value out of range: 10:55:100.1
15331535
select make_time(24, 0, 2.1);

src/test/regress/expected/horology.out

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3754,6 +3754,14 @@ SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS'); -- ok
37543754

37553755
SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS');
37563756
ERROR: date/time field value out of range: "2015-02-11 86400"
3757+
SELECT to_timestamp('1000000000,999', 'Y,YYY');
3758+
ERROR: value for "Y,YYY" in source string is out of range
3759+
SELECT to_timestamp('0.-2147483648', 'SS.MS');
3760+
ERROR: date/time field value out of range: "0.-2147483648"
3761+
SELECT to_timestamp('613566758', 'W');
3762+
ERROR: date/time field value out of range: "613566758"
3763+
SELECT to_timestamp('2024 613566758 1', 'YYYY WW D');
3764+
ERROR: date/time field value out of range: "2024 613566758 1"
37573765
SELECT to_date('2016-13-10', 'YYYY-MM-DD');
37583766
ERROR: date/time field value out of range: "2016-13-10"
37593767
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
37943802
02-01-0001 BC
37953803
(1 row)
37963804

3805+
SELECT to_date('100000000', 'CC');
3806+
ERROR: date/time field value out of range: "100000000"
3807+
SELECT to_date('-100000000', 'CC');
3808+
ERROR: date/time field value out of range: "-100000000"
3809+
SELECT to_date('-2147483648 01', 'CC YY');
3810+
ERROR: date/time field value out of range: "-2147483648 01"
3811+
SELECT to_date('2147483647 01', 'CC YY');
3812+
ERROR: date/time field value out of range: "2147483647 01"
37973813
-- to_char's TZ format code produces zone abbrev if known
37983814
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');
37993815
to_char

src/test/regress/sql/date.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,5 +371,6 @@ select make_date(0, 7, 15);
371371
select make_date(2013, 2, 30);
372372
select make_date(2013, 13, 1);
373373
select make_date(2013, 11, -1);
374+
SELECT make_date(-2147483648, 1, 1);
374375
select make_time(10, 55, 100.1);
375376
select make_time(24, 0, 2.1);

src/test/regress/sql/horology.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,10 @@ SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSS'); -- ok
651651
SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSS');
652652
SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS'); -- ok
653653
SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS');
654+
SELECT to_timestamp('1000000000,999', 'Y,YYY');
655+
SELECT to_timestamp('0.-2147483648', 'SS.MS');
656+
SELECT to_timestamp('613566758', 'W');
657+
SELECT to_timestamp('2024 613566758 1', 'YYYY WW D');
654658
SELECT to_date('2016-13-10', 'YYYY-MM-DD');
655659
SELECT to_date('2016-02-30', 'YYYY-MM-DD');
656660
SELECT to_date('2016-02-29', 'YYYY-MM-DD'); -- ok
@@ -661,6 +665,10 @@ SELECT to_date('2016 365', 'YYYY DDD'); -- ok
661665
SELECT to_date('2016 366', 'YYYY DDD'); -- ok
662666
SELECT to_date('2016 367', 'YYYY DDD');
663667
SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be
668+
SELECT to_date('100000000', 'CC');
669+
SELECT to_date('-100000000', 'CC');
670+
SELECT to_date('-2147483648 01', 'CC YY');
671+
SELECT to_date('2147483647 01', 'CC YY');
664672

665673
-- to_char's TZ format code produces zone abbrev if known
666674
SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ');

0 commit comments

Comments
 (0)