Skip to content

Commit 031382c

Browse files
committed
[3.10] pythongh-101765: Fix SystemError / segmentation fault in iter __reduce__ when internal access of builtins.__dict__ exhausts the iterator (pythonGH-101769).
(cherry picked from commit 54dfa14) Co-authored-by: Ionite <[email protected]>
1 parent 2e2ab67 commit 031382c

8 files changed

+142
-24
lines changed

Lib/test/test_iter.py

+81
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
from test.support import check_free_after_iterating, ALWAYS_EQ, NEVER_EQ
88
import pickle
99
import collections.abc
10+
import functools
11+
import contextlib
12+
import builtins
1013

1114
# Test result of triple loop (too big to inline)
1215
TRIPLETS = [(0, 0, 0), (0, 0, 1), (0, 0, 2),
@@ -81,6 +84,12 @@ class BadIterableClass:
8184
def __iter__(self):
8285
raise ZeroDivisionError
8386

87+
class EmptyIterClass:
88+
def __len__(self):
89+
return 0
90+
def __getitem__(self, i):
91+
raise StopIteration
92+
8493
# Main test suite
8594

8695
class TestCase(unittest.TestCase):
@@ -228,6 +237,78 @@ def test_mutating_seq_class_exhausted_iter(self):
228237
self.assertEqual(list(empit), [5, 6])
229238
self.assertEqual(list(a), [0, 1, 2, 3, 4, 5, 6])
230239

240+
def test_reduce_mutating_builtins_iter(self):
241+
# This is a reproducer of issue #101765
242+
# where iter `__reduce__` calls could lead to a segfault or SystemError
243+
# depending on the order of C argument evaluation, which is undefined
244+
245+
# Backup builtins
246+
builtins_dict = builtins.__dict__
247+
orig = {"iter": iter, "reversed": reversed}
248+
249+
def run(builtin_name, item, sentinel=None):
250+
it = iter(item) if sentinel is None else iter(item, sentinel)
251+
252+
class CustomStr:
253+
def __init__(self, name, iterator):
254+
self.name = name
255+
self.iterator = iterator
256+
def __hash__(self):
257+
return hash(self.name)
258+
def __eq__(self, other):
259+
# Here we exhaust our iterator, possibly changing
260+
# its `it_seq` pointer to NULL
261+
# The `__reduce__` call should correctly get
262+
# the pointers after this call
263+
list(self.iterator)
264+
return other == self.name
265+
266+
# del is required here
267+
# to not prematurely call __eq__ from
268+
# the hash collision with the old key
269+
del builtins_dict[builtin_name]
270+
builtins_dict[CustomStr(builtin_name, it)] = orig[builtin_name]
271+
272+
return it.__reduce__()
273+
274+
types = [
275+
(EmptyIterClass(),),
276+
(bytes(8),),
277+
(bytearray(8),),
278+
((1, 2, 3),),
279+
(lambda: 0, 0),
280+
(tuple[int],) # GenericAlias
281+
]
282+
283+
try:
284+
run_iter = functools.partial(run, "iter")
285+
# The returned value of `__reduce__` should not only be valid
286+
# but also *empty*, as `it` was exhausted during `__eq__`
287+
# i.e "xyz" returns (iter, ("",))
288+
self.assertEqual(run_iter("xyz"), (orig["iter"], ("",)))
289+
self.assertEqual(run_iter([1, 2, 3]), (orig["iter"], ([],)))
290+
291+
# _PyEval_GetBuiltin is also called for `reversed` in a branch of
292+
# listiter_reduce_general
293+
self.assertEqual(
294+
run("reversed", orig["reversed"](list(range(8)))),
295+
(iter, ([],))
296+
)
297+
298+
for case in types:
299+
self.assertEqual(run_iter(*case), (orig["iter"], ((),)))
300+
finally:
301+
# Restore original builtins
302+
for key, func in orig.items():
303+
# need to suppress KeyErrors in case
304+
# a failed test deletes the key without setting anything
305+
with contextlib.suppress(KeyError):
306+
# del is required here
307+
# to not invoke our custom __eq__ from
308+
# the hash collision with the old key
309+
del builtins_dict[key]
310+
builtins_dict[key] = func
311+
231312
# Test a new_style class with __iter__ but no next() method
232313
def test_new_style_iter_class(self):
233314
class IterClass(object):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix SystemError / segmentation fault in iter ``__reduce__`` when internal access of ``builtins.__dict__`` keys mutates the iter object.

Objects/bytearrayobject.c

+8-3
Original file line numberDiff line numberDiff line change
@@ -2442,11 +2442,16 @@ static PyObject *
24422442
bytearrayiter_reduce(bytesiterobject *it, PyObject *Py_UNUSED(ignored))
24432443
{
24442444
_Py_IDENTIFIER(iter);
2445+
PyObject *iter = _PyEval_GetBuiltinId(&PyId_iter);
2446+
2447+
/* _PyEval_GetBuiltinId can invoke arbitrary code,
2448+
* call must be before access of iterator pointers.
2449+
* see issue #101765 */
2450+
24452451
if (it->it_seq != NULL) {
2446-
return Py_BuildValue("N(O)n", _PyEval_GetBuiltinId(&PyId_iter),
2447-
it->it_seq, it->it_index);
2452+
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
24482453
} else {
2449-
return Py_BuildValue("N(())", _PyEval_GetBuiltinId(&PyId_iter));
2454+
return Py_BuildValue("N(())", iter);
24502455
}
24512456
}
24522457

Objects/bytesobject.c

+8-3
Original file line numberDiff line numberDiff line change
@@ -3146,11 +3146,16 @@ static PyObject *
31463146
striter_reduce(striterobject *it, PyObject *Py_UNUSED(ignored))
31473147
{
31483148
_Py_IDENTIFIER(iter);
3149+
PyObject *iter = _PyEval_GetBuiltinId(&PyId_iter);
3150+
3151+
/* _PyEval_GetBuiltinId can invoke arbitrary code,
3152+
* call must be before access of iterator pointers.
3153+
* see issue #101765 */
3154+
31493155
if (it->it_seq != NULL) {
3150-
return Py_BuildValue("N(O)n", _PyEval_GetBuiltinId(&PyId_iter),
3151-
it->it_seq, it->it_index);
3156+
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
31523157
} else {
3153-
return Py_BuildValue("N(())", _PyEval_GetBuiltinId(&PyId_iter));
3158+
return Py_BuildValue("N(())", iter);
31543159
}
31553160
}
31563161

Objects/iterobject.c

+16-6
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,16 @@ PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(list(
104104
static PyObject *
105105
iter_reduce(seqiterobject *it, PyObject *Py_UNUSED(ignored))
106106
{
107+
PyObject *iter = _PyEval_GetBuiltinId(&PyId_iter);
108+
109+
/* _PyEval_GetBuiltinId can invoke arbitrary code,
110+
* call must be before access of iterator pointers.
111+
* see issue #101765 */
112+
107113
if (it->it_seq != NULL)
108-
return Py_BuildValue("N(O)n", _PyEval_GetBuiltinId(&PyId_iter),
109-
it->it_seq, it->it_index);
114+
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
110115
else
111-
return Py_BuildValue("N(())", _PyEval_GetBuiltinId(&PyId_iter));
116+
return Py_BuildValue("N(())", iter);
112117
}
113118

114119
PyDoc_STRVAR(reduce_doc, "Return state information for pickling.");
@@ -243,11 +248,16 @@ calliter_iternext(calliterobject *it)
243248
static PyObject *
244249
calliter_reduce(calliterobject *it, PyObject *Py_UNUSED(ignored))
245250
{
251+
PyObject *iter = _PyEval_GetBuiltinId(&PyId_iter);
252+
253+
/* _PyEval_GetBuiltinId can invoke arbitrary code,
254+
* call must be before access of iterator pointers.
255+
* see issue #101765 */
256+
246257
if (it->it_callable != NULL && it->it_sentinel != NULL)
247-
return Py_BuildValue("N(OO)", _PyEval_GetBuiltinId(&PyId_iter),
248-
it->it_callable, it->it_sentinel);
258+
return Py_BuildValue("N(OO)", iter, it->it_callable, it->it_sentinel);
249259
else
250-
return Py_BuildValue("N(())", _PyEval_GetBuiltinId(&PyId_iter));
260+
return Py_BuildValue("N(())", iter);
251261
}
252262

253263
static PyMethodDef calliter_methods[] = {

Objects/listobject.c

+12-6
Original file line numberDiff line numberDiff line change
@@ -3405,17 +3405,23 @@ listiter_reduce_general(void *_it, int forward)
34053405
_Py_IDENTIFIER(reversed);
34063406
PyObject *list;
34073407

3408+
/* _PyEval_GetBuiltinId can invoke arbitrary code,
3409+
* call must be before access of iterator pointers.
3410+
* see issue #101765 */
3411+
34083412
/* the objects are not the same, index is of different types! */
34093413
if (forward) {
3414+
PyObject *iter = _PyEval_GetBuiltinId(&PyId_iter);
34103415
listiterobject *it = (listiterobject *)_it;
3411-
if (it->it_seq)
3412-
return Py_BuildValue("N(O)n", _PyEval_GetBuiltinId(&PyId_iter),
3413-
it->it_seq, it->it_index);
3416+
if (it->it_seq) {
3417+
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
3418+
}
34143419
} else {
3420+
PyObject *reversed = _PyEval_GetBuiltinId(&PyId_reversed);
34153421
listreviterobject *it = (listreviterobject *)_it;
3416-
if (it->it_seq)
3417-
return Py_BuildValue("N(O)n", _PyEval_GetBuiltinId(&PyId_reversed),
3418-
it->it_seq, it->it_index);
3422+
if (it->it_seq) {
3423+
return Py_BuildValue("N(O)n", reversed, it->it_seq, it->it_index);
3424+
}
34193425
}
34203426
/* empty iterator, create an empty list */
34213427
list = PyList_New(0);

Objects/tupleobject.c

+8-3
Original file line numberDiff line numberDiff line change
@@ -1115,11 +1115,16 @@ static PyObject *
11151115
tupleiter_reduce(tupleiterobject *it, PyObject *Py_UNUSED(ignored))
11161116
{
11171117
_Py_IDENTIFIER(iter);
1118+
PyObject *iter = _PyEval_GetBuiltinId(&PyId_iter);
1119+
1120+
/* _PyEval_GetBuiltinId can invoke arbitrary code,
1121+
* call must be before access of iterator pointers.
1122+
* see issue #101765 */
1123+
11181124
if (it->it_seq)
1119-
return Py_BuildValue("N(O)n", _PyEval_GetBuiltinId(&PyId_iter),
1120-
it->it_seq, it->it_index);
1125+
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
11211126
else
1122-
return Py_BuildValue("N(())", _PyEval_GetBuiltinId(&PyId_iter));
1127+
return Py_BuildValue("N(())", iter);
11231128
}
11241129

11251130
static PyObject *

Objects/unicodeobject.c

+8-3
Original file line numberDiff line numberDiff line change
@@ -16070,14 +16070,19 @@ static PyObject *
1607016070
unicodeiter_reduce(unicodeiterobject *it, PyObject *Py_UNUSED(ignored))
1607116071
{
1607216072
_Py_IDENTIFIER(iter);
16073+
PyObject *iter = _PyEval_GetBuiltinId(&PyId_iter);
16074+
16075+
/* _PyEval_GetBuiltinId can invoke arbitrary code,
16076+
* call must be before access of iterator pointers.
16077+
* see issue #101765 */
16078+
1607316079
if (it->it_seq != NULL) {
16074-
return Py_BuildValue("N(O)n", _PyEval_GetBuiltinId(&PyId_iter),
16075-
it->it_seq, it->it_index);
16080+
return Py_BuildValue("N(O)n", iter, it->it_seq, it->it_index);
1607616081
} else {
1607716082
PyObject *u = (PyObject *)_PyUnicode_New(0);
1607816083
if (u == NULL)
1607916084
return NULL;
16080-
return Py_BuildValue("N(N)", _PyEval_GetBuiltinId(&PyId_iter), u);
16085+
return Py_BuildValue("N(N)", iter, u);
1608116086
}
1608216087
}
1608316088

0 commit comments

Comments
 (0)