From 78542928df41b4f0862a10ff12e26b6e71588b63 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Mon, 2 Jan 2023 16:45:04 -0800
Subject: [PROPOSED] Clarify detection of size overflow

* private.h (INDEX_MAX): New macro.  Use this in several places
to make size-overflow detection clearer.  Inspired by a suggestion
from Christos Zoulas.
---
 date.c    |  9 ++++-----
 private.h |  8 ++++++++
 zdump.c   |  9 ++++-----
 zic.c     | 19 ++++++++-----------
 4 files changed, 24 insertions(+), 21 deletions(-)

diff --git a/date.c b/date.c
index 97df6ab0..5819aa0e 100644
--- a/date.c
+++ b/date.c
@@ -124,10 +124,10 @@ dogmt(void)
 			continue;
 #if defined ckd_add && defined ckd_mul
 		if (!ckd_add(&n, n, 2) && !ckd_mul(&n, n, sizeof *fakeenv)
-		    && n <= SIZE_MAX)
+		    && n <= INDEX_MAX)
 		  fakeenv = malloc(n);
 #else
-		if (n <= min(PTRDIFF_MAX, SIZE_MAX) / sizeof *fakeenv - 2)
+		if (n <= INDEX_MAX / sizeof *fakeenv - 2)
 		  fakeenv = malloc((n + 2) * sizeof *fakeenv);
 #endif
 		if (fakeenv == NULL) {
@@ -194,10 +194,9 @@ timeout(FILE *fp, char const *format, struct tm const *tmp)
 
 	for ( ; ; ) {
 #ifdef ckd_mul
-		bool bigger = !ckd_mul(&size, size, 2) && size <= SIZE_MAX;
+		bool bigger = !ckd_mul(&size, size, 2) && size <= INDEX_MAX;
 #else
-		bool bigger = (size <= min(PTRDIFF_MAX, SIZE_MAX) / 2
-			       && (size *= 2, true));
+		bool bigger = size <= INDEX_MAX / 2 && (size *= 2, true);
 #endif
 		char *newcp = bigger ? realloc(cp, size) : NULL;
 		if (!newcp) {
diff --git a/private.h b/private.h
index 7fbbb64c..03ee4d10 100644
--- a/private.h
+++ b/private.h
@@ -419,6 +419,14 @@ typedef unsigned long uintmax_t;
 
 #endif /* PORT_TO_C89 */
 
+/* The maximum size of any created object, as a signed integer.
+   Although the C standard does not outright prohibit larger objects,
+   behavior is undefined if the result of pointer subtraction does not
+   fit into ptrdiff_t, and the code assumes in several places that
+   pointer subtraction works.  As a practical matter it's OK to not
+   support objects larger than this.  */
+#define INDEX_MAX ((ptrdiff_t) min(PTRDIFF_MAX, SIZE_MAX))
+
 /* 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
diff --git a/zdump.c b/zdump.c
index 5173c9ac..98537f4e 100644
--- a/zdump.c
+++ b/zdump.c
@@ -139,11 +139,10 @@ sumsize(size_t a, size_t b)
 {
 #ifdef ckd_add
   ptrdiff_t sum;
-  if (!ckd_add(&sum, a, b) && sum <= SIZE_MAX)
+  if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX)
     return sum;
 #else
-  ptrdiff_t sum_max = min(PTRDIFF_MAX, SIZE_MAX);
-  if (a <= sum_max && b <= sum_max - a)
+  if (a <= INDEX_MAX && b <= INDEX_MAX - a)
     return a + b;
 #endif
   size_overflow();
@@ -262,10 +261,10 @@ tzalloc(char const *val)
     while (*e++) {
 #  ifdef ckd_add
       if (ckd_add(&initial_nenvptrs, initial_envptrs, 1)
-	  || SIZE_MAX < initial_envptrs)
+	  || INDEX_MAX < initial_envptrs)
 	size_overflow();
 #  else
-      if (initial_nenvptrs == min(PTRDIFF_MAX, SIZE_MAX) / sizeof *environ)
+      if (initial_nenvptrs == INDEX_MAX / sizeof *environ)
 	size_overflow();
       initial_nenvptrs++;
 #  endif
diff --git a/zic.c b/zic.c
index 77417c6c..55180513 100644
--- a/zic.c
+++ b/zic.c
@@ -479,11 +479,10 @@ size_sum(size_t a, size_t b)
 {
 #ifdef ckd_add
   ptrdiff_t sum;
-  if (!ckd_add(&sum, a, b) && sum <= SIZE_MAX)
+  if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX)
     return sum;
 #else
-  ptrdiff_t sum_max = min(PTRDIFF_MAX, SIZE_MAX);
-  if (a <= sum_max && b <= sum_max - a)
+  if (a <= INDEX_MAX && b <= INDEX_MAX - a)
     return a + b;
 #endif
   size_overflow();
@@ -494,10 +493,10 @@ size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
 {
 #ifdef ckd_mul
   ptrdiff_t product;
-  if (!ckd_mul(&product, nitems, itemsize) && product <= SIZE_MAX)
+  if (!ckd_mul(&product, nitems, itemsize) && product <= INDEX_MAX)
     return product;
 #else
-  ptrdiff_t nitems_max = min(PTRDIFF_MAX, SIZE_MAX) / itemsize;
+  ptrdiff_t nitems_max = INDEX_MAX / itemsize;
   if (nitems <= nitems_max)
     return nitems * itemsize;
 #endif
@@ -553,11 +552,10 @@ grow_nitems_alloc(ptrdiff_t *nitems_alloc, ptrdiff_t itemsize)
 #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)
+      && !ckd_mul(&product, *nitems_alloc, itemsize) && product <= INDEX_MAX)
     return product;
 #else
-  ptrdiff_t amax = min(PTRDIFF_MAX, SIZE_MAX);
-  if (*nitems_alloc <= ((amax - 1) / 3 * 2) / itemsize) {
+  if (*nitems_alloc <= ((INDEX_MAX - 1) / 3 * 2) / itemsize) {
     *nitems_alloc += addend;
     return *nitems_alloc * itemsize;
   }
@@ -1399,7 +1397,7 @@ static char *
 relname(char const *target, char const *linkname)
 {
   size_t i, taillen, dir_len = 0, dotdots = 0;
-  ptrdiff_t dotdotetcsize, linksize = min(PTRDIFF_MAX, SIZE_MAX);
+  ptrdiff_t dotdotetcsize, linksize = INDEX_MAX;
   char const *f = target;
   char *result = NULL;
   if (*linkname == '/') {
@@ -1674,8 +1672,7 @@ infile(int fnum, char const *name)
 	wantcont = false;
 	for (num = 1; ; ++num) {
 		enum { bufsize_bound
-		  = (min(INT_MAX, min(PTRDIFF_MAX, SIZE_MAX))
-		     / FORMAT_LEN_GROWTH_BOUND) };
+		  = (min(INT_MAX, INDEX_MAX) / FORMAT_LEN_GROWTH_BOUND) };
 		char buf[min(_POSIX2_LINE_MAX, bufsize_bound)];
 		int nfields;
 		char *fields[MAX_FIELDS];
-- 
2.37.2

