* NEWS: Mention this.
* localtime.c (increment_overflow_64)
(increment_overflow_time_64, utoff_diff): New functions.
(time2sub, timeq): Avoid undefined behavior due to integer
overflow when dealing with outlandish but valid UT offsets like
-2**31 + 1 and 2**31 - 1.
---
 NEWS        |  4 +++
 localtime.c | 84 +++++++++++++++++++++++++++++++++++++++++++----------
 2 files changed, 73 insertions(+), 15 deletions(-)

diff --git a/NEWS b/NEWS
index ba0b13c5..d2aade51 100644
--- a/NEWS
+++ b/NEWS
@@ -30,6 +30,10 @@ Unreleased, experimental changes
     obsolete in release 2019b, and fixes some undefined behavior.
     (Undefined behavior reported by GitHub user Naveed8951.)
 
+    Some other undefined behavior, triggered by TZif files containing
+    outlandish but conforming UT offsets, has also been fixed.
+    (Also reported by Naveed8951.)
+
     zic no longer generates a no-op transition when
     simultaneous Rule and Zone changes cancel each other out.
     This occurs in tzdata only in Asia/Tbilisi on 1997-03-30.
diff --git a/localtime.c b/localtime.c
index 53a443b7..a2f92b2a 100644
--- a/localtime.c
+++ b/localtime.c
@@ -2416,6 +2416,19 @@ increment_overflow(int *ip, int j)
 #endif
 }
 
+static bool
+increment_overflow_64(int *ip, int_fast64_t j)
+{
+#ifdef ckd_add
+  return ckd_add(ip, *ip, j);
+#else
+  if (j < 0 ? *ip < INT_MIN - j : INT_MAX - j < *ip)
+    return true;
+  *ip += j;
+  return false;
+#endif
+}
+
 static bool
 increment_overflow_time_iinntt(time_t *tp, iinntt j)
 {
@@ -2431,6 +2444,21 @@ increment_overflow_time_iinntt(time_t *tp, iinntt j)
 #endif
 }
 
+static bool
+increment_overflow_time_64(time_t *tp, int_fast64_t j)
+{
+#ifdef ckd_add
+  return ckd_add(tp, *tp, j);
+#else
+  if (j < 0
+      ? (TYPE_SIGNED(time_t) ? *tp < TIME_T_MIN - j : *tp <= -1 - j)
+      : TIME_T_MAX - j < *tp)
+    return true;
+  *tp += j;
+  return false;
+#endif
+}
+
 static bool
 increment_overflow_time(time_t *tp, int_fast32_t j)
 {
@@ -2451,6 +2479,15 @@ increment_overflow_time(time_t *tp, int_fast32_t j)
 #endif
 }
 
+/* Return A - B, where both are in the range -2**31 + 1 .. 2**31 - 1.
+   The result cannot overflow.  */
+static int_fast64_t
+utoff_diff (int_fast32_t a, int_fast32_t b)
+{
+  int_fast64_t aa = a;
+  return aa - b;
+}
+
 static int
 tmcomp(register const struct tm *const atmp,
        register const struct tm *const btmp)
@@ -2647,8 +2684,18 @@ time2sub(struct tm *const tmp,
                     It's OK if YOURTM.TM_GMTOFF contains uninitialized data,
                     since the guess gets checked.  */
                  time_t altt = t;
-                 int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF;
-                 if (!increment_overflow_time(&altt, diff)) {
+                 int_fast64_t offdiff;
+                 bool v;
+# ifdef ckd_sub
+                 v = ckd_sub(&offdiff, mytm.TM_GMTOFF, yourtm.TM_GMTOFF);
+# else
+                 /* A ckd_sub approximation that is good enough here.  */
+                 v = !(-TWO_31_MINUS_1 <= yourm.TM_GMTOFF
+                       && your.TM_GMTOFF <= TWO_31_MINUS_1);
+                 if (!v)
+                   offdiff = utoff_diff(mytm.TM_GMTOFF, yourtm.TM_GMTOFF);
+# endif
+                 if (!v && !increment_overflow_time_64(&altt, offdiff)) {
                    struct tm alttm;
                    if (funcp(sp, &altt, offset, &alttm)
                        && alttm.tm_isdst == mytm.tm_isdst
@@ -2678,8 +2725,12 @@ time2sub(struct tm *const tmp,
                                        continue;
                                if (ttunspecified(sp, j))
                                  continue;
-                               newt = (t + sp->ttis[j].tt_utoff
-                                       - sp->ttis[i].tt_utoff);
+                               newt = t;
+                               if (increment_overflow_time_64
+                                   (&newt,
+                                    utoff_diff(sp->ttis[j].tt_utoff,
+                                               sp->ttis[i].tt_utoff)))
+                                 continue;
                                if (! funcp(sp, &newt, offset, &mytm))
                                        continue;
                                if (tmcomp(&mytm, &yourtm) != 0)
@@ -2778,17 +2829,20 @@ time1(struct tm *const tmp,
                        continue;
                for (otherind = 0; otherind < nseen; ++otherind) {
                        otheri = types[otherind];
-                       if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst)
-                               continue;
-                       tmp->tm_sec += (sp->ttis[otheri].tt_utoff
-                                       - sp->ttis[samei].tt_utoff);
-                       tmp->tm_isdst = !tmp->tm_isdst;
-                       t = time2(tmp, funcp, sp, offset, &okay);
-                       if (okay)
-                               return t;
-                       tmp->tm_sec -= (sp->ttis[otheri].tt_utoff
-                                       - sp->ttis[samei].tt_utoff);
-                       tmp->tm_isdst = !tmp->tm_isdst;
+                       if (sp->ttis[otheri].tt_isdst != tmp->tm_isdst) {
+                         int sec = tmp->tm_sec;
+                         if (!increment_overflow_64
+                             (&tmp->tm_sec,
+                              utoff_diff(sp->ttis[otheri].tt_utoff,
+                                         sp->ttis[samei].tt_utoff))) {
+                           tmp->tm_isdst = !tmp->tm_isdst;
+                           t = time2(tmp, funcp, sp, offset, &okay);
+                           if (okay)
+                             return t;
+                           tmp->tm_isdst = !tmp->tm_isdst;
+                         }
+                         tmp->tm_sec = sec;
+                       }
                }
        }
        return WRONG;
-- 
2.52.0

Reply via email to