Skip to content

Commit 574cb34

Browse files
committed
Fix heap buffer overflow in set_clear_internal (GH-143546)
Added a bounds check in set_clear_internal to prevent heap buffer overflow when the set is mutated re-entrantly during iteration (e.g. via __eq__). Added regression test in Lib/test/test_set.py.
1 parent e0fb278 commit 574cb34

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

Lib/test/test_set.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,42 @@ def __hash__(self):
676676
myset.discard(elem2)
677677

678678

679+
680+
def test_reentrant_clear_in_iand(self):
681+
# Issue 143546: Heap buffer overflow in set_clear_internal
682+
# via re-entrant __eq__ during set_iand
683+
import random
684+
aux = {object()}
685+
targets = []
686+
687+
class Victim:
688+
def __hash__(self): return 0
689+
def __eq__(self, other): return NotImplemented
690+
691+
class Trigger:
692+
def __hash__(self): return 0
693+
def __eq__(self, other):
694+
if not targets: return False
695+
for s in targets:
696+
op = random.randrange(7)
697+
if op == 0: s.clear()
698+
elif op == 1: s.add(Victim())
699+
elif op == 2: s.discard(Victim())
700+
else: s ^= aux
701+
return False
702+
703+
random.seed(0)
704+
for _ in range(50):
705+
left = {Victim() for _ in range(6)}
706+
right = {Victim() for _ in range(6)}
707+
for _ in range(3):
708+
right.add(Trigger())
709+
targets[:] = [left, right]
710+
try:
711+
left &= right
712+
except (RuntimeError, IndexError):
713+
pass
714+
679715
class SetSubclass(set):
680716
pass
681717

Objects/setobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ set_clear_internal(PyObject *self)
678678
* assert that the refcount on table is 1 now, i.e. that this function
679679
* has unique access to it, so decref side-effects can't alter it.
680680
*/
681-
for (entry = table; used > 0; entry++) {
681+
for (entry = table; used > 0 && entry < table + oldsize; entry++) {
682682
if (entry->key && entry->key != dummy) {
683683
used--;
684684
Py_DECREF(entry->key);

0 commit comments

Comments
 (0)