Skip to content

Commit d177460

Browse files
miss-islingtonVanshAgarwal24036skirpichevencukou
authored
[3.13] gh-143543: Fix re-entrant use-after-free in itertools.groupby (GH-143738) (GH-144627)
(cherry picked from commit a91b5c3) Co-authored-by: VanshAgarwal24036 <148854295+VanshAgarwal24036@users.noreply.github.com> Co-authored-by: Sergey B Kirpichev <skirpichev@gmail.com> Co-authored-by: Petr Viktorin <encukou@gmail.com>
1 parent 7fbdec4 commit d177460

File tree

3 files changed

+35
-2
lines changed

3 files changed

+35
-2
lines changed

Lib/test/test_itertools.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,27 @@ def keyfunc(obj):
997997
keyfunc.skip = 1
998998
self.assertRaises(ExpectedError, gulp, [None, None], keyfunc)
999999

1000+
def test_groupby_reentrant_eq_does_not_crash(self):
1001+
# regression test for gh-143543
1002+
class Key:
1003+
def __init__(self, do_advance):
1004+
self.do_advance = do_advance
1005+
1006+
def __eq__(self, other):
1007+
if self.do_advance:
1008+
self.do_advance = False
1009+
next(g)
1010+
return NotImplemented
1011+
return False
1012+
1013+
def keys():
1014+
yield Key(True)
1015+
yield Key(False)
1016+
1017+
g = itertools.groupby([None, None], keys().send)
1018+
next(g)
1019+
next(g) # must pass with address sanitizer
1020+
10001021
def test_filter(self):
10011022
self.assertEqual(list(filter(isEven, range(6))), [0,2,4])
10021023
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+
Fix a crash in itertools.groupby that could occur when a user-defined
2+
:meth:`~object.__eq__` method re-enters the iterator during key comparison.

Modules/itertoolsmodule.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,9 +531,19 @@ groupby_next(groupbyobject *gbo)
531531
else if (gbo->tgtkey == NULL)
532532
break;
533533
else {
534-
int rcmp;
534+
/* A user-defined __eq__ can re-enter groupby and advance the iterator,
535+
mutating gbo->tgtkey / gbo->currkey while we are comparing them.
536+
Take local snapshots and hold strong references so INCREF/DECREF
537+
apply to the same objects even under re-entrancy. */
538+
PyObject *tgtkey = gbo->tgtkey;
539+
PyObject *currkey = gbo->currkey;
540+
541+
Py_INCREF(tgtkey);
542+
Py_INCREF(currkey);
543+
int rcmp = PyObject_RichCompareBool(tgtkey, currkey, Py_EQ);
544+
Py_DECREF(tgtkey);
545+
Py_DECREF(currkey);
535546

536-
rcmp = PyObject_RichCompareBool(gbo->tgtkey, gbo->currkey, Py_EQ);
537547
if (rcmp == -1)
538548
return NULL;
539549
else if (rcmp == 0)

0 commit comments

Comments
 (0)