[PROPOSED 1/2] tzalloc fails with EFTYPE on invalid TZif files
* localtime.c (tzloadbody): For a TZif file that is not regular or that has a bad format, fail with errno set to EFTYPE, not EINVAL. This fits better with BSD tradition going back to 4.4BSD and Berkeley DB in the early 1990s, and is what NetBSD already does when the file is not a regular file. This changes user-visible behavior only for the value of errno just after tzalloc fails. * private.h (EFTYPE): Default to EINVAL, so that behavior does not change on platforms lacking EFTYPE. --- localtime.c | 30 +++++++++++++++--------------- private.h | 3 +++ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/localtime.c b/localtime.c index e3b8bb94..158f0150 100644 --- a/localtime.c +++ b/localtime.c @@ -1025,7 +1025,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, < 0) return errno; if (!S_ISREG(st.st_mode)) - return EINVAL; + return EFTYPE; } fid = OPENAT_TZDIR ? openat(dd, relname, oflags) : open(name, oflags); err = errno; @@ -1049,7 +1049,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, } up = &lsp->u.u; nread = read(fid, up->buf, sizeof up->buf); - err = tzheadsize <= nread ? 0 : nread < 0 ? errno : EINVAL; + err = tzheadsize <= nread ? 0 : nread < 0 ? errno : EFTYPE; } close(fid); if (err) @@ -1077,7 +1077,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, && 0 <= charcnt && charcnt <= TZ_MAX_CHARS && 0 <= ttisstdcnt && ttisstdcnt <= TZ_MAX_TYPES && 0 <= ttisutcnt && ttisutcnt <= TZ_MAX_TYPES)) - return EINVAL; + return EFTYPE; datablock_size = (timecnt * stored /* ats */ + timecnt /* types */ @@ -1087,12 +1087,12 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, + ttisstdcnt /* ttisstds */ + ttisutcnt); /* ttisuts */ if (nread < tzheadsize + datablock_size) - return EINVAL; + return EFTYPE; if (skip_datablock) p += datablock_size; else if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) && (ttisutcnt == typecnt || ttisutcnt == 0))) - return EINVAL; + return EFTYPE; else { int_fast64_t prevtr = -1; int_fast32_2s prevcorr; @@ -1115,7 +1115,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, ? TIME_T_MIN : at); if (timecnt && attime <= sp->ats[timecnt - 1]) { if (attime < sp->ats[timecnt - 1]) - return EINVAL; + return EFTYPE; sp->types[i - 1] = 0; timecnt--; } @@ -1128,7 +1128,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, for (i = 0; i < sp->timecnt; ++i) { unsigned char typ = *p++; if (sp->typecnt <= typ) - return EINVAL; + return EFTYPE; if (sp->types[i]) sp->types[timecnt++] = typ; } @@ -1142,18 +1142,18 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, cause trouble both in this file and in callers. Also, it violates RFC 9636 section 3.2. */ if (utoff < -TWO_31_MINUS_1) - return EINVAL; + return EFTYPE; ttisp = &sp->ttis[i]; ttisp->tt_utoff = utoff; p += 4; isdst = *p++; if (! (isdst < 2)) - return EINVAL; + return EFTYPE; ttisp->tt_isdst = isdst; desigidx = *p++; if (! (desigidx < sp->charcnt)) - return EINVAL; + return EFTYPE; ttisp->tt_desigidx = desigidx; } for (i = 0; i < sp->charcnt; ++i) @@ -1172,7 +1172,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, /* Leap seconds cannot occur before the Epoch, or out of order. */ if (tr <= prevtr) - return EINVAL; + return EFTYPE; /* To avoid other botches in this code, each leap second's correction must differ from the previous one's by 1 @@ -1184,7 +1184,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, ? corr == prevcorr + 1 : (corr == prevcorr || corr == prevcorr - 1)))) - return EINVAL; + return EFTYPE; prevtr = tr; prevcorr = corr; @@ -1206,7 +1206,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, ttisp->tt_ttisstd = false; else { if (*p != true && *p != false) - return EINVAL; + return EFTYPE; ttisp->tt_ttisstd = *p++; } } @@ -1218,7 +1218,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, ttisp->tt_ttisut = false; else { if (*p != true && *p != false) - return EINVAL; + return EFTYPE; ttisp->tt_ttisut = *p++; } } @@ -1303,7 +1303,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, } } if (sp->typecnt == 0) - return EINVAL; + return EFTYPE; return 0; } diff --git a/private.h b/private.h index 1e6db08c..b0faaeb9 100644 --- a/private.h +++ b/private.h @@ -243,6 +243,9 @@ strnlen (char const *s, size_t maxlen) # define EINVAL ERANGE #endif +#ifndef EFTYPE +# define EFTYPE EINVAL +#endif #ifndef ELOOP # define ELOOP EINVAL #endif -- 2.54.0
--- NEWS | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/NEWS b/NEWS index 459af904..0f613604 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,16 @@ Unreleased, experimental changes and ‘Zone Ouch -2562047788015215:30:08 - %%z’. This avoids undefined behavior in C. (Problems reported by Naveed Khan.) + On platforms that have EFTYPE, tzalloc now fails with errno set to + EFTYPE, not EINVAL, if it detects that the TZif file has an + invalid format or is not a regular file. Formerly it did this + only on NetBSD, and only when the file was not a regular file. + + Unprivileged programs no longer require TZif files to be regular + files or reject relative names containing ".." components. This + reverts to the more-permissive 2025b behavior, as the stricter + behavior did not catch on in FreeBSD. + Changes to commentary Northwest Territories is expected to move to permanent -06 prior to -- 2.54.0
Paul Eggert via tz <tz@iana.org> writes:
+ Unprivileged programs no longer require TZif files to be regular + files or reject relative names containing ".." components. This + reverts to the more-permissive 2025b behavior, as the stricter + behavior did not catch on in FreeBSD.
I have no idea what this refers to but I am still waiting for a reaction to https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290520#c5 I wish you would make an effort to talk directly to us instead of leaving passive-aggressive notes in the tzcode change log. DES -- Dag-Erling Smørgrav - des@des.no
On 2026-07-01 02:52, Dag-Erling Smørgrav wrote:
I have no idea what this refers to
It refers to whether to allow TZ settings like TZ="../zoneinfo-leaps/America/Los_Angeles", where I'm assuming "make REDO=posix_right" in the default TZDB build. More generally, it's whether relative TZ strings should be allowed to escape from /usr/share/zoneinfo and to read from any file on the system. The traditional tzcode behavior was to allow this in nonprivileged programs, but reject it in privileged programs. In tzdb 2025c I changed things to reject it for nonprivileged programs also. However, when 2025c was merged into FreeBSD this change was explicitly omitted, and <https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290520#c5> says that this omission was because it caused some unit tests to fail. So yesterday in commit ef93554c322b689f9e310a0e7ac699da86e1a70a I changed tzcode back to its traditional behavior in this area.
I am still waiting for a reaction to https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290520#c5
The last time I changed tzcode's behavior to be more like FreeBSD's, in <https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290520#c3> you indicated a preference to wait until after a tzcode release before taking the time to integrate the resulting tzcode into FreeBSD. With that in mind, I had planned to bring this new matter up in the FreeBSD bug report after the next tzcode release.
I wish you would make an effort to talk directly to us instead of leaving passive-aggressive notes in the tzcode change log. It's unfortunate that this attempt to work with you was misinterpreted as being passive-aggressive. That was not the intent.
This time around would you prefer to merge immediately, rather than wait for the next tzcode release? The next tzcode release should be fairly soon, if that matters.
participants (2)
-
Dag-Erling Smørgrav -
Paul Eggert