Skip to content

Commit 92efaff

Browse files
authored
gh-154751: Fix use-after-free in curses.initscr() after newterm() (GH-154752)
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.
1 parent b8862ae commit 92efaff

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

Lib/test/test_curses.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3072,6 +3072,24 @@ def test_new_prescr(self):
30723072
del screen
30733073
gc_collect()
30743074

3075+
def test_initscr_after_newterm_keeps_screen_alive(self):
3076+
# initscr() called while a newterm() screen is current returns that
3077+
# screen's own standard window, so the window keeps the screen alive.
3078+
# It used to be a second wrapper created without a screen: using it
3079+
# after the screen was collected read freed memory, and both wrappers
3080+
# could delwin() the same window.
3081+
s1 = self.make_pty()
3082+
s2 = self.make_pty()
3083+
screen1 = curses.newterm('xterm', s1, s1)
3084+
screen2 = curses.newterm('xterm', s2, s2)
3085+
curses.set_term(screen1)
3086+
win = curses.initscr()
3087+
self.assertIs(win, screen1.stdscr)
3088+
curses.set_term(screen2)
3089+
del screen1
3090+
gc_collect()
3091+
win.addstr(0, 0, 'x')
3092+
30753093
@cpython_only
30763094
def test_disallow_instantiation(self):
30773095
# The screen type cannot be instantiated directly (bpo-43916).

Modules/_cursesmodule.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6573,7 +6573,23 @@ _curses_initscr_impl(PyObject *module)
65736573
_curses_set_null_error(state, "wrefresh", "initscr");
65746574
return NULL;
65756575
}
6576-
PyObject *winobj = PyCursesWindow_New(state, stdscr, NULL, NULL, NULL);
6576+
if (state->topscreen != NULL) {
6577+
/* The current screen is one made by newterm(); return its own
6578+
standard window instead of a second wrapper over the same
6579+
WINDOW, which would delwin() it on its own. */
6580+
PyCursesScreenObject *so = (PyCursesScreenObject *)state->topscreen;
6581+
if (so->stdscr_win != NULL) {
6582+
if (curses_update_screen_encoding(so->stdscr_win) < 0) {
6583+
return NULL;
6584+
}
6585+
return Py_NewRef(so->stdscr_win);
6586+
}
6587+
}
6588+
/* Attach the current screen, like newwin(), newpad() and getwin() do,
6589+
so that the window keeps its screen alive. It is NULL for the
6590+
screen created by initscr(), which has no screen object. */
6591+
PyObject *winobj = PyCursesWindow_New(state, stdscr, NULL, NULL,
6592+
state->topscreen);
65776593
if (winobj == NULL) {
65786594
return NULL;
65796595
}

0 commit comments

Comments
 (0)