https://github.com/python/cpython/commit/92efaff49b100371d66e356b0ab62c35ec7c5fe8
commit: 92efaff49b100371d66e356b0ab62c35ec7c5fe8
branch: main
author: Vyron Vasileiadis <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-07-27T21:24:10+03:00
summary:
gh-154751: Fix use-after-free in curses.initscr() after newterm() (GH-154752)
initscr() called while a newterm() screen is current returned a second
window object over that screen's standard window, with no reference to
the screen. Either wrapper could then free the window used by the other.
Return the screen's own standard window instead.
files:
M Lib/test/test_curses.py
M Modules/_cursesmodule.c
diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py
index ad5893e6754f68c..31b7371abd32300 100644
--- a/Lib/test/test_curses.py
+++ b/Lib/test/test_curses.py
@@ -3072,6 +3072,24 @@ def test_new_prescr(self):
del screen
gc_collect()
+ def test_initscr_after_newterm_keeps_screen_alive(self):
+ # initscr() called while a newterm() screen is current returns that
+ # screen's own standard window, so the window keeps the screen alive.
+ # It used to be a second wrapper created without a screen: using it
+ # after the screen was collected read freed memory, and both wrappers
+ # could delwin() the same window.
+ s1 = self.make_pty()
+ s2 = self.make_pty()
+ screen1 = curses.newterm('xterm', s1, s1)
+ screen2 = curses.newterm('xterm', s2, s2)
+ curses.set_term(screen1)
+ win = curses.initscr()
+ self.assertIs(win, screen1.stdscr)
+ curses.set_term(screen2)
+ del screen1
+ gc_collect()
+ win.addstr(0, 0, 'x')
+
@cpython_only
def test_disallow_instantiation(self):
# The screen type cannot be instantiated directly (bpo-43916).
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index b2d745332317a36..2b580c3475e6d93 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -6573,7 +6573,23 @@ _curses_initscr_impl(PyObject *module)
_curses_set_null_error(state, "wrefresh", "initscr");
return NULL;
}
- PyObject *winobj = PyCursesWindow_New(state, stdscr, NULL, NULL, NULL);
+ if (state->topscreen != NULL) {
+ /* The current screen is one made by newterm(); return its own
+ standard window instead of a second wrapper over the same
+ WINDOW, which would delwin() it on its own. */
+ PyCursesScreenObject *so = (PyCursesScreenObject
*)state->topscreen;
+ if (so->stdscr_win != NULL) {
+ if (curses_update_screen_encoding(so->stdscr_win) < 0) {
+ return NULL;
+ }
+ return Py_NewRef(so->stdscr_win);
+ }
+ }
+ /* Attach the current screen, like newwin(), newpad() and getwin() do,
+ so that the window keeps its screen alive. It is NULL for the
+ screen created by initscr(), which has no screen object. */
+ PyObject *winobj = PyCursesWindow_New(state, stdscr, NULL, NULL,
+ state->topscreen);
if (winobj == NULL) {
return NULL;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]