curses.set_term() accepts a screen returned by curses.new_prescr(), and the next
window refresh then segfaults the interpreter.
new_prescr() returns a screen that has no terminal attached. The documentation says so:
it is "a new screen that can be used to call functions that affect global state before
initscr() or newterm() is called". set_term() documents its argument as "a screen
returned by newterm()", but it does not check that, so it will happily make a
terminal-less screen the current one:
// Modules/_cursesmodule.c:6833-6837PyCursesScreenObject*so=curses_check_screen(module, screen);
if (so==NULL) {
returnNULL;
}
set_term(so->screen);
curses_check_screen() only rejects objects that are not screens and screens that are
already closed. After that, any wrefresh() dereferences NULL inside ncurses and the
process dies with SIGSEGV.
The stack is inside ncurses, reached from window.refresh():
libncursesw.so.6, at doupdate_sp+0x274
libncursesw.so.6, at wrefresh+0x4f
_curses.cpython-316-x86_64-linux-gnu.so
It is not specific to the window that belongs to the switched-away screen:
curses.newwin(3, 3).refresh() after the same set_term() call also gives rc=139, and so
does the initscr() variant run under pty.fork().
Two things narrow it down:
Switching between two real newterm() screens and refreshing does not crash, so the
problem is the terminal-less screen, not cross-screen refresh in general.
In the same broken state, curses.doupdate() and curses.endwin() both raise
curses.error rather than crashing, so only the refresh path is affected.
A new_prescr() screen is distinguishable from a newterm() screen at the C level: the
screen object's stdscr_win is NULL for the former and set for the latter, which is also
visible from Python as screen.stdscr being None:
>>>curses.new_prescr().stdscrisNoneTrue
So set_term() can reject exactly this input. Raising curses.error for a screen with no
standard window looks like the right behaviour, since such a screen has no terminal to
become current, but a maintainer may prefer to make the refresh path safe instead.
set_term() and new_prescr() are both new in 3.16 (gh-90092, GH-151748), so this has
never been in a release.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
The build is from a local branch, but Modules/_cursesmodule.c and
Doc/library/curses.rst in it are byte identical to main at 5afbb60, so the crash is
reproducible on a clean main build.
Activity
gh-154749: Reject a terminal-less screen in curses.set_term() (GH-154750
serhiy-storchaka commented on Jul 27, 2026
Thank you for your contribution @fedonman.
pythongh-154749: Reject a terminal-less screen in curses.set_term() (p…