[tz] [PROPOSED] Prefer <stdckdint.h> for overflow checking

Paul Eggert eggert at cs.ucla.edu
Sun Nov 20 05:50:56 UTC 2022


If <stdckdint.h> is available, or if its macros can be implemented
easily, use the macros for overflow checking.  This should help avoid
future integer overflow bugs.
* date.c (dogmt, timeout):
* localtime.c (localsub, timesub, increment_overflow)
(increment_overflow32, increment_overflow_time, time2sub):
* zdump.c (sumsize, tzalloc):
* zic.c (size_sum, size_product, grow_nitems_alloc, oadd, tadd):
Prefer ckd_add etc. to doing overflow checking by hand.
* private.h (HAVE_STDCKDINT_H): Default to true if
__has_include(<stdckdint.h>).  Include <stdckdint.h>
if available.
(ckd_add, ckd_sub, ckd_mul): Provide substitutes on
recent-enough GCC-compatible compilers.
---
 Makefile    |  2 ++
 NEWS        |  5 ++++-
 date.c      | 10 ++++++++++
 localtime.c | 33 +++++++++++++++++++++++++++++++++
 private.h   | 29 +++++++++++++++++++++++++++++
 zdump.c     | 12 ++++++++++++
 zic.c       | 31 +++++++++++++++++++++++++++++++
 7 files changed, 121 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 5ec0d4a..92d78cb 100644
--- a/Makefile
+++ b/Makefile
@@ -230,6 +230,8 @@ LDLIBS=
 #	functions like 'link' or variables like 'tzname' required by POSIX
 #  -DHAVE_SETENV=0 if your system lacks the setenv function
 #  -DHAVE_SNPRINTF=0 if your system lacks the snprintf function
+#  -DHAVE_STDCKDINT_H=0 if neither <stdckdint.h> nor substitutes like
+#	__builtin_add_overflow work*
 #  -DHAVE_STDINT_H=0 if <stdint.h> does not work*
 #  -DHAVE_STRFTIME_L if <time.h> declares locale_t and strftime_l
 #  -DHAVE_STRDUP=0 if your system lacks the strdup function
diff --git a/NEWS b/NEWS
index 7c46d3c..a401bee 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ News for the tz database
     Fix some pre-1996 timestamps in northern Canada.
     C89 is now deprecated; please use C99 or later.
     Portability fixes for AIX, libintl, MS-Windows, musl, z/OS
+    In C code, use more C23 features if available.
     C23 timegm now supported by default
     Fixes for unlikely integer overflows
 
@@ -55,7 +56,9 @@ Unreleased, experimental changes
     uninitialized data has undefined behavior (strftime problem
     reported by Robert Elz).
 
-    Check more carefully for unlikely integer overflows.
+    Check more carefully for unlikely integer overflows, preferring
+    C23 <stdckdint.h> to overflow checking by hand, as the latter has
+    had obscure bugs.
 
   Changes to build procedure
 
diff --git a/date.c b/date.c
index cbc0ec1..1319462 100644
--- a/date.c
+++ b/date.c
@@ -122,8 +122,14 @@ dogmt(void)
 
 		for (n = 0;  environ[n] != NULL;  ++n)
 			continue;
+#if defined ckd_add && defined ckd_mul
+		if (!ckd_add(&n, n, 2) && !ckd_mul(&n, n, sizeof *fakeenv)
+		    && n <= SIZE_MAX)
+		  fakeenv = malloc(n);
+#else
 		if (n <= min(PTRDIFF_MAX, SIZE_MAX) / sizeof *fakeenv - 2)
 		  fakeenv = malloc((n + 2) * sizeof *fakeenv);
+#endif
 		if (fakeenv == NULL) {
 			fprintf(stderr, _("date: Memory exhausted\n"));
 			errensure();
@@ -187,8 +193,12 @@ timeout(FILE *fp, char const *format, struct tm const *tmp)
 	ptrdiff_t size = 1024 / 2;
 
 	for ( ; ; ) {
+#ifdef ckd_mul
+		bool bigger = !ckd_mul(&size, size, 2) && size <= SIZE_MAX;
+#else
 		bool bigger = (size <= min(PTRDIFF_MAX, SIZE_MAX) / 2
 			       && (size *= 2, true));
+#endif
 		char *newcp = bigger ? realloc(cp, size) : NULL;
 		if (!newcp) {
 			fprintf(stderr,
diff --git a/localtime.c b/localtime.c
index 7326567..7faa5d3 100644
--- a/localtime.c
+++ b/localtime.c
@@ -1565,6 +1565,14 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
 					return NULL;	/* "cannot happen" */
 			result = localsub(sp, &newt, setname, tmp);
 			if (result) {
+#if defined ckd_add && defined ckd_sub
+				if (t < sp->ats[0]
+				    ? ckd_sub(&result->tm_year,
+					      result->tm_year, years)
+				    : ckd_add(&result->tm_year,
+					      result->tm_year, years))
+				  return NULL;
+#else
 				register int_fast64_t newy;
 
 				newy = result->tm_year;
@@ -1574,6 +1582,7 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
 				if (! (INT_MIN <= newy && newy <= INT_MAX))
 					return NULL;
 				result->tm_year = newy;
+#endif
 			}
 			return result;
 	}
@@ -1783,6 +1792,12 @@ timesub(const time_t *timep, int_fast32_t offset,
 		y = newy;
 	}
 
+#ifdef ckd_add
+	if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE)) {
+	  errno = EOVERFLOW;
+	  return NULL;
+	}
+#else
 	if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) {
 	  int signed_y = y;
 	  tmp->tm_year = signed_y - TM_YEAR_BASE;
@@ -1793,6 +1808,7 @@ timesub(const time_t *timep, int_fast32_t offset,
 	  errno = EOVERFLOW;
 	  return NULL;
 	}
+#endif
 	tmp->tm_yday = idays;
 	/*
 	** The "extra" mods below avoid overflow problems.
@@ -1867,6 +1883,9 @@ ctime_r(const time_t *timep, char *buf)
 static bool
 increment_overflow(int *ip, int j)
 {
+#ifdef ckd_add
+	return ckd_add(ip, *ip, j);
+#else
 	register int const	i = *ip;
 
 	/*
@@ -1879,22 +1898,30 @@ increment_overflow(int *ip, int j)
 		return true;
 	*ip += j;
 	return false;
+#endif
 }
 
 static bool
 increment_overflow32(int_fast32_t *const lp, int const m)
 {
+#ifdef ckd_add
+	return ckd_add(lp, *lp, m);
+#else
 	register int_fast32_t const	l = *lp;
 
 	if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l))
 		return true;
 	*lp += m;
 	return false;
+#endif
 }
 
 static bool
 increment_overflow_time(time_t *tp, int_fast32_t j)
 {
+#ifdef ckd_add
+	return ckd_add(tp, *tp, j);
+#else
 	/*
 	** This is like
 	** 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...',
@@ -1906,6 +1933,7 @@ increment_overflow_time(time_t *tp, int_fast32_t j)
 		return true;
 	*tp += j;
 	return false;
+#endif
 }
 
 static bool
@@ -2029,11 +2057,16 @@ time2sub(struct tm *const tmp,
 				return WRONG;
 		}
 	}
+#ifdef ckd_add
+	if (ckd_add(&yourtm.tm_year, y, -TM_YEAR_BASE))
+	  return WRONG;
+#else
 	if (increment_overflow32(&y, -TM_YEAR_BASE))
 		return WRONG;
 	if (! (INT_MIN <= y && y <= INT_MAX))
 		return WRONG;
 	yourtm.tm_year = y;
+#endif
 	if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN)
 		saved_seconds = 0;
 	else if (yourtm.tm_year < EPOCH_YEAR - TM_YEAR_BASE) {
diff --git a/private.h b/private.h
index 4315a85..4d40582 100644
--- a/private.h
+++ b/private.h
@@ -392,6 +392,35 @@ typedef unsigned long uintmax_t;
 # define SIZE_MAX ((size_t) -1)
 #endif
 
+/* Support ckd_add, ckd_sub, ckd_mul on C23 or recent-enough GCC-like
+   hosts, unless compiled with -DHAVE_STDCKDINT_H=0 or with pre-C23 EDG.  */
+#if !defined HAVE_STDCKDINT_H && defined __has_include
+# if __has_include(<stdckdint.h>)
+#  define HAVE_STDCKDINT_H true
+# endif
+#endif
+#ifdef HAVE_STDCKDINT_H
+# if HAVE_STDCKDINT_H
+#  include <stdckdint.h>
+# endif
+#elif defined __EDG__
+/* Do nothing, to work around EDG bug <https://bugs.gnu.org/53256>.  */
+#elif defined __has_builtin
+# if __has_builtin(__builtin_add_overflow)
+#  define ckd_add(r, a, b) __builtin_add_overflow(a, b, r)
+# endif
+# if __has_builtin(__builtin_sub_overflow)
+#  define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r)
+# endif
+# if __has_builtin(__builtin_mul_overflow)
+#  define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
+# endif
+#elif 7 <= __GNUC__
+# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r)
+# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r)
+# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
+#endif
+
 #if 3 <= __GNUC__
 # define ATTRIBUTE_CONST __attribute__((const))
 # define ATTRIBUTE_MALLOC __attribute__((__malloc__))
diff --git a/zdump.c b/zdump.c
index 669d8fd..89bcd25 100644
--- a/zdump.c
+++ b/zdump.c
@@ -137,9 +137,15 @@ size_overflow(void)
 static ATTRIBUTE_PURE ptrdiff_t
 sumsize(size_t a, size_t b)
 {
+#ifdef ckd_add
+  ptrdiff_t sum;
+  if (!ckd_add(&sum, a, b) && sum <= SIZE_MAX)
+    return sum;
+#else
   ptrdiff_t sum_max = min(PTRDIFF_MAX, SIZE_MAX);
   if (a <= sum_max && b <= sum_max - a)
     return a + b;
+#endif
   size_overflow();
 }
 
@@ -254,9 +260,15 @@ tzalloc(char const *val)
     ptrdiff_t initial_nenvptrs = 1;  /* Counting the trailing NULL pointer.  */
 
     while (*e++) {
+#  ifdef ckd_add
+      if (ckd_add(&initial_nenvptrs, initial_envptrs, 1)
+	  || SIZE_MAX < initial_envptrs)
+	size_overflow();
+#  else
       if (initial_nenvptrs == min(PTRDIFF_MAX, SIZE_MAX) / sizeof *environ)
 	size_overflow();
       initial_nenvptrs++;
+#  endif
     }
     fakeenv0size = sumsize(valsize, valsize);
     fakeenv0size = max(fakeenv0size, 64);
diff --git a/zic.c b/zic.c
index a69d4e0..8f8088e 100644
--- a/zic.c
+++ b/zic.c
@@ -475,18 +475,30 @@ size_overflow(void)
 static ATTRIBUTE_PURE ptrdiff_t
 size_sum(size_t a, size_t b)
 {
+#ifdef ckd_add
+  ptrdiff_t sum;
+  if (!ckd_add(&sum, a, b) && sum <= SIZE_MAX)
+    return sum;
+#else
   ptrdiff_t sum_max = min(PTRDIFF_MAX, SIZE_MAX);
   if (a <= sum_max && b <= sum_max - a)
     return a + b;
+#endif
   size_overflow();
 }
 
 static ATTRIBUTE_PURE ptrdiff_t
 size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
 {
+#ifdef ckd_mul
+  ptrdiff_t product;
+  if (!ckd_mul(&product, nitems, itemsize) && product <= SIZE_MAX)
+    return product;
+#else
   ptrdiff_t nitems_max = min(PTRDIFF_MAX, SIZE_MAX) / itemsize;
   if (nitems <= nitems_max)
     return nitems * itemsize;
+#endif
   size_overflow();
 }
 
@@ -536,11 +548,18 @@ static ptrdiff_t
 grow_nitems_alloc(ptrdiff_t *nitems_alloc, ptrdiff_t itemsize)
 {
   ptrdiff_t addend = (*nitems_alloc >> 1) + 1;
+#if defined ckd_add && defined ckd_mul
+  ptrdiff_t product;
+  if (!ckd_add(nitems_alloc, *nitems_alloc, addend)
+      && !ckd_mul(&product, *nitems_alloc, itemsize) && product <= SIZE_MAX)
+    return product;
+#else
   ptrdiff_t amax = min(PTRDIFF_MAX, SIZE_MAX);
   if (*nitems_alloc <= ((amax - 1) / 3 * 2) / itemsize) {
     *nitems_alloc += addend;
     return *nitems_alloc * itemsize;
   }
+#endif
   memory_exhausted(_("integer overflow"));
 }
 
@@ -3716,16 +3735,28 @@ time_overflow(void)
 static ATTRIBUTE_PURE zic_t
 oadd(zic_t t1, zic_t t2)
 {
+#ifdef ckd_add
+  zic_t sum;
+  if (!ckd_add(&sum, t1, t2))
+    return sum;
+#else
   if (t1 < 0 ? ZIC_MIN - t1 <= t2 : t2 <= ZIC_MAX - t1)
     return t1 + t2;
+#endif
   time_overflow();
 }
 
 static ATTRIBUTE_PURE zic_t
 tadd(zic_t t1, zic_t t2)
 {
+#ifdef ckd_add
+  zic_t sum;
+  if (!ckd_add(&sum, t1, t2) && min_time <= sum && sum <= max_time)
+    return sum;
+#else
   if (t1 < 0 ? min_time - t1 <= t2 : t2 <= max_time - t1)
     return t1 + t2;
+#endif
   if (t1 == min_time || t1 == max_time)
     return t1;
   time_overflow();
-- 
2.38.1



More information about the tz mailing list