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

Commit a70e13a

Browse files
committed
Be more careful about out-of-range dates and timestamps.
Tighten the semantics of boundary-case timestamptz so that we allow timestamps >= '4714-11-24 00:00+00 BC' and < 'ENDYEAR-01-01 00:00+00 AD' exactly, no more and no less, but it is allowed to enter timestamps within that range using non-GMT timezone offsets (which could make the nominal date 4714-11-23 BC or ENDYEAR-01-01 AD). This eliminates dump/reload failure conditions for timestamps near the endpoints. To do this, separate checking of the inputs for date2j() from the final range check, and allow the Julian date code to handle a range slightly wider than the nominal range of the datatypes. Also add a bunch of checks to detect out-of-range dates and timestamps that formerly could be returned by operations such as date-plus-integer. All C-level functions that return date, timestamp, or timestamptz should now be proof against returning a value that doesn't pass IS_VALID_DATE() or IS_VALID_TIMESTAMP(). Vitaly Burovoy, reviewed by Anastasia Lubennikova, and substantially whacked around by me
1 parent f2b74b0 commit a70e13a

File tree

13 files changed

+364
-53
lines changed

13 files changed

+364
-53
lines changed

src/backend/utils/adt/date.c

+102-16
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,20 @@ date_in(PG_FUNCTION_ARGS)
160160
break;
161161
}
162162

163+
/* Prevent overflow in Julian-day routines */
163164
if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
164165
ereport(ERROR,
165166
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
166167
errmsg("date out of range: \"%s\"", str)));
167168

168169
date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
169170

171+
/* Now check for just-out-of-range dates */
172+
if (!IS_VALID_DATE(date))
173+
ereport(ERROR,
174+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
175+
errmsg("date out of range: \"%s\"", str)));
176+
170177
PG_RETURN_DATEADT(date);
171178
}
172179

@@ -209,8 +216,7 @@ date_recv(PG_FUNCTION_ARGS)
209216
/* Limit to the same range that date_in() accepts. */
210217
if (DATE_NOT_FINITE(result))
211218
/* ok */ ;
212-
else if (result < -POSTGRES_EPOCH_JDATE ||
213-
result >= JULIAN_MAX - POSTGRES_EPOCH_JDATE)
219+
else if (!IS_VALID_DATE(result))
214220
ereport(ERROR,
215221
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
216222
errmsg("date out of range")));
@@ -258,6 +264,7 @@ make_date(PG_FUNCTION_ARGS)
258264
errmsg("date field value out of range: %d-%02d-%02d",
259265
tm.tm_year, tm.tm_mon, tm.tm_mday)));
260266

267+
/* Prevent overflow in Julian-day routines */
261268
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
262269
ereport(ERROR,
263270
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
@@ -266,6 +273,13 @@ make_date(PG_FUNCTION_ARGS)
266273

267274
date = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
268275

276+
/* Now check for just-out-of-range dates */
277+
if (!IS_VALID_DATE(date))
278+
ereport(ERROR,
279+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
280+
errmsg("date out of range: %d-%02d-%02d",
281+
tm.tm_year, tm.tm_mon, tm.tm_mday)));
282+
269283
PG_RETURN_DATEADT(date);
270284
}
271285

@@ -427,11 +441,21 @@ date_pli(PG_FUNCTION_ARGS)
427441
{
428442
DateADT dateVal = PG_GETARG_DATEADT(0);
429443
int32 days = PG_GETARG_INT32(1);
444+
DateADT result;
430445

431446
if (DATE_NOT_FINITE(dateVal))
432-
days = 0; /* can't change infinity */
447+
PG_RETURN_DATEADT(dateVal); /* can't change infinity */
448+
449+
result = dateVal + days;
450+
451+
/* Check for integer overflow and out-of-allowed-range */
452+
if ((days >= 0 ? (result < dateVal) : (result > dateVal)) ||
453+
!IS_VALID_DATE(result))
454+
ereport(ERROR,
455+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
456+
errmsg("date out of range")));
433457

434-
PG_RETURN_DATEADT(dateVal + days);
458+
PG_RETURN_DATEADT(result);
435459
}
436460

437461
/* Subtract a number of days from a date, giving a new date.
@@ -441,11 +465,21 @@ date_mii(PG_FUNCTION_ARGS)
441465
{
442466
DateADT dateVal = PG_GETARG_DATEADT(0);
443467
int32 days = PG_GETARG_INT32(1);
468+
DateADT result;
444469

445470
if (DATE_NOT_FINITE(dateVal))
446-
days = 0; /* can't change infinity */
471+
PG_RETURN_DATEADT(dateVal); /* can't change infinity */
472+
473+
result = dateVal - days;
474+
475+
/* Check for integer overflow and out-of-allowed-range */
476+
if ((days >= 0 ? (result > dateVal) : (result < dateVal)) ||
477+
!IS_VALID_DATE(result))
478+
ereport(ERROR,
479+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
480+
errmsg("date out of range")));
447481

448-
PG_RETURN_DATEADT(dateVal - days);
482+
PG_RETURN_DATEADT(result);
449483
}
450484

451485
/*
@@ -464,14 +498,18 @@ date2timestamp(DateADT dateVal)
464498
TIMESTAMP_NOEND(result);
465499
else
466500
{
467-
#ifdef HAVE_INT64_TIMESTAMP
468-
/* date is days since 2000, timestamp is microseconds since same... */
469-
result = dateVal * USECS_PER_DAY;
470-
/* Date's range is wider than timestamp's, so check for overflow */
471-
if (result / USECS_PER_DAY != dateVal)
501+
/*
502+
* Date's range is wider than timestamp's, so check for boundaries.
503+
* Since dates have the same minimum values as timestamps, only upper
504+
* boundary need be checked for overflow.
505+
*/
506+
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
472507
ereport(ERROR,
473508
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
474509
errmsg("date out of range for timestamp")));
510+
#ifdef HAVE_INT64_TIMESTAMP
511+
/* date is days since 2000, timestamp is microseconds since same... */
512+
result = dateVal * USECS_PER_DAY;
475513
#else
476514
/* date is days since 2000, timestamp is seconds since same... */
477515
result = dateVal * (double) SECS_PER_DAY;
@@ -495,6 +533,16 @@ date2timestamptz(DateADT dateVal)
495533
TIMESTAMP_NOEND(result);
496534
else
497535
{
536+
/*
537+
* Date's range is wider than timestamp's, so check for boundaries.
538+
* Since dates have the same minimum values as timestamps, only upper
539+
* boundary need be checked for overflow.
540+
*/
541+
if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
542+
ereport(ERROR,
543+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
544+
errmsg("date out of range for timestamp")));
545+
498546
j2date(dateVal + POSTGRES_EPOCH_JDATE,
499547
&(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
500548
tm->tm_hour = 0;
@@ -504,14 +552,18 @@ date2timestamptz(DateADT dateVal)
504552

505553
#ifdef HAVE_INT64_TIMESTAMP
506554
result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC;
507-
/* Date's range is wider than timestamp's, so check for overflow */
508-
if ((result - tz * USECS_PER_SEC) / USECS_PER_DAY != dateVal)
509-
ereport(ERROR,
510-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
511-
errmsg("date out of range for timestamp")));
512555
#else
513556
result = dateVal * (double) SECS_PER_DAY + tz;
514557
#endif
558+
559+
/*
560+
* Since it is possible to go beyond allowed timestamptz range because
561+
* of time zone, check for allowed timestamp range after adding tz.
562+
*/
563+
if (!IS_VALID_TIMESTAMP(result))
564+
ereport(ERROR,
565+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
566+
errmsg("date out of range for timestamp")));
515567
}
516568

517569
return result;
@@ -1053,7 +1105,17 @@ abstime_date(PG_FUNCTION_ARGS)
10531105

