gh-156946: Unlink a curses panel before dropping its user pointer - #156947
Conversation
PyCursesPanel_Dealloc() dropped the panel's user pointer first, through PyCursesPanel_Clear(), and only then called del_panel() and remove_lop(). Dropping that reference can run a __del__, and until del_panel() has run the dying panel is still on the panel stack and still in lop, so top_panel(), bottom_panel(), above() and below() hand the finalizer a new reference to an object whose refcount is already zero. Releasing that reference re-enters the deallocator and the interpreter segfaults. Take the panel out of lop and off the panel stack first, and release the user pointer and the window afterwards. set_panel_userptr() and panel_userptr() need the PANEL, so they still run before del_panel(). remove_lop() now also runs before del_panel(), which closes a second window where the registry held an entry whose PANEL had been freed. The deallocator cannot simply call PyCursesPanel_Clear() in the new order because that function is also tp_clear, where del_panel() must not run.
There was a problem hiding this comment.
There is a similar issue in PyCursesPanel_Clear(). Py_DECREF(extra) should be called after set_panel_userptr().
PyCursesPanel_Clear() released the user pointer before clearing it from the panel, leaving the panel pointing at an object whose refcount could reach zero. Same ordering as the deallocator.
|
Thanks @fedonman for the PR, and @serhiy-storchaka for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14, 3.15. |
|
Sorry, @fedonman and @serhiy-storchaka, I could not cleanly backport this to |
|
Sorry, @fedonman and @serhiy-storchaka, I could not cleanly backport this to |
|
GH-157540 is a backport of this pull request to the 3.15 branch. |
|
GH-157541 is a backport of this pull request to the 3.14 branch. |
|
GH-157542 is a backport of this pull request to the 3.13 branch. |
|
|
PyCursesPanel_Dealloc()released the panel's user pointer before it took the panel off the panel stack and out oflop, so a__del__running from thatPy_DECREFcould fetch the dying panel back throughtop_panel()and crash the interpreter.The deallocator now unlinks first (
set_panel_userptr(NULL),remove_lop(),del_panel()) and releases the user pointer and the window afterwards.remove_lop()also moves ahead ofdel_panel(), closing a second window where the registry held an entry whosePANELhad already been freed. It cannot just callPyCursesPanel_Clear()in the new order, because that function is alsotp_clear, wheredel_panel()must not run.The new test segfaults the worker without the change to
Modules/_curses_panel.c, which is also true of the neighbouringtest_userptr_segfault.With the test but without the C change:
With the change:
Full module: