Commit 38e29961 authored by Alexander Belopolsky's avatar Alexander Belopolsky

Issue #6608: time.asctime is now checking struct tm fields its input

before passing it to the system asctime.  Patch by MunSic Jeong.
parent 0b0ebb48
...@@ -37,57 +37,60 @@ class TimeTestCase(unittest.TestCase): ...@@ -37,57 +37,60 @@ class TimeTestCase(unittest.TestCase):
except ValueError: except ValueError:
self.fail('conversion specifier: %r failed.' % format) self.fail('conversion specifier: %r failed.' % format)
def test_strftime_bounds_checking(self): def _bounds_checking(self, func=time.strftime):
# Make sure that strftime() checks the bounds of the various parts # Make sure that strftime() checks the bounds of the various parts
#of the time tuple (0 is valid for *all* values). #of the time tuple (0 is valid for *all* values).
# Check year [1900, max(int)] # Check year [1900, max(int)]
self.assertRaises(ValueError, time.strftime, '', self.assertRaises(ValueError, func,
(1899, 1, 1, 0, 0, 0, 0, 1, -1)) (1899, 1, 1, 0, 0, 0, 0, 1, -1))
if time.accept2dyear: if time.accept2dyear:
self.assertRaises(ValueError, time.strftime, '', self.assertRaises(ValueError, func,
(-1, 1, 1, 0, 0, 0, 0, 1, -1)) (-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)) (100, 1, 1, 0, 0, 0, 0, 1, -1))
# Check month [1, 12] + zero support # Check month [1, 12] + zero support
self.assertRaises(ValueError, time.strftime, '', self.assertRaises(ValueError, func,
(1900, -1, 1, 0, 0, 0, 0, 1, -1)) (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)) (1900, 13, 1, 0, 0, 0, 0, 1, -1))
# Check day of month [1, 31] + zero support # 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)) (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)) (1900, 1, 32, 0, 0, 0, 0, 1, -1))
# Check hour [0, 23] # Check hour [0, 23]
self.assertRaises(ValueError, time.strftime, '', self.assertRaises(ValueError, func,
(1900, 1, 1, -1, 0, 0, 0, 1, -1)) (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)) (1900, 1, 1, 24, 0, 0, 0, 1, -1))
# Check minute [0, 59] # Check minute [0, 59]
self.assertRaises(ValueError, time.strftime, '', self.assertRaises(ValueError, func,
(1900, 1, 1, 0, -1, 0, 0, 1, -1)) (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)) (1900, 1, 1, 0, 60, 0, 0, 1, -1))
# Check second [0, 61] # Check second [0, 61]
self.assertRaises(ValueError, time.strftime, '', self.assertRaises(ValueError, func,
(1900, 1, 1, 0, 0, -1, 0, 1, -1)) (1900, 1, 1, 0, 0, -1, 0, 1, -1))
# C99 only requires allowing for one leap second, but Python's docs say # C99 only requires allowing for one leap second, but Python's docs say
# allow two leap seconds (0..61) # allow two leap seconds (0..61)
self.assertRaises(ValueError, time.strftime, '', self.assertRaises(ValueError, func,
(1900, 1, 1, 0, 0, 62, 0, 1, -1)) (1900, 1, 1, 0, 0, 62, 0, 1, -1))
# No check for upper-bound day of week; # No check for upper-bound day of week;
# value forced into range by a ``% 7`` calculation. # value forced into range by a ``% 7`` calculation.
# Start check at -2 since gettmarg() increments value before taking # Start check at -2 since gettmarg() increments value before taking
# modulo. # modulo.
self.assertRaises(ValueError, time.strftime, '', self.assertRaises(ValueError, func,
(1900, 1, 1, 0, 0, 0, -2, 1, -1)) (1900, 1, 1, 0, 0, 0, -2, 1, -1))
# Check day of the year [1, 366] + zero support # 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)) (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)) (1900, 1, 1, 0, 0, 0, 0, 367, -1))
def test_strftime_bounding_check(self):
self._bounds_checking(lambda tup: time.strftime('', tup))
def test_default_values_for_zero(self): def test_default_values_for_zero(self):
# Make sure that using all zeros uses the proper default values. # Make sure that using all zeros uses the proper default values.
# No test for daylight savings since strftime() does not change output # No test for daylight savings since strftime() does not change output
...@@ -120,6 +123,9 @@ class TimeTestCase(unittest.TestCase): ...@@ -120,6 +123,9 @@ class TimeTestCase(unittest.TestCase):
time.asctime(time.gmtime(self.t)) time.asctime(time.gmtime(self.t))
self.assertRaises(TypeError, time.asctime, 0) self.assertRaises(TypeError, time.asctime, 0)
def test_asctime_bounding_check(self):
self._bounds_checking(time.asctime)
def test_tzset(self): def test_tzset(self):
if not hasattr(time, "tzset"): if not hasattr(time, "tzset"):
return # Can't test this; don't want the test suite to fail return # Can't test this; don't want the test suite to fail
......
...@@ -405,7 +405,7 @@ Jack Jansen ...@@ -405,7 +405,7 @@ Jack Jansen
Bill Janssen Bill Janssen
Drew Jenkins Drew Jenkins
Flemming Kjær Jensen Flemming Kjær Jensen
Jiba MunSic Jeong
Orjan Johansen Orjan Johansen
Fredrik Johansson Fredrik Johansson
Gregory K. Johnson Gregory K. Johnson
...@@ -462,6 +462,7 @@ Ivan Krstić ...@@ -462,6 +462,7 @@ Ivan Krstić
Andrew Kuchling Andrew Kuchling
Vladimir Kushnir Vladimir Kushnir
Cameron Laird Cameron Laird
Jean-Baptiste "Jiba" Lamy
Torsten Landschoff Torsten Landschoff
Łukasz Langa Łukasz Langa
Tino Lange Tino Lange
......
...@@ -410,6 +410,9 @@ Core and Builtins ...@@ -410,6 +410,9 @@ Core and Builtins
Extensions Extensions
---------- ----------
- Issue #6608: time.asctime is now checking struct tm fields its input
before passing it to the system asctime. Patch by MunSic Jeong.
- Issue #8734: Avoid crash in msvcrt.get_osfhandle() when an invalid file - Issue #8734: Avoid crash in msvcrt.get_osfhandle() when an invalid file
descriptor is provided. Patch by Pascal Chambon. descriptor is provided. Patch by Pascal Chambon.
......
...@@ -315,6 +315,9 @@ PyDoc_STRVAR(localtime_doc, ...@@ -315,6 +315,9 @@ PyDoc_STRVAR(localtime_doc,
Convert seconds since the Epoch to a time tuple expressing local time.\n\ Convert seconds since the Epoch to a time tuple expressing local time.\n\
When 'seconds' is not passed in, convert the current time instead."); When 'seconds' is not passed in, convert the current time instead.");
/* Convert 9-item tuple to tm structure. Return 1 on success, set
* an exception and return 0 on error.
*/
static int static int
gettmarg(PyObject *args, struct tm *p) gettmarg(PyObject *args, struct tm *p)
{ {
...@@ -377,50 +380,16 @@ gettmarg(PyObject *args, struct tm *p) ...@@ -377,50 +380,16 @@ gettmarg(PyObject *args, struct tm *p)
return 1; return 1;
} }
#ifdef HAVE_STRFTIME /* Check values of the struct tm fields before it is passed to strftime() and
#ifdef HAVE_WCSFTIME * asctime(). Return 1 if all values are valid, otherwise set an exception
#define time_char wchar_t * and returns 0.
#define format_time wcsftime
#define time_strlen wcslen
#else
#define time_char char
#define format_time strftime
#define time_strlen strlen
#endif
static PyObject *
time_strftime(PyObject *self, PyObject *args)
{
PyObject *tup = NULL;
struct tm buf;
const time_char *fmt;
#ifdef HAVE_WCSFTIME
wchar_t *format;
#else
PyObject *format;
#endif
size_t fmtlen, buflen;
time_char *outbuf = NULL;
size_t i;
PyObject *ret = NULL;
memset((void *) &buf, '\0', sizeof(buf));
/* Will always expect a unicode string to be passed as format.
Given that there's no str type anymore in py3k this seems safe.
*/ */
if (!PyArg_ParseTuple(args, "U|O:strftime", &format, &tup)) static int
return NULL; checktm(struct tm* buf)
{
if (tup == NULL) { /* Checks added to make sure strftime() and asctime() does not crash Python by
time_t tt = time(NULL);
buf = *localtime(&tt);
} else if (!gettmarg(tup, &buf))
return NULL;
/* Checks added to make sure strftime() does not crash Python by
indexing blindly into some array for a textual representation indexing blindly into some array for a textual representation
by some bad index (fixes bug #897625). by some bad index (fixes bug #897625 and #6608).
Also support values of zero from Python code for arguments in which 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 that is out of range by forcing that value to the lowest value that
...@@ -442,42 +411,87 @@ time_strftime(PyObject *self, PyObject *args) ...@@ -442,42 +411,87 @@ time_strftime(PyObject *self, PyObject *args)
(2) Python's acceptable range is one greater than the range in C, (2) Python's acceptable range is one greater than the range in C,
thus need to check against automatic decrement by gettmarg(). thus need to check against automatic decrement by gettmarg().
*/ */
if (buf.tm_mon == -1) if (buf->tm_mon == -1)
buf.tm_mon = 0; buf->tm_mon = 0;
else if (buf.tm_mon < 0 || buf.tm_mon > 11) { else if (buf->tm_mon < 0 || buf->tm_mon > 11) {
PyErr_SetString(PyExc_ValueError, "month out of range"); PyErr_SetString(PyExc_ValueError, "month out of range");
return NULL; return 0;
} }
if (buf.tm_mday == 0) if (buf->tm_mday == 0)
buf.tm_mday = 1; buf->tm_mday = 1;
else if (buf.tm_mday < 0 || buf.tm_mday > 31) { else if (buf->tm_mday < 0 || buf->tm_mday > 31) {
PyErr_SetString(PyExc_ValueError, "day of month out of range"); PyErr_SetString(PyExc_ValueError, "day of month out of range");
return NULL; return 0;
} }
if (buf.tm_hour < 0 || buf.tm_hour > 23) { if (buf->tm_hour < 0 || buf->tm_hour > 23) {
PyErr_SetString(PyExc_ValueError, "hour out of range"); PyErr_SetString(PyExc_ValueError, "hour out of range");
return NULL; return 0;
} }
if (buf.tm_min < 0 || buf.tm_min > 59) { if (buf->tm_min < 0 || buf->tm_min > 59) {
PyErr_SetString(PyExc_ValueError, "minute out of range"); PyErr_SetString(PyExc_ValueError, "minute out of range");
return NULL; return 0;
} }
if (buf.tm_sec < 0 || buf.tm_sec > 61) { if (buf->tm_sec < 0 || buf->tm_sec > 61) {
PyErr_SetString(PyExc_ValueError, "seconds out of range"); PyErr_SetString(PyExc_ValueError, "seconds out of range");
return NULL; return 0;
} }
/* tm_wday does not need checking of its upper-bound since taking /* tm_wday does not need checking of its upper-bound since taking
``% 7`` in gettmarg() automatically restricts the range. */ ``% 7`` in gettmarg() automatically restricts the range. */
if (buf.tm_wday < 0) { if (buf->tm_wday < 0) {
PyErr_SetString(PyExc_ValueError, "day of week out of range"); PyErr_SetString(PyExc_ValueError, "day of week out of range");
return NULL; return 0;
} }
if (buf.tm_yday == -1) if (buf->tm_yday == -1)
buf.tm_yday = 0; buf->tm_yday = 0;
else if (buf.tm_yday < 0 || buf.tm_yday > 365) { else if (buf->tm_yday < 0 || buf->tm_yday > 365) {
PyErr_SetString(PyExc_ValueError, "day of year out of range"); PyErr_SetString(PyExc_ValueError, "day of year out of range");
return 0;
}
return 1;
}
#ifdef HAVE_STRFTIME
#ifdef HAVE_WCSFTIME
#define time_char wchar_t
#define format_time wcsftime
#define time_strlen wcslen
#else
#define time_char char
#define format_time strftime
#define time_strlen strlen
#endif
static PyObject *
time_strftime(PyObject *self, PyObject *args)
{
PyObject *tup = NULL;
struct tm buf;
const time_char *fmt;
#ifdef HAVE_WCSFTIME
wchar_t *format;
#else
PyObject *format;
#endif
size_t fmtlen, buflen;
time_char *outbuf = NULL;
size_t i;
PyObject *ret = NULL;
memset((void *) &buf, '\0', sizeof(buf));
/* Will always expect a unicode string to be passed as format.
Given that there's no str type anymore in py3k this seems safe.
*/
if (!PyArg_ParseTuple(args, "U|O:strftime", &format, &tup))
return NULL; return NULL;
if (tup == NULL) {
time_t tt = time(NULL);
buf = *localtime(&tt);
} }
else if (!gettmarg(tup, &buf) || !checktm(&buf))
return NULL;
/* Normalize tm_isdst just in case someone foolishly implements %Z /* Normalize tm_isdst just in case someone foolishly implements %Z
based on the assumption that tm_isdst falls within the range of based on the assumption that tm_isdst falls within the range of
[-1, 1] */ [-1, 1] */
...@@ -603,7 +617,7 @@ time_asctime(PyObject *self, PyObject *args) ...@@ -603,7 +617,7 @@ time_asctime(PyObject *self, PyObject *args)
if (tup == NULL) { if (tup == NULL) {
time_t tt = time(NULL); time_t tt = time(NULL);
buf = *localtime(&tt); buf = *localtime(&tt);
} else if (!gettmarg(tup, &buf)) } else if (!gettmarg(tup, &buf) || !checktm(&buf))
return NULL; return NULL;
p = asctime(&buf); p = asctime(&buf);
if (p[24] == '\n') if (p[24] == '\n')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment