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

  1. added a commit that references this issue on Sep 15, 2026
  2. added 2 commits that reference this issue on Sep 15, 2026
  3. added a commit that references this issue on Oct 10, 2026
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