[tz] [PROPOSED 3/6] date: integer overflow fixes

Paul Eggert eggert at cs.ucla.edu
Sun Nov 20 05:47:57 UTC 2022


* NEWS: Mention this.
* date.c (dogmt, timeout): Limit allocations to at most
PTRDIFF_MAX bytes to avoid undefined behavior on pointer subtraction.
(dogmt): Work even if ‘environ’ has more than INT_MAX entries.
(timeout): Avoid unnecessary pointer test, as the caller does this.
Avoid unnecessary copy of struct tm.  Don’t infloop on size_t overflow.
Double size of array if it’s too small, to avoid O(N**2) CPU.
* private.h (PTRDIFF_MAX): Move default value here ...
* zic.c: ... from here, since date.c now uses it.
---
 NEWS      |  3 +++
 date.c    | 32 +++++++++++---------------------
 private.h |  4 ++++
 zic.c     |  5 -----
 4 files changed, 18 insertions(+), 26 deletions(-)

diff --git a/NEWS b/NEWS
index 240bae8..7c46d3c 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,7 @@ News for the tz database
     C89 is now deprecated; please use C99 or later.
     Portability fixes for AIX, libintl, MS-Windows, musl, z/OS
     C23 timegm now supported by default
+    Fixes for unlikely integer overflows
 
 Unreleased, experimental changes
 
@@ -54,6 +55,8 @@ Unreleased, experimental changes
     uninitialized data has undefined behavior (strftime problem
     reported by Robert Elz).
 
+    Check more carefully for unlikely integer overflows.
+
   Changes to build procedure
 
     New Makefile rule check_mild that skips checking whether Link
diff --git a/date.c b/date.c
index 4e4b355..cbc0ec1 100644
--- a/date.c
+++ b/date.c
@@ -117,14 +117,13 @@ dogmt(void)
 	static char **	fakeenv;
 
 	if (fakeenv == NULL) {
-		register int	from;
-		register int	to;
-		register int	n;
 		static char	tzeutc0[] = "TZ=UTC0";
+		ptrdiff_t from, to, n;
 
 		for (n = 0;  environ[n] != NULL;  ++n)
 			continue;
-		fakeenv = malloc((n + 2) * sizeof *fakeenv);
+		if (n <= min(PTRDIFF_MAX, SIZE_MAX) / sizeof *fakeenv - 2)
+		  fakeenv = malloc((n + 2) * sizeof *fakeenv);
 		if (fakeenv == NULL) {
 			fprintf(stderr, _("date: Memory exhausted\n"));
 			errensure();
@@ -183,33 +182,24 @@ display(char const *format, time_t now)
 static void
 timeout(FILE *fp, char const *format, struct tm const *tmp)
 {
-	char *	cp;
-	size_t	result;
-	size_t	size;
-	struct tm tm;
-	int INCR = 1024;
+	char *cp = NULL;
+	ptrdiff_t result;
+	ptrdiff_t size = 1024 / 2;
 
-	if (!tmp) {
-		fprintf(stderr, _("date: error: time out of range\n"));
-		errensure();
-		return;
-	}
-	tm = *tmp;
-	tmp = &tm;
-	size = INCR;
-	cp = malloc(size);
 	for ( ; ; ) {
-		if (cp == NULL) {
+		bool bigger = (size <= min(PTRDIFF_MAX, SIZE_MAX) / 2
+			       && (size *= 2, true));
+		char *newcp = bigger ? realloc(cp, size) : NULL;
+		if (!newcp) {
 			fprintf(stderr,
 				_("date: error: can't get memory\n"));
 			errensure();
 			exit(retval);
 		}
+		cp = newcp;
 		result = strftime(cp, size, format, tmp);
 		if (result != 0)
 			break;
-		size += INCR;
-		cp = realloc(cp, size);
 	}
 	fwrite(cp + 1, 1, result - 1, fp);
 	free(cp);
diff --git a/private.h b/private.h
index bdadd61..4315a85 100644
--- a/private.h
+++ b/private.h
@@ -353,6 +353,10 @@ typedef long intmax_t;
 # endif
 #endif
 
+#ifndef PTRDIFF_MAX
+# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t))
+#endif
+
 #ifndef UINT_FAST32_MAX
 typedef unsigned long uint_fast32_t;
 #endif
diff --git a/zic.c b/zic.c
index 2db5486..752ac48 100644
--- a/zic.c
+++ b/zic.c
@@ -63,11 +63,6 @@ static zic_t const
 # define MKDIR_UMASK 0755
 #endif
 
-/* The maximum ptrdiff_t value, for pre-C99 platforms.  */
-#ifndef PTRDIFF_MAX
-static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t));
-#endif
-
 /* The minimum alignment of a type, for pre-C23 platforms.  */
 #if __STDC_VERSION__ < 201112
 # define alignof(type) offsetof(struct { char a; type b; }, b)
-- 
2.38.1



More information about the tz mailing list