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.
This commit is contained in:
Amaury Forgeot d'Arc 2008-10-30 21:18:34 +00:00
parent 1fac5a4505
commit 69a9c5b539
3 changed files with 59 additions and 26 deletions

View 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();