Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork34k
Closed
Description
What happened?
A user-defined__index__ can clear/shrink the targetarray during index conversion. The bounds check happens before the callback, but the write inb_setitem happens after, using a stale buffer and causing a write into freed/zero-length memory.
Proof of Concept:
importarrayvictim=array.array('b', [0]*64)classEvil:def__index__(self):# Re-entrant mutation: shrink the array while __setitem__ still holds# a pointer to the pre-clear buffer.victim.clear()return0victim[1]=Evil()
Affected Versions:
Details
| Python Version | Status | Exit Code |
|---|---|---|
Python 3.9.24+ (heads/3.9:9c4638d, Oct 17 2025, 11:19:30) | Exception | 1 |
Python 3.10.19+ (heads/3.10:0142619, Oct 17 2025, 11:20:05) [GCC 13.3.0] | Exception | 1 |
Python 3.11.14+ (heads/3.11:88f3f5b, Oct 17 2025, 11:20:44) [GCC 13.3.0] | Exception | 1 |
Python 3.12.12+ (heads/3.12:8cb2092, Oct 17 2025, 11:21:35) [GCC 13.3.0] | Exception | 1 |
Python 3.13.9+ (heads/3.13:0760a57, Oct 17 2025, 11:22:25) [GCC 13.3.0] | ASAN | 1 |
Python 3.14.0+ (heads/3.14:889e918, Oct 17 2025, 11:23:02) [GCC 13.3.0] | ASAN | 1 |
Python 3.15.0a1+ (heads/main:fbf0843, Oct 17 2025, 11:23:37) [GCC 13.3.0] | ASAN | 1 |
Vulnerable Code Snippet
Details
intPyObject_SetItem(PyObject*o,PyObject*key,PyObject*value){if (o==NULL||key==NULL||value==NULL) {null_error();return-1; }PyMappingMethods*m=Py_TYPE(o)->tp_as_mapping;if (m&&m->mp_ass_subscript) {intres=m->mp_ass_subscript(o,key,value);assert(_Py_CheckSlotResult(o,"__setitem__",res >=0));returnres; }if (Py_TYPE(o)->tp_as_sequence) {if (_PyIndex_Check(key)) {Py_ssize_tkey_value;key_value=PyNumber_AsSsize_t(key,PyExc_IndexError);if (key_value==-1&&PyErr_Occurred())return-1;returnPySequence_SetItem(o,key_value,value); }elseif (Py_TYPE(o)->tp_as_sequence->sq_ass_item) {type_error("sequence index must be ""integer, not '%.200s'",key);return-1; } }type_error("'%.200s' object does not support item assignment",o);return-1;}staticintarray_ass_subscr(PyObject*op,PyObject*item,PyObject*value){Py_ssize_tstart,stop,step,slicelength,needed;arrayobject*self=arrayobject_CAST(op);array_state*state=find_array_state_by_type(Py_TYPE(self));arrayobject*other;intitemsize;if (PyIndex_Check(item)) {Py_ssize_ti=PyNumber_AsSsize_t(item,PyExc_IndexError);if (i==-1&&PyErr_Occurred())return-1;if (i<0)i+=Py_SIZE(self);if (i<0||i >=Py_SIZE(self)) {PyErr_SetString(PyExc_IndexError,"array assignment index out of range");return-1; }if (value==NULL) {/* Fall through to slice assignment */start=i;stop=i+1;step=1;slicelength=1; }else// Bug: SetItem happensreturn (*self->ob_descr->setitem)(self,i,value); } ...}staticintb_setitem(arrayobject*ap,Py_ssize_ti,PyObject*v){shortx;// Bug: Value's __index__ method has been called where the array buffer has been freed./* PyArg_Parse's 'b' formatter is for an unsigned char, therefore must use the next size up that is signed ('h') and manually do the overflow checking */if (!PyArg_Parse(v,"h;array item must be integer",&x))return-1;elseif (x<-128) {PyErr_SetString(PyExc_OverflowError,"signed char is less than minimum");return-1; }elseif (x>127) {PyErr_SetString(PyExc_OverflowError,"signed char is greater than maximum");return-1; }if (i >=0)// Freed buffer has been visited. ((char*)ap->ob_item)[i]= (char)x;return0;}
Sanitizer
Details
===================================================================1453430==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000001 (pc 0x7aef85b7e366 bp 0x7ffc096c5ca0 sp 0x7ffc096c5c00 T0)==1453430==The signal is caused by a WRITE memory access.==1453430==Hint: address points to the zero page. #0 0x7aef85b7e366 in b_setitem Modules/arraymodule.c:235 #1 0x7aef85b8155a in array_ass_subscr Modules/arraymodule.c:2528 #2 0x61f8093f2be7 in PyObject_SetItem Objects/abstract.c:237 #3 0x61f8096de2f6 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:11245 #4 0x61f8096e4e54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121 #5 0x61f8096e5148 in _PyEval_Vector Python/ceval.c:2001 #6 0x61f8096e53f8 in PyEval_EvalCode Python/ceval.c:884 #7 0x61f8097dc507 in run_eval_code_obj Python/pythonrun.c:1365 #8 0x61f8097dc723 in run_mod Python/pythonrun.c:1459 #9 0x61f8097dd57a in pyrun_file Python/pythonrun.c:1293 #10 0x61f8097e0220 in _PyRun_SimpleFileObject Python/pythonrun.c:521 #11 0x61f8097e04f6 in _PyRun_AnyFileObject Python/pythonrun.c:81 #12 0x61f80983174d in pymain_run_file_obj Modules/main.c:410 #13 0x61f8098319b4 in pymain_run_file Modules/main.c:429 #14 0x61f8098331b2 in pymain_run_python Modules/main.c:691 #15 0x61f809833842 in Py_RunMain Modules/main.c:772 #16 0x61f809833a2e in pymain_main Modules/main.c:802 #17 0x61f809833db3 in Py_BytesMain Modules/main.c:826 #18 0x61f8092b7645 in main Programs/python.c:15 #19 0x7aef8642a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 #20 0x7aef8642a28a in __libc_start_main_impl ../csu/libc-start.c:360 #21 0x61f8092b7574 in _start (/home/jackfromeast/Desktop/entropy/tasks/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: ff3dc40ea460bd4beb2c3a72283cca525b319bf0)AddressSanitizer can not provide additional info.SUMMARY: AddressSanitizer: SEGV Modules/arraymodule.c:235 in b_setitem==1453430==ABORTINGLinked PRs
- gh-142555: Fix null pointer dereference in array.__setitem__ via re-entrant __index__ #142713
- [3.14] gh-142555: Fix null pointer dereference in array.__setitem__ via re-entrant __index__ (GH-142713) #144396
- [3.13] gh-142555: Fix null pointer dereference in array.__setitem__ via re-entrant __index__ (GH-142713) #144397