Repository navigation
Use-after-free when curses.initscr() follows newterm() #154751
Description
What happened?
When curses.initscr() is called after curses.newterm(), it returns a window that does
not keep its screen alive. The screen can then be collected while the window is still in
use, and the next method call on that window reads freed memory.
The documentation promises the opposite, in the screen objects section
(Doc/library/curses.rst:2052-2055):
A screen is freed automatically once it is no longer referenced,
either directly or through one of its windows.
Each window keeps its screen alive,
so a screen remains valid as long as any of its windows does.
In the "already initialised" branch of initscr() the window is built with no screen
(Modules/_cursesmodule.c:6576):
if (curses_initscr_called) {
cursesmodule_state *state = get_cursesmodule_state(module);
int code = wrefresh(stdscr);
...
PyObject *winobj = PyCursesWindow_New(state, stdscr, NULL, NULL, NULL);Every other window factory passes the current screen instead: getwin() at
Modules/_cursesmodule.c:6038, newpad() at :7247 and newwin() at :7287 all pass
state->topscreen, and newterm() attaches its own screen at :6795.
Reproducer, only documented APIs:
import curses, gc, os
s1 = os.openpty()[1]; s2 = os.openpty()[1]
a = curses.newterm('xterm', s1, s1)
b = curses.newterm('xterm', s2, s2)
curses.set_term(a)
w = curses.initscr()
curses.set_term(b)
del a; gc.collect()
w.addstr(0, 0, 'boom')
print("no crash")$ TERM=xterm ./python repro.py; echo "rc=$?"
Segmentation fault (core dumped)
rc=139
Two things confirm the missing screen reference is the cause:
-
Keeping a proper window of the same screen alive (
keep = a.stdscr) before the
initscr()call makes the same script finish normally. That window does carry a screen
reference, so the screen survives and the memory stays valid. -
The window returned by
initscr()is not the screen's own window:>>> a = curses.newterm('xterm', s, s); curses.set_term(a) >>> curses.initscr() is a.stdscr False
So this branch hands out a second, independent wrapper over the same
WINDOW *.
PyCursesWindow_dealloconly skipsdelwin()when the window is the current screen's
stdscr, so after aset_term()both wrappers can calldelwin()on the same window.
newterm() and set_term() are new in 3.16 (gh-90092, GH-151748), which is also where
the code at :6576 came from, so no released version is affected.
This is not the encoding use-after-free fixed by GH-151696 (gh-151695), which was about
the borrowed curses_screen_encoding pointer and landed before this code existed. It is
also separate from gh-154749, which is about set_term() accepting a screen that has no
terminal at all.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.16.0a0 (heads/fix-curses-initscr-screen-ref:5afbb60e028, Jul 27 2026, 00:05:00) [GCC 15.2.0]
The branch name is local. Its content is unmodified main at 5afbb60.
Linked PRs
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
picnixz commentedon Jul 27, 2026
cc @serhiy-storchaka
gh-154751: Fix use-after-free in curses.initscr() after newterm() (GH…
serhiy-storchaka commentedon Jul 27, 2026
Thank you for your contribution @fedonman.
pythongh-154751: Fix use-after-free in curses.initscr() after newterm…