[tz] source code question regarding localtime.c PS

Paul Eggert eggert at cs.ucla.edu
Wed Aug 7 16:05:55 UTC 2013


Thanks for mentioning the problem.  The code relies on undefined
behavior when a time_t value is outside int_fast64_t range.
It's better to avoid undefined behavior, and the code would be
clearer if we put in some comments about this issue.  Here's
a proposed patch for this, which I've pushed to the experimental
github repository.

>From b0bf6d8fee915393274deb7df0ba2aacc8756d99 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert at cs.ucla.edu>
Date: Wed, 7 Aug 2013 09:03:09 -0700
Subject: [PATCH] Avoid undefined behavior on integer overflow.

Problem reported by Alois Treindl in
<http://mm.icann.org/pipermail/tz/2013-August/019493.html>.
* localtime.c (truncate_time): New function.
(localsub): Use it to avoid undefined behavior on integer overflow.
* private.h (INTMAX_MAX, INTMAX_MIN, UINTMAX_MAX):
New macros, for older platforms that lack them.
---
 localtime.c | 50 ++++++++++++++++++++++++++++++++++++++------------
 private.h   | 15 +++++++++++++++
 2 files changed, 53 insertions(+), 12 deletions(-)

diff --git a/localtime.c b/localtime.c
index 23bc635..353c73b 100644
--- a/localtime.c
+++ b/localtime.c
@@ -1254,6 +1254,38 @@ tzset(void)
 	settzname();
 }
 
+/* Return T with any fractional part discarded.  */
+static time_t
+truncate_time(time_t t)
+{
+	/*
+	** If time_t is floating-point, convert it to integer and
+	** back; this discards the fraction.  Avoid using <math.h>
+	** functions, as we don't want to depend on <math.h>.  Use <,
+	** not <=, when comparing against *_MIN and *_MAX values, as
+	** this avoids undefined behavior when, for example,
+	** INTMAX_MAX is converted to a larger time_t value before it
+	** is compared.
+	**
+	** On all platforms that we know of, if a time value is
+	** outside intmax_t/uintmax_t range, then it is an integer so we can
+	** simply return it.  There's no simple, portable way to check this.
+	** If you know of a counterexample platform, please report a bug.
+	*/
+	if (!TYPE_INTEGRAL(time_t)) {
+		if (INTMAX_MIN < t && t < INTMAX_MAX) {
+			intmax_t i = t;
+			return i;
+		}
+		if (0 <= t && t < UINTMAX_MAX) {
+			uintmax_t i = t;
+			return i;
+		}
+	}
+
+	return t;
+}
+
 /*
 ** The easy way to behave "as if no library function calls" localtime
 ** is to not call it--so we drop its guts into "localsub", which can be
@@ -1283,21 +1315,15 @@ localsub(const time_t *const timep, const int_fast32_t offset,
 		(sp->goahead && t > sp->ats[sp->timecnt - 1])) {
 			time_t			newt = t;
 			register time_t		seconds;
-			register time_t		tcycles;
-			register int_fast64_t	icycles;
+			register time_t		years;
 
 			if (t < sp->ats[0])
 				seconds = sp->ats[0] - t;
 			else	seconds = t - sp->ats[sp->timecnt - 1];
 			--seconds;
-			tcycles = seconds / YEARSPERREPEAT / AVGSECSPERYEAR;
-			++tcycles;
-			icycles = tcycles;
-			if (tcycles - icycles >= 1 || icycles - tcycles >= 1)
-				return NULL;
-			seconds = icycles;
-			seconds *= YEARSPERREPEAT;
-			seconds *= AVGSECSPERYEAR;
+			years = (truncate_time (seconds / SECSPERREPEAT + 1)
+				 * YEARSPERREPEAT);
+			seconds = years * AVGSECSPERYEAR;
 			if (t < sp->ats[0])
 				newt += seconds;
 			else	newt -= seconds;
@@ -1310,8 +1336,8 @@ localsub(const time_t *const timep, const int_fast32_t offset,
 
 				newy = tmp->tm_year;
 				if (t < sp->ats[0])
-					newy -= icycles * YEARSPERREPEAT;
-				else	newy += icycles * YEARSPERREPEAT;
+					newy -= years;
+				else	newy += years;
 				tmp->tm_year = newy;
 				if (tmp->tm_year != newy)
 					return NULL;
diff --git a/private.h b/private.h
index a31a26e..f00a926 100644
--- a/private.h
+++ b/private.h
@@ -167,9 +167,18 @@ typedef int int_fast32_t;
 # if defined LLONG_MAX || defined __LONG_LONG_MAX__
 typedef long long intmax_t;
 #  define PRIdMAX "lld"
+#  ifdef LLONG_MAX
+#   define INTMAX_MAX LLONG_MAX
+#   define INTMAX_MIN LLONG_MIN
+#  else
+#   define INTMAX_MAX __LONG_LONG_MAX__
+#   define INTMAX_MIN __LONG_LONG_MIN__
+#  endif
 # else
 typedef long intmax_t;
 #  define PRIdMAX "ld"
+#  define INTMAX_MAX LONG_MAX
+#  define INTMAX_MIN LONG_MIN
 # endif
 #endif
 
@@ -177,9 +186,15 @@ typedef long intmax_t;
 # if defined ULLONG_MAX || defined __LONG_LONG_MAX__
 typedef unsigned long long uintmax_t;
 #  define PRIuMAX "llu"
+#  ifdef ULLONG_MAX
+#   define UINTMAX_MAX ULLONG_MAX
+#  else
+#   define UINTMAX_MAX (__LONG_LONG_MAX__ * 2ULL + 1)
+#  endif
 # else
 typedef unsigned long uintmax_t;
 #  define PRIuMAX "lu"
+#  define UINTMAX_MAX ULONG_MAX
 # endif
 #endif
 
-- 
1.8.1.2




More information about the tz mailing list