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