bpo-31339: Rewrite time.asctime() and time.ctime() (#3293)
* bpo-31339: Rewrite time.asctime() and time.ctime()
Backport and adapt the _asctime() function from the master branch to
not depend on the implementation of asctime() and ctime() from the
external C library. This change fixes a bug when Python is run using
the musl C library.
* bound checks for time.asctime()
* bound checks for time.strftime()
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index 12c43b0..61b8d61 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -388,6 +388,76 @@
return 1;
}
+/* Check values of the struct tm fields before it is passed to strftime() and
+ * asctime(). Return 1 if all values are valid, otherwise set an exception
+ * and returns 0.
+ */
+static int
+checktm(struct tm* buf)
+{
+ /* Checks added to make sure strftime() and asctime() does not crash Python by
+ indexing blindly into some array for a textual representation
+ by some bad index (fixes bug #897625 and #6608).
+
+ 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 0;
+ }
+ 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 0;
+ }
+ if (buf->tm_hour < 0 || buf->tm_hour > 23) {
+ PyErr_SetString(PyExc_ValueError, "hour out of range");
+ return 0;
+ }
+ if (buf->tm_min < 0 || buf->tm_min > 59) {
+ PyErr_SetString(PyExc_ValueError, "minute out of range");
+ return 0;
+ }
+ if (buf->tm_sec < 0 || buf->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 (buf->tm_wday < 0) {
+ PyErr_SetString(PyExc_ValueError, "day of week out of range");
+ return 0;
+ }
+ 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 0;
+ }
+ return 1;
+}
+
#ifdef HAVE_STRFTIME
static PyObject *
time_strftime(PyObject *self, PyObject *args)
@@ -407,8 +477,10 @@
if (tup == NULL) {
time_t tt = time(NULL);
buf = *localtime(&tt);
- } else if (!gettmarg(tup, &buf))
+ } else if (!gettmarg(tup, &buf)
+ || !checktm(&buf)) {
return NULL;
+ }
/* Checks added to make sure strftime() does not crash Python by
indexing blindly into some array for a textual representation
@@ -559,26 +631,50 @@
static PyObject *
+_asctime(struct tm *timeptr)
+{
+ /* Inspired by Open Group reference implementation available at
+ * http://pubs.opengroup.org/onlinepubs/009695399/functions/asctime.html */
+ static const char wday_name[7][4] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+ };
+ static const char mon_name[12][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ };
+ PyObject *unicode, *str;
+ /* PyString_FromString() cannot be used because it doesn't support %3d */
+ unicode = PyUnicode_FromFormat(
+ "%s %s%3d %.2d:%.2d:%.2d %d",
+ wday_name[timeptr->tm_wday],
+ mon_name[timeptr->tm_mon],
+ timeptr->tm_mday, timeptr->tm_hour,
+ timeptr->tm_min, timeptr->tm_sec,
+ 1900 + timeptr->tm_year);
+ if (unicode == NULL) {
+ return NULL;
+ }
+
+ str = PyUnicode_AsASCIIString(unicode);
+ Py_DECREF(unicode);
+ return str;
+}
+
+static PyObject *
time_asctime(PyObject *self, PyObject *args)
{
PyObject *tup = NULL;
struct tm buf;
- char *p;
if (!PyArg_UnpackTuple(args, "asctime", 0, 1, &tup))
return NULL;
if (tup == NULL) {
time_t tt = time(NULL);
buf = *localtime(&tt);
- } else if (!gettmarg(tup, &buf))
- return NULL;
- p = asctime(&buf);
- if (p == NULL) {
- PyErr_SetString(PyExc_ValueError, "invalid time");
+ } else if (!gettmarg(tup, &buf)
+ || !checktm(&buf)) {
return NULL;
}
- if (p[24] == '\n')
- p[24] = '\0';
- return PyString_FromString(p);
+ return _asctime(&buf);
}
PyDoc_STRVAR(asctime_doc,
@@ -593,7 +689,7 @@
{
PyObject *ot = NULL;
time_t tt;
- char *p;
+ struct tm *buf;
if (!PyArg_UnpackTuple(args, "ctime", 0, 1, &ot))
return NULL;
@@ -607,14 +703,16 @@
if (tt == (time_t)-1 && PyErr_Occurred())
return NULL;
}
- p = ctime(&tt);
- if (p == NULL) {
- PyErr_SetString(PyExc_ValueError, "unconvertible time");
- return NULL;
+ buf = localtime(&tt);
+ if (buf == NULL) {
+#ifdef EINVAL
+ if (errno == 0) {
+ errno = EINVAL;
+ }
+#endif
+ return PyErr_SetFromErrno(PyExc_ValueError);
}
- if (p[24] == '\n')
- p[24] = '\0';
- return PyString_FromString(p);
+ return _asctime(buf);
}
PyDoc_STRVAR(ctime_doc,