]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-152503: Fix garbage text from curses wide-character cell reads (GH-152505)
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 29 Jun 2026 12:06:47 +0000 (15:06 +0300)
committerGitHub <noreply@github.com>
Mon, 29 Jun 2026 12:06:47 +0000 (12:06 +0000)
window.in_wch(), window.in_wchstr() and window.getbkgrnd() read a cell
into an uninitialized cchar_t, relying on the curses library to leave the
text NUL-terminated -- which ncurses does but X/Open does not require, so
some libraries (such as NetBSD curses) returned uninitialized bytes as
wide characters.  Zero-initialize the cell buffers before the read and
default the getcchar() output to an empty string.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Misc/NEWS.d/next/Library/2026-06-28-16-20-07.gh-issue-152503.N55ose.rst [new file with mode: 0644]
Modules/_cursesmodule.c

diff --git a/Misc/NEWS.d/next/Library/2026-06-28-16-20-07.gh-issue-152503.N55ose.rst b/Misc/NEWS.d/next/Library/2026-06-28-16-20-07.gh-issue-152503.N55ose.rst
new file mode 100644 (file)
index 0000000..e97c5d7
--- /dev/null
@@ -0,0 +1,4 @@
+Fix :meth:`curses.window.in_wch`, :meth:`curses.window.in_wchstr` and
+:meth:`curses.window.getbkgrnd` returning garbage text when :mod:`curses` is
+built against a curses library that does not NUL-terminate the ``cchar_t``
+text array (such as NetBSD curses).
index 4e7b27cdb7be6e37044de3c7622343a14e380841..07883848992a208bf56a45ab7fd8e8e587763948 100644 (file)
@@ -765,6 +765,9 @@ static int
 curses_getcchar(const cchar_t *wcval, wchar_t *wstr, attr_t *attrs, int *pair)
 {
     short spair = 0;
+    /* getcchar() is not guaranteed to write the text of an empty cell, so make
+       the output an empty string by default. */
+    wstr[0] = L'\0';
 #if _NCURSES_EXTENDED_COLOR_FUNCS
     int rtn = getcchar(wcval, wstr, attrs, &spair, pair);
 #else
@@ -3079,7 +3082,8 @@ _curses_window_in_wch_impl(PyCursesWindowObject *self, int group_right_1,
                            int y, int x)
 /*[clinic end generated code: output=846ca8a82f2ecab4 input=a55dd215367dfbb1]*/
 {
-    curses_cell_t wcval;
+    /* Zeroed so getcchar() sees a NUL-terminated text array on read. */
+    curses_cell_t wcval = {0};
     cursesmodule_state *state = get_cursesmodule_state_by_win(self);
 #ifdef HAVE_NCURSESW
     int rtn;
@@ -3126,7 +3130,8 @@ static PyObject *
 _curses_window_getbkgrnd_impl(PyCursesWindowObject *self)
 /*[clinic end generated code: output=afec19cad00eff71 input=e06bf3d6bf90d2ec]*/
 {
-    curses_cell_t wcval;
+    /* Zeroed so getcchar() sees a NUL-terminated text array on read. */
+    curses_cell_t wcval = {0};
     cursesmodule_state *state = get_cursesmodule_state_by_win(self);
 #ifdef HAVE_NCURSESW
     if (wgetbkgrnd(self->win, &wcval) == ERR) {
@@ -3842,7 +3847,10 @@ PyCursesWindow_in_wchstr(PyObject *op, PyObject *args)
 
     n = Py_MIN(n, max_buf_size - 1);
     cursesmodule_state *state = get_cursesmodule_state_by_win(self);
-    curses_cell_t *buf = PyMem_New(curses_cell_t, n + 1);
+    /* Zero the cells: reading a cell back through getcchar() relies on the
+       cchar_t text array being NUL-terminated, which some curses libraries
+       only guarantee for the characters they actually write. */
+    curses_cell_t *buf = PyMem_Calloc(n + 1, sizeof(curses_cell_t));
     if (buf == NULL) {
         return PyErr_NoMemory();
     }