[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 551f8e16f8)
(cherry picked from commit 7b55e9a93e)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serhiy Storchaka 2026-06-19 13:30:35 +03:00 committed by GitHub
parent b836d40269
commit e514b4a279
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 41 additions and 5 deletions

View file

@ -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.

View file

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