Skip to content

Commit e39f990

Browse files
committed
Fix overflow hazards in interval input and output conversions.
DecodeInterval (interval input) was careless about integer-overflow hazards, allowing bogus results to be obtained for sufficiently large input values. Also, since it initially converted the input to a "struct tm", it was impossible to produce the full range of representable interval values. Meanwhile, EncodeInterval (interval output) and a few other functions could suffer failures if asked to process sufficiently large interval values, because they also relied on being able to represent an interval in "struct tm" which is not designed to handle that. Fix all this stuff by introducing new struct types that are more fit for purpose. While this is clearly a bug fix, it's also an API break for any code that's calling these functions directly. So back-patching doesn't seem wise, especially in view of the lack of field complaints. Joe Koshakow, editorialized a bit by me Discussion: https://postgr.es/m/CAAvxfHff0JLYHwyBrtMx_=6wr=k2Xp+D+-X3vEhHjJYMj+mQcg@mail.gmail.com
1 parent f7e4d5c commit e39f990

File tree

9 files changed

+1473
-372
lines changed

9 files changed

+1473
-372
lines changed

src/backend/utils/adt/datetime.c

+474-260
Large diffs are not rendered by default.

src/backend/utils/adt/formatting.c

+65-21
Original file line numberDiff line numberDiff line change
@@ -491,11 +491,28 @@ typedef struct
491491

492492
/* ----------
493493
* Datetime to char conversion
494+
*
495+
* To support intervals as well as timestamps, we use a custom "tm" struct
496+
* that is almost like struct pg_tm, but has a 64-bit tm_hour field.
497+
* We omit the tm_isdst and tm_zone fields, which are not used here.
494498
* ----------
495499
*/
500+
struct fmt_tm
501+
{
502+
int tm_sec;
503+
int tm_min;
504+
int64 tm_hour;
505+
int tm_mday;
506+
int tm_mon;
507+
int tm_year;
508+
int tm_wday;
509+
int tm_yday;
510+
long int tm_gmtoff;
511+
};
512+
496513
typedef struct TmToChar
497514
{
498-
struct pg_tm tm; /* classic 'tm' struct */
515+
struct fmt_tm tm; /* almost the classic 'tm' struct */
499516
fsec_t fsec; /* fractional seconds */
500517
const char *tzn; /* timezone */
501518
} TmToChar;
@@ -504,12 +521,25 @@ typedef struct TmToChar
504521
#define tmtcTzn(_X) ((_X)->tzn)
505522
#define tmtcFsec(_X) ((_X)->fsec)
506523

524+
/* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */
525+
#define COPY_tm(_DST, _SRC) \
526+
do { \
527+
(_DST)->tm_sec = (_SRC)->tm_sec; \
528+
(_DST)->tm_min = (_SRC)->tm_min; \
529+
(_DST)->tm_hour = (_SRC)->tm_hour; \
530+
(_DST)->tm_mday = (_SRC)->tm_mday; \
531+
(_DST)->tm_mon = (_SRC)->tm_mon; \
532+
(_DST)->tm_year = (_SRC)->tm_year; \
533+
(_DST)->tm_wday = (_SRC)->tm_wday; \
534+
(_DST)->tm_yday = (_SRC)->tm_yday; \
535+
(_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \
536+
} while(0)
537+
538+
/* Caution: this is used to zero both pg_tm and fmt_tm structs */
507539
#define ZERO_tm(_X) \
508540
do { \
509-
(_X)->tm_sec = (_X)->tm_year = (_X)->tm_min = (_X)->tm_wday = \
510-
(_X)->tm_hour = (_X)->tm_yday = (_X)->tm_isdst = 0; \
511-
(_X)->tm_mday = (_X)->tm_mon = 1; \
512-
(_X)->tm_zone = NULL; \
541+
memset(_X, 0, sizeof(*(_X))); \
542+
(_X)->tm_mday = (_X)->tm_mon = 1; \
513543
} while(0)
514544

515545
#define ZERO_tmtc(_X) \
@@ -2649,7 +2679,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
26492679
{
26502680
FormatNode *n;
26512681
char *s;
2652-
struct pg_tm *tm = &in->tm;
2682+
struct fmt_tm *tm = &in->tm;
26532683
int i;
26542684

26552685
/* cache localized days and months */
@@ -2698,16 +2728,17 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
26982728
* display time as shown on a 12-hour clock, even for
26992729
* intervals
27002730
*/
2701-
sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
2702-
tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? HOURS_PER_DAY / 2 :
2703-
tm->tm_hour % (HOURS_PER_DAY / 2));
2731+
sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
2732+
tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ?
2733+
(long long) (HOURS_PER_DAY / 2) :
2734+
(long long) (tm->tm_hour % (HOURS_PER_DAY / 2)));
27042735
if (S_THth(n->suffix))
27052736
str_numth(s, s, S_TH_TYPE(n->suffix));
27062737
s += strlen(s);
27072738
break;
27082739
case DCH_HH24:
2709-
sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
2710-
tm->tm_hour);
2740+
sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
2741+
(long long) tm->tm_hour);
27112742
if (S_THth(n->suffix))
27122743
str_numth(s, s, S_TH_TYPE(n->suffix));
27132744
s += strlen(s);
@@ -2755,9 +2786,10 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
27552786
break;
27562787
#undef DCH_to_char_fsec
27572788
case DCH_SSSS:
2758-
sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
2759-
tm->tm_min * SECS_PER_MINUTE +
2760-
tm->tm_sec);
2789+
sprintf(s, "%lld",
2790+
(long long) (tm->tm_hour * SECS_PER_HOUR +
2791+
tm->tm_min * SECS_PER_MINUTE +
2792+
tm->tm_sec));
27612793
if (S_THth(n->suffix))
27622794
str_numth(s, s, S_TH_TYPE(n->suffix));
27632795
s += strlen(s);
@@ -4088,7 +4120,8 @@ timestamp_to_char(PG_FUNCTION_ARGS)
40884120
text *fmt = PG_GETARG_TEXT_PP(1),
40894121
*res;
40904122
TmToChar tmtc;
4091-
struct pg_tm *tm;
4123+
struct pg_tm tt;
4124+
struct fmt_tm *tm;
40924125
int thisdate;
40934126

