Skip to content

Commit e514b4a

Browse files
[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 551f8e1) (cherry picked from commit 7b55e9a) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b836d40 commit e514b4a

2 files changed

Lines changed: 41 additions & 5 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a use-after-free in the :mod:`curses` module. The encoding of the initial
2+
screen, used by :func:`curses.unctrl` and :func:`curses.ungetch` to encode
3+
non-ASCII characters, is now kept as a private copy instead of a borrowed
4+
pointer to a window object that may be deallocated.

Modules/_cursesmodule.c

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ static int initialised = FALSE;
179179
/* Tells whether start_color() has been called to initialise color usage. */
180180
static int initialisedcolors = FALSE;
181181

182+
/* Encoding of the initial screen, used by module-level functions that have
183+
no window object to take it from (e.g. unctrl(), ungetch()). This is a
184+
private copy: the window object that initscr() returns may be deallocated
185+
while these functions are still in use. */
182186
static char *screen_encoding = NULL;
183187

184188
/* Utility Macros */
@@ -3274,6 +3278,21 @@ _curses_init_pair_impl(PyObject *module, int pair_number, int fg, int bg)
32743278

32753279
static PyObject *ModDict;
32763280

3281+
/* Refresh the private copy of the screen encoding from a freshly created
3282+
stdscr window object. Returns 0 on success, -1 with an exception set. */
3283+
static int
3284+
curses_update_screen_encoding(PyObject *winobj)
3285+
{
3286+
char *copy = _PyMem_Strdup(((PyCursesWindowObject *)winobj)->encoding);
3287+
if (copy == NULL) {
3288+
PyErr_NoMemory();
3289+
return -1;
3290+
}
3291+
PyMem_Free(screen_encoding);
3292+
screen_encoding = copy;
3293+
return 0;
3294+
}
3295+
32773296
/*[clinic input]
32783297
_curses.initscr
32793298
@@ -3287,11 +3306,18 @@ _curses_initscr_impl(PyObject *module)
32873306
/*[clinic end generated code: output=619fb68443810b7b input=514f4bce1821f6b5]*/
32883307
{
32893308
WINDOW *win;
3290-
PyCursesWindowObject *winobj;
32913309

32923310
if (initialised) {
32933311
wrefresh(stdscr);
3294-
return (PyObject *)PyCursesWindow_New(stdscr, NULL, NULL);
3312+
PyObject *winobj = PyCursesWindow_New(stdscr, NULL, NULL);
3313+
if (winobj == NULL) {
3314+
return NULL;
3315+
}
3316+
if (curses_update_screen_encoding(winobj) < 0) {
3317+
Py_DECREF(winobj);
3318+
return NULL;
3319+
}
3320+
return winobj;
32953321
}
32963322

32973323
win = initscr();
@@ -3383,9 +3409,15 @@ _curses_initscr_impl(PyObject *module)
33833409
SetDictInt("LINES", LINES);
33843410
SetDictInt("COLS", COLS);
33853411

3386-
winobj = (PyCursesWindowObject *)PyCursesWindow_New(win, NULL, NULL);
3387-
screen_encoding = winobj->encoding;
3388-
return (PyObject *)winobj;
3412+
PyObject *winobj = PyCursesWindow_New(win, NULL, NULL);
3413+
if (winobj == NULL) {
3414+
return NULL;
3415+
}
3416+
if (curses_update_screen_encoding(winobj) < 0) {
3417+
Py_DECREF(winobj);
3418+
return NULL;
3419+
}
3420+
return winobj;
33893421
}
33903422

33913423
/*[clinic input]

0 commit comments

Comments
 (0)