]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-154751: Fix use-after-free in curses.initscr() after newterm() (GH-154752)
authorVyron Vasileiadis <hi@fedonman.com>
Mon, 27 Jul 2026 18:24:10 +0000 (21:24 +0300)
committerGitHub <noreply@github.com>
Mon, 27 Jul 2026 18:24:10 +0000 (21:24 +0300)
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.

Lib/test/test_curses.py
Modules/_cursesmodule.c

index ad5893e6754f68c8269f56099895c404c7c3036a..31b7371abd32300efb0d7f8e317cb1fc8e4646c0 100644 (file)
@@ -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).
index b2d745332317a36e380404d5b420ed81da362cea..2b580c3475e6d93ba3dd585f2c151eaed32fe1dc 100644 (file)
@@ -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;
         }