From a2b1177e6ef0640fccdff55103975d2043f0d891 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Wed, 10 May 2017 00:36:09 -0700 Subject: [PATCH] Reject outlandish leap seconds Remove support for double leap seconds, or more generally, leap seconds that are so close together that they cannot possibly be valid. Also, do not allow leap seconds before the Epoch, as they cannot happen and they complicate overflow checking. This fixes a GCC warning reported by Robert Elz in: http://mm.icann.org/pipermail/tz/2017-May/024995.html with followup help by Bradley White in: http://mm.icann.org/pipermail/tz/2017-May/024996.html * NEWS, tzfile.5: Mention this. * localtime.c (tzloadbody): Disallow leap seconds that occur before the epoch, or occur too close together. * localtime.c (timesub): Remove unused code that worried about double leap seconds, which cannot happen any more. Simplify. * zic.c (inleap): Do not worry about double leap seconds. Do not allow leap seconds before the Epoch. Remove no-longer-needed check about repeated leap seconds; superseded by new check in adjleap. (adjleap): Do not allow leap seconds that are too close together. * zic.8: Remove comment about the removed feature. --- NEWS | 8 ++++++++ localtime.c | 40 +++++++++++++++++----------------------- tzfile.5 | 4 +++- zic.8 | 14 -------------- zic.c | 23 +++++++++-------------- 5 files changed, 37 insertions(+), 52 deletions(-) diff --git a/NEWS b/NEWS index e82e03e..4e8091b 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,14 @@ Unreleased, experimental changes Changes to code + zic and the reference runtime now reject multiple leap seconds + within 28 days of each other, or leap seconds before the Epoch. + As a result, support for double leap seconds, which was + obsolescent and undocumented, has been removed. Double leap + seconds were an error in the C89 standard; they have never existed + in civil timekeeping. (Thanks to Robert Elz and Bradley White for + noticing glitches in the code that uncovered this problem.) + Several minor changes have been made to the code to make it a bit easier to port to MS-Windows. (Thanks to Kees Dekker for reporting the problems.) diff --git a/localtime.c b/localtime.c index 8645f01..0ccd8ba 100644 --- a/localtime.c +++ b/localtime.c @@ -425,6 +425,8 @@ tzloadbody(char const *name, struct state *sp, bool doextend, for (stored = 4; stored <= 8; stored *= 2) { int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); int_fast32_t ttisgmtcnt = detzcode(up->tzhead.tzh_ttisgmtcnt); + int_fast64_t prevtr = 0; + int_fast32_t prevcorr = 0; int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt); int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt); int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt); @@ -510,17 +512,20 @@ tzloadbody(char const *name, struct state *sp, bool doextend, int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p); int_fast32_t corr = detzcode(p + stored); p += stored + 4; + /* Leap seconds cannot occur before the Epoch. */ + if (tr < 0) + return EINVAL; if (tr <= time_t_max) { - time_t trans - = ((TYPE_SIGNED(time_t) ? tr < time_t_min : tr < 0) - ? time_t_min : tr); - if (leapcnt && trans <= sp->lsis[leapcnt - 1].ls_trans) { - if (trans < sp->lsis[leapcnt - 1].ls_trans) - return EINVAL; - leapcnt--; - } - sp->lsis[leapcnt].ls_trans = trans; - sp->lsis[leapcnt].ls_corr = corr; + /* Leap seconds cannot occur more than once per UTC month, + and UTC months are at least 28 days long (minus 1 + second for a negative leap second). Each leap second's + correction must differ from the previous one's by 1 + second. */ + if (tr - prevtr < 28 * SECSPERDAY - 1 + || (corr != prevcorr - 1 && corr != prevcorr + 1)) + return EINVAL; + sp->lsis[leapcnt].ls_trans = prevtr = tr; + sp->lsis[leapcnt].ls_corr = prevcorr = corr; leapcnt++; } } @@ -1610,20 +1615,9 @@ timesub(const time_t *timep, int_fast32_t offset, while (--i >= 0) { lp = &sp->lsis[i]; if (*timep >= lp->ls_trans) { - if (*timep == lp->ls_trans) { - hit = ((i == 0 && lp->ls_corr > 0) || - lp->ls_corr > sp->lsis[i - 1].ls_corr); - if (hit) - while (i > 0 && - sp->lsis[i].ls_trans == - sp->lsis[i - 1].ls_trans + 1 && - sp->lsis[i].ls_corr == - sp->lsis[i - 1].ls_corr + 1) { - ++hit; - --i; - } - } corr = lp->ls_corr; + hit = (*timep == lp->ls_trans + && (i == 0 ? 0 : lp[-1].ls_corr) < corr); break; } } diff --git a/tzfile.5 b/tzfile.5 index 2e88763..52c5301 100644 --- a/tzfile.5 +++ b/tzfile.5 @@ -98,7 +98,7 @@ structure(s) in the file. Then there are .I tzh_leapcnt pairs of four-byte values, written in standard byte order; -the first value of each pair gives the time +the first value of each pair gives the nonnegative time (as returned by .IR time(2)) at which a leap second occurs; @@ -107,6 +107,8 @@ the second gives the number of leap seconds to be applied during the time period starting at the given time. The pairs of values are sorted in ascending order by time. +Each transition is for one leap second, either positive or negative; +transitions always separated by at least 28 days minus 1 second. .PP Then there are .I tzh_ttisstdcnt diff --git a/zic.8 b/zic.8 index 25dac78..c59ccfc 100644 --- a/zic.8 +++ b/zic.8 @@ -459,20 +459,6 @@ if a second was added or .q "\*-" if a second was skipped. -.\" There's no need to document the following, since it's impossible for more -.\" than one leap second to be inserted or deleted at a time. -.\" The C Standard is in error in suggesting the possibility. -.\" See Terry J Quinn, The BIPM and the accurate measure of time, -.\" Proc IEEE 79, 7 (July 1991), 894-905, reprinted in -.\" Hackman C, Sullivan DB (eds), Time and frequency measurement, -.\" American Association of Physics Teachers (1996), -.\" , 75-86." -.\" or -.\" .q ++ -.\" if two seconds were added -.\" or -.\" .q -- -.\" if two seconds were skipped. The .B R/S field diff --git a/zic.c b/zic.c index 74f0527..486631b 100644 --- a/zic.c +++ b/zic.c @@ -1434,15 +1434,9 @@ inleap(char **fields, int nfields) if (strcmp(cp, "") == 0) { /* infile() turns "-" into "" */ positive = false; count = 1; - } else if (strcmp(cp, "--") == 0) { - positive = false; - count = 2; } else if (strcmp(cp, "+") == 0) { positive = true; count = 1; - } else if (strcmp(cp, "++") == 0) { - positive = true; - count = 2; } else { error(_("illegal CORRECTION field on Leap line")); return; @@ -1454,8 +1448,8 @@ inleap(char **fields, int nfields) return; } t = tadd(t, tod); - if (t < early_time) { - error(_("leap second precedes Big Bang")); + if (t < 0) { + error(_("leap second precedes Epoch")); return; } leapadd(t, positive, lp->l_value, count); @@ -2753,13 +2747,8 @@ leapadd(zic_t t, bool positive, int rolling, int count) exit(EXIT_FAILURE); } for (i = 0; i < leapcnt; ++i) - if (t <= trans[i]) { - if (t == trans[i]) { - error(_("repeated leap second moment")); - exit(EXIT_FAILURE); - } + if (t <= trans[i]) break; - } do { for (j = leapcnt; j > i; --j) { trans[j] = trans[j - 1]; @@ -2778,11 +2767,17 @@ adjleap(void) { register int i; register zic_t last = 0; + register zic_t prevtrans = 0; /* ** propagate leap seconds forward */ for (i = 0; i < leapcnt; ++i) { + if (trans[i] - prevtrans < 28 * SECSPERDAY) { + error(_("Leap seconds too close together")); + exit(EXIT_FAILURE); + } + prevtrans = trans[i]; trans[i] = tadd(trans[i], last); last = corr[i] += last; } -- 2.7.4