[tz] [PROPOSED] Fix zdump undefined behavior if !USE_LTZ

Paul Eggert eggert at cs.ucla.edu
Thu Oct 27 05:51:49 UTC 2022


When compiled with -DUSE_LTZ=0, zdump modified pointers in
the environ vector, which POSIX says results in undefined behavior.
Fix this by using setenv if available, otherwise by arranging for
the modifications to occur to the vector before it's assigned
or re-assigned to ‘environ’.
* Makefile, NEWS: Mention this.
* private.h (HAVE_SETENV): Default to 1.
* zdump.c (tzalloc): If HAVE_SETENV, just use setenv; it’s simpler.
Otherwise, avoid modifying environ, environ[0], environ[0][0], etc.
while environ is in use by getenv etc.  Do this by keeping two
independent environ vectors; the original one and fakeenv.
If !HAVE_SETENV, return the initial environ array.
(tzalloc, tzfree): Give the getenv family a chance of getting the
correct answer, by calling tzset after each time the environment
is modified.
(tzfree): Restore the initial environ array.
---
 Makefile  |  1 +
 NEWS      |  4 ++++
 private.h |  4 ++++
 zdump.c   | 57 ++++++++++++++++++++++++++++++++++++++-----------------
 4 files changed, 49 insertions(+), 17 deletions(-)

diff --git a/Makefile b/Makefile
index d3cfbd0..34cec49 100644
--- a/Makefile
+++ b/Makefile
@@ -223,6 +223,7 @@ LDLIBS=
 #  -DHAVE_MALLOC_ERRNO=0 if malloc etc. do not set errno on failure.
 #  -DHAVE_POSIX_DECLS=0 if your system's include files do not declare
 #	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_STDINT_H if you have a non-C99 compiler with <stdint.h>*
 #  -DHAVE_STRFTIME_L if <time.h> declares locale_t and strftime_l
diff --git a/NEWS b/NEWS
index a918aed..546fe11 100644
--- a/NEWS
+++ b/NEWS
@@ -73,6 +73,10 @@ Unreleased, experimental changes
     releases have been out of support since 2019.  This change affects
     only fat TZif files, as thin files never had the workaround.
 
+    zdump no longer modifies the environ vector when compiled on
+    platforms lacking tm_zone or when compiled with -DUSE_LTZ=0.
+    This avoid undefined behavior on POSIX platforms.
+
 
 Release 2022e - 2022-10-11 11:13:02 -0700
 
diff --git a/private.h b/private.h
index 2c7535d..ffb9d48 100644
--- a/private.h
+++ b/private.h
@@ -87,6 +87,10 @@
 # define HAVE_POSIX_DECLS 1
 #endif
 
+#ifndef HAVE_SETENV
+# define HAVE_SETENV 1
+#endif
+
 #ifndef HAVE_STRDUP
 # define HAVE_STRDUP 1
 #endif
diff --git a/zdump.c b/zdump.c
index 168f72a..ffb321a 100644
--- a/zdump.c
+++ b/zdump.c
@@ -228,33 +228,56 @@ mktime_z(timezone_t tz, struct tm *tmp)
 static timezone_t
 tzalloc(char const *val)
 {
+# if HAVE_SETENV
+  if (setenv("TZ", val, 1) != 0) {
+    perror("setenv");
+    exit(EXIT_FAILURE);
+  }
+  tzset();
+  return NULL;
+# else
+  enum { TZeqlen = 3 };
+  static char const TZeq[TZeqlen] = "TZ=";
   static char **fakeenv;
-  char **env = fakeenv;
-  char *env0;
-  if (! env) {
-    char **e = environ;
-    int to;
+  static size_t fakeenv0size;
+  void *freeable = NULL;
+  char **env = fakeenv, **initial_environ;
+  size_t valsize = strlen(val) + 1;
+  if (fakeenv0size < valsize) {
+    char **e = environ, **to;
+    ptrdiff_t initial_nenvptrs;  /* Counting the trailing NULL pointer.  */
 
     while (*e++)
       continue;
-    env = xmalloc(sumsize(sizeof *environ,
-			  (e - environ) * sizeof *environ));
-    to = 1;
-    for (e = environ; (env[to] = *e); e++)
-      to += strncmp(*e, "TZ=", 3) != 0;
+    initial_nenvptrs = e - environ;
+    fakeenv0size = sumsize(valsize, valsize);
+    fakeenv0size = max(fakeenv0size, 64);
+    freeable = env;
+    fakeenv = env =
+      xmalloc(sumsize(sumsize(sizeof *environ,
+			      initial_nenvptrs * sizeof *environ),
+		      sumsize(TZeqlen, fakeenv0size)));
+    to = env + 1;
+    for (e = environ; (*to = *e); e++)
+      to += strncmp(*e, TZeq, TZeqlen) != 0;
+    env[0] = memcpy(to + 1, TZeq, TZeqlen);
   }
-  env0 = xmalloc(sumsize(sizeof "TZ=", strlen(val)));
-  env[0] = strcat(strcpy(env0, "TZ="), val);
-  environ = fakeenv = env;
+  memcpy(env[0] + TZeqlen, val, valsize);
+  initial_environ = environ;
+  environ = env;
   tzset();
-  return env;
+  free(freeable);
+  return initial_environ;
+# endif
 }
 
 static void
-tzfree(timezone_t env)
+tzfree(timezone_t initial_environ)
 {
-  environ = env + 1;
-  free(env[0]);
+# if !HAVE_SETENV
+  environ = initial_environ;
+  tzset();
+# endif
 }
 #endif /* ! USE_LOCALTIME_RZ */
 
-- 
2.37.3



More information about the tz mailing list