Skip to content

Commit b87e630

Browse files
committed
gh-154749: Reject a terminal-less screen in curses.set_term()
curses.set_term() accepted a screen returned by new_prescr(), which has no terminal attached. Making such a screen current left curses without a terminal, and the next window refresh dereferenced NULL and crashed the interpreter. set_term() now raises curses.error for a screen with no standard window, which is exactly a new_prescr() screen. The documentation already said the argument comes from newterm().
1 parent 5afbb60 commit b87e630

4 files changed

Lines changed: 24 additions & 1 deletion

File tree

Doc/library/curses.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ Initialization and termination
129129
and return the previously current screen.
130130
Returns ``None`` if the previous screen was the one created by
131131
:func:`initscr`.
132+
Raises :exc:`error` if *screen* has no terminal,
133+
as is the case for a screen returned by :func:`new_prescr`.
132134

133135
.. versionadded:: next
134136

Lib/test/test_curses.py

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

3075+
@unittest.skipUnless(hasattr(curses, 'new_prescr'),
3076+
'requires curses.new_prescr()')
3077+
def test_set_term_prescr_screen(self):
3078+
# A new_prescr() screen has no terminal, so it cannot become the
3079+
# current one. It used to be accepted, and the next refresh then
3080+
# crashed inside curses.
3081+
s = self.make_pty()
3082+
screen = curses.newterm('xterm', s, s)
3083+
self.assertRaises(curses.error, curses.set_term, curses.new_prescr())
3084+
# The current screen is unchanged, so refreshing it still works.
3085+
screen.stdscr.refresh()
3086+
30753087
@cpython_only
30763088
def test_disallow_instantiation(self):
30773089
# The screen type cannot be instantiated directly (bpo-43916).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`curses.set_term` now raises :exc:`curses.error` when given a screen with
2+
no terminal, such as one returned by :func:`curses.new_prescr`. Such a screen
3+
was accepted before, and the next window refresh crashed the interpreter.

Modules/_cursesmodule.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6834,11 +6834,17 @@ _curses_set_term(PyObject *module, PyObject *screen)
68346834
if (so == NULL) {
68356835
return NULL;
68366836
}
6837+
cursesmodule_state *state = get_cursesmodule_state(module);
6838+
if (so->stdscr_win == NULL) {
6839+
/* A screen from new_prescr() has no terminal, so it cannot become the
6840+
current one: a later refresh would dereference NULL in curses. */
6841+
PyErr_SetString(state->error, "the screen has no terminal");
6842+
return NULL;
6843+
}
68376844
set_term(so->screen);
68386845
if (!update_lines_cols(module)) {
68396846
return NULL;
68406847
}
6841-
cursesmodule_state *state = get_cursesmodule_state(module);
68426848
PyObject *prev = state->topscreen; /* steal the owned reference */
68436849
state->topscreen = Py_NewRef(screen);
68446850
return prev != NULL ? prev : Py_NewRef(Py_None);

0 commit comments

Comments
 (0)