Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.11] gh-92930: _pickle.c: Acquire strong references before calling save() (GH-92931) #93707

Merged
merged 1 commit into from
Jun 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions Lib/test/pickletester.py
Original file line number Diff line number Diff line change
Expand Up @@ -3035,6 +3035,67 @@ def check_array(arr):
# 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:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a crash in ``_pickle.c`` from mutating collections during ``__reduce__`` or ``persistent_id``.
47 changes: 36 additions & 11 deletions Modules/_pickle.c
Original file line number Diff line number Diff line change
Expand Up @@ -3006,7 +3006,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;
Expand All @@ -3021,7 +3024,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)
Expand Down Expand Up @@ -3259,10 +3265,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;
Expand All @@ -3274,10 +3286,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;
}
Expand All @@ -3292,6 +3310,10 @@ batch_dict_exact(PicklerObject *self, PyObject *obj)

} while (i == BATCHSIZE);
return 0;
error:
Py_XDECREF(key);
Py_XDECREF(value);
return -1;
}

static int
Expand Down Expand Up @@ -3409,7 +3431,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;
Expand Down