@@ -1939,37 +1939,55 @@ term nif_erlang_universaltime_0(Context *ctx, int argc, term argv[])
19391939 return build_datetime_from_tm (ctx , gmtime_r (& ts .tv_sec , & broken_down_time ));
19401940}
19411941
1942- // Workaround for newlib setenv memory leak: use putenv with a fixed-size
1943- // static buffer. The buffer is installed once and then modified in place.
1942+ // Workaround for newlib/picolibc setenv memory leak: use putenv with a
1943+ // fixed-size static buffer. The buffer is installed once via putenv and then
1944+ // modified in place so repeated TZ changes never allocate.
19441945// See: https://github.com/espressif/esp-idf/issues/3046
1946+ // Both newlib and picolibc leak the old "NAME=value" string on overwrite.
1947+ #if defined(__NEWLIB__ ) || defined(__PICOLIBC__ )
1948+ #define AVM_TZ_SETENV_LEAKS 1
1949+ #else
1950+ #define AVM_TZ_SETENV_LEAKS 0
1951+ #endif
1952+
1953+ #if AVM_TZ_SETENV_LEAKS
19451954
1955+ // Max TZ value length is 60 bytes; longest POSIX TZ strings (e.g.
1956+ // "CET-1CEST,M3.5.0/2,M10.5.0/3") are well under this limit.
19461957#define TZ_BUFFER_SIZE 64
1958+ #define TZ_MAX_VALUE_LEN (TZ_BUFFER_SIZE - 4) // 3 for "TZ=" + 1 for '\0'
19471959
19481960static char tz_buffer [TZ_BUFFER_SIZE ] = "TZ=" ;
19491961static bool tz_buffer_installed = false;
19501962static char * tz_env_value = NULL ;
19511963
1952- static void set_tz_value (const char * tz )
1964+ // Write a TZ value into the static buffer. Returns false if the value is
1965+ // too long to fit (the buffer is left unchanged in that case).
1966+ // Caller must hold env_spinlock.
1967+ static bool set_tz_value (const char * tz )
19531968{
19541969 size_t tz_len = strlen (tz );
1955- if (tz_len > TZ_BUFFER_SIZE - 4 ) {
1956- tz_len = TZ_BUFFER_SIZE - 4 ;
1970+ if (tz_len > TZ_MAX_VALUE_LEN ) {
1971+ return false ;
19571972 }
19581973 if (!tz_buffer_installed ) {
1959- memset (tz_buffer + 3 , ' ' , TZ_BUFFER_SIZE - 4 );
1960- tz_buffer [TZ_BUFFER_SIZE - 1 ] = '\0' ;
1961- putenv (tz_buffer );
1974+ memset (tz_buffer + 3 , 0 , TZ_BUFFER_SIZE - 3 );
1975+ if (putenv (tz_buffer ) != 0 ) {
1976+ return false;
1977+ }
19621978 tz_buffer_installed = true;
19631979 tz_env_value = getenv ("TZ" );
19641980 if (tz_env_value == NULL ) {
19651981 tz_env_value = tz_buffer + 3 ;
19661982 }
19671983 }
19681984 memcpy (tz_env_value , tz , tz_len );
1969- memset (tz_env_value + tz_len , ' ' , ( TZ_BUFFER_SIZE - 4 ) - tz_len );
1970- tz_env_value [ TZ_BUFFER_SIZE - 4 ] = '\0' ;
1985+ memset (tz_env_value + tz_len , 0 , TZ_MAX_VALUE_LEN - tz_len );
1986+ return true ;
19711987}
19721988
1989+ #endif // AVM_TZ_SETENV_LEAKS
1990+
19731991term nif_erlang_localtime (Context * ctx , int argc , term argv [])
19741992{
19751993 char * tz ;
@@ -1997,15 +2015,38 @@ term nif_erlang_localtime(Context *ctx, int argc, term argv[])
19972015 char * oldtz_env = getenv ("TZ" );
19982016 if (oldtz_env ) {
19992017 oldtz = strdup (oldtz_env );
2018+ if (UNLIKELY (oldtz == NULL )) {
2019+ #ifndef AVM_NO_SMP
2020+ smp_spinlock_unlock (& ctx -> global -> env_spinlock );
2021+ #endif
2022+ free (tz );
2023+ RAISE_ERROR (OUT_OF_MEMORY_ATOM );
2024+ }
20002025 }
2026+
2027+ #if AVM_TZ_SETENV_LEAKS
20012028 set_tz_value (tz );
2029+ #else
2030+ setenv ("TZ" , tz , 1 );
2031+ #endif
20022032 tzset ();
20032033 localtime = localtime_r (& ts .tv_sec , & storage );
2034+
20042035 if (oldtz ) {
2036+ #if AVM_TZ_SETENV_LEAKS
20052037 set_tz_value (oldtz );
2038+ #else
2039+ setenv ("TZ" , oldtz , 1 );
2040+ #endif
20062041 free (oldtz );
20072042 } else {
2008- set_tz_value ("" );
2043+ #if AVM_TZ_SETENV_LEAKS
2044+ // Cannot truly unset TZ with the static buffer approach.
2045+ // Setting to "UTC0" gives well-defined UTC behavior.
2046+ set_tz_value ("UTC0" );
2047+ #else
2048+ unsetenv ("TZ" );
2049+ #endif
20092050 }
20102051 tzset ();
20112052 } else {
@@ -2017,6 +2058,11 @@ term nif_erlang_localtime(Context *ctx, int argc, term argv[])
20172058#endif
20182059
20192060 free (tz );
2061+
2062+ if (UNLIKELY (localtime == NULL )) {
2063+ RAISE_ERROR (BADARG_ATOM );
2064+ }
2065+
20202066 return build_datetime_from_tm (ctx , localtime );
20212067}
20222068
0 commit comments