Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d1080a3

Browse files
committed
Have strftime() check its time tuple argument to make sure the tuple's values
are within proper boundaries as specified in the docs. This can break possible code (datetime module needed changing, for instance) that uses 0 for values that need to be greater 1 or greater (month, day, and day of year). Fixes bug #897625.
1 parent 0a4977c commit d1080a3

6 files changed

Lines changed: 112 additions & 4 deletions

File tree

Doc/lib/libtime.tex

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,11 @@ \section{\module{time} ---
211211
by \function{gmtime()} or \function{localtime()} to a string as
212212
specified by the \var{format} argument. If \var{t} is not
213213
provided, the current time as returned by \function{localtime()} is
214-
used. \var{format} must be a string.
214+
used. \var{format} must be a string. \exception{ValueError} is raised
215+
if any field in \var{t} is outside of the allowed range.
215216
\versionchanged[Allowed \var{t} to be omitted]{2.1}
217+
\versionchanged[\exception{ValueError} raised if a field in \var{t} is
218+
out of range.]{2.4}
216219

217220
The following directives can be embedded in the \var{format} string.
218221
They are shown without the optional field width and precision

Lib/test/test_strftime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def strftest(now):
3838
if now[3] < 12: ampm='(AM|am)'
3939
else: ampm='(PM|pm)'
4040

41-
jan1 = time.localtime(time.mktime((now[0], 1, 1) + (0,)*6))
41+
jan1 = time.localtime(time.mktime((now[0], 1, 1, 0, 0, 0, 0, 1, 0)))
4242

4343
try:
4444
if now[8]: tz = time.tzname[1]

Lib/test/test_time.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,62 @@ def test_strftime(self):
3737
except ValueError:
3838
self.fail('conversion specifier: %r failed.' % format)
3939

40+
def test_strftime_bounds_checking(self):
41+
# Make sure that strftime() checks the bounds of the various parts
42+
#of the time tuple.
43+
44+
# Check year
45+
self.assertRaises(ValueError, time.strftime, '',
46+
(1899, 1, 1, 0, 0, 0, 0, 1, -1))
47+
if time.accept2dyear:
48+
self.assertRaises(ValueError, time.strftime, '',
49+
(-1, 1, 1, 0, 0, 0, 0, 1, -1))
50+
self.assertRaises(ValueError, time.strftime, '',
51+
(100, 1, 1, 0, 0, 0, 0, 1, -1))
52+
# Check month
53+
self.assertRaises(ValueError, time.strftime, '',
54+
(1900, 0, 1, 0, 0, 0, 0, 1, -1))
55+
self.assertRaises(ValueError, time.strftime, '',
56+
(1900, 13, 1, 0, 0, 0, 0, 1, -1))
57+
# Check day of month
58+
self.assertRaises(ValueError, time.strftime, '',
59+
(1900, 1, 0, 0, 0, 0, 0, 1, -1))
60+
self.assertRaises(ValueError, time.strftime, '',
61+
(1900, 1, 32, 0, 0, 0, 0, 1, -1))
62+
# Check hour
63+
self.assertRaises(ValueError, time.strftime, '',
64+
(1900, 1, 1, -1, 0, 0, 0, 1, -1))
65+
self.assertRaises(ValueError, time.strftime, '',
66+
(1900, 1, 1, 24, 0, 0, 0, 1, -1))
67+
# Check minute
68+
self.assertRaises(ValueError, time.strftime, '',
69+
(1900, 1, 1, 0, -1, 0, 0, 1, -1))
70+
self.assertRaises(ValueError, time.strftime, '',
71+
(1900, 1, 1, 0, 60, 0, 0, 1, -1))
72+
# Check second
73+
self.assertRaises(ValueError, time.strftime, '',
74+
(1900, 1, 1, 0, 0, -1, 0, 1, -1))
75+
# C99 only requires allowing for one leap second, but Python's docs say
76+
# allow two leap seconds (0..61)
77+
self.assertRaises(ValueError, time.strftime, '',
78+
(1900, 1, 1, 0, 0, 62, 0, 1, -1))
79+
# No check for upper-bound day of week;
80+
# value forced into range by a ``% 7`` calculation.
81+
# Start check at -2 since gettmarg() increments value before taking
82+
# modulo.
83+
self.assertRaises(ValueError, time.strftime, '',
84+
(1900, 1, 1, 0, 0, 0, -2, 1, -1))
85+
# Check day of the year
86+
self.assertRaises(ValueError, time.strftime, '',
87+
(1900, 1, 1, 0, 0, 0, 0, 0, -1))
88+
self.assertRaises(ValueError, time.strftime, '',
89+
(1900, 1, 1, 0, 0, 0, 0, 367, -1))
90+
# Check daylight savings flag
91+
self.assertRaises(ValueError, time.strftime, '',
92+
(1900, 1, 1, 0, 0, 0, 0, 1, -2))
93+
self.assertRaises(ValueError, time.strftime, '',
94+
(1900, 1, 1, 0, 0, 0, 0, 1, 2))
95+
4096
def test_strptime(self):
4197
tt = time.gmtime(self.t)
4298
for directive in ('a', 'A', 'b', 'B', 'c', 'd', 'H', 'I',

Misc/NEWS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ Core and builtins
167167
Extension modules
168168
-----------------
169169

170+
- time.strftime() now checks that the values in its time tuple argument
171+
are within the proper boundaries to prevent possible crashes from the
172+
platform's C library implementation of strftime(). Can possibly
173+
break code that uses values outside the range that didn't cause
174+
problems previously (such as sitting day of year to 0). Fixes bug
175+
#897625.
176+
170177
- The socket module now supports Bluetooth sockets, if the
171178
system has <bluetooth/bluetooth.h>
172179

Modules/datetimemodule.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3189,11 +3189,11 @@ time_strftime(PyDateTime_Time *self, PyObject *args, PyObject *kw)
31893189
* 1900 to worm around that.
31903190
*/
31913191
tuple = Py_BuildValue("iiiiiiiii",
3192-
1900, 0, 0, /* year, month, day */
3192+
1900, 1, 1, /* year, month, day */
31933193
TIME_GET_HOUR(self),
31943194
TIME_GET_MINUTE(self),
31953195
TIME_GET_SECOND(self),
3196-
0, 0, -1); /* weekday, daynum, dst */
3196+
0, 1, -1); /* weekday, daynum, dst */
31973197
if (tuple == NULL)
31983198
return NULL;
31993199
assert(PyTuple_Size(tuple) == 9);

Modules/timemodule.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,48 @@ time_strftime(PyObject *self, PyObject *args)
346346
} else if (!gettmarg(tup, &buf))
347347
return NULL;
348348

