diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index f582336fae17344..2c08a3566f9091a 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -3166,6 +3166,31 @@ def test_use_prescr_screen(self): # The current screen is unchanged. screen.stdscr.refresh() + @unittest.skipUnless(hasattr(curses, 'new_prescr'), + 'requires curses.new_prescr()') + def test_newterm_after_new_prescr_keeps_screen_alive(self): + # newterm() adopts the SCREEN created by new_prescr(). Dropping the + # pre-screen wrapper must not delete the live screen. + s = self.make_pty() + pre = curses.new_prescr() + screen = curses.newterm('xterm', s, s) + del pre + gc_collect() + screen.stdscr.addstr(0, 0, 'x') + screen.stdscr.refresh() + + @unittest.skipUnless(hasattr(curses, 'new_prescr'), + 'requires curses.new_prescr()') + def test_initscr_after_new_prescr_keeps_screen_alive(self): + # initscr() adopts the SCREEN created by new_prescr(). Dropping the + # pre-screen wrapper must not delete the live screen. + pre = curses.new_prescr() + stdscr = curses.initscr() + del pre + gc_collect() + stdscr.addstr(0, 0, 'x') + stdscr.refresh() + 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. diff --git a/Misc/NEWS.d/next/Library/2026-08-16-22-00-25.gh-issue-155875.6TI-oX.rst b/Misc/NEWS.d/next/Library/2026-08-16-22-00-25.gh-issue-155875.6TI-oX.rst new file mode 100644 index 000000000000000..136853686239002 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-16-22-00-25.gh-issue-155875.6TI-oX.rst @@ -0,0 +1,2 @@ +Fix a use-after-free in :mod:`curses` when :func:`curses.initscr` or +:func:`curses.newterm` follows :func:`curses.new_prescr`. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 006e27d55d8925d..6a6a0232d3d51e2 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -159,6 +159,8 @@ typedef struct { PyTypeObject *complexstr_type; // _curses.complexstr PyObject *topscreen; // owned ref to the current screen object, // or NULL for the initscr() screen + PyObject *prescreen; // owned ref to the pending new_prescr() screen, + // or NULL if there is no pending pre-screen } cursesmodule_state; static inline cursesmodule_state * @@ -6619,13 +6621,21 @@ _curses_initscr_impl(PyObject *module) return NULL; } + cursesmodule_state *state = get_cursesmodule_state(module); + if (state->prescreen != NULL) { + PyCursesScreenObject *prescreen = + _PyCursesScreenObject_CAST(state->prescreen); + assert(prescreen->screen != NULL); + prescreen->screen = NULL; + Py_CLEAR(state->prescreen); + } + curses_initscr_called = curses_setupterm_called = TRUE; if (curses_init_dict(module) < 0) { return NULL; } - cursesmodule_state *state = get_cursesmodule_state(module); PyObject *winobj = PyCursesWindow_New(state, win, NULL, NULL, NULL); if (winobj == NULL) { return NULL; @@ -6801,6 +6811,13 @@ _curses_newterm_impl(PyObject *module, const char *type, PyObject *fd, cursesmodule_state *state = get_cursesmodule_state(module); /* The screen object owns the SCREEN and the streams; deleting it (when it is no longer referenced) calls delscreen() and closes the streams. */ + if (state->prescreen != NULL) { + PyCursesScreenObject *prescreen = + _PyCursesScreenObject_CAST(state->prescreen); + assert(prescreen->screen == screen); + prescreen->screen = NULL; + Py_CLEAR(state->prescreen); + } PyObject *screenobj = PyCursesScreen_New(state, screen, outfp, infp, NULL); if (screenobj == NULL) { delscreen(screen); @@ -6898,7 +6915,13 @@ _curses_new_prescr_impl(PyObject *module) return NULL; } cursesmodule_state *state = get_cursesmodule_state(module); - return PyCursesScreen_New(state, screen, NULL, NULL, NULL); + PyObject *screenobj = PyCursesScreen_New(state, screen, NULL, NULL, NULL); + if (screenobj == NULL) { + delscreen(screen); + return NULL; + } + Py_XSETREF(state->prescreen, Py_NewRef(screenobj)); + return screenobj; } #endif /* HAVE_CURSES_NEW_PRESCR */ @@ -8894,6 +8917,7 @@ cursesmodule_traverse(PyObject *mod, visitproc visit, void *arg) Py_VISIT(state->complexchar_type); Py_VISIT(state->complexstr_type); Py_VISIT(state->topscreen); + Py_VISIT(state->prescreen); return 0; } @@ -8907,6 +8931,7 @@ cursesmodule_clear(PyObject *mod) Py_CLEAR(state->complexchar_type); Py_CLEAR(state->complexstr_type); Py_CLEAR(state->topscreen); + Py_CLEAR(state->prescreen); return 0; }