https://github.com/python/cpython/commit/e514b4a279b9cd301292e7454e7e79cf2cba9e38
commit: e514b4a279b9cd301292e7454e7e79cf2cba9e38
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-06-19T10:30:35Z
summary:

[3.13] gh-151695: Fix use-after-free of the curses screen encoding (GH-151696) 
(GH-151706) (GH-151723)

The module-global 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.
(cherry picked from commit 551f8e16f8bb38a1e9c6df259a2a0969493de070)
(cherry picked from commit 7b55e9a93e67913d9086a7b31f5b911958f2bf14)

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-06-19-07-26-20.gh-issue-151695.IBDlkN.rst
M 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
index 00000000000000..f44cb6b9307165
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-19-07-26-20.gh-issue-151695.IBDlkN.rst
@@ -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.
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index efa2ea3daf4f78..b87a9862f839f7 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -179,6 +179,10 @@ static int initialised = FALSE;
 /* Tells whether start_color() has been called to initialise color usage. */
 static int initialisedcolors = FALSE;
 
+/* 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 *screen_encoding = NULL;
 
 /* Utility Macros */
@@ -3274,6 +3278,21 @@ _curses_init_pair_impl(PyObject *module, int 
pair_number, int fg, int bg)
 
 static PyObject *ModDict;
 
+/* 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(screen_encoding);
+    screen_encoding = copy;
+    return 0;
+}
+
 /*[clinic input]
 _curses.initscr
 
@@ -3287,11 +3306,18 @@ _curses_initscr_impl(PyObject *module)
 /*[clinic end generated code: output=619fb68443810b7b input=514f4bce1821f6b5]*/
 {
     WINDOW *win;
-    PyCursesWindowObject *winobj;
 
     if (initialised) {
         wrefresh(stdscr);
-        return (PyObject *)PyCursesWindow_New(stdscr, NULL, NULL);
+        PyObject *winobj = PyCursesWindow_New(stdscr, NULL, NULL);
+        if (winobj == NULL) {
+            return NULL;
+        }
+        if (curses_update_screen_encoding(winobj) < 0) {
+            Py_DECREF(winobj);
+            return NULL;
+        }
+        return winobj;
     }
 
     win = initscr();
@@ -3383,9 +3409,15 @@ _curses_initscr_impl(PyObject *module)
     SetDictInt("LINES", LINES);
     SetDictInt("COLS", COLS);
 
-    winobj = (PyCursesWindowObject *)PyCursesWindow_New(win, NULL, NULL);
-    screen_encoding = winobj->encoding;
-    return (PyObject *)winobj;
+    PyObject *winobj = PyCursesWindow_New(win, NULL, NULL);
+    if (winobj == NULL) {
+        return NULL;
+    }
+    if (curses_update_screen_encoding(winobj) < 0) {
+        Py_DECREF(winobj);
+        return NULL;
+    }
+    return winobj;
 }
 
 /*[clinic input]

_______________________________________________
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]

Reply via email to