Repository navigation
Segfault when a curses panel's userptr finalizer runs at deallocation #156946
Description
What happened?
PyCursesPanel_Dealloc() in Modules/_curses_panel.c drops the panel's user pointer before it takes the panel off the panel stack and out of the module's internal registry. Dropping that reference can run a __del__, and at that moment top_panel(), bottom_panel(), above() and below() still hand out a new reference to the panel that is being deallocated, whose refcount is already zero. The panel is then torn down a second time and the interpreter dies with SIGSEGV.
Doc/library/curses.panel.rst says the user pointer "can be any Python object", so an object with a finalizer is a documented use, and there is no way for that finalizer to tell it is running inside the panel's own deallocation.
Reproducer:
import pty, curses, curses.panel as P
curses.setupterm(fd=pty.openpty()[1])
curses.initscr()
p = P.new_panel(curses.newwin(3, 6, 0, 0))
class A:
def __del__(self):
print("in __del__: top_panel() ->", P.top_panel())
p.set_userptr(A())
del p
print("survived")$ TERM=xterm-256color ./python -u -X faulthandler repro.py; echo "rc=$?"
in __del__: top_panel() -> <curses.panel.panel object at 0x700b22abfbb0>
in __del__: top_panel() -> None
Fatal Python error: Segmentation fault
Current thread 0x0000700b23470780 [python] (most recent call first):
File "repro.py", line 9 in <module>
Current thread's C stack trace (most recent call first):
...
Binary file "/usr/lib/x86_64-linux-gnu/libtinfo.so.6", at _nc_screen_of+0x9 [0x700b22925649]
Binary file "/usr/lib/x86_64-linux-gnu/libpanelw.so.6", at del_panel+0x29 [0x700b2290e669]
Binary file ".../_curses_panel.cpython-316-x86_64-linux-gnu.so", at +0x333a [0x700b231d133a]
Binary file "./python", at _Py_Dealloc+0x176 [0x5a6c4d1efb76]
...
rc=139
__del__ runs twice for the same panel: the first call is handed the dying panel, and dropping that reference re-enters the deallocator. survived is never printed.
This is not new in 3.16. The same script segfaults on released 3.14:
$ TERM=xterm-256color /usr/bin/python3.14 -u repro.py; echo "rc=$?"
in __del__: top_panel() -> <_curses_panel.panel object at 0x7eadd6bc02e0>
in __del__: top_panel() -> None
rc=139
The same hazard was fixed for panel.set_userptr() itself in gh-62313, where the fix was to clear the stored pointer before dropping the reference; test_userptr_segfault in Lib/test/test_curses.py still pins it. The deallocator has the same shape and was never covered. gh-155860 is a different panel crash in the same file (replace() with a detached window) and is already fixed.
CPython versions tested on:
CPython main branch, 3.14
Operating systems tested on:
Linux
Linked PRs
- gh-156946: Unlink a curses panel before dropping its user pointer #156947
- [3.15] gh-156946: Unlink a curses panel before dropping its user pointer (GH-156947) #157540
- [3.14] gh-156946: Unlink a curses panel before dropping its user pointer (GH-156947) #157541
- [3.13] gh-156946: Unlink a curses panel before dropping its user pointer (GH-156947) #157542
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
gh-156946: Unlink a curses panel before dropping its user pointer (GH…
[3.13] gh-156946: Unlink a curses panel before dropping its user poin…
[3.14] gh-156946: Unlink a curses panel before dropping its user poin…
[3.15] gh-156946: Unlink a curses panel before dropping its user poin…