10541106
default:
10551107
abstime2tm(abstime, &tz, tm, NULL);
1108+
/* Prevent overflow in Julian-day routines */
1109+
if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
1110+
ereport(ERROR,
1111+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
1112+
errmsg("abstime out of range for date")));
10561113
result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
1114+
/* Now check for just-out-of-range dates */
1115+
if (!IS_VALID_DATE(result))
1116+
ereport(ERROR,
1117+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
1118+
errmsg("abstime out of range for date")));
10571119
break;
10581120
}
10591121

@@ -1678,7 +1740,13 @@ datetime_timestamp(PG_FUNCTION_ARGS)
16781740

16791741
result = date2timestamp(date);
16801742
if (!TIMESTAMP_NOT_FINITE(result))
1743+
{
16811744
result += time;
1745+
if (!IS_VALID_TIMESTAMP(result))
1746+
ereport(ERROR,
1747+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
1748+
errmsg("timestamp out of range")));
1749+
}
16821750

16831751
PG_RETURN_TIMESTAMP(result);
16841752
}
@@ -2550,11 +2618,29 @@ datetimetz_timestamptz(PG_FUNCTION_ARGS)
25502618
TIMESTAMP_NOEND(result);
25512619
else
25522620
{
2621+
/*
2622+
* Date's range is wider than timestamp's, so check for boundaries.
2623+
* Since dates have the same minimum values as timestamps, only upper
2624+
* boundary need be checked for overflow.
2625+
*/
2626+
if (date >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE))
2627+
ereport(ERROR,
2628+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2629+
errmsg("date out of range for timestamp")));
25532630
#ifdef HAVE_INT64_TIMESTAMP
25542631
result = date * USECS_PER_DAY + time->time + time->zone * USECS_PER_SEC;
25552632
#else
25562633
result = date * (double) SECS_PER_DAY + time->time + time->zone;
25572634
#endif
2635+
2636+
/*
2637+
* Since it is possible to go beyond allowed timestamptz range because
2638+
* of time zone, check for allowed timestamp range after adding tz.
2639+
*/
2640+
if (!IS_VALID_TIMESTAMP(result))
2641+
ereport(ERROR,
2642+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2643+
errmsg("date out of range for timestamp")));
25582644
}
25592645

25602646
PG_RETURN_TIMESTAMP(result);

src/backend/utils/adt/datetime.c

+5-3
Original file line numberDiff line numberDiff line change
@@ -280,14 +280,16 @@ strtoi(const char *nptr, char **endptr, int base)
280280
* and calendar date for all non-negative Julian days
281281
* (i.e. from Nov 24, -4713 on).
282282
*
283-
* These routines will be used by other date/time packages
284-
* - thomas 97/02/25
285-
*
286283
* Rewritten to eliminate overflow problems. This now allows the
287284
* routines to work correctly for all Julian day counts from
288285
* 0 to 2147483647 (Nov 24, -4713 to Jun 3, 5874898) assuming
289286
* a 32-bit integer. Longer types should also work to the limits
290287
* of their precision.
288+
*
289+
* Actually, date2j() will work sanely, in the sense of producing
290+
* valid negative Julian dates, significantly before Nov 24, -4713.
291+
* We rely on it to do so back to Nov 1, -4713; see IS_VALID_JULIAN()
292+
* and associated commentary in timestamp.h.
291293
*/
292294

293295
int

src/backend/utils/adt/formatting.c

+8
Original file line numberDiff line numberDiff line change
@@ -3525,6 +3525,7 @@ to_date(PG_FUNCTION_ARGS)
35253525

35263526
do_to_timestamp(date_txt, fmt, &tm, &fsec);
35273527

3528+
/* Prevent overflow in Julian-day routines */
35283529
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
35293530
ereport(ERROR,
35303531
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
@@ -3533,6 +3534,13 @@ to_date(PG_FUNCTION_ARGS)
35333534

35343535
result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
35353536

3537+
/* Now check for just-out-of-range dates */
3538+
if (!IS_VALID_DATE(result))
3539+
ereport(ERROR,
3540+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3541+
errmsg("date out of range: \"%s\"",
3542+
text_to_cstring(date_txt))));
3543+
35363544
PG_RETURN_DATEADT(result);
35373545
}
35383546

0 commit comments

Comments
 (0)