Skip to content

Segfault when a curses panel's userptr finalizer runs at deallocation #156946

Description

@fedonman

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions