]> granicus.if.org Git - python/commitdiff
SF bug 847019 datetime.datetime initialization needs more strict checking
authorTim Peters <tim.peters@gmail.com>
Sun, 21 Mar 2004 23:38:41 +0000 (23:38 +0000)
committerTim Peters <tim.peters@gmail.com>
Sun, 21 Mar 2004 23:38:41 +0000 (23:38 +0000)
It's possible to create insane datetime objects by using the constructor
"backdoor" inserted for fast unpickling.  Doing extensive range checking
would eliminate the backdoor's purpose (speed), but at least a little
checking can stop honest mistakes.

Bugfix candidate.

Lib/test/test_datetime.py
Modules/datetimemodule.c

index c6dbb4895624f3451002b9dfb2bd7210a08122be..347b1a9d8643b8587665a3b60c64b2a33770b4e4 100644 (file)
@@ -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
index c68c368b7ddcfbe0d59cc9af46f7d9813246f597..225a6b16e2b9268688514186fa3ac2fec06d624e 100644 (file)
  */
 #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;