]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #4176: Pickle would crash the interpreter when a __reduce__ function
authorAmaury Forgeot d'Arc <amauryfa@gmail.com>
Thu, 30 Oct 2008 21:29:12 +0000 (21:29 +0000)
committerAmaury Forgeot d'Arc <amauryfa@gmail.com>
Thu, 30 Oct 2008 21:29:12 +0000 (21:29 +0000)
does not return an iterator for the 4th and 5th items.
(sequence-like and mapping-like state)

Backport of r67049

Lib/test/pickletester.py
Misc/NEWS
Modules/cPickle.c

index e1bc078154190975745df2a8a8f5c7605c683316..bf9bca78f98734d3a94ef31a02e0adffb55a825c 100644 (file)
@@ -849,6 +849,29 @@ class AbstractPickleTests(unittest.TestCase):
             y = self.loads(s)
             self.assertEqual(y._reduce_called, 1)
 
+    def test_reduce_bad_iterator(self):
+        # Issue4176: crash when 4th and 5th items of __reduce__()
+        # are not iterators
+        class C(object):
+            def __reduce__(self):
+                # 4th item is not an iterator
+                return list, (), None, [], None
+        class D(object):
+            def __reduce__(self):
+                # 5th item is not an iterator
+                return dict, (), None, None, []
+
+        # Protocol 0 is less strict and also accept iterables.
+        for proto in 0, 1, 2:
+            try:
+                self.dumps(C(), proto)
+            except (AttributeError, pickle.PickleError, cPickle.PickleError):
+                pass
+            try:
+                self.dumps(D(), proto)
+            except (AttributeError, pickle.PickleError, cPickle.PickleError):
+                pass
+
 # Test classes for reduce_ex
 
 class REX_one(object):
index 78ecd342644c8a705109f6b0be202a5a6fb94bd3..f2ca0e0b021ce100a8bd2817bc52bb4c9e9dffb9 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,9 @@ What's New in Python 2.6.1 alpha 1
 Core and Builtins
 -----------------
 
+- Issue #4176: Fixed a crash when pickling an object which ``__reduce__``
+  method does not return iterators for the 4th and 5th items.
+
 - Issue #4209: Enabling unicode_literals and the print_function in the same
   __future__ import didn't work.
 
index 95d619dc9100612fb969acb2d1094297b11e04cd..f777286a6a7bca234cc869a13ec14601b0ba6f70 100644 (file)
@@ -2206,13 +2206,14 @@ save_pers(Picklerobject *self, PyObject *args, PyObject *f)
  * appropriate __reduce__ method for ob.
  */
 static int
-save_reduce(Picklerobject *self, PyObject *args, PyObject *ob)
+save_reduce(Picklerobject *self, PyObject *args, PyObject *fn, PyObject *ob)
 {
        PyObject *callable;
        PyObject *argtup;
-        PyObject *state = NULL;
-        PyObject *listitems = NULL;
-        PyObject *dictitems = NULL;
+       PyObject *state = NULL;
+       PyObject *listitems = Py_None;
+       PyObject *dictitems = Py_None;
+       Py_ssize_t size;
 
        int use_newobj = self->proto >= 2;
 
@@ -2220,6 +2221,14 @@ save_reduce(Picklerobject *self, PyObject *args, PyObject *ob)
        static char build = BUILD;
        static char newobj = NEWOBJ;
 
+       size = PyTuple_Size(args);
+       if (size < 2 || size > 5) {
+               cPickle_ErrFormat(PicklingError, "tuple returned by "
+                       "%s must contain 2 through 5 elements",
+                       "O", fn);
+               return -1;
+       }
+
        if (! PyArg_UnpackTuple(args, "save_reduce", 2, 5,
                                &callable,
                                &argtup,
@@ -2229,17 +2238,32 @@ save_reduce(Picklerobject *self, PyObject *args, PyObject *ob)
                return -1;
 
        if (!PyTuple_Check(argtup)) {
-               PyErr_SetString(PicklingError,
-                               "args from reduce() should be a tuple");
+               cPickle_ErrFormat(PicklingError, "Second element of "
+                       "tuple returned by %s must be a tuple",
+                       "O", fn);
                return -1;
        }
 
        if (state == Py_None)
                state = NULL;
+
        if (listitems == Py_None)
                listitems = NULL;
+       else if (!PyIter_Check(listitems)) {
+               cPickle_ErrFormat(PicklingError, "Fourth element of "
+                       "tuple returned by %s must be an iterator, not %s",
+                       "Os", fn, Py_TYPE(listitems)->tp_name);
+               return -1;
+       }
+
        if (dictitems == Py_None)
                dictitems = NULL;
+       else if (!PyIter_Check(dictitems)) {
+               cPickle_ErrFormat(PicklingError, "Fifth element of "
+                       "tuple returned by %s must be an iterator, not %s",
+                       "Os", fn, Py_TYPE(dictitems)->tp_name);
+               return -1;
+       }
 
         /* Protocol 2 special case: if callable's name is __newobj__, use
          * NEWOBJ.  This consumes a lot of code.
@@ -2363,9 +2387,8 @@ save(Picklerobject *self, PyObject *args, int pers_save)
 {
        PyTypeObject *type;
        PyObject *py_ob_id = 0, *__reduce__ = 0, *t = 0;
-       PyObject *arg_tup;
        int res = -1;
-       int tmp, size;
+       int tmp;
 
        if (Py_EnterRecursiveCall(" while pickling an object"))
                return -1;
@@ -2591,30 +2614,14 @@ save(Picklerobject *self, PyObject *args, int pers_save)
                goto finally;
        }
 
-       if (! PyTuple_Check(t)) {
+       if (!PyTuple_Check(t)) {
                cPickle_ErrFormat(PicklingError, "Value returned by "
                                "%s must be string or tuple",
                                "O", __reduce__);
                goto finally;
        }
 
-       size = PyTuple_Size(t);
-       if (size < 2 || size > 5) {
-               cPickle_ErrFormat(PicklingError, "tuple returned by "
-                       "%s must contain 2 through 5 elements",
-                       "O", __reduce__);
-               goto finally;
-       }
-
-       arg_tup = PyTuple_GET_ITEM(t, 1);
-       if (!(PyTuple_Check(arg_tup) || arg_tup == Py_None))  {
-               cPickle_ErrFormat(PicklingError, "Second element of "
-                       "tuple returned by %s must be a tuple",
-                       "O", __reduce__);
-               goto finally;
-       }
-
-       res = save_reduce(self, t, args);
+       res = save_reduce(self, t, __reduce__, args);
 
   finally:
        Py_LeaveRecursiveCall();