40944127
if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
@@ -4097,10 +4130,11 @@ timestamp_to_char(PG_FUNCTION_ARGS)
40974130
ZERO_tmtc(&tmtc);
40984131
tm = tmtcTm(&tmtc);
40994132

4100-
if (timestamp2tm(dt, NULL, tm, &tmtcFsec(&tmtc), NULL, NULL) != 0)
4133+
if (timestamp2tm(dt, NULL, &tt, &tmtcFsec(&tmtc), NULL, NULL) != 0)
41014134
ereport(ERROR,
41024135
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
41034136
errmsg("timestamp out of range")));
4137+
COPY_tm(tm, &tt);
41044138

41054139
thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
41064140
tm->tm_wday = (thisdate + 1) % 7;
@@ -4120,7 +4154,8 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
41204154
*res;
41214155
TmToChar tmtc;
41224156
int tz;
4123-
struct pg_tm *tm;
4157+
struct pg_tm tt;
4158+
struct fmt_tm *tm;
41244159
int thisdate;
41254160

41264161
if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
@@ -4129,10 +4164,11 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
41294164
ZERO_tmtc(&tmtc);
41304165
tm = tmtcTm(&tmtc);
41314166

4132-
if (timestamp2tm(dt, &tz, tm, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
4167+
if (timestamp2tm(dt, &tz, &tt, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
41334168
ereport(ERROR,
41344169
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
41354170
errmsg("timestamp out of range")));
4171+
COPY_tm(tm, &tt);
41364172

41374173
thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
41384174
tm->tm_wday = (thisdate + 1) % 7;
@@ -4156,16 +4192,24 @@ interval_to_char(PG_FUNCTION_ARGS)
41564192
text *fmt = PG_GETARG_TEXT_PP(1),
41574193
*res;
41584194
TmToChar tmtc;
4159-
struct pg_tm *tm;
4195+
struct fmt_tm *tm;
4196+
struct pg_itm tt,
4197+
*itm = &tt;
41604198

41614199
if (VARSIZE_ANY_EXHDR(fmt) <= 0)
41624200
PG_RETURN_NULL();
41634201

41644202
ZERO_tmtc(&tmtc);
41654203
tm = tmtcTm(&tmtc);
41664204

4167-
if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0)
4168-
PG_RETURN_NULL();
4205+
interval2itm(*it, itm);
4206+
tmtc.fsec = itm->tm_usec;
4207+
tm->tm_sec = itm->tm_sec;
4208+
tm->tm_min = itm->tm_min;
4209+
tm->tm_hour = itm->tm_hour;
4210+
tm->tm_mday = itm->tm_mday;
4211+
tm->tm_mon = itm->tm_mon;
4212+
tm->tm_year = itm->tm_year;
41694213

41704214
/* wday is meaningless, yday approximates the total span in days */
41714215
tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;

0 commit comments

Comments
 (0)