[tz] [PATCH 6/8] Fix more overflow issues with extreme timestamps
Paul Eggert
eggert at cs.ucla.edu
Wed Mar 24 03:58:51 UTC 2021
* localtime.c (leaps_thru_end_of_nonneg, leaps_thru_end_of):
Args and result are now of type time_t not int, so as to
not mishandle arguments that do not fit in int.
All callers changed to not pass negative arguments unless
time_t is signed.
(timesub): Prefer int_fast32_t to int_fast64_t when either will do.
Use int_fast32_t instead of int when values might not fit into 16-bit
range (the C standard still allows 16-bit ints). Use time_t instead
of int when values might not fit into even 64-bit int (at least in
theory, time_t could unsigned and wider than 64 bits), taking care to
avoid negative values when time_t is unsigned. Fix glitches in
range testing by simply testing whether the year is in range
before assigning it to tm_year. Avoid potential overflow in
subtraction, by computing with TM_WDAY_BASE and tm_year rather than
EPOCH_WDAY and (y - EPOCH_YEAR).
* private.h (TM_WDAY_BASE): New macro.
---
localtime.c | 111 ++++++++++++++++++++++++----------------------------
private.h | 1 +
2 files changed, 52 insertions(+), 60 deletions(-)
diff --git a/localtime.c b/localtime.c
index f37280e..b0a03bb 100644
--- a/localtime.c
+++ b/localtime.c
@@ -1655,14 +1655,14 @@ offtime(const time_t *timep, long offset)
** where, to make the math easy, the answer for year zero is defined as zero.
*/
-static int
-leaps_thru_end_of_nonneg(int y)
+static time_t
+leaps_thru_end_of_nonneg(time_t y)
{
return y / 4 - y / 100 + y / 400;
}
-static int
-leaps_thru_end_of(register const int y)
+static time_t
+leaps_thru_end_of(time_t y)
{
return (y < 0
? -1 - leaps_thru_end_of_nonneg(-1 - y)
@@ -1675,13 +1675,12 @@ timesub(const time_t *timep, int_fast32_t offset,
{
register const struct lsinfo * lp;
register time_t tdays;
- register int idays; /* unsigned would be so 2003 */
- register int_fast64_t rem;
- int y;
register const int * ip;
register int_fast32_t corr;
register bool hit;
register int i;
+ int_fast32_t idays, rem, dayoff, dayrem;
+ time_t y;
corr = 0;
hit = false;
@@ -1695,67 +1694,63 @@ timesub(const time_t *timep, int_fast32_t offset,
break;
}
}
- y = EPOCH_YEAR;
+
+ /* Calculate the year, avoiding integer overflow even if
+ time_t is unsigned. */
tdays = *timep / SECSPERDAY;
rem = *timep % SECSPERDAY;
- while (tdays < 0 || tdays >= year_lengths[isleap(y)]) {
- int newy;
- register time_t tdelta;
- register int idelta;
+ rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY;
+ dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3;
+ rem %= SECSPERDAY;
+ /* y = (EPOCH_YEAR
+ + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT),
+ sans overflow. But calculate against 1570 (EPOCH_YEAR -
+ YEARSPERREPEAT) instead of against 1970 so that things work
+ for localtime values before 1970 when time_t is unsigned. */
+ dayrem = tdays % DAYSPERREPEAT;
+ dayrem += dayoff % DAYSPERREPEAT;
+ y = (EPOCH_YEAR - YEARSPERREPEAT
+ + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT
+ - ((dayrem % DAYSPERREPEAT) < 0)
+ + tdays / DAYSPERREPEAT)
+ * YEARSPERREPEAT));
+ /* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */
+ idays = tdays % DAYSPERREPEAT;
+ idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT;
+ idays %= DAYSPERREPEAT;
+ /* Increase Y and decrease IDAYS until IDAYS is in range for Y. */
+ while (year_lengths[isleap(y)] <= idays) {
+ int tdelta = idays / DAYSPERLYEAR;
+ int_fast32_t ydelta = tdelta + !tdelta;
+ time_t newy = y + ydelta;
register int leapdays;
-
- tdelta = tdays / DAYSPERLYEAR;
- if (! ((! TYPE_SIGNED(time_t) || INT_MIN <= tdelta)
- && tdelta <= INT_MAX))
- goto out_of_range;
- idelta = tdelta;
- if (idelta == 0)
- idelta = (tdays < 0) ? -1 : 1;
- newy = y;
- if (increment_overflow(&newy, idelta))
- goto out_of_range;
leapdays = leaps_thru_end_of(newy - 1) -
leaps_thru_end_of(y - 1);
- tdays -= ((time_t) newy - y) * DAYSPERNYEAR;
- tdays -= leapdays;
+ idays -= ydelta * DAYSPERNYEAR;
+ idays -= leapdays;
y = newy;
}
- /*
- ** Given the range, we can now fearlessly cast...
- */
- idays = tdays;
- rem += offset - corr;
- while (rem < 0) {
- rem += SECSPERDAY;
- --idays;
- }
- while (rem >= SECSPERDAY) {
- rem -= SECSPERDAY;
- ++idays;
- }
- while (idays < 0) {
- if (increment_overflow(&y, -1))
- goto out_of_range;
- idays += year_lengths[isleap(y)];
- }
- while (idays >= year_lengths[isleap(y)]) {
- idays -= year_lengths[isleap(y)];
- if (increment_overflow(&y, 1))
- goto out_of_range;
+
+ if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) {
+ int signed_y = y;
+ tmp->tm_year = signed_y - TM_YEAR_BASE;
+ } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y)
+ && y - TM_YEAR_BASE <= INT_MAX)
+ tmp->tm_year = y - TM_YEAR_BASE;
+ else {
+ errno = EOVERFLOW;
+ return NULL;
}
- tmp->tm_year = y;
- if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
- goto out_of_range;
tmp->tm_yday = idays;
/*
** The "extra" mods below avoid overflow problems.
*/
- tmp->tm_wday = EPOCH_WDAY +
- ((y - EPOCH_YEAR) % DAYSPERWEEK) *
- (DAYSPERNYEAR % DAYSPERWEEK) +
- leaps_thru_end_of(y - 1) -
- leaps_thru_end_of(EPOCH_YEAR - 1) +
- idays;
+ tmp->tm_wday = (TM_WDAY_BASE
+ + ((tmp->tm_year % DAYSPERWEEK)
+ * (DAYSPERNYEAR % DAYSPERWEEK))
+ + leaps_thru_end_of(y - 1)
+ - leaps_thru_end_of(TM_YEAR_BASE - 1)
+ + idays);
tmp->tm_wday %= DAYSPERWEEK;
if (tmp->tm_wday < 0)
tmp->tm_wday += DAYSPERWEEK;
@@ -1776,10 +1771,6 @@ timesub(const time_t *timep, int_fast32_t offset,
tmp->TM_GMTOFF = offset;
#endif /* defined TM_GMTOFF */
return tmp;
-
- out_of_range:
- errno = EOVERFLOW;
- return NULL;
}
char *
diff --git a/private.h b/private.h
index e38a4f3..7e587e4 100644
--- a/private.h
+++ b/private.h
@@ -748,6 +748,7 @@ char *ctime_r(time_t const *, char *);
#define TM_DECEMBER 11
#define TM_YEAR_BASE 1900
+#define TM_WDAY_BASE TM_MONDAY
#define EPOCH_YEAR 1970
#define EPOCH_WDAY TM_THURSDAY
--
2.27.0
More information about the tz
mailing list