[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