[PROPOSED 0/6] Fix integer overflows and drop posixrules
GitHub user Naveed8951 reported a potential (but unlikely) security flaw due to undefined behavior in localtime.c after integer overflow. The patches in this series should fix the reported problems. Some of the problem occurred in a part of the localtime.c code that was implementing a feature declared obsolete in TZDB release 2019b. This feature let the sysadmin specify default DST rules for DST-but-ruleless old-style POSIX settings like TZ="AST4ADT". Rather than try to fix the feature this patch series removes it; the feature is hardly ever used and would stop working in 2038 anyway. As a result, tzcode by default uses current US DST for these settings, with no opportunity for sysadmin override (though the default can be overriden when tzcode is compiled). This behavior is already common elsewhere. Paul Eggert (6): Add comment re UT offsets equal to -2**31 Document zic -p better zic now warns about -p Remove POSIXRULES installation option Remove TZDEFRULES ("posixrules") from localtime.c Fix remaining Naveed8951-reported overflows Makefile | 28 +--- NEWS | 19 +++ localtime.c | 388 ++++++++++++++++++++++++---------------------------- newctime.3 | 4 +- newtzset.3 | 29 +--- tzfile.5 | 7 +- tzfile.h | 12 +- zic.8 | 17 ++- zic.c | 8 ++ 9 files changed, 228 insertions(+), 284 deletions(-) -- 2.52.0
--- localtime.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/localtime.c b/localtime.c index a506f097..917e957d 100644 --- a/localtime.c +++ b/localtime.c @@ -507,7 +507,8 @@ typedef int_fast64_t int_fast32_2s; #endif struct ttinfo { /* time type information */ - int_least32_t tt_utoff; /* UT offset in seconds */ + int_least32_t tt_utoff; /* UT offset in seconds; in the range + -2**31 + 1 .. 2**31 - 1 */ desigidx_type tt_desigidx; /* abbreviation list index */ bool tt_isdst; /* used to set tm_isdst */ bool tt_ttisstd; /* transition is std time */ -- 2.52.0
* zic.8: Give more details about why -p is obsolete. --- zic.8 | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/zic.8 b/zic.8 index 75ca288e..73b19ca9 100644 --- a/zic.8 +++ b/zic.8 @@ -110,12 +110,7 @@ If this option is not used, no leap second information appears in output files. .TP .BI "\-p " timezone -Use -.IR timezone 's -rules when handling nonstandard -TZ strings like "EET\-2EEST" that lack transition rules. -.B zic -will act as if the input contained a link line of the form +Act as if the input contained a link line of the form .sp .ti +2 Link \fItimezone\fP posixrules @@ -129,13 +124,21 @@ is Unless .I timezone is .q "\-" , -this option is obsolete and poorly supported. +this option is obsolete and is no longer supported by most runtimes. Among other things it should not be used for timestamps after the year 2037, and it should not be combined with .B "\-b slim" if .IR timezone 's transitions are at standard time or Universal Time (UT) instead of local time. +The option is present only to support obsolete runtimes that used +.IR timezone 's +rules when handling obsolescent +TZ strings like "AST4ADT" that lack transition rules; +modern runtimes that support these TZ strings +typically just use current US rules +as the TZ strings were mainly used in the US. +Similarly, any Zone or Link named "posixrules" is obsolete and problematic. .TP .BI "\-m " mode Create TZif files with the given file mode bits. -- 2.52.0
* NEWS: Mention this. * zic.c (main): Warn if '-p XXX' is used, unless XXX is '-'. --- NEWS | 3 +++ zic.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 30f757df..f095e26a 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,9 @@ Unreleased, experimental changes no longer accepts them, as they can cause trouble in both localtime.c and its callers. RFC 9636 prohibits such offsets. + zic -p now warns that the -p option is obsolete and likely + ineffective. + Release 2025c - 2025-12-10 14:42:37 -0800 diff --git a/zic.c b/zic.c index d50ad012..6203a75b 100644 --- a/zic.c +++ b/zic.c @@ -1286,6 +1286,9 @@ main(int argc, char **argv) case 'p': if (psxrules) duplicate_options("-p"); + if (strcmp(optarg, "-") != 0) + warning(_("-p is obsolete" + " and likely ineffective")); psxrules = optarg; break; case 't': -- 2.52.0
* Makefile (POSIXRULES): Remove. All uses removed. * NEWS, tzfile.5: Mention this. --- Makefile | 28 ++-------------------------- NEWS | 4 ++++ tzfile.5 | 7 ++++--- 3 files changed, 10 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index d88760a8..570518c4 100644 --- a/Makefile +++ b/Makefile @@ -66,28 +66,6 @@ DATAFORM= main LOCALTIME= Factory -# The POSIXRULES macro controls interpretation of POSIX-like TZ -# settings like TZ='EET-2EEST' that lack DST transition rules. -# If POSIXRULES is '-', no template is installed; this is the default. -# Any other value for POSIXRULES is obsolete and should not be relied on, as: -# * It does not work correctly in popular implementations such as GNU/Linux. -# * It does not work even in tzcode, except for historical timestamps -# that precede the last explicit transition in the POSIXRULES file. -# Hence it typically does not work for current and future timestamps. -# If, despite the above, you want a template for handling these settings, -# you can change the line below (after finding the timezone you want in the -# one of the $(TDATA) source files, or adding it to a source file). -# Alternatively, if you discover you've got the wrong timezone, you can just -# 'zic -p -' to remove it, or 'zic -p rightzone' to change it. -# Use the command -# make zonenames -# to get a list of the values you can use for POSIXRULES. - -POSIXRULES= - - -# Also see TZDEFRULESTRING below, which takes effect only -# if POSIXRULES is '-' or if the template file cannot be accessed. - # Installation locations. # @@ -351,9 +329,8 @@ LDLIBS= # -DTZ_DOMAINDIR=\"/path\" to use "/path" for gettext directory; # the default is system-supplied, typically "/usr/lib/locale" # -DTZDEFRULESTRING=\",date/time,date/time\" to default to the specified -# DST transitions for proleptic format TZ strings lacking them, -# in the usual case where POSIXRULES is '-'. If not specified, -# TZDEFRULESTRING defaults to US rules for future DST transitions. +# DST transitions for proleptic format TZ strings lacking them. +# If not specified, it defaults to US rules for future DST transitions. # This mishandles some past timestamps, as US DST rules have changed. # It also mishandles settings like TZ='EET-2EEST' for eastern Europe, # as Europe and US DST rules differ. @@ -726,7 +703,6 @@ install: all $(DATA) $(REDO) $(MANS) '$(DESTDIR)$(MANDIR)/man3' '$(DESTDIR)$(MANDIR)/man5' \ '$(DESTDIR)$(MANDIR)/man8' $(ZIC_INSTALL) -l $(LOCALTIME) \ - -p $(POSIXRULES) \ -t '$(DESTDIR)$(TZDEFAULT)' cp -f $(TABDATA) '$(DESTDIR)$(TZDIR)/.' cp tzselect '$(DESTDIR)$(BINDIR)/.' diff --git a/NEWS b/NEWS index f095e26a..ecccccd0 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,10 @@ Unreleased, experimental changes 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 + thus no longer optionally installs the obsolete posixrules file. + Changes to code zic no longer generates a no-op transition when diff --git a/tzfile.5 b/tzfile.5 index 10e1ba78..edf96346 100644 --- a/tzfile.5 +++ b/tzfile.5 @@ -188,14 +188,15 @@ for another time zone specified via a proleptic TZ string that lacks rules. For example, when TZ="EET\-2EEST" and there is no TZif file "EET\-2EEST", the idea was to adapt the transition times from a TZif file with the -well-known name "posixrules" that is present only for this purpose and -is a copy of the file "Europe/Brussels", a file with a different UT offset. +well-known name "posixrules" that was present only for this purpose and +was a copy of the file "Europe/Brussels", a file with a different UT offset. POSIX does not specify the details of this obsolete transformational behavior, the default rules are installation-dependent, and no implementation is known to support this feature for timestamps past 2037, so users desiring (say) Greek time should instead specify TZ="Europe/Athens" for better historical coverage, falling back on -TZ="EET\-2EEST,M3.5.0/3,M10.5.0/4" if POSIX conformance is required +TZ="EET\-2EEST,M3.5.0/3,M10.5.0/4" +if conformance to POSIX.1-2017 or earlier is required and older timestamps need not be handled accurately. .PP The -- 2.52.0
This removes library support for the old posixrules feature that was declared obsolete in 2019b. Naveed8951 recently reported that the implementation has undefined behavior when accessing contrived TZif files, and there’s little point to fixing this. * localtime.c (tzparse): For settings like TZ="AST4ADT" simply default to TZDEFRULESTRING (typically current US rules) rather than trying to load from TZDEFRULES, a feature that has been obsolete for some time as it does not work in general. This simplifies the code and avoids some undefined behavior on signed integer overflow. * newctime.3, newtzset.3: Mention this. * tzfile.h (TZDEFRULES): Move from here ... * zic.c (TZDEFRULES): ... to here, as only zic.c needs it now. --- NEWS | 8 ++ localtime.c | 301 ++++++++++++++++++---------------------------------- newctime.3 | 4 +- newtzset.3 | 29 ++--- tzfile.h | 12 +-- zic.c | 5 + 6 files changed, 127 insertions(+), 232 deletions(-) diff --git a/NEWS b/NEWS index ecccccd0..ba0b13c5 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,14 @@ Unreleased, experimental changes Changes to code + localtime.c no longer accesses the posixrules file generated by + zic -p. Hence for obsolete and nonconforming settings like + TZ="AST4ADT" it now typically falls back on US DST rules, rather + than attempting to override this fallback with the contents of the + posixrules file. This removes library support that was declared + obsolete in release 2019b, and fixes some undefined behavior. + (Undefined behavior reported by GitHub user Naveed8951.) + zic no longer generates a no-op transition when simultaneous Rule and Zone changes cancel each other out. This occurs in tzdata only in Asia/Tbilisi on 1997-03-30. diff --git a/localtime.c b/localtime.c index 917e957d..53a443b7 100644 --- a/localtime.c +++ b/localtime.c @@ -446,7 +446,7 @@ static char const *utc = etc_utc + sizeof "Etc/" - 1; #endif /* -** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. +** The DST rules to use if TZ has no rules. ** Default to US rules as of 2017-05-07. ** POSIX does not specify the default DST rules; ** for historical reasons, US rules are a common default. @@ -1617,7 +1617,6 @@ tzparse(const char *name, struct state *sp, struct state const *basep) int_fast32_t stdoffset; int_fast32_t dstoffset; register char * cp; - register bool load_ok; ptrdiff_t stdlen, dstlen, charcnt; time_t atlo = TIME_T_MIN, leaplo = TIME_T_MIN; @@ -1643,18 +1642,20 @@ tzparse(const char *name, struct state *sp, struct state const *basep) if (basep) { if (0 < basep->timecnt) atlo = basep->ats[basep->timecnt - 1]; - load_ok = false; sp->leapcnt = basep->leapcnt; - memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis); - } else { - load_ok = tzload(TZDEFRULES, sp, 0) == 0; - if (!load_ok) - sp->leapcnt = 0; /* So, we're off a little. */ - } - if (0 < sp->leapcnt) - leaplo = sp->lsis[sp->leapcnt - 1].ls_trans; + if (0 < sp->leapcnt) { + memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis); + leaplo = sp->lsis[sp->leapcnt - 1].ls_trans; + } + } else + sp->leapcnt = 0; /* So, we're off a little. */ sp->goback = sp->goahead = false; if (*name != '\0') { + struct rule start, end; + int year, yearbeg, yearlim, timecnt; + time_t janfirst; + int_fast32_t janoffset = 0; + if (*name == '<') { dstname = ++name; name = getqzname(name, '>'); @@ -1675,194 +1676,102 @@ tzparse(const char *name, struct state *sp, struct state const *basep) if (name == NULL) return false; } else dstoffset = stdoffset - SECSPERHOUR; - if (*name == '\0' && !load_ok) + + if (*name == '\0') name = TZDEFRULESTRING; - if (*name == ',' || *name == ';') { - struct rule start; - struct rule end; - register int year; - register int timecnt; - time_t janfirst; - int_fast32_t janoffset = 0; - int yearbeg, yearlim; - - ++name; - if ((name = getrule(name, &start)) == NULL) - return false; - if (*name++ != ',') - return false; - if ((name = getrule(name, &end)) == NULL) - return false; - if (*name != '\0') - return false; - sp->typecnt = 2; /* standard time and DST */ - /* - ** Two transitions per year, from EPOCH_YEAR forward. - */ - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); - timecnt = 0; - janfirst = 0; - yearbeg = EPOCH_YEAR; - - do { - int_fast32_t yearsecs - = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; - time_t janfirst1 = janfirst; - yearbeg--; - if (increment_overflow_time(&janfirst1, -yearsecs)) { - janoffset = -yearsecs; - break; - } - janfirst = janfirst1; - } while (atlo < janfirst - && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); - - while (true) { - int_fast32_t yearsecs - = year_lengths[isleap(yearbeg)] * SECSPERDAY; - int yearbeg1 = yearbeg; - time_t janfirst1 = janfirst; - if (increment_overflow_time(&janfirst1, yearsecs) - || increment_overflow(&yearbeg1, 1) - || atlo <= janfirst1) - break; - yearbeg = yearbeg1; - janfirst = janfirst1; - } + if (! (*name == ',' || *name == ';')) + return false; - yearlim = yearbeg; - if (increment_overflow(&yearlim, years_of_observations)) - yearlim = INT_MAX; - for (year = yearbeg; year < yearlim; year++) { - int_fast32_t - starttime = transtime(year, &start, stdoffset), - endtime = transtime(year, &end, dstoffset); - int_fast32_t - yearsecs = (year_lengths[isleap(year)] - * SECSPERDAY); - bool reversed = endtime < starttime; - if (reversed) { - int_fast32_t swap = starttime; - starttime = endtime; - endtime = swap; - } - if (reversed - || (starttime < endtime - && endtime - starttime < yearsecs)) { - if (TZ_MAX_TIMES - 2 < timecnt) - break; - sp->ats[timecnt] = janfirst; - if (! increment_overflow_time - (&sp->ats[timecnt], - janoffset + starttime) - && atlo <= sp->ats[timecnt]) - sp->types[timecnt++] = !reversed; - sp->ats[timecnt] = janfirst; - if (! increment_overflow_time - (&sp->ats[timecnt], - janoffset + endtime) - && atlo <= sp->ats[timecnt]) { - sp->types[timecnt++] = reversed; - } - } - if (endtime < leaplo) { - yearlim = year; - if (increment_overflow(&yearlim, - years_of_observations)) - yearlim = INT_MAX; - } - if (increment_overflow_time - (&janfirst, janoffset + yearsecs)) - break; - janoffset = 0; - } - sp->timecnt = timecnt; - if (! timecnt) { - sp->ttis[0] = sp->ttis[1]; - sp->typecnt = 1; /* Perpetual DST. */ - } else if (years_of_observations <= year - yearbeg) - sp->goback = sp->goahead = true; - } else { - register int_fast32_t theirstdoffset; - register int_fast32_t theirdstoffset; - register int_fast32_t theiroffset; - register bool isdst; - register int i; - register int j; - - if (*name != '\0') - return false; - /* - ** Initial values of theirstdoffset and theirdstoffset. - */ - theirstdoffset = 0; - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - if (!sp->ttis[j].tt_isdst) { - theirstdoffset = - - sp->ttis[j].tt_utoff; - break; - } - } - theirdstoffset = 0; - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - if (sp->ttis[j].tt_isdst) { - theirdstoffset = - - sp->ttis[j].tt_utoff; - break; - } - } - /* - ** Initially we're assumed to be in standard time. - */ - isdst = false; - /* - ** Now juggle transition times and types - ** tracking offsets as you do. - */ - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - sp->types[i] = sp->ttis[j].tt_isdst; - if (sp->ttis[j].tt_ttisut) { - /* No adjustment to transition time */ - } else { - /* - ** If daylight saving time is in - ** effect, and the transition time was - ** not specified as standard time, add - ** the daylight saving time offset to - ** the transition time; otherwise, add - ** the standard time offset to the - ** transition time. - */ - /* - ** Transitions from DST to DDST - ** will effectively disappear since - ** proleptic TZ strings have only one - ** DST offset. - */ - if (isdst && !sp->ttis[j].tt_ttisstd) { - sp->ats[i] += dstoffset - - theirdstoffset; - } else { - sp->ats[i] += stdoffset - - theirstdoffset; - } - } - theiroffset = -sp->ttis[j].tt_utoff; - if (sp->ttis[j].tt_isdst) - theirdstoffset = theiroffset; - else theirstdoffset = theiroffset; - } - /* - ** Finally, fill in ttis. - */ - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); - sp->typecnt = 2; + name = getrule(name + 1, &start); + if (!name) + return false; + if (*name++ != ',') + return false; + name = getrule(name, &end); + if (!name || *name) + return false; + sp->typecnt = 2; /* standard time and DST */ + /* + ** Two transitions per year, from EPOCH_YEAR forward. + */ + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); + timecnt = 0; + janfirst = 0; + yearbeg = EPOCH_YEAR; + + do { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; + time_t janfirst1 = janfirst; + yearbeg--; + if (increment_overflow_time(&janfirst1, -yearsecs)) { + janoffset = -yearsecs; + break; + } + janfirst = janfirst1; + } while (atlo < janfirst + && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); + + while (true) { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg)] * SECSPERDAY; + int yearbeg1 = yearbeg; + time_t janfirst1 = janfirst; + if (increment_overflow_time(&janfirst1, yearsecs) + || increment_overflow(&yearbeg1, 1) + || atlo <= janfirst1) + break; + yearbeg = yearbeg1; + janfirst = janfirst1; + } + + yearlim = yearbeg; + if (increment_overflow(&yearlim, years_of_observations)) + yearlim = INT_MAX; + for (year = yearbeg; year < yearlim; year++) { + int_fast32_t + starttime = transtime(year, &start, stdoffset), + endtime = transtime(year, &end, dstoffset), + yearsecs = year_lengths[isleap(year)] * SECSPERDAY; + bool reversed = endtime < starttime; + if (reversed) { + int_fast32_t swap = starttime; + starttime = endtime; + endtime = swap; + } + if (reversed + || (starttime < endtime + && endtime - starttime < yearsecs)) { + if (TZ_MAX_TIMES - 2 < timecnt) + break; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time(&sp->ats[timecnt], + janoffset + starttime) + && atlo <= sp->ats[timecnt]) + sp->types[timecnt++] = !reversed; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time(&sp->ats[timecnt], + janoffset + endtime) + && atlo <= sp->ats[timecnt]) { + sp->types[timecnt++] = reversed; + } + } + if (endtime < leaplo) { + yearlim = year; + if (increment_overflow(&yearlim, years_of_observations)) + yearlim = INT_MAX; + } + if (increment_overflow_time(&janfirst, janoffset + yearsecs)) + break; + janoffset = 0; } + sp->timecnt = timecnt; + if (! timecnt) { + sp->ttis[0] = sp->ttis[1]; + sp->typecnt = 1; /* Perpetual DST. */ + } else if (years_of_observations <= year - yearbeg) + sp->goback = sp->goahead = true; } else { dstlen = 0; sp->typecnt = 1; /* only standard time */ diff --git a/newctime.3 b/newctime.3 index 8c611bd0..a8779e6a 100644 --- a/newctime.3 +++ b/newctime.3 @@ -311,13 +311,11 @@ and functions might (or might not) also behave this way. This is for compatibility with older platforms, as required by POSIX. .SH FILES -.ta \w'/usr/share/zoneinfo/posixrules\0\0'u +.ta \w'/usr/share/zoneinfo/GMT\0\0'u /etc/localtime local timezone file .br /usr/share/zoneinfo timezone directory .br -/usr/share/zoneinfo/posixrules default DST rules (obsolete) -.br /usr/share/zoneinfo/GMT for UTC leap seconds .PP If /usr/share/zoneinfo/GMT is absent, diff --git a/newtzset.3 b/newtzset.3 index 028cfd25..77e76c9d 100644 --- a/newtzset.3 +++ b/newtzset.3 @@ -277,7 +277,7 @@ is a placeholder. .TP .B <\-03>3<\-02>,M3.5.0/\-2,M10.5.0/\-1 stands for time in western Greenland, 3 hours behind UT, where clocks -follow the EU rules of +follow the EU rule of springing forward on March's last Sunday at 01:00 UT (\-02:00 local time, i.e., 22:00 the previous day) and falling back on October's last Sunday at 01:00 UT (\-01:00 local time, i.e., 23:00 the previous day). @@ -290,28 +290,13 @@ If .I TZ specifies daylight saving time but does not specify a .IR rule , -and the optional -.BR tzfile (5)-format -file -.B posixrules -is present in the system time conversion information directory, the -rules in -.B posixrules -are used, with the -.B posixrules -standard and daylight saving time offsets from UT -replaced by those specified by the -.I offset -values in -.IR TZ . -However, the -.B posixrules -file is obsolete: if it is present it is only for backward compatibility, -and it does not work reliably. +the rule typically defaults to the current US daylight-saving rule, +although such a default is not guaranteed and +is incorrect for many locations outside the US. Therefore, if a .I TZ string directly specifies a timezone with daylight saving time, -it should specify the daylight saving rules explicitly. +it should specify the daylight saving rule explicitly. .PP For compatibility with System V Release 3.1, a semicolon .RB ( ; ) @@ -403,13 +388,11 @@ for any of the errors specified for the routines and .BR read (2). .SH FILES -.ta \w'/usr/share/zoneinfo/posixrules\0\0'u +.ta \w'/usr/share/zoneinfo/GMT\0\0'u /etc/localtime local timezone file .br /usr/share/zoneinfo timezone directory .br -/usr/share/zoneinfo/posixrules default DST rules (obsolete) -.br /usr/share/zoneinfo/GMT for UTC leap seconds .PP If /usr/share/zoneinfo/GMT is absent, diff --git a/tzfile.h b/tzfile.h index b00eb9e6..d1f0fbe6 100644 --- a/tzfile.h +++ b/tzfile.h @@ -17,16 +17,8 @@ ** Thank you! */ -/* -** Information about time zone files. -*/ - -#ifndef TZDEFRULES -# define TZDEFRULES "posixrules" -#endif /* !defined TZDEFRULES */ - - -/* See Internet RFC 9636 for more details about the following format. */ +/* Information about time zone files. + See Internet RFC 9636 for more details about the following format. */ /* ** Each file begins with. . . diff --git a/zic.c b/zic.c index 6203a75b..73155138 100644 --- a/zic.c +++ b/zic.c @@ -164,6 +164,11 @@ static uid_t output_owner = -1; # include <stdalign.h> #endif +/* The name used for the file implementing the obsolete -p option. */ +#ifndef TZDEFRULES +# define TZDEFRULES "posixrules" +#endif + /* The maximum length of a text line, including the trailing newline. */ #ifndef _POSIX2_LINE_MAX # define _POSIX2_LINE_MAX 2048 -- 2.52.0
* NEWS: Mention this. * localtime.c (increment_overflow_64) (increment_overflow_time_64, utoff_diff): New functions. (time2sub, timeq): Avoid undefined behavior due to integer overflow when dealing with outlandish but valid UT offsets like -2**31 + 1 and 2**31 - 1. --- NEWS | 4 +++ localtime.c | 84 +++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 73 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index ba0b13c5..d2aade51 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,10 @@ Unreleased, experimental changes obsolete in release 2019b, and fixes some undefined behavior. (Undefined behavior reported by GitHub user Naveed8951.) + Some other undefined behavior, triggered by TZif files containing + outlandish but conforming UT offsets, has also been fixed. + (Also reported by Naveed8951.) + zic no longer generates a no-op transition when simultaneous Rule and Zone changes cancel each other out. This occurs in tzdata only in Asia/Tbilisi on 1997-03-30. diff --git a/localtime.c b/localtime.c index 53a443b7..a2f92b2a 100644 --- a/localtime.c +++ b/localtime.c @@ -2416,6 +2416,19 @@ increment_overflow(int *ip, int j) #endif } +static bool +increment_overflow_64(int *ip, int_fast64_t j) +{ +#ifdef ckd_add + return ckd_add(ip, *ip, j); +#else + if (j < 0 ? *ip < INT_MIN - j : INT_MAX - j < *ip) + return true; + *ip += j; + return false; +#endif +} + static bool increment_overflow_time_iinntt(time_t *tp, iinntt j) { @@ -2431,6 +2444,21 @@ increment_overflow_time_iinntt(time_t *tp, iinntt j) #endif } +static bool +increment_overflow_time_64(time_t *tp, int_fast64_t j) +{ +#ifdef ckd_add + return ckd_add(tp, *tp, j); +#else + if (j < 0 + ? (TYPE_SIGNED(time_t) ? *tp < TIME_T_MIN - j : *tp <= -1 - j) + : TIME_T_MAX - j < *tp) + return true; + *tp += j; + return false; +#endif +} + static bool increment_overflow_time(time_t *tp, int_fast32_t j) { @@ -2451,6 +2479,15 @@ increment_overflow_time(time_t *tp, int_fast32_t j) #endif } +/* Return A - B, where both are in the range -2**31 + 1 .. 2**31 - 1. + The result cannot overflow. */ +static int_fast64_t +utoff_diff (int_fast32_t a, int_fast32_t b) +{ + int_fast64_t aa = a; + return aa - b; +} + static int tmcomp(register const struct tm *const atmp, register const struct tm *const btmp) @@ -2647,8 +2684,18 @@ time2sub(struct tm *const tmp, It's OK if YOURTM.TM_GMTOFF contains uninitialized data, since the guess gets checked. */ time_t altt = t; - int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF; - if (!increment_overflow_time(&altt, diff)) { + int_fast64_t offdiff; + bool v; +# ifdef ckd_sub + v = ckd_sub(&offdiff, mytm.TM_GMTOFF, yourtm.TM_GMTOFF); +# else + /* A ckd_sub approximation that is good enough here. */ + v = !(-TWO_31_MINUS_1 <= yourm.TM_GMTOFF + && your.TM_GMTOFF <= TWO_31_MINUS_1); + if (!v) + offdiff = utoff_diff(mytm.TM_GMTOFF, yourtm.TM_GMTOFF); +# endif + if (!v && !increment_overflow_time_64(&altt, offdiff)) { struct tm alttm; if (funcp(sp, &altt, offset, &alttm) && alttm.tm_isdst == mytm.tm_isdst @@ -2678,8 +2725,12 @@ time2sub(struct tm *const tmp, continue; if (ttunspecified(sp, j)) continue; - newt = (t + sp->ttis[j].tt_utoff - - sp->ttis[i].tt_utoff); + newt = t; + if (increment_overflow_time_64 + (&newt, + utoff_diff(sp->ttis[j].tt_utoff, + sp->ttis[i].tt_utoff))) + continue; if (! funcp(sp, &newt, offset, &mytm)) continue; if (tmcomp(&mytm, &yourtm) != 0) @@ -2778,17 +2829,20 @@ time1(struct tm *const tmp, continue; for (otherind = 0; otherind < nseen; ++otherind) { otheri = types[otherind]; - if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst) - continue; - tmp->tm_sec += (sp->ttis[otheri].tt_utoff - - sp->ttis[samei].tt_utoff); - tmp->tm_isdst = !tmp->tm_isdst; - t = time2(tmp, funcp, sp, offset, &okay); - if (okay) - return t; - tmp->tm_sec -= (sp->ttis[otheri].tt_utoff - - sp->ttis[samei].tt_utoff); - tmp->tm_isdst = !tmp->tm_isdst; + if (sp->ttis[otheri].tt_isdst != tmp->tm_isdst) { + int sec = tmp->tm_sec; + if (!increment_overflow_64 + (&tmp->tm_sec, + utoff_diff(sp->ttis[otheri].tt_utoff, + sp->ttis[samei].tt_utoff))) { + tmp->tm_isdst = !tmp->tm_isdst; + t = time2(tmp, funcp, sp, offset, &okay); + if (okay) + return t; + tmp->tm_isdst = !tmp->tm_isdst; + } + tmp->tm_sec = sec; + } } } return WRONG; -- 2.52.0
participants (1)
-
Paul Eggert