gh-154751: Fix use-after-free in curses.initscr() after newterm() (GH-154752)
serhiy-storchaka <[email protected]> Mon, 27 Jul 2026 14:24:21 -0400 (EDT)
| Newsgroups | gmane.comp.python.cvs |
|---|---|
| Message-ID | <[email protected]> |
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]