-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
Closed
Labels
3.14bugs and security fixesbugs and security fixes3.15new features, bugs and security fixesnew features, bugs and security fixestopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Description
What happened?
During Task teardown TaskObj_dealloc() calls the finalizer (TaskObj_finalize) which invokes the loop's call_exception_handler (user code). If that handler re-registers the same Task (e.g. calls _asyncio._register_task(context["task"])), the runtime may add the freed task pointer into a registry while tp_free is still running, causing a heap use-after-free when the registry later touches the pointer.
Proof of Concept:
import _asyncio
class EvilLoop:
def get_debug(self):
return False
def call_exception_handler(self, context):
_asyncio._register_task(context["task"])
async def coro():
pass
loop = EvilLoop()
task = _asyncio.Task(coro(), loop=loop)Affected Versions:
| 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] |
Exception | 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
static void
TaskObj_dealloc(PyObject *self)
{
_PyObject_ResurrectStart(self);
// Unregister the task here so that even if any subclass of Task
// which doesn't end up calling TaskObj_finalize not crashes.
unregister_task((TaskObj *)self);
PyObject_CallFinalizer(self); // Call TaskObj_finalize
if (_PyObject_ResurrectEnd(self)) {
return;
}
PyTypeObject *tp = Py_TYPE(self);
PyObject_GC_UnTrack(self);
PyObject_ClearWeakRefs(self);
(void)TaskObj_clear(self);
tp->tp_free(self);
Py_DECREF(tp);
}
static void
TaskObj_finalize(PyObject *op)
{
TaskObj *task = (TaskObj*)op;
PyObject *context;
PyObject *message = NULL;
PyObject *func;
if (task->task_state != STATE_PENDING || !task->task_log_destroy_pending) {
goto done;
}
/* Save the current exception, if any. */
PyObject *exc = PyErr_GetRaisedException();
context = PyDict_New();
if (context == NULL) {
goto finally;
}
message = PyUnicode_FromString("Task was destroyed but it is pending!");
if (message == NULL) {
goto finally;
}
if (PyDict_SetItem(context, &_Py_ID(message), message) < 0 ||
PyDict_SetItem(context, &_Py_ID(task), (PyObject*)task) < 0)
{
goto finally;
}
if (task->task_source_tb != NULL) {
if (PyDict_SetItem(context, &_Py_ID(source_traceback),
task->task_source_tb) < 0)
{
goto finally;
}
}
// Bug: In the call_exception_handler, we re-register the task so that the registry holds its pointer
// While later, the tp->tp_free(self); in TaskObj_dealloc, the task will be freed and any access to the pointer will cause UAF.
// When task has ended, the call_exception_handler method will be invoked.
func = PyObject_GetAttr(task->task_loop, &_Py_ID(call_exception_handler));
if (func != NULL) {
PyObject *res = PyObject_CallOneArg(func, context);
if (res == NULL) {
PyErr_FormatUnraisable("Exception ignored while calling asyncio "
"function %R", func);
}
else {
Py_DECREF(res);
}
Py_DECREF(func);
}
finally:
Py_XDECREF(context);
Py_XDECREF(message);
/* Restore the saved exception. */
PyErr_SetRaisedException(exc);
done:
FutureObj_finalize((PyObject*)task);
}
Sanitizer
=================================================================
==1485200==ERROR: AddressSanitizer: heap-use-after-free on address 0x5150000657d0 at pc 0x62a676285a06 bp 0x7ffe931f2c30 sp 0x7ffe931f2c20
WRITE of size 8 at 0x5150000657d0 thread T0
#0 0x62a676285a05 in llist_concat Include/internal/pycore_llist.h:95
#1 0x62a676285a05 in PyThreadState_Clear Python/pystate.c:1687
#2 0x62a676285bb1 in interpreter_clear Python/pystate.c:746
#3 0x62a676286636 in _PyInterpreterState_Clear Python/pystate.c:904
#4 0x62a67627cb73 in finalize_interp_clear Python/pylifecycle.c:1920
#5 0x62a67627d669 in _Py_Finalize Python/pylifecycle.c:2337
#6 0x62a67627d6f2 in Py_FinalizeEx Python/pylifecycle.c:2378
#7 0x62a6762df847 in Py_RunMain Modules/main.c:774
#8 0x62a6762dfa2e in pymain_main Modules/main.c:802
#9 0x62a6762dfdb3 in Py_BytesMain Modules/main.c:826
#10 0x62a675d63645 in main Programs/python.c:15
#11 0x7405bae2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#12 0x7405bae2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#13 0x62a675d63574 in _start (/home/jackfromeast/Desktop/entropy/tasks/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: ff3dc40ea460bd4beb2c3a72283cca525b319bf0)
0x5150000657d0 is located 208 bytes inside of 504-byte region [0x515000065700,0x5150000658f8)
freed by thread T0 here:
#0 0x7405bb2fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0x62a675f9596d in _PyMem_RawFree Objects/obmalloc.c:91
#2 0x62a675f97cd9 in _PyMem_DebugRawFree Objects/obmalloc.c:2955
#3 0x62a675f97d1a in _PyMem_DebugFree Objects/obmalloc.c:3100
#4 0x62a675fc006c in PyObject_Free Objects/obmalloc.c:1522
#5 0x62a6761fecf7 in PyObject_GC_Del Python/gc.c:2435
#6 0x62a6762eef87 in TaskObj_dealloc Modules/_asynciomodule.c:3010
#7 0x62a675f8c481 in _Py_Dealloc Objects/object.c:3200
#8 0x62a675fdd81c in Py_DECREF Include/refcount.h:401
#9 0x62a675ff5646 in type_call Objects/typeobject.c:2463
#10 0x62a675ecec71 in _PyObject_MakeTpCall Objects/call.c:242
#11 0x62a675ecef19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
#12 0x62a675ecef72 in PyObject_Vectorcall Objects/call.c:327
#13 0x62a676154c60 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:2920
#14 0x62a676190e54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#15 0x62a676191148 in _PyEval_Vector Python/ceval.c:2001
#16 0x62a6761913f8 in PyEval_EvalCode Python/ceval.c:884
#17 0x62a676288507 in run_eval_code_obj Python/pythonrun.c:1365
#18 0x62a676288723 in run_mod Python/pythonrun.c:1459
#19 0x62a67628957a in pyrun_file Python/pythonrun.c:1293
#20 0x62a67628c220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#21 0x62a67628c4f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
#22 0x62a6762dd74d in pymain_run_file_obj Modules/main.c:410
#23 0x62a6762dd9b4 in pymain_run_file Modules/main.c:429
#24 0x62a6762df1b2 in pymain_run_python Modules/main.c:691
#25 0x62a6762df842 in Py_RunMain Modules/main.c:772
#26 0x62a6762dfa2e in pymain_main Modules/main.c:802
#27 0x62a6762dfdb3 in Py_BytesMain Modules/main.c:826
#28 0x62a675d63645 in main Programs/python.c:15
#29 0x7405bae2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
previously allocated by thread T0 here:
#0 0x7405bb2fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x62a675f96284 in _PyMem_RawMalloc Objects/obmalloc.c:63
#2 0x62a675f95655 in _PyMem_DebugRawAlloc Objects/obmalloc.c:2887
#3 0x62a675f956bd in _PyMem_DebugRawMalloc Objects/obmalloc.c:2920
#4 0x62a675f96f3b in _PyMem_DebugMalloc Objects/obmalloc.c:3085
#5 0x62a675fbff28 in PyObject_Malloc Objects/obmalloc.c:1493
#6 0x62a675ff203b in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
#7 0x62a675ff203b in _PyType_AllocNoTrack Objects/typeobject.c:2504
#8 0x62a675ff21c7 in PyType_GenericAlloc Objects/typeobject.c:2535
#9 0x62a675fd9dd9 in PyType_GenericNew Objects/typeobject.c:2549
#10 0x62a675ff5346 in type_call Objects/typeobject.c:2448
#11 0x62a675ecec71 in _PyObject_MakeTpCall Objects/call.c:242
#12 0x62a675ecef19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
#13 0x62a675ecef72 in PyObject_Vectorcall Objects/call.c:327
#14 0x62a676154c60 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:2920
#15 0x62a676190e54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#16 0x62a676191148 in _PyEval_Vector Python/ceval.c:2001
#17 0x62a6761913f8 in PyEval_EvalCode Python/ceval.c:884
#18 0x62a676288507 in run_eval_code_obj Python/pythonrun.c:1365
#19 0x62a676288723 in run_mod Python/pythonrun.c:1459
#20 0x62a67628957a in pyrun_file Python/pythonrun.c:1293
#21 0x62a67628c220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#22 0x62a67628c4f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
#23 0x62a6762dd74d in pymain_run_file_obj Modules/main.c:410
#24 0x62a6762dd9b4 in pymain_run_file Modules/main.c:429
#25 0x62a6762df1b2 in pymain_run_python Modules/main.c:691
#26 0x62a6762df842 in Py_RunMain Modules/main.c:772
#27 0x62a6762dfa2e in pymain_main Modules/main.c:802
#28 0x62a6762dfdb3 in Py_BytesMain Modules/main.c:826
#29 0x62a675d63645 in main Programs/python.c:15
#30 0x7405bae2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
SUMMARY: AddressSanitizer: heap-use-after-free Include/internal/pycore_llist.h:95 in llist_concat
Shadow bytes around the buggy address:
0x515000065500: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x515000065580: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x515000065600: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa
0x515000065680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x515000065700: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
=>0x515000065780: fd fd fd fd fd fd fd fd fd fd[fd]fd fd fd fd fd
0x515000065800: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x515000065880: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa
0x515000065900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x515000065980: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x515000065a00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
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
==1485200==ABORTINGLinked PRs
Metadata
Metadata
Assignees
Labels
3.14bugs and security fixesbugs and security fixes3.15new features, bugs and security fixesnew features, bugs and security fixestopic-asynciotype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
Projects
Status
Done