Skip to content

Commit 7fc8507

Browse files
[3.13] gh-146613: Fix re-entrant use-after-free in itertools._grouper (GH-147962) (#148011)
gh-146613: Fix re-entrant use-after-free in `itertools._grouper` (GH-147962) (cherry picked from commit fc7a188) Co-authored-by: Ma Yukun <68433685+TheSkyC@users.noreply.github.com>
1 parent 4c8d6f4 commit 7fc8507

File tree

3 files changed

+44
-1
lines changed

3 files changed

+44
-1
lines changed

Lib/test/test_itertools.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,38 @@ def keys():
10181018
next(g)
10191019
next(g) # must pass with address sanitizer
10201020

1021+
def test_grouper_reentrant_eq_does_not_crash(self):
1022+
# regression test for gh-146613
1023+
grouper_iter = None
1024+
1025+
class Key:
1026+
__hash__ = None
1027+
1028+
def __init__(self, do_advance):
1029+
self.do_advance = do_advance
1030+
1031+
def __eq__(self, other):
1032+
nonlocal grouper_iter
1033+
if self.do_advance:
1034+
self.do_advance = False
1035+
if grouper_iter is not None:
1036+
try:
1037+
next(grouper_iter)
1038+
except StopIteration:
1039+
pass
1040+
return NotImplemented
1041+
return True
1042+
1043+
def keyfunc(element):
1044+
if element == 0:
1045+
return Key(do_advance=True)
1046+
return Key(do_advance=False)
1047+
1048+
g = itertools.groupby(range(4), keyfunc)
1049+
key, grouper_iter = next(g)
1050+
items = list(grouper_iter)
1051+
self.assertEqual(len(items), 1)
1052+
10211053
def test_filter(self):
10221054
self.assertEqual(list(filter(isEven, range(6))), [0,2,4])
10231055
self.assertEqual(list(filter(None, [0,1,0,2,0])), [1,2])
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`itertools`: Fix a crash in :func:`itertools.groupby` when
2+
the grouper iterator is concurrently mutated.

Modules/itertoolsmodule.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,16 @@ _grouper_next(_grouperobject *igo)
712712
}
713713

714714
assert(gbo->currkey != NULL);
715-
rcmp = PyObject_RichCompareBool(igo->tgtkey, gbo->currkey, Py_EQ);
715+
/* A user-defined __eq__ can re-enter the grouper and advance the iterator,
716+
mutating gbo->currkey while we are comparing them.
717+
Take local snapshots and hold strong references so INCREF/DECREF
718+
apply to the same objects even under re-entrancy. */
719+
PyObject *tgtkey = Py_NewRef(igo->tgtkey);
720+
PyObject *currkey = Py_NewRef(gbo->currkey);
721+
rcmp = PyObject_RichCompareBool(tgtkey, currkey, Py_EQ);
722+
Py_DECREF(tgtkey);
723+
Py_DECREF(currkey);
724+
716725
if (rcmp <= 0)
717726
/* got any error or current group is end */
718727
return NULL;

0 commit comments

Comments
 (0)