These integer overflows are not likely to occur in practice,
as usually sp->ats[0] < 0 and 0 <= sp->sp->ats[sp->timecnt - 1].
But they can occur with contrived TZif files.
* NEWS: Mention this.
* localtime.c (localsub): Avoid integer overflow bug in unlikely
cases such as 0 <= sp->ats[0] && t == TIME_T_MIN, or
sp->ats[sp->timecnt - 1] < 0 && t == TIME_T_MAX.
---
 NEWS        |  3 +++
 localtime.c | 38 +++++++++++++++++++-------------------
 2 files changed, 22 insertions(+), 19 deletions(-)

diff --git a/NEWS b/NEWS
index 35696fcb..9a941a73 100644
--- a/NEWS
+++ b/NEWS
@@ -56,6 +56,9 @@ Unreleased, experimental changes
     large for them, they now consistently fail instead of sometimes
     silently ignoring excess parts of the file.
 
+    localtime-related functions no longer mishandle extreme timestamps
+    when given TZif files holding some unlikely timezone histories.
+
   Changes to documentation
 
     URLs for release tarballs in tz-link.html have been updated to
diff --git a/localtime.c b/localtime.c
index 06cffbe5..29c7ca7c 100644
--- a/localtime.c
+++ b/localtime.c
@@ -2064,23 +2064,23 @@ 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;
-                       register time_t         seconds;
-                       register time_t         years;
-
-                       if (t < sp->ats[0])
-                               seconds = sp->ats[0] - t;
-                       else    seconds = t - sp->ats[sp->timecnt - 1];
-
-                       /* 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 = t + seconds + SECSPERREPEAT;
-                       else
-                         newt = t - seconds - SECSPERREPEAT;
+                       /* Avoid integer overflow when time_t is signed, by
+                          using secs_div_2 twice; the full value would
+                          always be even, so halving does not round.  */
+                       bool early = t < sp->ats[0];
+                       time_t
+                         tlo = early ? t : sp->ats[sp->timecnt - 1],
+                         thi = early ? sp->ats[0] : t,
+                         diffyears = ((thi / 2 - tlo / 2
+                                       + ((thi % 2 - tlo % 2 + 2) / 2 - 1))
+                                      / (SECSPERREPEAT / 2)
+                                      * YEARSPERREPEAT),
+                         years = diffyears + YEARSPERREPEAT,
+                         secs_div_2 = (diffyears * (AVGSECSPERYEAR / 2)
+                                       + SECSPERREPEAT / 2),
+                         newt = (early
+                                 ? t + secs_div_2 + secs_div_2
+                                 : t - secs_div_2 - secs_div_2);
 
                        if (newt < sp->ats[0] ||
                                newt > sp->ats[sp->timecnt - 1])
@@ -2088,7 +2088,7 @@ localsub(struct state const *sp, time_t const *timep, 
int_fast32_t setname,
                        result = localsub(sp, &newt, setname, tmp);
                        if (result) {
 # if defined ckd_add && defined ckd_sub
-                               if (t < sp->ats[0]
+                               if (early
                                    ? ckd_sub(&result->tm_year,
                                              result->tm_year, years)
                                    : ckd_add(&result->tm_year,
@@ -2098,7 +2098,7 @@ localsub(struct state const *sp, time_t const *timep, 
int_fast32_t setname,
                                register int_fast64_t newy;
 
                                newy = result->tm_year;
-                               if (t < sp->ats[0])
+                               if (early)
                                        newy -= years;
                                else    newy += years;
                                if (! (INT_MIN <= newy && newy <= INT_MAX))
-- 
2.55.0

Reply via email to