gh-146427: Fix use-after-free when clearing interpreter's queue items by aisk · Pull Request #146428 · python/cpython
Test code:
from concurrent import interpreters from concurrent.interpreters import _queues as queues q = queues.create() q.put(1) interp = interpreters.create() interp.exec(f"from concurrent.interpreters import _queues as queues; queues.Queue({q.id}).put(2, unbounditems=queues.UNBOUND_REMOVE)") # del interp interp.close() q.put(3)
Run with ASan before this change:
./python.exe /tmp/test_queue_uaf.py
python.exe(30924,0x7ff855eaedc0) malloc: nano zone abandoned due to inability to reserve vm space.
=================================================================
==30924==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000014758 at pc 0x0002d78f050f bp 0x7ff7b18a0b90 sp 0x7ff7b18a0b88
WRITE of size 8 at 0x603000014758 thread T0
#0 0x2d78f050e in _interpqueues_put _interpqueuesmodule.c.h:265
#1 0x10eb6d600 in _Py_BuiltinCallFastWithKeywords_StackRefSteal ceval.c:859
#2 0x10eb75727 in _PyEval_EvalFrameDefault generated_cases.c.h:2444
#3 0x10eb6ab2e in PyEval_EvalCode ceval.c:686
#4 0x10ed09431 in run_mod pythonrun.c:1472
#5 0x10ed02915 in _PyRun_SimpleFileObject pythonrun.c:518
#6 0x10ed01d12 in _PyRun_AnyFileObject pythonrun.c:81
#7 0x10ed7d9d9 in Py_RunMain main.c:795
#8 0x10ed7f166 in pymain_main main.c:825
#9 0x10ed7f470 in Py_BytesMain main.c:849
#10 0x7ff81410352f in start+0xbef (dyld:x86_64+0xfffffffffffe652f)
0x603000014758 is located 24 bytes inside of 32-byte region [0x603000014740,0x603000014760)
freed by thread T0 here:
#0 0x10fc07956 in free+0xa6 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0xdd956)
#1 0x2d78f486a in clear_interpreter _interpqueuesmodule.c:1470
#2 0x10ed9975e in _PyAtExit_Fini atexitmodule.c:98
#3 0x10ecf9aa3 in interpreter_clear pystate.c:871
#4 0x10ecee073 in finalize_interp_clear pylifecycle.c:2026
#5 0x10eceabec in Py_EndInterpreter pylifecycle.c:2707
#6 0x10ec150bb in _PyXI_EndInterpreter crossinterp.c:3298
#7 0x2d76e8a0f in _interpreters_destroy _interpretersmodule.c.h:162
#8 0x10e784599 in PyObject_Vectorcall call.c:327
#9 0x10eb6c631 in _Py_VectorCallInstrumentation_StackRefSteal ceval.c:775
#10 0x10eb8b3e9 in _PyEval_EvalFrameDefault generated_cases.c.h:3222
#11 0x10eb6ab2e in PyEval_EvalCode ceval.c:686
#12 0x10ed09431 in run_mod pythonrun.c:1472
#13 0x10ed02915 in _PyRun_SimpleFileObject pythonrun.c:518
#14 0x10ed01d12 in _PyRun_AnyFileObject pythonrun.c:81
#15 0x10ed7d9d9 in Py_RunMain main.c:795
#16 0x10ed7f166 in pymain_main main.c:825
#17 0x10ed7f470 in Py_BytesMain main.c:849
#18 0x7ff81410352f in start+0xbef (dyld:x86_64+0xfffffffffffe652f)
previously allocated by thread T0 here:
#0 0x10fc0780d in malloc+0x9d (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0xdd80d)
#1 0x2d78f017e in _interpqueues_put _interpqueuesmodule.c.h:265
#2 0x10e784599 in PyObject_Vectorcall call.c:327
#3 0x10eb6c631 in _Py_VectorCallInstrumentation_StackRefSteal ceval.c:775
#4 0x10eb9100a in _PyEval_EvalFrameDefault generated_cases.c.h:1841
#5 0x10eb6ab2e in PyEval_EvalCode ceval.c:686
#6 0x2d76ec73b in _run_in_interpreter _interpretersmodule.c:690
#7 0x2d76e9b83 in _interpreters_exec _interpretersmodule.c.h:438
#8 0x10e784599 in PyObject_Vectorcall call.c:327
#9 0x10eb6c631 in _Py_VectorCallInstrumentation_StackRefSteal ceval.c:775
#10 0x10eb8b3e9 in _PyEval_EvalFrameDefault generated_cases.c.h:3222
#11 0x10eb6ab2e in PyEval_EvalCode ceval.c:686
#12 0x10ed09431 in run_mod pythonrun.c:1472
#13 0x10ed02915 in _PyRun_SimpleFileObject pythonrun.c:518
#14 0x10ed01d12 in _PyRun_AnyFileObject pythonrun.c:81
#15 0x10ed7d9d9 in Py_RunMain main.c:795
#16 0x10ed7f166 in pymain_main main.c:825
#17 0x10ed7f470 in Py_BytesMain main.c:849
#18 0x7ff81410352f in start+0xbef (dyld:x86_64+0xfffffffffffe652f)
SUMMARY: AddressSanitizer: heap-use-after-free _interpqueuesmodule.c.h:265 in _interpqueues_put
Shadow bytes around the buggy address:
0x603000014480: fd fa fa fa fd fd fd fa fa fa fd fd fd fd fa fa
0x603000014500: fd fd fd fa fa fa fd fd fd fa fa fa fd fd fd fa
0x603000014580: fa fa fd fd fd fa fa fa fd fd fd fa fa fa fd fd
0x603000014600: fd fa fa fa fd fd fd fa fa fa fd fd fd fa fa fa
0x603000014680: fd fd fd fd fa fa fd fd fd fa fa fa fd fd fd fd
=>0x603000014700: fa fa fd fd fd fa fa fa fd fd fd[fd]fa fa 00 00
0x603000014780: 00 00 fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x603000014800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x603000014880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x603000014900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x603000014980: 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
==30924==ABORTING
fish: Job 1, './python.exe /tmp/test_queue_ua…' terminated by signal SIGABRT (Abort)
After this change, runs without crash or error.