Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3166,6 +3166,31 @@ def test_use_prescr_screen(self):
# The current screen is unchanged.
screen.stdscr.refresh()

@unittest.skipUnless(hasattr(curses, 'new_prescr'),
'requires curses.new_prescr()')
def test_newterm_after_new_prescr_keeps_screen_alive(self):
# newterm() adopts the SCREEN created by new_prescr(). Dropping the
# pre-screen wrapper must not delete the live screen.
s = self.make_pty()
pre = curses.new_prescr()
screen = curses.newterm('xterm', s, s)
del pre
gc_collect()
screen.stdscr.addstr(0, 0, 'x')
screen.stdscr.refresh()

@unittest.skipUnless(hasattr(curses, 'new_prescr'),
'requires curses.new_prescr()')
def test_initscr_after_new_prescr_keeps_screen_alive(self):
# initscr() adopts the SCREEN created by new_prescr(). Dropping the
# pre-screen wrapper must not delete the live screen.
pre = curses.new_prescr()
stdscr = curses.initscr()
del pre
gc_collect()
stdscr.addstr(0, 0, 'x')
stdscr.refresh()

def test_initscr_after_newterm_keeps_screen_alive(self):
# initscr() called while a newterm() screen is current returns that
# screen's own standard window, so the window keeps the screen alive.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a use-after-free in :mod:`curses` when :func:`curses.initscr` or
:func:`curses.newterm` follows :func:`curses.new_prescr`.
29 changes: 27 additions & 2 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ typedef struct {
PyTypeObject *complexstr_type; // _curses.complexstr
PyObject *topscreen; // owned ref to the current screen object,
// or NULL for the initscr() screen
PyObject *prescreen; // owned ref to the pending new_prescr() screen,
// or NULL if there is no pending pre-screen
} cursesmodule_state;

static inline cursesmodule_state *
Expand Down Expand Up @@ -6619,13 +6621,21 @@ _curses_initscr_impl(PyObject *module)
return NULL;
}

cursesmodule_state *state = get_cursesmodule_state(module);
if (state->prescreen != NULL) {
PyCursesScreenObject *prescreen =
_PyCursesScreenObject_CAST(state->prescreen);
assert(prescreen->screen != NULL);
prescreen->screen = NULL;
Py_CLEAR(state->prescreen);
}

curses_initscr_called = curses_setupterm_called = TRUE;

if (curses_init_dict(module) < 0) {
return NULL;
}

cursesmodule_state *state = get_cursesmodule_state(module);
PyObject *winobj = PyCursesWindow_New(state, win, NULL, NULL, NULL);
if (winobj == NULL) {
return NULL;
Expand Down Expand Up @@ -6801,6 +6811,13 @@ _curses_newterm_impl(PyObject *module, const char *type, PyObject *fd,
cursesmodule_state *state = get_cursesmodule_state(module);
/* The screen object owns the SCREEN and the streams; deleting it (when it
is no longer referenced) calls delscreen() and closes the streams. */
if (state->prescreen != NULL) {
PyCursesScreenObject *prescreen =
_PyCursesScreenObject_CAST(state->prescreen);
assert(prescreen->screen == screen);
prescreen->screen = NULL;
Py_CLEAR(state->prescreen);
}
PyObject *screenobj = PyCursesScreen_New(state, screen, outfp, infp, NULL);
if (screenobj == NULL) {
delscreen(screen);
Expand Down Expand Up @@ -6898,7 +6915,13 @@ _curses_new_prescr_impl(PyObject *module)
return NULL;
}
cursesmodule_state *state = get_cursesmodule_state(module);
return PyCursesScreen_New(state, screen, NULL, NULL, NULL);
PyObject *screenobj = PyCursesScreen_New(state, screen, NULL, NULL, NULL);
if (screenobj == NULL) {
delscreen(screen);
return NULL;
}
Py_XSETREF(state->prescreen, Py_NewRef(screenobj));
return screenobj;
}
#endif /* HAVE_CURSES_NEW_PRESCR */

Expand Down Expand Up @@ -8894,6 +8917,7 @@ cursesmodule_traverse(PyObject *mod, visitproc visit, void *arg)
Py_VISIT(state->complexchar_type);
Py_VISIT(state->complexstr_type);
Py_VISIT(state->topscreen);
Py_VISIT(state->prescreen);
return 0;
}

Expand All @@ -8907,6 +8931,7 @@ cursesmodule_clear(PyObject *mod)
Py_CLEAR(state->complexchar_type);
Py_CLEAR(state->complexstr_type);
Py_CLEAR(state->topscreen);
Py_CLEAR(state->prescreen);
return 0;
}

Expand Down
Loading