Index: Lib/test/test_time.py =================================================================== --- Lib/test/test_time.py (revision 81540) +++ Lib/test/test_time.py (working copy) @@ -37,57 +37,60 @@ except ValueError: self.fail('conversion specifier: %r failed.' % format) - def test_strftime_bounds_checking(self): - # Make sure that strftime() checks the bounds of the various parts + def _bounds_checking(self, func): + # Make sure that func() checks the bounds of the various parts #of the time tuple (0 is valid for *all* values). # Check year [1900, max(int)] - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1899, 1, 1, 0, 0, 0, 0, 1, -1)) if time.accept2dyear: - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (-1, 1, 1, 0, 0, 0, 0, 1, -1)) - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (100, 1, 1, 0, 0, 0, 0, 1, -1)) # Check month [1, 12] + zero support - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, -1, 1, 0, 0, 0, 0, 1, -1)) - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 13, 1, 0, 0, 0, 0, 1, -1)) # Check day of month [1, 31] + zero support - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, -1, 0, 0, 0, 0, 1, -1)) - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, 32, 0, 0, 0, 0, 1, -1)) # Check hour [0, 23] - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, 1, -1, 0, 0, 0, 1, -1)) - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, 1, 24, 0, 0, 0, 1, -1)) # Check minute [0, 59] - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, 1, 0, -1, 0, 0, 1, -1)) - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, 1, 0, 60, 0, 0, 1, -1)) # Check second [0, 61] - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, 1, 0, 0, -1, 0, 1, -1)) # C99 only requires allowing for one leap second, but Python's docs say # allow two leap seconds (0..61) - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, 1, 0, 0, 62, 0, 1, -1)) # No check for upper-bound day of week; # value forced into range by a ``% 7`` calculation. # Start check at -2 since gettmarg() increments value before taking # modulo. - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, 1, 0, 0, 0, -2, 1, -1)) # Check day of the year [1, 366] + zero support - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, 1, 0, 0, 0, 0, -1, -1)) - self.assertRaises(ValueError, time.strftime, '', + self.assertRaises(ValueError, func, (1900, 1, 1, 0, 0, 0, 0, 367, -1)) + def test_strftime_bounds_checking(self): + self._bounds_checking(lambda t: time.strftime('', t)) + def test_default_values_for_zero(self): # Make sure that using all zeros uses the proper default values. # No test for daylight savings since strftime() does not change output @@ -115,6 +118,9 @@ time.asctime(time.gmtime(self.t)) self.assertRaises(TypeError, time.asctime, 0) + def test_asctime_bounds_checking(self): + self._bounds_checking(time.asctime) + def test_tzset(self): if not hasattr(time, "tzset"): return # Can't test this; don't want the test suite to fail Index: Modules/timemodule.c =================================================================== --- Modules/timemodule.c (revision 81540) +++ Modules/timemodule.c (working copy) @@ -382,6 +382,75 @@ return 1; } +/* Check if all the fields of a struct tm are in range. + + Also support values of zero from Python code for arguments in which + that is out of range by forcing that value to the lowest value that + is valid (fixed bug #1520914). + + Valid ranges based on what is allowed in struct tm: + + - tm_year: [0, max(int)] (1) + - tm_mon: [0, 11] (2) + - tm_mday: [1, 31] + - tm_hour: [0, 23] + - tm_min: [0, 59] + - tm_sec: [0, 60] + - tm_wday: [0, 6] (1) + - tm_yday: [0, 365] (2) + - tm_isdst: [-max(int), max(int)] + + (1) gettmarg() handles bounds-checking. + (2) Python's acceptable range is one greater than the range in C, + thus need to check against automatic decrement by gettmarg(). +*/ +static int +tm_bounds_check(struct tm *p) +{ + if (p->tm_mon == -1) + p->tm_mon = 0; + else if (p->tm_mon < 0 || p->tm_mon > 11) { + PyErr_SetString(PyExc_ValueError, "month out of range"); + return 0; + } + if (p->tm_mday == 0) + p->tm_mday = 1; + else if (p->tm_mday < 0 || p->tm_mday > 31) { + PyErr_SetString(PyExc_ValueError, "day of month out of range"); + return 0; + } + if (p->tm_hour < 0 || p->tm_hour > 23) { + PyErr_SetString(PyExc_ValueError, "hour out of range"); + return 0; + } + if (p->tm_min < 0 || p->tm_min > 59) { + PyErr_SetString(PyExc_ValueError, "minute out of range"); + return 0; + } + if (p->tm_sec < 0 || p->tm_sec > 61) { + PyErr_SetString(PyExc_ValueError, "seconds out of range"); + return 0; + } + /* tm_wday does not need checking of its upper-bound since taking + ``% 7`` in gettmarg() automatically restricts the range. */ + if (p->tm_wday < 0) { + PyErr_SetString(PyExc_ValueError, "day of week out of range"); + return 0; + } + if (p->tm_yday == -1) + p->tm_yday = 0; + else if (p->tm_yday < 0 || p->tm_yday > 365) { + PyErr_SetString(PyExc_ValueError, "day of year out of range"); + return 0; + } + if (p->tm_isdst < -1 || p->tm_isdst > 1) { + PyErr_SetString(PyExc_ValueError, + "daylight savings flag out of range"); + return 0; + } + return 1; +} + #ifdef HAVE_STRFTIME static PyObject * time_strftime(PyObject *self, PyObject *args) @@ -407,71 +476,9 @@ /* Checks added to make sure strftime() does not crash Python by indexing blindly into some array for a textual representation by some bad index (fixes bug #897625). - - Also support values of zero from Python code for arguments in which - that is out of range by forcing that value to the lowest value that - is valid (fixed bug #1520914). - - Valid ranges based on what is allowed in struct tm: - - - tm_year: [0, max(int)] (1) - - tm_mon: [0, 11] (2) - - tm_mday: [1, 31] - - tm_hour: [0, 23] - - tm_min: [0, 59] - - tm_sec: [0, 60] - - tm_wday: [0, 6] (1) - - tm_yday: [0, 365] (2) - - tm_isdst: [-max(int), max(int)] - - (1) gettmarg() handles bounds-checking. - (2) Python's acceptable range is one greater than the range in C, - thus need to check against automatic decrement by gettmarg(). */ - if (buf.tm_mon == -1) - buf.tm_mon = 0; - else if (buf.tm_mon < 0 || buf.tm_mon > 11) { - PyErr_SetString(PyExc_ValueError, "month out of range"); - return NULL; - } - if (buf.tm_mday == 0) - buf.tm_mday = 1; - else if (buf.tm_mday < 0 || buf.tm_mday > 31) { - PyErr_SetString(PyExc_ValueError, "day of month out of range"); - return NULL; - } - if (buf.tm_hour < 0 || buf.tm_hour > 23) { - PyErr_SetString(PyExc_ValueError, "hour out of range"); - return NULL; - } - if (buf.tm_min < 0 || buf.tm_min > 59) { - PyErr_SetString(PyExc_ValueError, "minute out of range"); - return NULL; - } - if (buf.tm_sec < 0 || buf.tm_sec > 61) { - PyErr_SetString(PyExc_ValueError, "seconds out of range"); - return NULL; - } - /* tm_wday does not need checking of its upper-bound since taking - ``% 7`` in gettmarg() automatically restricts the range. */ - if (buf.tm_wday < 0) { - PyErr_SetString(PyExc_ValueError, "day of week out of range"); - return NULL; - } - if (buf.tm_yday == -1) - buf.tm_yday = 0; - else if (buf.tm_yday < 0 || buf.tm_yday > 365) { - PyErr_SetString(PyExc_ValueError, "day of year out of range"); - return NULL; - } - /* Normalize tm_isdst just in case someone foolishly implements %Z - based on the assumption that tm_isdst falls within the range of - [-1, 1] */ - if (buf.tm_isdst < -1) - buf.tm_isdst = -1; - else if (buf.tm_isdst > 1) - buf.tm_isdst = 1; - + if (!tm_bounds_check(&buf)) + return NULL; #ifdef MS_WINDOWS /* check that the format string contains only valid directives */ for(outbuf = strchr(fmt, '%'); @@ -552,6 +559,27 @@ See the library reference manual for formatting codes (same as strftime())."); +#ifndef USE_SYSTEM_ASCTIME +static char * +asctime_internal(const struct tm *p) +{ + static const char *wday_name[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + static const char *mon_name[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + static char result[26]; + + sprintf(result, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n", + wday_name[p->tm_wday], mon_name[p->tm_mon], p->tm_mday, + p->tm_hour, p->tm_min, p->tm_sec, + 1900 + p->tm_year); + return result; +} +#endif + static PyObject * time_asctime(PyObject *self, PyObject *args) { @@ -565,7 +593,13 @@ buf = *localtime(&tt); } else if (!gettmarg(tup, &buf)) return NULL; + if (!tm_bounds_check(&buf)) + return NULL; +#ifdef USE_SYSTEM_ASCTIME p = asctime(&buf); +#else + p = asctime_internal(&buf); +#endif if (p[24] == '\n') p[24] = '\0'; return PyString_FromString(p); @@ -597,7 +631,11 @@ if (tt == (time_t)-1 && PyErr_Occurred()) return NULL; } +#ifdef USE_SYSTEM_ASCTIME p = ctime(&tt); +#else + p = asctime_internal(localtime(&tt)); +#endif if (p == NULL) { PyErr_SetString(PyExc_ValueError, "unconvertible time"); return NULL;