From: Vyron Vasileiadis Date: Mon, 27 Jul 2026 18:24:10 +0000 (+0300) Subject: gh-154751: Fix use-after-free in curses.initscr() after newterm() (GH-154752) X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=92efaff49b100371d66e356b0ab62c35ec7c5fe8;p=thirdparty%2FPython%2Fcpython.git 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. --- diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index ad5893e6754f..31b7371abd32 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -3072,6 +3072,24 @@ class ScreenTests(NewtermTestBase): 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 b2d745332317..2b580c3475e6 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; }