diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index c6dbb489562..347b1a9d864 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -1029,6 +1029,26 @@ class TestDate(HarmlessMixedComparison): self.assertEqual(dt1.toordinal(), dt2.toordinal()) self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7) + def test_backdoor_resistance(self): + # For fast unpickling, the constructor accepts a pickle string. + # This is a low-overhead backdoor. A user can (by intent or + # mistake) pass a string directly, which (if it's the right length) + # will get treated like a pickle, and bypass the normal sanity + # checks in the constructor. This can create insane objects. + # The constructor doesn't want to burn the time to validate all + # fields, but does check the month field. This stops, e.g., + # datetime.datetime('1995-03-25') from yielding an insane object. + base = '1995-03-25' + if not issubclass(self.theclass, datetime): + base = base[:4] + for month_byte in '9', chr(0), chr(13), '\xff': + self.assertRaises(TypeError, self.theclass, + base[:2] + month_byte + base[3:]) + for ord_byte in range(1, 13): + # This shouldn't blow up because of the month byte alone. If + # the implementation changes to do more-careful checking, it may + # blow up because other fields are insane. + self.theclass(base[:2] + chr(ord_byte) + base[3:]) ############################################################################# # datetime tests diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index c68c368b7dd..225a6b16e2b 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -80,6 +80,12 @@ */ #define HASTZINFO(p) (((_PyDateTime_BaseTZInfo *)(p))->hastzinfo) +/* M is a char or int claiming to be a valid month. The macro is equivalent + * to the two-sided Python test + * 1 <= M <= 12 + */ +#define MONTH_IS_SANE(M) ((unsigned int)(M) - 1 < 12) + /* Forward declarations. */ static PyTypeObject PyDateTime_DateType; static PyTypeObject PyDateTime_DateTimeType; @@ -2195,7 +2201,8 @@ date_new(PyTypeObject *type, PyObject *args, PyObject *kw) /* Check for invocation from pickle with __getstate__ state */ if (PyTuple_GET_SIZE(args) == 1 && PyString_Check(state = PyTuple_GET_ITEM(args, 0)) && - PyString_GET_SIZE(state) == _PyDateTime_DATE_DATASIZE) + PyString_GET_SIZE(state) == _PyDateTime_DATE_DATASIZE && + MONTH_IS_SANE(PyString_AS_STRING(state)[2])) { PyDateTime_Date *me; @@ -3550,7 +3557,8 @@ datetime_new(PyTypeObject *type, PyObject *args, PyObject *kw) if (PyTuple_GET_SIZE(args) >= 1 && PyTuple_GET_SIZE(args) <= 2 && PyString_Check(state = PyTuple_GET_ITEM(args, 0)) && - PyString_GET_SIZE(state) == _PyDateTime_DATETIME_DATASIZE) + PyString_GET_SIZE(state) == _PyDateTime_DATETIME_DATASIZE && + MONTH_IS_SANE(PyString_AS_STRING(state)[2])) { PyDateTime_DateTime *me; char aware;