* 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