diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py index 554716aed1e98b..c6061ae0accb1e 100644 --- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -706,6 +706,42 @@ def test_hash_collision_remove_add(self): self.assertEqual(s, set(elems)) + + def test_reentrant_clear_in_iand(self): + # Issue 143546: Heap buffer overflow in set_clear_internal + # via re-entrant __eq__ during set_iand + import random + aux = {object()} + targets = [] + + class Victim: + def __hash__(self): return 0 + def __eq__(self, other): return NotImplemented + + class Trigger: + def __hash__(self): return 0 + def __eq__(self, other): + if not targets: return False + for s in targets: + op = random.randrange(7) + if op == 0: s.clear() + elif op == 1: s.add(Victim()) + elif op == 2: s.discard(Victim()) + else: s ^= aux + return False + + random.seed(0) + for _ in range(50): + left = {Victim() for _ in range(6)} + right = {Victim() for _ in range(6)} + for _ in range(3): + right.add(Trigger()) + targets[:] = [left, right] + try: + left &= right + except (RuntimeError, IndexError): + pass + class SetSubclass(set): pass diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-01-10-01-46-00.gh-issue-143546.fixed.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-10-01-46-00.gh-issue-143546.fixed.rst new file mode 100644 index 00000000000000..0049fabb56a8fb --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-01-10-01-46-00.gh-issue-143546.fixed.rst @@ -0,0 +1 @@ +Fixed a heap buffer overflow in the set execution logic when a set is mutated re-entrantly during an intersection operation. diff --git a/Objects/setobject.c b/Objects/setobject.c index 378f221bcfd1e1..04158f94625eed 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -681,7 +681,7 @@ set_clear_internal(PyObject *self) * assert that the refcount on table is 1 now, i.e. that this function * has unique access to it, so decref side-effects can't alter it. */ - for (entry = table; used > 0; entry++) { + for (entry = table; used > 0 && entry < table + oldsize; entry++) { if (entry->key && entry->key != dummy) { used--; Py_DECREF(entry->key);