]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-151695: Fix use-after-free of the curses screen encoding (GH-151696)
authorSerhiy Storchaka <storchaka@gmail.com>
Fri, 19 Jun 2026 08:38:15 +0000 (11:38 +0300)
committerGitHub <noreply@github.com>
Fri, 19 Jun 2026 08:38:15 +0000 (08:38 +0000)
The module-global curses_screen_encoding stored a borrowed pointer to the
encoding owned by the window returned by the first initscr() call.  That
window can be deallocated while unctrl() and ungetch(), which have no window
of their own, still use the pointer to encode non-ASCII characters.

Keep a private copy of the encoding instead.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Misc/NEWS.d/next/Library/2026-06-19-07-26-20.gh-issue-151695.IBDlkN.rst [new file with mode: 0644]
Modules/_cursesmodule.c

diff --git a/Misc/NEWS.d/next/Library/2026-06-19-07-26-20.gh-issue-151695.IBDlkN.rst b/Misc/NEWS.d/next/Library/2026-06-19-07-26-20.gh-issue-151695.IBDlkN.rst
new file mode 100644 (file)
index 0000000..f44cb6b
--- /dev/null
@@ -0,0 +1,4 @@
+Fix a use-after-free in the :mod:`curses` module.  The encoding of the initial
+screen, used by :func:`curses.unctrl` and :func:`curses.ungetch` to encode
+non-ASCII characters, is now kept as a private copy instead of a borrowed
+pointer to a window object that may be deallocated.
index 01cb6786e88aec40c19c78038eb3c1d176d9bf56..02a8e2c1b1bc1053d035bf79575bf2c1f510e4b1 100644 (file)
@@ -208,7 +208,11 @@ static int curses_initscr_called = FALSE;
 /* Tells whether start_color() has been called to initialise color usage. */
 static int curses_start_color_called = FALSE;
 
-static const char *curses_screen_encoding = NULL;
+/* Encoding of the initial screen, used by module-level functions that have
+   no window object to take it from (e.g. unctrl(), ungetch()).  This is a
+   private copy: the window object that initscr() returns may be deallocated
+   while these functions are still in use. */
+static char *curses_screen_encoding = NULL;
 
 /* Utility Error Procedures */
 
@@ -3799,6 +3803,21 @@ _curses_init_pair_impl(PyObject *module, int pair_number, int fg, int bg)
     Py_RETURN_NONE;
 }
 
+/* Refresh the private copy of the screen encoding from a freshly created
+   stdscr window object.  Returns 0 on success, -1 with an exception set. */
+static int
+curses_update_screen_encoding(PyObject *winobj)
+{
+    char *copy = _PyMem_Strdup(((PyCursesWindowObject *)winobj)->encoding);
+    if (copy == NULL) {
+        PyErr_NoMemory();
+        return -1;
+    }
+    PyMem_Free(curses_screen_encoding);
+    curses_screen_encoding = copy;
+    return 0;
+}
+
 /*[clinic input]
 _curses.initscr
 
@@ -3820,7 +3839,15 @@ _curses_initscr_impl(PyObject *module)
             _curses_set_null_error(state, "wrefresh", "initscr");
             return NULL;
         }
-        return PyCursesWindow_New(state, stdscr, NULL, NULL);
+        PyObject *winobj = PyCursesWindow_New(state, stdscr, NULL, NULL);
+        if (winobj == NULL) {
+            return NULL;
+        }
+        if (curses_update_screen_encoding(winobj) < 0) {
+            Py_DECREF(winobj);
+            return NULL;
+        }
+        return winobj;
     }
 
     win = initscr();
@@ -3927,7 +3954,10 @@ _curses_initscr_impl(PyObject *module)
     if (winobj == NULL) {
         return NULL;
     }
-    curses_screen_encoding = ((PyCursesWindowObject *)winobj)->encoding;
+    if (curses_update_screen_encoding(winobj) < 0) {
+        Py_DECREF(winobj);
+        return NULL;
+    }
     return winobj;
 }
 
@@ -5480,6 +5510,8 @@ static void
 cursesmodule_free(void *mod)
 {
     (void)cursesmodule_clear((PyObject *)mod);
+    PyMem_Free(curses_screen_encoding);
+    curses_screen_encoding = NULL;
     curses_module_loaded = 0;  // allow reloading once garbage-collected
 }