summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDag-Erling Smørgrav <des@FreeBSD.org>2026-04-24 19:54:03 +0000
committerDag-Erling Smørgrav <des@FreeBSD.org>2026-04-24 19:54:03 +0000
commitb17ccc1f4591d37eb3a671b37aecbd30546842f1 (patch)
treeb336b08d9de53c25227eabd42b3fe4b8f5ea9762
parentcd59570c180e34a6716c2d61a3047ac2f9407795 (diff)
-rw-r--r--NEWS53
-rw-r--r--tz-art.html2
-rw-r--r--tz-link.html22
-rw-r--r--version2
-rw-r--r--zic.c155
5 files changed, 166 insertions, 68 deletions
diff --git a/NEWS b/NEWS
index acd22280cb3a..ea52e67e804a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,10 +1,47 @@
News for the tz database
+Release 2026b - 2026-04-22 23:06:43 -0700
+
+ Briefly:
+ British Columbia moved to permanent -07 on 2026-03-09.
+ Some more overflow bugs have been fixed in zic.
+
+ Changes to future timestamps
+
+ British Columbia’s 2026-03-08 spring forward was its last
+ foreseeable clock change, as it moved to permanent -07 thereafter.
+ (Thanks to Arthur David Olson.) Although the change to permanent
+ -07 legally took place on 2026-03-09, temporarily model the change
+ to occur on 2026-11-01 at 02:00 instead. This works around a
+ limitation in CLDR v48.2 (2026-03-17). This temporary hack is
+ planned to be removed after CLDR is fixed.
+
+ Changes to code
+
+ zic no longer mishandles a last transition to a new time type.
+
+ zic no longer overflows a buffer when generating a TZ string like
+ "PST-167:59:58PDT-167:59:59,M11.5.6/-167:59:59,M12.5.6/-167:59:59",
+ which can occur with adversarial input. (Thanks to Naveed Khan.)
+
+ zic no longer generates a longer TZif file than necessary when
+ an earlier time zone abbreviation is a suffix of a later one.
+ As a nice side effect, zic no longer overflows a buffer when given
+ a long series of abbreviations, each a suffix of the next.
+ (Buffer overflow reported by Arthur Chan.)
+
+ zic no longer overflows an int when processing input like ‘Zone
+ Ouch 2147483648:00:00 - LMT’. The int overflow can lead to buffer
+ overflow in adversarial cases. (Thanks to Naveed Khan.)
+
+ zic now checks for signals more often.
+
+
Release 2026a - 2026-03-01 22:59:49 -0800
Briefly:
Moldova has used EU transition times since 2022.
- The "right" TZif files are no longer installed by default.
+ The “right” TZif files are no longer installed by default.
-DTZ_RUNTIME_LEAPS=0 disables runtime support for leap seconds.
TZif files are no longer limited to 50 bytes of abbreviations.
zic is no longer limited to 50 leap seconds.
@@ -25,23 +62,23 @@ Release 2026a - 2026-03-01 22:59:49 -0800
The Makefile no longer by default installs an alternate set
of TZif files for system clocks that count leap seconds.
- Install with 'make REDO=posix_right' to get the old default,
+ Install with ‘make REDO=posix_right’ to get the old default,
which is rarely used in major downstream distributions.
If your system clock counts leap seconds (contrary to POSIX),
- it is better to install with 'make REDO=right_only'.
+ it is better to install with ‘make REDO=right_only’.
This change does not affect the leapseconds file, which is still
installed as before.
- The Makefile's POSIXRULES option, which was declared obsolete in
- release 2019b, has been removed. The Makefile's build procedure
+ The Makefile’s POSIXRULES option, which was declared obsolete in
+ release 2019b, has been removed. The Makefile’s build procedure
thus no longer optionally installs the obsolete posixrules file.
Changes to code
Compiling with the new option -DTZ_RUNTIME_LEAPS=0 disables
runtime support for leap seconds. Although this conforms to
- POSIX, shrinks tzcode's attack surface, and is more efficient,
- it fails to support Internet RFC 9636's leap seconds.
+ POSIX, shrinks tzcode’s attack surface, and is more efficient,
+ it fails to support Internet RFC 9636’s leap seconds.
zic now can generate, and localtime.c can now use, TZif files that
hold up to 256 bytes of abbreviations, counting trailing NULs.
@@ -51,7 +88,7 @@ Release 2026a - 2026-03-01 22:59:49 -0800
zic -L can now generate TZif files with more than 50 leap seconds.
This helps test TZif readers not limited to 50 leap seconds, as
- tzcode's localtime.c is; it has little immediate need for
+ tzcode’s localtime.c is; it has little immediate need for
practical timekeeping as there have been only 27 leap seconds and
possibly there will be no more, due to planned changes to UTC.
zic -v warns if its output exceeds the old 50-second limit.
diff --git a/tz-art.html b/tz-art.html
index 1bd1bf781166..7870a6187390 100644
--- a/tz-art.html
+++ b/tz-art.html
@@ -446,7 +446,7 @@ Includes the song “Does Anybody Really Know What Time It Is?”.
</li>
<li>
Emanuele Arciuli,
-<a href="https://neumarecords.org/ols/products/william-duckworth-the-time-curve-preludes"><em>The Time Curve Preludes</em></a> (2023).
+<a href="https://williamduckworth.bandcamp.com/album/the-time-curve-preludes"><em>The Time Curve Preludes</em></a> (2023).
Neuma 174, 44:46.
The title piece, composed by
<a href="https://en.wikipedia.org/wiki/William_Duckworth_(composer)">William
diff --git a/tz-link.html b/tz-link.html
index 5f1989f014b3..4d2c12b09452 100644
--- a/tz-link.html
+++ b/tz-link.html
@@ -441,7 +441,7 @@ transition in the <code><abbr>tz</abbr></code> database.</li>
Database Parser</a> is a
<a href="https://en.wikipedia.org/wiki/C++">C++</a> parser and
runtime library with a <a
-href="https://en.cppreference.com/w/cpp/chrono.html"><code>std::chrono</code> API</a>
+href="https://en.cppreference.com/cpp/chrono"><code>std::chrono</code> API</a>
that is a standard part of C++.
It is freely available under the
<abbr title="Massachusetts Institute of Technology">MIT</abbr> license.</li>
@@ -1135,8 +1135,7 @@ Network Time Protocol Best Current Practices</a>
applications requiring accurate <abbr>UTC</abbr> or civil time,
and is intended for use only in single, well-controlled environments.</li>
<li>The <a
-href="https://pairlist6.pair.net/mailman/listinfo/leapsecs">Leap
-Second Discussion List</a> covers <a
+href="https://groups.io/g/LEAPSECS">LEAPSECS List</a> covers <a
href="https://gge.ext.unb.ca/Resources/gpsworld.november99.pdf">McCarthy
and Klepczynski’s 1999 proposal to discontinue leap seconds</a>,
discussed further in
@@ -1146,21 +1145,30 @@ leap second: its history and possible future</a>.
might be redefined
without Leap Seconds</a> gives pointers on this
contentious issue.
-The General Conference on Weights and Measures
+The General Conference on Weights and Measures (CGPM)
<a href="https://www.bipm.org/en/cgpm-2022/resolution-4">decided in 2022</a>
to discontinue the use of leap seconds by 2035, and requested that no
discontinuous adjustments be made to UTC for at least a century.
The World Radiocommunication Conference <a
href="https://www.itu.int/dms_pub/itu-r/opb/act/R-ACT-WRC.15-2023-PDF-E.pdf">resolved
-in 2023</a> to cooperate with this process. One proposal to implement this
+in 2023</a> to cooperate with this process. A draft <a
+href="https://www.bipm.org/documents/d/guest/cgpm-2026-draft-resolutions">Resolution
+C to make continuous UTC effective on 2027-05-20</a>,
+and thereby discontinue leap seconds,
+has been scheduled for the 28th CGPM starting 2026-10-13 in Paris.
+One proposal to implement this
would replace leap seconds with seven 13-second leap smears occurring once per
decade until 2100, with leap smears after that gradually increasing in size.
See:
<ul>
-<li>Levine J. <a href="https://www.preprints.org/manuscript/202406.0043">A
+<li>Levine J. <a href="https://tf.nist.gov/general/pdf/3242.pdf">A
proposal to change the leap-second adjustments to
coordinated universal time</a>. <em>Metrologia.</em> 2024;61(5):055002. doi:<a
-href="https://doi.org/10.1088/1681-7575/ad6266">10.1088/1681-7575/ad6266</a>.</li>
+href="https://doi.org/10.1088/1681-7575/ad6266">10.1088/1681-7575/ad6266</a>
+with followups in doi:<a
+href="https://doi.org/10.1088/1681-7575/ade314">10.1088/1681-7575/ade314</a>
+and doi:<a
+href="https://doi.org/10.1088/1681-7575/ade315">10.1088/1681-7575/ade315</a>.</li>
</ul>
However, there is still no consensus on whether this is the best way
to replace leap seconds.
diff --git a/version b/version
index 5d9126009e7f..75d34ee38931 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-2026a
+2026b
diff --git a/zic.c b/zic.c
index c10be426d66b..7ecf3b9115f3 100644
--- a/zic.c
+++ b/zic.c
@@ -253,11 +253,13 @@ symlink(char const *target, char const *linkname)
(errno = ENOTSUP, -1)
#endif
+static int addabbr(char[TZ_MAX_CHARS], int *, char const *);
static void addtt(zic_t starttime, int type);
static int addtype(zic_t, char const *, bool, bool, bool);
-static void leapadd(zic_t, int, int);
static void adjleap(void);
static void associate(void);
+static void checkabbr(char const *);
+static void check_for_signal(void);
static void dolink(const char *, const char *, bool);
static int getfields(char *, char **, int);
static zic_t gethms(const char * string, const char * errstring);
@@ -270,11 +272,11 @@ static void inrule(char ** fields, int nfields);
static bool inzcont(char ** fields, int nfields);
static bool inzone(char ** fields, int nfields);
static bool inzsub(char **, int, bool);
-static int itssymlink(char const *, int *);
static bool is_alpha(char a);
+static int itssymlink(char const *, int *);
+static void leapadd(zic_t, int, int);
static char lowerit(char);
static void mkdirs(char const *, bool);
-static void newabbr(const char * abbr);
static zic_t oadd(zic_t t1, zic_t t2);
static zic_t omul(zic_t, zic_t);
static void outzone(const struct zone * zp, ptrdiff_t ntzones);
@@ -704,6 +706,7 @@ eat(int fnum, lineno num)
ATTRIBUTE_FORMAT((printf, 1, 0)) static void
verror(const char *const string, va_list args)
{
+ check_for_signal();
/*
** Match the format of "cc" to allow sh users to
** zic ... 2>&1 | error -t "*" -v
@@ -1074,6 +1077,7 @@ make_links(void)
warning(_("link %s targeting link %s"),
links[i].l_linkname, links[i].l_target);
}
+ check_for_signal();
}
}
@@ -1391,6 +1395,7 @@ main(int argc, char **argv)
for (j = i + 1; j < nzones && zones[j].z_name == NULL; ++j)
continue;
outzone(&zones[i], j - i);
+ check_for_signal();
}
make_links();
if (lcltime != NULL) {
@@ -1486,9 +1491,11 @@ get_rand_u64(void)
static int nwords;
if (!nwords) {
ssize_t s;
- do
+ for (;; check_for_signal()) {
s = getrandom(entropy_buffer, sizeof entropy_buffer, 0);
- while (s < 0 && errno == EINTR);
+ if (! (s < 0 && errno == EINTR))
+ break;
+ }
nwords = s < 0 ? -1 : s / sizeof *entropy_buffer;
}
@@ -1516,7 +1523,7 @@ get_rand_u64(void)
rmod = INT_MAX < UINT_FAST64_MAX ? 0 : UINT_FAST64_MAX / nrand + 1,
r = 0, rmax = 0;
- do {
+ for (;; check_for_signal()) {
uint_fast64_t rmax1 = rmax;
if (rmod) {
/* Avoid signed integer overflow on theoretical platforms
@@ -1527,7 +1534,9 @@ get_rand_u64(void)
rmax1 = nrand * rmax1 + rand_max;
r = nrand * r + rand();
rmax = rmax < rmax1 ? rmax1 : UINT_FAST64_MAX;
- } while (rmax < UINT_FAST64_MAX);
+ if (UINT_FAST64_MAX <= rmax)
+ break;
+ }
return r;
}
@@ -1574,9 +1583,11 @@ random_dirent(char const **name, char **namealloc)
*name = *namealloc = dst;
}
- do
+ for (;; check_for_signal()) {
r = get_rand_u64();
- while (unfair_min <= r);
+ if (r < unfair_min)
+ break;
+ }
for (i = 0; i < suffixlen; i++) {
dst[dirlen + prefixlen + i] = alphabet[r % alphabetlen];
@@ -1611,7 +1622,7 @@ open_outfile(char const **outname, char **tempname)
if (!*tempname)
random_dirent(outname, tempname);
- while (true) {
+ for (;; check_for_signal()) {
int oflags = O_WRONLY | O_BINARY | O_CREAT | O_EXCL;
int fd = open(*outname, oflags, creat_perms);
int err;
@@ -1725,8 +1736,6 @@ dolink(char const *target, char const *linkname, bool staysymlink)
char const *outname = linkname;
int targetissym = -2, linknameissym = -2;
- check_for_signal();
-
if (strcmp(target, "-") == 0) {
if (remove(linkname) == 0 || errno == ENOENT || errno == ENOTDIR)
return;
@@ -1739,7 +1748,7 @@ dolink(char const *target, char const *linkname, bool staysymlink)
}
}
- while (true) {
+ for (;; check_for_signal()) {
if (linkat(AT_FDCWD, target, AT_FDCWD, outname, AT_SYMLINK_FOLLOW)
== 0) {
link_errno = 0;
@@ -1791,7 +1800,7 @@ dolink(char const *target, char const *linkname, bool staysymlink)
int symlink_errno = -1;
if (contents) {
- while (true) {
+ for (;; check_for_signal()) {
if (symlink(contents, outname) == 0) {
symlink_errno = 0;
break;
@@ -1822,7 +1831,7 @@ dolink(char const *target, char const *linkname, bool staysymlink)
exit(EXIT_FAILURE);
}
tp = open_outfile(&outname, &tempname);
- while ((c = getc(fp)) != EOF)
+ for (; (c = getc(fp)) != EOF; check_for_signal())
putc(c, tp);
close_file(tp, directory, linkname, tempname);
close_file(fp, directory, target, NULL);
@@ -1946,7 +1955,7 @@ static bool
inputline(FILE *fp, char *buf, ptrdiff_t bufsize)
{
ptrdiff_t linelen = 0, ch;
- while ((ch = getc(fp)) != '\n') {
+ for (; (ch = getc(fp)) != '\n'; check_for_signal()) {
if (ch < 0) {
if (ferror(fp)) {
error(_("input error"));
@@ -2033,6 +2042,7 @@ infile(int fnum, char const *name)
default: unreachable();
}
}
+ check_for_signal();
}
close_file(fp, NULL, filename(fnum), NULL);
if (wantcont)
@@ -2927,30 +2937,27 @@ writezone(const char *const name, const char *const string, char version,
: i == thisdefaulttype ? old0 : i]
= thistypecnt++;
- for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i)
- indmap[i] = -1;
thischarcnt = stdcnt = utcnt = 0;
for (i = old0; i < typecnt; i++) {
- register char * thisabbr;
-
if (omittype[i])
continue;
if (ttisstds[i])
stdcnt = thistypecnt;
if (ttisuts[i])
utcnt = thistypecnt;
- if (indmap[desigidx[i]] >= 0)
- continue;
- thisabbr = &chars[desigidx[i]];
- for (j = 0; j < thischarcnt; ++j)
- if (strcmp(&thischars[j], thisabbr) == 0)
- break;
- if (j == thischarcnt) {
- strcpy(&thischars[thischarcnt], thisabbr);
- thischarcnt += strlen(thisabbr) + 1;
- }
- indmap[desigidx[i]] = j;
+ addabbr(thischars, &thischarcnt, &chars[desigidx[i]]);
}
+
+ /* Now that all abbrevs have been added to THISCHARS,
+ it is safe to set INDMAP without worrying about
+ whether the abbrevs might move later. */
+ for (i = 0; i < TZ_MAX_CHARS; i++)
+ indmap[i] = -1;
+ for (i = old0; i < typecnt; i++)
+ if (!omittype[i] && indmap[desigidx[i]] < 0)
+ indmap[desigidx[i]] = addabbr(thischars, &thischarcnt,
+ &chars[desigidx[i]]);
+
if (pass == 1 && !want_bloat()) {
hicut = thisleapexpiry = false;
pretranstype = -1;
@@ -3173,11 +3180,11 @@ stringoffset(char *result, zic_t offset)
offset /= SECSPERMIN;
minutes = offset % MINSPERHOUR;
offset /= MINSPERHOUR;
- hours = offset;
- if (hours >= HOURSPERDAY * DAYSPERWEEK) {
+ if (offset >= HOURSPERDAY * DAYSPERWEEK) {
result[0] = '\0';
return 0;
}
+ hours = offset;
len += sprintf(result + len, "%d", hours);
if (minutes != 0 || seconds != 0) {
len += sprintf(result + len, ":%02d", minutes);
@@ -3416,12 +3423,16 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
int nonTZlimtype = -1;
zic_t max_year0;
int defaulttype = -1;
+ int max_stringoffset_len = sizeof "-167:59:59" - 1;
+ int max_comma_stringrule_len = (sizeof ",M12.5.6/" - 1
+ + max_stringoffset_len);
check_for_signal();
/* This cannot overflow; see FORMAT_LEN_GROWTH_BOUND. */
max_abbr_len = 2 + max_format_len + max_abbrvar_len;
- max_envvar_len = 2 * max_abbr_len + 5 * 9;
+ max_envvar_len = 2 * (max_abbr_len + max_stringoffset_len
+ + max_comma_stringrule_len);
startbuf = xmalloc(max_abbr_len + 1);
ab = xmalloc(max_abbr_len + 1);
@@ -3522,7 +3533,7 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
startttisut);
if (usestart) {
addtt(starttime, type);
- if (useuntil && nonTZlimtime < starttime) {
+ if (nonTZlimtime < starttime) {
nonTZlimtime = starttime;
nonTZlimtype = type;
}
@@ -3769,6 +3780,7 @@ static int
addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut)
{
register int i, j;
+ int charcnt0;
/* RFC 9636 section 3.2 specifies this range for utoff. */
if (! (-TWO_31_MINUS_1 <= utoff && utoff <= TWO_31_MINUS_1)) {
@@ -3778,12 +3790,18 @@ addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut)
if (!want_bloat())
ttisstd = ttisut = false;
- for (j = 0; j < charcnt; ++j)
- if (strcmp(&chars[j], abbr) == 0)
- break;
- if (j == charcnt)
- newabbr(abbr);
- else {
+ checkabbr(abbr);
+
+ charcnt0 = charcnt;
+ j = addabbr(chars, &charcnt, abbr);
+ if (charcnt0 < charcnt) {
+ /* If an abbreviation was inserted, increment indexes no
+ earlier than the insert by the size of the insertion,
+ so that they continue to point to the same contents. */
+ for (i = 0; i < typecnt; i++)
+ if (j <= desigidx[i])
+ desigidx[i] += charcnt - charcnt0;
+ } else {
/* If there's already an entry, return its index. */
for (i = 0; i < typecnt; i++)
if (utoff == utoffs[i] && isdst == isdsts[i] && j == desigidx[i]
@@ -4168,10 +4186,8 @@ will not work with pre-2004 versions of zic"));
}
static void
-newabbr(const char *string)
+checkabbr(char const *string)
{
- register int i;
-
if (strcmp(string, GRANDPARENTED) != 0) {
register const char * cp;
const char * mp;
@@ -4190,13 +4206,50 @@ mp = _("time zone abbreviation differs from POSIX standard");
if (mp != NULL)
warning("%s (%s)", mp, string);
}
- i = strnlen(string, TZ_MAX_CHARS - charcnt) + 1;
- if (charcnt + i > TZ_MAX_CHARS) {
- error(_("too many, or too long, time zone abbreviations"));
- exit(EXIT_FAILURE);
- }
- strcpy(&chars[charcnt], string);
- charcnt += i;
+}
+
+/* Put into CHS, which currently contains *PNCHS bytes containing
+ NUL-terminated abbreviations none of which are suffixes of another,
+ the abbreviation ABBR including its trailing NUL.
+ If ABBR does not already appear in CHS,
+ possibly as a suffix of an existing abbreviation,
+ add ABBR to CHS, remove from CHS any abbreviation
+ that is a suffix of ABBR, and increment *PNCHS accordingly.
+ Return the index of ABBR after any modifications to CHS are made.
+
+ If all abbreviations have already been added, this function
+ lets the caller look up the index of an existing abbreviation. */
+static int
+addabbr(char chs[TZ_MAX_CHARS], int *pnchs, char const *abbr)
+{
+ int nchs = *pnchs;
+ int alen = strlen(abbr), nchs_incr = alen + 1;
+ int i;
+ for (i = 0; i < nchs; ) {
+ int clen = strlen(&chs[i]);
+ if (alen <= clen) {
+ /* If ABBR is a suffix of an abbreviation in CHS,
+ return the index of ABBR in CHS. */
+ int isuff = i + (clen - alen);
+ if (memcmp(&chs[isuff], abbr, alen) == 0)
+ return isuff;
+ } else if (memcmp(&chs[i], &abbr[alen - clen], clen) == 0) {
+ /* An abbreviation in CHS is a substring of ABBR.
+ Replace it with ABBR, instead of the more-common
+ actions of appending ABBR or doing nothing. */
+ nchs_incr = alen - clen;
+ break;
+ }
+ i += clen + 1;
+ }
+ if (TZ_MAX_CHARS < nchs + nchs_incr) {
+ error(_("too many, or too long, time zone abbreviations"));
+ exit(EXIT_FAILURE);
+ }
+ memmove(&chs[i + nchs_incr], &chs[i], nchs - i);
+ memcpy(&chs[i], abbr, nchs_incr);
+ *pnchs = nchs + nchs_incr;
+ return i;
}
/* Ensure that the directories of ARGNAME exist, by making any missing