[tz] infinite loop in time2sub
enh
enh at google.com
Thu Aug 22 18:35:32 UTC 2013
i recently upgraded Android's copy of tzcode to be closer to tzcode2013d.
with Android's signed 32-bit time_t i'm seeing a change in behavior for
out-of-range dates. previously given something like this, mktime would
return an error, but now it hangs in the binary search in time2sub.
struct tm t;
memset(&t, 0, sizeof(tm));
t.tm_year = 200;
t.tm_mon = 2;
t.tm_mday = 10;
changing the
if (t == lo) {
++t;
if (t <= lo)
return WRONG;
++lo;
} else if (t == hi) {
--t;
if (t >= hi)
return WRONG;
--hi;
}
back to
if (t == lo) {
if (t == TIME_T_MAX)
return WRONG;
++t;
++lo;
} else if (t == hi) {
if (t == TIME_T_MIN)
return WRONG;
--t;
--hi;
}
fixes things. looking at the git history in
https://github.com/eggert/tz.gitit looks like the old code, found in
Android up to and including jb-mr2
(but no longer in AOSP ToT), was never in upstream tzcode. here's where the
upstream tzcode's overflow checks were introduced:
commit 46fa7e98177cae664f673c6034c9ac74d0ccd0ec
Author: Arthur David Olson <olsona at elsie>
Date: Tue Aug 9 09:55:06 2005 -0400
overflow work
SCCS-file: localtime.c
SCCS-SID: 7.96
diff --git a/localtime.c b/localtime.c
index 1c3e9b0..d3edb5a 100644
--- a/localtime.c
+++ b/localtime.c
@@ -1616,9 +1616,13 @@ const int do_norm_secs;
if (dir != 0) {
if (t == lo) {
++t;
+ if (t <= lo)
+ return WRONG;
++lo;
} else if (t == hi) {
--t;
+ if (t >= hi)
+ return WRONG;
--hi;
}
if (lo > hi)
looking at the Android AOSP git history, it looks like we found and fixed
this bug years ago but never talked to upstream about it:
commit 2093d350be21ff086f9e145404877941b9a42c5c
Author: David 'Digit' Turner <digit at google.com>
Date: Wed Sep 9 17:41:59 2009 -0700
Fix an infinite loop in time2sub.
The problem is that time_t is signed, and the original code relied on
the
fact that (X + c < X) in case of overflow for c >= 0. Unfortunately,
this
condition is only guaranteed by the standard for unsigned arithmetic,
and
the gcc 4.4.0 optimizer did completely remove the corresponding test
from
the code. This resulted in a missing boundary check, and an infinite
loop.
The problem is solved by testing explicitely for TIME_T_MIN and
TIME_T_MAX
in the loop that uses this.
Also fix increment_overflow and long_increment_overflow which were buggy
for exactly the same reasons.
Note: a similar fix is needed for system/core/libcutils
diff --git a/libc/tzcode/localtime.c b/libc/tzcode/localtime.c
index 9c5f218..83c1011 100644
--- a/libc/tzcode/localtime.c
+++ b/libc/tzcode/localtime.c
@@ -75,6 +75,31 @@ static __inline__ void _tzUnlock(void)
pthread_mutex_unlock(&_tzMutex);
}
+/* Complex computations to determine the min/max of time_t depending
+ * on TYPE_BIT / TYPE_SIGNED / TYPE_INTEGRAL.
+ * These macros cannot be used in pre-processor directives, so we
+ * let the C compiler do the work, which makes things a bit funky.
+ */
+static const time_t TIME_T_MAX =
+ TYPE_INTEGRAL(time_t) ?
+ ( TYPE_SIGNED(time_t) ?
+ ~((time_t)1 << (TYPE_BIT(time_t)-1))
+ :
+ ~(time_t)0
+ )
+ : /* if time_t is a floating point number */
+ ( sizeof(time_t) > sizeof(float) ? (time_t)DBL_MAX :
(time_t)FLT_MAX );
+
+static const time_t TIME_T_MIN =
+ TYPE_INTEGRAL(time_t) ?
+ ( TYPE_SIGNED(time_t) ?
+ ((time_t)1 << (TYPE_BIT(time_t)-1))
+ :
+ 0
+ )
+ :
+ ( sizeof(time_t) > sizeof(float) ? (time_t)DBL_MIN :
(time_t)FLT_MIN );
+
#ifndef WILDABBR
/*
** Someone might make incorrect use of a time zone abbreviation:
@@ -1683,11 +1708,16 @@ increment_overflow(number, delta)
int * number;
int delta;
{
- int number0;
+ unsigned number0 = (unsigned)*number;
+ unsigned number1 = (unsigned)(number0 + delta);
+
+ *number = (int)number1;
- number0 = *number;
- *number += delta;
- return (*number < number0) != (delta < 0);
+ if (delta >= 0) {
+ return ((int)number1 < (int)number0);
+ } else {
+ return ((int)number1 > (int)number0);
+ }
}
static int
@@ -1695,11 +1725,16 @@ long_increment_overflow(number, delta)
long * number;
int delta;
{
- long number0;
+ unsigned long number0 = (unsigned long)*number;
+ unsigned long number1 = (unsigned long)(number0 + delta);
+
+ *number = (long)number1;
- number0 = *number;
- *number += delta;
- return (*number < number0) != (delta < 0);
+ if (delta >= 0) {
+ return ((long)number1 < (long)number0);
+ } else {
+ return ((long)number1 > (long)number0);
+ }
}
static int
@@ -1868,14 +1903,14 @@ const int do_norm_secs;
} else dir = tmcomp(&mytm, &yourtm);
if (dir != 0) {
if (t == lo) {
- ++t;
- if (t <= lo)
+ if (t == TIME_T_MAX)
return WRONG;
+ ++t;
++lo;
} else if (t == hi) {
- --t;
- if (t >= hi)
+ if (t == TIME_T_MIN)
return WRONG;
+ --t;
--hi;
}
if (lo > hi)
right now i'm going to work around this by building the tzcode part of the
C library with -fno-strict-overflow (because i don't know how many other
places are relying on this unspecified behavior), but i'll apply whatever
patch goes into upstream as soon as it's available.
--elliott
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mm.icann.org/pipermail/tz/attachments/20130822/d6bb65ed/attachment.html>
More information about the tz
mailing list