mirror of
https://github.com/python/cpython.git
synced 2025-08-02 16:13:13 +00:00
gh-92930: _pickle.c: Acquire strong references before calling save() (GH-92931)
(cherry picked from commit 4c496f1f11
)
Co-authored-by: Dennis Sweeney <36520290+sweeneyde@users.noreply.github.com>
This commit is contained in:
parent
65e2a940fa
commit
1190b63721
3 changed files with 98 additions and 11 deletions
|
@ -3032,6 +3032,67 @@ class AbstractPickleTests:
|
|||
# 2-D, non-contiguous
|
||||
check_array(arr[::2])
|
||||
|
||||
def test_evil_class_mutating_dict(self):
|
||||
# https://github.com/python/cpython/issues/92930
|
||||
from random import getrandbits
|
||||
|
||||
global Bad
|
||||
class Bad:
|
||||
def __eq__(self, other):
|
||||
return ENABLED
|
||||
def __hash__(self):
|
||||
return 42
|
||||
def __reduce__(self):
|
||||
if getrandbits(6) == 0:
|
||||
collection.clear()
|
||||
return (Bad, ())
|
||||
|
||||
for proto in protocols:
|
||||
for _ in range(20):
|
||||
ENABLED = False
|
||||
collection = {Bad(): Bad() for _ in range(20)}
|
||||
for bad in collection:
|
||||
bad.bad = bad
|
||||
bad.collection = collection
|
||||
ENABLED = True
|
||||
try:
|
||||
data = self.dumps(collection, proto)
|
||||
self.loads(data)
|
||||
except RuntimeError as e:
|
||||
expected = "changed size during iteration"
|
||||
self.assertIn(expected, str(e))
|
||||
|
||||
def test_evil_pickler_mutating_collection(self):
|
||||
# https://github.com/python/cpython/issues/92930
|
||||
if not hasattr(self, "pickler"):
|
||||
raise self.skipTest(f"{type(self)} has no associated pickler type")
|
||||
|
||||
global Clearer
|
||||
class Clearer:
|
||||
pass
|
||||
|
||||
def check(collection):
|
||||
class EvilPickler(self.pickler):
|
||||
def persistent_id(self, obj):
|
||||
if isinstance(obj, Clearer):
|
||||
collection.clear()
|
||||
return None
|
||||
pickler = EvilPickler(io.BytesIO(), proto)
|
||||
try:
|
||||
pickler.dump(collection)
|
||||
except RuntimeError as e:
|
||||
expected = "changed size during iteration"
|
||||
self.assertIn(expected, str(e))
|
||||
|
||||
for proto in protocols:
|
||||
check([Clearer()])
|
||||
check([Clearer(), Clearer()])
|
||||
check({Clearer()})
|
||||
check({Clearer(), Clearer()})
|
||||
check({Clearer(): 1})
|
||||
check({Clearer(): 1, Clearer(): 2})
|
||||
check({1: Clearer(), 2: Clearer()})
|
||||
|
||||
|
||||
class BigmemPickleTests:
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fixed a crash in ``_pickle.c`` from mutating collections during ``__reduce__`` or ``persistent_id``.
|
|
@ -3005,7 +3005,10 @@ batch_list_exact(PicklerObject *self, PyObject *obj)
|
|||
|
||||
if (PyList_GET_SIZE(obj) == 1) {
|
||||
item = PyList_GET_ITEM(obj, 0);
|
||||
if (save(self, item, 0) < 0)
|
||||
Py_INCREF(item);
|
||||
int err = save(self, item, 0);
|
||||
Py_DECREF(item);
|
||||
if (err < 0)
|
||||
return -1;
|
||||
if (_Pickler_Write(self, &append_op, 1) < 0)
|
||||
return -1;
|
||||
|
@ -3020,7 +3023,10 @@ batch_list_exact(PicklerObject *self, PyObject *obj)
|
|||
return -1;
|
||||
while (total < PyList_GET_SIZE(obj)) {
|
||||
item = PyList_GET_ITEM(obj, total);
|
||||
if (save(self, item, 0) < 0)
|
||||
Py_INCREF(item);
|
||||
int err = save(self, item, 0);
|
||||
Py_DECREF(item);
|
||||
if (err < 0)
|
||||
return -1;
|
||||
total++;
|
||||
if (++this_batch == BATCHSIZE)
|
||||
|
@ -3258,10 +3264,16 @@ batch_dict_exact(PicklerObject *self, PyObject *obj)
|
|||
/* Special-case len(d) == 1 to save space. */
|
||||
if (dict_size == 1) {
|
||||
PyDict_Next(obj, &ppos, &key, &value);
|
||||
if (save(self, key, 0) < 0)
|
||||
return -1;
|
||||
if (save(self, value, 0) < 0)
|
||||
return -1;
|
||||
Py_INCREF(key);
|
||||
Py_INCREF(value);
|
||||
if (save(self, key, 0) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (save(self, value, 0) < 0) {
|
||||
goto error;
|
||||
}
|
||||
Py_CLEAR(key);
|
||||
Py_CLEAR(value);
|
||||
if (_Pickler_Write(self, &setitem_op, 1) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
|
@ -3273,10 +3285,16 @@ batch_dict_exact(PicklerObject *self, PyObject *obj)
|
|||
if (_Pickler_Write(self, &mark_op, 1) < 0)
|
||||
return -1;
|
||||
while (PyDict_Next(obj, &ppos, &key, &value)) {
|
||||
if (save(self, key, 0) < 0)
|
||||
return -1;
|
||||
if (save(self, value, 0) < 0)
|
||||
return -1;
|
||||
Py_INCREF(key);
|
||||
Py_INCREF(value);
|
||||
if (save(self, key, 0) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (save(self, value, 0) < 0) {
|
||||
goto error;
|
||||
}
|
||||
Py_CLEAR(key);
|
||||
Py_CLEAR(value);
|
||||
if (++i == BATCHSIZE)
|
||||
break;
|
||||
}
|
||||
|
@ -3291,6 +3309,10 @@ batch_dict_exact(PicklerObject *self, PyObject *obj)
|
|||
|
||||
} while (i == BATCHSIZE);
|
||||
return 0;
|
||||
error:
|
||||
Py_XDECREF(key);
|
||||
Py_XDECREF(value);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -3410,7 +3432,10 @@ save_set(PicklerObject *self, PyObject *obj)
|
|||
if (_Pickler_Write(self, &mark_op, 1) < 0)
|
||||
return -1;
|
||||
while (_PySet_NextEntry(obj, &ppos, &item, &hash)) {
|
||||
if (save(self, item, 0) < 0)
|
||||
Py_INCREF(item);
|
||||
int err = save(self, item, 0);
|
||||
Py_CLEAR(item);
|
||||
if (err < 0)
|
||||
return -1;
|
||||
if (++i == BATCHSIZE)
|
||||
break;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue