On 2025-09-23 12:19, Dag-Erling Smørgrav wrote:
Paul Eggert via tz <[email protected]> writes:
(By the way, offtime_r is not documented in FreeBSD, so is it present
only as a compatibility hack there?)
I held off on documenting it after you rejected the patch.
Might not hurt to keep holding off until we finish this chat....
FreeBSD's current behavior seems more useful to me than what the C
standard mandates. I realize it's easier said than done, but I would
prefer at least trying to get the standard changed instead.
Changed to what, though?
FreeBSD gmtime and localtime return pointers to malloc'ed storage that
might be freed before their callers use the pointers, leading to
undefined behavior. (This cannot happen on platforms that conform to ISO
C and POSIX.) If a change is proposed to ISO C and POSIX, this issue
should be mentioned and taken into account in the wording.
To see the issue I'm talking about, compile and run the attached stress
test. Since it has undefined behavior on FreeBSD due to accessing freed
memory, I compiled it on a CheriBSD (FreeBSD 15) platform with "cc
-march=morello -mabi=purecap gmt3.c -lpthread". The stress test failed
with "In-address space security exception (core dumped)"; gdb reports
that the failure occurs in eqtm because its first pointer A is invalid.
The stress test trivially succeeds on GNU/Linux, which conforms to ISO C
and POSIX and which therefore does not attempt to free struct tm objects
dynamically (indeed, eqtm's two arguments are always the same pointer).
Regardless of whether ISO C and POSIX are changed, the current FreeBSD
behavior (assuming it's still wanted) should be covered in its man pages
so that FreeBSD's conflict with the current standards is documented.
An earlier version of this patch was posted here and rejected in 2021:
https://mm.icann.org/pipermail/tz/2021-September/030335.html
Thanks, I'll take a further look at that.
#include <errno.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* Number of threads to create, one at a time.
We wait for each thread to finish before creating the next one. */
#ifndef NTHREADS
# define NTHREADS 1000000
#endif
/* The result of calling gmtime in each thread. */
struct tm *gmtime_result[NTHREADS];
void *
thread_gmtime (void *arg)
{
void *r = gmtime (arg);
if (!r)
{
perror ("subthread gmtime");
exit (1);
}
return r;
}
bool
eqtm (struct tm *a, struct tm *b)
{
return (a->tm_year == b->tm_year
&& a->tm_mon == b->tm_mon
&& a->tm_mday == b->tm_mday
&& a->tm_wday == b->tm_wday
&& a->tm_yday == b->tm_yday
&& a->tm_hour == b->tm_hour
&& a->tm_min == b->tm_min
&& a->tm_sec == b->tm_sec
&& a->tm_isdst == b->tm_isdst
&& a->tm_gmtoff == b->tm_gmtoff
&& strcmp (a->tm_zone, b->tm_zone) == 0);
}
int
main ()
{
for (int i = 0; i < NTHREADS; i++)
{
pthread_t th;
time_t t = i;
int err = pthread_create (&th, 0, thread_gmtime, &t);
if (err)
return fprintf (stderr, "pthread_create: %s\n", strerror (err)), 1;
void *r;
err = pthread_join (th, &r);
if (err)
return fprintf (stderr, "pthread_join: %s\n", strerror (err)), 1;
gmtime_result[i] = r;
}
for (int i = 0; i < NTHREADS; i++)
{
time_t t = i;
struct tm *tm = gmtime (&t);
if (!tm)
return perror ("main thread gmtime"), 1;
if (!eqtm (gmtime_result[i], tm))
{
char abuf[100], bbuf[100];
char const *format = "%Y-%m-%d %H:%M:%S w=%d y=%d %z";
strftime (abuf, sizeof abuf, format, gmtime_result[i]);
strftime (bbuf, sizeof bbuf, format, tm);
fprintf (stderr,
("old gmtime (%d) results say\n"
"%s (%s), should say:\n"
"%s (%s)\n"),
i, abuf, gmtime_result[i]->tm_zone, bbuf, tm->tm_zone);
return 1;
}
}
}