more changes (and a bug fix)
Hi, Most of these changes make sure that errno is always set appropriately when the time functions fail. The bug fix below that when lclptr is allocated, is not zeroed and the result from zoneinit() was not checked. If zoneinit failed, then lclptr would contain garbage, and settzname() would coredump because it assumed that the lclptr contents were sane. christos --- localtime.c.orig 2014-10-07 16:20:32.000000000 -0400 +++ localtime.c 2014-10-16 14:10:03.000000000 -0400 @@ -690,17 +690,23 @@ register char c; register int num; - if (strp == NULL || !is_digit(c = *strp)) + if (strp == NULL || !is_digit(c = *strp)) { + errno = EINVAL; return NULL; + } num = 0; do { num = num * 10 + (c - '0'); - if (num > max) + if (num > max) { + errno = ERANGE; return NULL; /* illegal value */ + } c = *++strp; } while (is_digit(c)); - if (num < min) + if (num < min) { + errno = ERANGE; return NULL; /* illegal value */ + } *nump = num; return strp; } @@ -1202,14 +1208,20 @@ ? lcl_is_set < 0 : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0) return; - if (0 < lcl) - strcpy(lcl_TZname, name); #ifdef ALL_STATE if (! lclptr) lclptr = malloc(sizeof *lclptr); #endif /* defined ALL_STATE */ - zoneinit(lclptr, name); + if (!zoneinit(lclptr, name)) { +#ifdef ALL_STATE + free(lclptr); + lclptr = NULL; +#endif + return; + } settzname(); + if (0 < lcl) + strcpy(lcl_TZname, name); lcl_is_set = lcl; } @@ -1328,8 +1340,10 @@ newt += seconds; else newt -= seconds; if (newt < sp->ats[0] || - newt > sp->ats[sp->timecnt - 1]) - return NULL; /* "cannot happen" */ + newt > sp->ats[sp->timecnt - 1]) { + errno = EINVAL; + return NULL; /* "cannot happen" */ + } result = localsub(sp, &newt, offset, tmp); if (result) { register int_fast64_t newy; @@ -1338,8 +1352,10 @@ if (t < sp->ats[0]) newy -= years; else newy += years; - if (! (INT_MIN <= newy && newy <= INT_MAX)) + if (! (INT_MIN <= newy && newy <= INT_MAX)) { + errno = EOVERFLOW; return NULL; + } result->tm_year = newy; } return result; @@ -1528,13 +1544,13 @@ tdelta = tdays / DAYSPERLYEAR; if (! ((! TYPE_SIGNED(time_t) || INT_MIN <= tdelta) && tdelta <= INT_MAX)) - return NULL; + goto overflow; idelta = tdelta; if (idelta == 0) idelta = (tdays < 0) ? -1 : 1; newy = y; if (increment_overflow(&newy, idelta)) - return NULL; + goto overflow; leapdays = leaps_thru_end_of(newy - 1) - leaps_thru_end_of(y - 1); tdays -= ((time_t) newy - y) * DAYSPERNYEAR; @@ -1563,17 +1579,17 @@ } while (idays < 0) { if (increment_overflow(&y, -1)) - return NULL; + goto overflow; idays += year_lengths[isleap(y)]; } while (idays >= year_lengths[isleap(y)]) { idays -= year_lengths[isleap(y)]; if (increment_overflow(&y, 1)) - return NULL; + goto overflow; } tmp->tm_year = y; if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE)) - return NULL; + goto overflow; tmp->tm_yday = idays; /* ** The "extra" mods below avoid overflow problems. @@ -1604,6 +1620,8 @@ tmp->TM_GMTOFF = offset; #endif /* defined TM_GMTOFF */ return tmp; +overflow: + return NULL; } char * @@ -1753,20 +1771,21 @@ if (do_norm_secs) { if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec, SECSPERMIN)) - return WRONG; + goto overflow; } if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR)) - return WRONG; + goto overflow; if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) - return WRONG; + goto overflow; y = yourtm.tm_year; if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR)) - return WRONG; + goto overflow; /* ** Turn y into an actual year number for now. ** It is converted back to an offset from TM_YEAR_BASE later. */ if (increment_overflow32(&y, TM_YEAR_BASE)) + goto overflow; return WRONG; while (yourtm.tm_mday <= 0) { if (increment_overflow32(&y, -1)) @@ -1792,9 +1811,9 @@ } } if (increment_overflow32(&y, -TM_YEAR_BASE)) - return WRONG; + goto overflow; if (! (INT_MIN <= y && y <= INT_MAX)) - return WRONG; + goto overflow; yourtm.tm_year = y; if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN) saved_seconds = 0; @@ -1844,17 +1863,17 @@ if (dir != 0) { if (t == lo) { if (t == time_t_max) - return WRONG; + goto overflow; ++t; ++lo; } else if (t == hi) { if (t == time_t_min) - return WRONG; + goto overflow; --t; --hi; } if (lo > hi) - return WRONG; + goto invalid; if (dir > 0) hi = t; else lo = t; @@ -1898,7 +1917,7 @@ ** gets checked. */ if (sp == NULL) - return WRONG; + goto invalid; for (i = sp->typecnt - 1; i >= 0; --i) { if (sp->ttis[i].tt_isdst != yourtm.tm_isdst) continue; @@ -1920,16 +1939,22 @@ goto label; } } - return WRONG; + goto invalid; } label: newt = t + saved_seconds; if ((newt < t) != (saved_seconds < 0)) - return WRONG; + overflow; t = newt; if (funcp(sp, &t, offset, tmp)) *okayp = true; return t; +invalid: + errno = EINVAL; + return WRONG; +overflow: + errno = EOVERFLOW; + return WRONG; } static time_t @@ -1966,6 +1991,7 @@ char seen[TZ_MAX_TYPES]; unsigned char types[TZ_MAX_TYPES]; bool okay; + int save_errno; if (tmp == NULL) { errno = EINVAL; @@ -1973,9 +1999,12 @@ } if (tmp->tm_isdst > 1) tmp->tm_isdst = 1; + save_errno = errno; t = time2(tmp, funcp, sp, offset, &okay); - if (okay) + if (okay) { + errno = save_errno; return t; + } if (tmp->tm_isdst < 0) #ifdef PCTS /* @@ -2013,8 +2042,10 @@ sp->ttis[samei].tt_gmtoff; tmp->tm_isdst = !tmp->tm_isdst; t = time2(tmp, funcp, sp, offset, &okay); - if (okay) + if (okay) { + errno = save_errno; return t; + } tmp->tm_sec -= sp->ttis[otheri].tt_gmtoff - sp->ttis[samei].tt_gmtoff; tmp->tm_isdst = !tmp->tm_isdst;
Thank you for reporting that bug with garbage data. I found another one while looking into that one, as the code didn't check that the entire tzfile header was read, which meant it could inspect garbage in the header. If I understand the errno-related fixes correctly they're trying to report detailed error values about various problems in parsing the TZ value as a POSIX string. However, the general tzcode philosophy is to prefer the new TZ interpretation first (i.e., read from an installed file somewhere), and to fall back on the POSIX interpretation only of the new interpretation does not work. If both the new and the POSIX interpretations fail, the new interpretation's errno value should suffice, which means there's no need to compute errno values when doing the POSIX interpretation. I'm attaching a proposed patch along those lines. This should also fix the abovementioned bugs. Plus, it gets rid of some related gotos.
On Oct 18, 12:49am, eggert@cs.ucla.edu (Paul Eggert) wrote: -- Subject: Re: [tz] more changes (and a bug fix) | Thank you for reporting that bug with garbage data. I found another one while | looking into that one, as the code didn't check that the entire tzfile header | was read, which meant it could inspect garbage in the header. | | If I understand the errno-related fixes correctly they're trying to report | detailed error values about various problems in parsing the TZ value as a POSIX | string. However, the general tzcode philosophy is to prefer the new TZ | interpretation first (i.e., read from an installed file somewhere), and to fall | back on the POSIX interpretation only of the new interpretation does not work. | If both the new and the POSIX interpretations fail, the new interpretation's | errno value should suffice, which means there's no need to compute errno values | when doing the POSIX interpretation. | | I'm attaching a proposed patch along those lines. This should also fix the | abovementioned bugs. Plus, it gets rid of some related gotos. Thanks, looks good. I will import the new code when it is released. Best, christos
On Oct 18, 5:24pm, eggert@cs.ucla.edu (Paul Eggert) wrote: -- Subject: Re: [tz] more changes (and a bug fix) | A further minor refactoring is attached. Yes, this is better. I still don't like to merge conditionals too much, it makes my brain hurt :-) It is easier to think in pieces. christos
participants (2)
-
christos@zoulas.com -
Paul Eggert