349+
/* Checks added to make sure strftime() does not crash Python by
350+
indexing blindly into some array for a textual representation
351+
by some bad index (fixes bug #897625).
352+
353+
No check for year since handled in gettmarg().
354+
*/
355+
if (buf.tm_mon < 0 || buf.tm_mon > 11) {
356+
PyErr_SetString(PyExc_ValueError, "month out of range");
357+
return NULL;
358+
}
359+
if (buf.tm_mday < 1 || buf.tm_mday > 31) {
360+
PyErr_SetString(PyExc_ValueError, "day of month out of range");
361+
return NULL;
362+
}
363+
if (buf.tm_hour < 0 || buf.tm_hour > 23) {
364+
PyErr_SetString(PyExc_ValueError, "hour out of range");
365+
return NULL;
366+
}
367+
if (buf.tm_min < 0 || buf.tm_min > 59) {
368+
PyErr_SetString(PyExc_ValueError, "minute out of range");
369+
return NULL;
370+
}
371+
if (buf.tm_sec < 0 || buf.tm_sec > 61) {
372+
PyErr_SetString(PyExc_ValueError, "seconds out of range");
373+
return NULL;
374+
}
375+
/* tm_wday does not need checking of its upper-bound since taking
376+
``% 7`` in gettmarg() automatically restricts the range. */
377+
if (buf.tm_wday < 0) {
378+
PyErr_SetString(PyExc_ValueError, "day of week out of range");
379+
return NULL;
380+
}
381+
if (buf.tm_yday < 0 || buf.tm_yday > 365) {
382+
PyErr_SetString(PyExc_ValueError, "day of year out of range");
383+
return NULL;
384+
}
385+
if (buf.tm_isdst < -1 || buf.tm_isdst > 1) {
386+
PyErr_SetString(PyExc_ValueError,
387+
"daylight savings flag out of range");
388+
return NULL;
389+
}
390+
349391
fmtlen = strlen(fmt);
350392

351393
/* I hate these functions that presume you know how big the output

0 commit comments

Comments
 (0)