[tz] [PATCH 4/4] Fix integer overflow with TZ="EST5EDT4, 0/0, J365/0"

Paul Eggert eggert at cs.ucla.edu
Thu Feb 18 03:29:44 UTC 2021


* localtime.c (localsub): Redo computation of NEWT to avoid
integer overflow when SECONDS is close to the maximum time_t value.
Without this fix, localtime mishandles TZ="EST5EDT4,0/0,J365/0" by
incorrectly omitting transitions before 1970.  For example, "zdump
-i 'EST5EDT4,0/0,J365/0'" incorrectly lists 1970-01-01 as the
first transition date.
---
 NEWS        |  4 ++++
 localtime.c | 14 ++++++++++----
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/NEWS b/NEWS
index 8e968eb..af5dd9f 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,10 @@ Unreleased, experimental changes
     set to a all-year DST string like "EST5EDT4,0/0,J365/25" that does
     not conform to POSIX but does conform to Internet RFC 8536.
 
+    Fix another bug that caused 'localtime' etc. to crash when TZ was
+    set to a POSIX-conforming but unusual TZ string like
+    "EST5EDT4,0/0,J365/0", where almost all the year is DST.
+
     Fix bug in zic -r; in some cases, the dummy time type after the
     last time transition disagreed with the TZ string, contrary to
     Internet RFC 8563 section 3.3.
diff --git a/localtime.c b/localtime.c
index 333c6ea..b40e5e8 100644
--- a/localtime.c
+++ b/localtime.c
@@ -1459,7 +1459,7 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
 	}
 	if ((sp->goback && t < sp->ats[0]) ||
 		(sp->goahead && t > sp->ats[sp->timecnt - 1])) {
-			time_t			newt = t;
+			time_t newt;
 			register time_t		seconds;
 			register time_t		years;
 
@@ -1467,11 +1467,17 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
 				seconds = sp->ats[0] - t;
 			else	seconds = t - sp->ats[sp->timecnt - 1];
 			--seconds;
-			years = (seconds / SECSPERREPEAT + 1) * YEARSPERREPEAT;
+
+			/* Beware integer overflow, as SECONDS might
+			   be close to the maximum time_t.  */
+			years = seconds / SECSPERREPEAT * YEARSPERREPEAT;
 			seconds = years * AVGSECSPERYEAR;
+			years += YEARSPERREPEAT;
 			if (t < sp->ats[0])
-				newt += seconds;
-			else	newt -= seconds;
+			  newt = t + seconds + SECSPERREPEAT;
+			else
+			  newt = t - seconds - SECSPERREPEAT;
+
 			if (newt < sp->ats[0] ||
 				newt > sp->ats[sp->timecnt - 1])
 					return NULL;	/* "cannot happen" */
-- 
2.27.0



More information about the tz mailing list