From 69a9c5b539dd848369efcd67574e447065f638e9 Mon Sep 17 00:00:00 2001 From: Amaury Forgeot d'Arc Date: Thu, 30 Oct 2008 21:18:34 +0000 Subject: [PATCH] Issue #4176: Pickle would crash the interpreter when a __reduce__ function does not return an iterator for the 4th and 5th items. (sequence-like and mapping-like state) A list is not an iterator... Will backport to 2.6 and 2.5. --- Lib/test/pickletester.py | 23 ++++++++++++++++ Misc/NEWS | 3 ++ Modules/cPickle.c | 59 ++++++++++++++++++++++------------------ 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index e1bc0781541..bf9bca78f98 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -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): diff --git a/Misc/NEWS b/Misc/NEWS index d6f3cae8d76..3017d4b52d5 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ What's New in Python 2.7 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. diff --git a/Modules/cPickle.c b/Modules/cPickle.c index 95d619dc910..f777286a6a7 100644 --- a/Modules/cPickle.c +++ b/Modules/cPickle.c @@ -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();