aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/formatting.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2022-04-02 16:12:26 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2022-04-02 16:12:29 -0400
commite39f9904671082c5ad3a2c5acbdbd028fa93bf35 (patch)
tree6ab9ab1ed3b78bab977b0949e3cfcdb6af9f9cde /src/backend/utils/adt/formatting.c
parentf7e4d5c64fb3977e3a773e7213472be1b59dab2f (diff)
downloadpostgresql-e39f9904671082c5ad3a2c5acbdbd028fa93bf35.tar.gz
postgresql-e39f9904671082c5ad3a2c5acbdbd028fa93bf35.zip
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
Diffstat (limited to 'src/backend/utils/adt/formatting.c')
-rw-r--r--src/backend/utils/adt/formatting.c86
1 files changed, 65 insertions, 21 deletions
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index ac74333be51..843b07d7d24 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -491,11 +491,28 @@ typedef struct
/* ----------
* Datetime to char conversion
+ *
+ * To support intervals as well as timestamps, we use a custom "tm" struct
+ * that is almost like struct pg_tm, but has a 64-bit tm_hour field.
+ * We omit the tm_isdst and tm_zone fields, which are not used here.
* ----------
*/
+struct fmt_tm
+{
+ int tm_sec;
+ int tm_min;
+ int64 tm_hour;
+ int tm_mday;
+ int tm_mon;
+ int tm_year;
+ int tm_wday;
+ int tm_yday;
+ long int tm_gmtoff;
+};
+
typedef struct TmToChar
{
- struct pg_tm tm; /* classic 'tm' struct */
+ struct fmt_tm tm; /* almost the classic 'tm' struct */
fsec_t fsec; /* fractional seconds */
const char *tzn; /* timezone */
} TmToChar;
@@ -504,12 +521,25 @@ typedef struct TmToChar
#define tmtcTzn(_X) ((_X)->tzn)
#define tmtcFsec(_X) ((_X)->fsec)
+/* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */
+#define COPY_tm(_DST, _SRC) \
+do { \
+ (_DST)->tm_sec = (_SRC)->tm_sec; \
+ (_DST)->tm_min = (_SRC)->tm_min; \
+ (_DST)->tm_hour = (_SRC)->tm_hour; \
+ (_DST)->tm_mday = (_SRC)->tm_mday; \
+ (_DST)->tm_mon = (_SRC)->tm_mon; \
+ (_DST)->tm_year = (_SRC)->tm_year; \
+ (_DST)->tm_wday = (_SRC)->tm_wday; \
+ (_DST)->tm_yday = (_SRC)->tm_yday; \
+ (_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \
+} while(0)
+
+/* Caution: this is used to zero both pg_tm and fmt_tm structs */
#define ZERO_tm(_X) \
do { \
- (_X)->tm_sec = (_X)->tm_year = (_X)->tm_min = (_X)->tm_wday = \
- (_X)->tm_hour = (_X)->tm_yday = (_X)->tm_isdst = 0; \
- (_X)->tm_mday = (_X)->tm_mon = 1; \
- (_X)->tm_zone = NULL; \
+ memset(_X, 0, sizeof(*(_X))); \
+ (_X)->tm_mday = (_X)->tm_mon = 1; \
} while(0)
#define ZERO_tmtc(_X) \
@@ -2649,7 +2679,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
{
FormatNode *n;
char *s;
- struct pg_tm *tm = &in->tm;
+ struct fmt_tm *tm = &in->tm;
int i;
/* cache localized days and months */
@@ -2698,16 +2728,17 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
* display time as shown on a 12-hour clock, even for
* intervals
*/
- sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
- tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? HOURS_PER_DAY / 2 :
- tm->tm_hour % (HOURS_PER_DAY / 2));
+ sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
+ tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ?
+ (long long) (HOURS_PER_DAY / 2) :
+ (long long) (tm->tm_hour % (HOURS_PER_DAY / 2)));
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
case DCH_HH24:
- sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
- tm->tm_hour);
+ sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
+ (long long) tm->tm_hour);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
@@ -2755,9 +2786,10 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
break;
#undef DCH_to_char_fsec
case DCH_SSSS:
- sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
- tm->tm_min * SECS_PER_MINUTE +
- tm->tm_sec);
+ sprintf(s, "%lld",
+ (long long) (tm->tm_hour * SECS_PER_HOUR +
+ tm->tm_min * SECS_PER_MINUTE +
+ tm->tm_sec));
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
@@ -4088,7 +4120,8 @@ timestamp_to_char(PG_FUNCTION_ARGS)
text *fmt = PG_GETARG_TEXT_PP(1),
*res;
TmToChar tmtc;
- struct pg_tm *tm;
+ struct pg_tm tt;
+ struct fmt_tm *tm;
int thisdate;
if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
@@ -4097,10 +4130,11 @@ timestamp_to_char(PG_FUNCTION_ARGS)
ZERO_tmtc(&tmtc);
tm = tmtcTm(&tmtc);
- if (timestamp2tm(dt, NULL, tm, &tmtcFsec(&tmtc), NULL, NULL) != 0)
+ if (timestamp2tm(dt, NULL, &tt, &tmtcFsec(&tmtc), NULL, NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ COPY_tm(tm, &tt);
thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
tm->tm_wday = (thisdate + 1) % 7;
@@ -4120,7 +4154,8 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
*res;
TmToChar tmtc;
int tz;
- struct pg_tm *tm;
+ struct pg_tm tt;
+ struct fmt_tm *tm;
int thisdate;
if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
@@ -4129,10 +4164,11 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
ZERO_tmtc(&tmtc);
tm = tmtcTm(&tmtc);
- if (timestamp2tm(dt, &tz, tm, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
+ if (timestamp2tm(dt, &tz, &tt, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ COPY_tm(tm, &tt);
thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
tm->tm_wday = (thisdate + 1) % 7;
@@ -4156,7 +4192,9 @@ interval_to_char(PG_FUNCTION_ARGS)
text *fmt = PG_GETARG_TEXT_PP(1),
*res;
TmToChar tmtc;
- struct pg_tm *tm;
+ struct fmt_tm *tm;
+ struct pg_itm tt,
+ *itm = &tt;
if (VARSIZE_ANY_EXHDR(fmt) <= 0)
PG_RETURN_NULL();
@@ -4164,8 +4202,14 @@ interval_to_char(PG_FUNCTION_ARGS)
ZERO_tmtc(&tmtc);
tm = tmtcTm(&tmtc);
- if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0)
- PG_RETURN_NULL();
+ interval2itm(*it, itm);
+ tmtc.fsec = itm->tm_usec;
+ tm->tm_sec = itm->tm_sec;
+ tm->tm_min = itm->tm_min;
+ tm->tm_hour = itm->tm_hour;
+ tm->tm_mday = itm->tm_mday;
+ tm->tm_mon = itm->tm_mon;
+ tm->tm_year = itm->tm_year;
/* wday is meaningless, yday approximates the total span in days */
tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;