From 2d7e971be2c5e3e4690d024e4fe48755934628ae Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 26 Jun 2026 17:43:57 +0300 Subject: [PATCH 1/2] gh-152275: Add integer overflow guards to the curses chtype and color-pair packing path curses.color_pair() now raises OverflowError for a pair number too large to be packed, instead of silently masking it to a different pair. The attr argument of the character-cell and attribute methods (addch, addstr, attron, attrset and others) now goes through the checked attr converter, so an out-of-range or non-integer attribute is rejected rather than silently truncated. Co-Authored-By: Claude Opus 4.8 --- Doc/library/curses.rst | 11 +- Lib/test/test_curses.py | 14 +++ ...-06-26-17-18-01.gh-issue-152275.V64zKa.rst | 7 ++ Modules/_cursesmodule.c | 103 +++++++++-------- Modules/clinic/_cursesmodule.c.h | 105 +++++++++--------- 5 files changed, 137 insertions(+), 103 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-26-17-18-01.gh-issue-152275.V64zKa.rst diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index 69af3b94d72da10..6a44442fff536c8 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -139,10 +139,13 @@ The module :mod:`!curses` defines the following functions: .. function:: color_pair(pair_number) Return the attribute value for displaying text in the specified color pair. - Only the first 256 color pairs are supported. This - attribute value can be combined with :const:`A_STANDOUT`, :const:`A_REVERSE`, - and the other :const:`!A_\*` attributes. :func:`pair_number` is the counterpart - to this function. + Only color pairs that fit in the color-pair field of the returned value can + be represented (usually the first 256); a larger *pair_number* raises + :exc:`OverflowError` rather than being silently masked to a different pair. + Use :meth:`~window.color_set` or :meth:`~window.attr_set` to display higher + pairs. This attribute value can be combined with :const:`A_STANDOUT`, + :const:`A_REVERSE`, and the other :const:`!A_\*` attributes. + :func:`pair_number` is the counterpart to this function. .. function:: curs_set(visibility) diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 27a64532b21fd85..b6f058f50b77c9b 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -784,6 +784,10 @@ def test_argument_errors(self): # A character argument must be an int, a byte or a one-element string. self.assertRaises(TypeError, win.addch, []) self.assertRaises(OverflowError, win.addch, 2**64) + # The attribute argument is rejected, not truncated, when out of range. + self.assertRaises(OverflowError, win.addch, 'a', 2**64) + self.assertRaises(OverflowError, win.addstr, 'a', 2**64) + self.assertRaises(TypeError, win.addch, 'a', 'bold') # A string method rejects a non-string, non-bytes argument. self.assertRaises(TypeError, win.addstr, 5) self.assertRaises(TypeError, win.addstr) @@ -939,6 +943,11 @@ def test_attributes(self): self.assertRaises(OverflowError, win.attr_set, -1) self.assertRaises(OverflowError, win.attr_on, -1) self.assertRaises(OverflowError, win.attr_set, 1 << 64) + # attron()/attroff()/attrset() reject a bad attribute too. + self.assertRaises(OverflowError, win.attron, 1 << 64) + self.assertRaises(OverflowError, win.attroff, -1) + self.assertRaises(OverflowError, win.attrset, 1 << 64) + self.assertRaises(TypeError, win.attron, 'x') @requires_colors def test_attr_color_pair(self): @@ -1630,6 +1639,11 @@ def test_color_attrs(self): self.assertEqual(curses.pair_number(attr | curses.A_BOLD), pair) self.assertEqual(curses.color_pair(0), 0) self.assertEqual(curses.pair_number(0), 0) + # A pair too large to fit is rejected, not silently masked (gh-119138). + max_pair = curses.pair_number(curses.A_COLOR) + self.assertEqual(curses.pair_number(curses.color_pair(max_pair)), max_pair) + self.assertRaises(OverflowError, curses.color_pair, max_pair + 1) + self.assertRaises(OverflowError, curses.color_pair, -1) @requires_curses_func('use_default_colors') @requires_colors diff --git a/Misc/NEWS.d/next/Library/2026-06-26-17-18-01.gh-issue-152275.V64zKa.rst b/Misc/NEWS.d/next/Library/2026-06-26-17-18-01.gh-issue-152275.V64zKa.rst new file mode 100644 index 000000000000000..ec33586df1a7381 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-26-17-18-01.gh-issue-152275.V64zKa.rst @@ -0,0 +1,7 @@ +The :mod:`curses` module now raises :exc:`OverflowError` instead of silently +truncating an out-of-range value: :func:`curses.color_pair` rejects a color +pair number that does not fit in the ``chtype`` color field, and the +``attr`` argument of the character-cell and attribute methods +(:meth:`~curses.window.addch`, :meth:`~curses.window.addstr`, +:meth:`~curses.window.attron`, :meth:`~curses.window.attrset` and others) is +checked against the ``chtype`` range. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 1274f291e15f98d..6e327520a2ead87 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -692,7 +692,7 @@ typedef struct { obj may also be a complexchar, whose cell is used directly; it carries its own rendition, so supplying *attr* too (attr_given) is rejected. */ static int -PyCurses_ConvertToCell(PyCursesWindowObject *win, PyObject *obj, long attr, +PyCurses_ConvertToCell(PyCursesWindowObject *win, PyObject *obj, attr_t attr, int attr_given, const char *funcname, chtype *pch, cchar_t *pwc) { @@ -1835,7 +1835,7 @@ _curses.window.addch Character to add. [ - attr: long + attr: attr Attributes for the character. ] / @@ -1851,8 +1851,8 @@ current settings for the window object. static PyObject * _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int group_right_1, - long attr) -/*[clinic end generated code: output=00f4c37af3378f45 input=ab196a1dac3d354c]*/ + attr_t attr) +/*[clinic end generated code: output=3306e15a7059998f input=0a09ecdd04aa0a2d]*/ { int coordinates_group = group_left_1; int rtn; @@ -1908,7 +1908,7 @@ _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1, #endif static int -curses_wattrset(PyCursesWindowObject *self, long attr, const char *funcname) +curses_wattrset(PyCursesWindowObject *self, attr_t attr, const char *funcname) { if (wattrset(self->win, attr) == ERR) { curses_window_set_error(self, "wattrset", funcname); @@ -1931,7 +1931,7 @@ _curses.window.addstr String to add. [ - attr: long + attr: attr Attributes for characters. ] / @@ -1947,8 +1947,8 @@ current settings for the window object. static PyObject * _curses_window_addstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int group_right_1, - long attr) -/*[clinic end generated code: output=65a928ea85ff3115 input=ff6cbb91448a22a3]*/ + attr_t attr) +/*[clinic end generated code: output=4942cdb202012076 input=0202b09895bcb472]*/ { int rtn; int strtype; @@ -2046,7 +2046,7 @@ _curses.window.addnstr Maximal number of characters. [ - attr: long + attr: attr Attributes for characters. ] / @@ -2062,8 +2062,8 @@ current settings for the window object. static PyObject * _curses_window_addnstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int n, - int group_right_1, long attr) -/*[clinic end generated code: output=6d21cee2ce6876d9 input=72718415c2744a2a]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=356ce38504dabf1d input=147405505606cd08]*/ { int rtn; int strtype; @@ -2146,7 +2146,7 @@ _curses.window.bkgd ch: object Background character. [ - attr: long + attr: attr Background attributes. ] / @@ -2156,8 +2156,8 @@ Set the background property of the window. static PyObject * _curses_window_bkgd_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr) -/*[clinic end generated code: output=73cb11ecca59612f input=a2129c1b709db432]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=4dc2599da3afa46a input=7aee8008ff8066a5]*/ { chtype bkgd; #ifdef HAVE_NCURSESW @@ -2183,15 +2183,15 @@ _curses_window_bkgd_impl(PyCursesWindowObject *self, PyObject *ch, /*[clinic input] _curses.window.attroff - attr: long + attr: attr / Remove attribute attr from the "background" set. [clinic start generated code]*/ static PyObject * -_curses_window_attroff_impl(PyCursesWindowObject *self, long attr) -/*[clinic end generated code: output=8a2fcd4df682fc64 input=786beedf06a7befe]*/ +_curses_window_attroff_impl(PyCursesWindowObject *self, attr_t attr) +/*[clinic end generated code: output=27c9e77df32fa5d3 input=a22d4035e962e9a7]*/ { int rtn = wattroff(self->win, (attr_t)attr); return curses_window_check_err(self, rtn, "wattroff", "attroff"); @@ -2200,15 +2200,15 @@ _curses_window_attroff_impl(PyCursesWindowObject *self, long attr) /*[clinic input] _curses.window.attron - attr: long + attr: attr / Add attribute attr to the "background" set. [clinic start generated code]*/ static PyObject * -_curses_window_attron_impl(PyCursesWindowObject *self, long attr) -/*[clinic end generated code: output=7afea43b237fa870 input=b57f824e1bf58326]*/ +_curses_window_attron_impl(PyCursesWindowObject *self, attr_t attr) +/*[clinic end generated code: output=150ff7c387068cc7 input=361b6389f4d08681]*/ { int rtn = wattron(self->win, (attr_t)attr); return curses_window_check_err(self, rtn, "wattron", "attron"); @@ -2217,15 +2217,15 @@ _curses_window_attron_impl(PyCursesWindowObject *self, long attr) /*[clinic input] _curses.window.attrset - attr: long + attr: attr / Set the "background" set of attributes. [clinic start generated code]*/ static PyObject * -_curses_window_attrset_impl(PyCursesWindowObject *self, long attr) -/*[clinic end generated code: output=84e379bff20c0433 input=42e400c0d0154ab5]*/ +_curses_window_attrset_impl(PyCursesWindowObject *self, attr_t attr) +/*[clinic end generated code: output=1b57b2a512603eb0 input=af748b1c18e35c34]*/ { int rtn = wattrset(self->win, (attr_t)attr); return curses_window_check_err(self, rtn, "wattrset", "attrset"); @@ -2356,7 +2356,7 @@ _curses.window.bkgdset ch: object Background character. [ - attr: long + attr: attr Background attributes. ] / @@ -2366,8 +2366,8 @@ Set the window's background. static PyObject * _curses_window_bkgdset_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr) -/*[clinic end generated code: output=3c32f2de5685a482 input=1f0811b24af821ca]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=32f5117c9e45422a input=64cf7cd3562b379b]*/ { chtype bkgd; #ifdef HAVE_NCURSESW @@ -2729,7 +2729,7 @@ _curses.window.echochar Character to add. [ - attr: long + attr: attr Attributes for the character. ] / @@ -2739,8 +2739,8 @@ Add character ch with attribute attr, and refresh. static PyObject * _curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr) -/*[clinic end generated code: output=f42da9e200c935e5 input=26e16855ec1b0e78]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=ab03afa580aa6a2a input=cd74c42aadcc7e30]*/ { chtype ch_; #ifdef HAVE_NCURSESW @@ -3167,7 +3167,7 @@ _curses.window.hline Line length. [ - attr: long + attr: attr Attributes for the characters. ] / @@ -3178,8 +3178,8 @@ Display a horizontal line. static PyObject * _curses_window_hline_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int n, - int group_right_1, long attr) -/*[clinic end generated code: output=c00d489d61fc9eef input=924f8c28521bc2ec]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=2c7489b8bd10c446 input=5d9f72ccba73975c]*/ { chtype ch_; #ifdef HAVE_NCURSESW @@ -3223,7 +3223,7 @@ _curses.window.insch Character to insert. [ - attr: long + attr: attr Attributes for the character. ] / @@ -3237,8 +3237,8 @@ right, with the rightmost characters on the line being lost. static PyObject * _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int group_right_1, - long attr) -/*[clinic end generated code: output=ade8cfe3a3bf3e34 input=47d2989159ae6ca7]*/ + attr_t attr) +/*[clinic end generated code: output=9d2576c0d8d982c4 input=f76641d529dbd8af]*/ { int rtn; chtype ch_ = 0; @@ -3571,7 +3571,7 @@ _curses.window.insstr String to insert. [ - attr: long + attr: attr Attributes for characters. ] / @@ -3588,8 +3588,8 @@ moving to y, x, if specified). static PyObject * _curses_window_insstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int group_right_1, - long attr) -/*[clinic end generated code: output=c259a5265ad0b777 input=dbfbdd3892155ea6]*/ + attr_t attr) +/*[clinic end generated code: output=2c8ed843880619ab input=f4a9d26b270058c2]*/ { int rtn; int strtype; @@ -3683,7 +3683,7 @@ _curses.window.insnstr Maximal number of characters. [ - attr: long + attr: attr Attributes for characters. ] / @@ -3701,8 +3701,8 @@ does not change (after moving to y, x, if specified). static PyObject * _curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int n, - int group_right_1, long attr) -/*[clinic end generated code: output=971a32ea6328ec8b input=fd0a9b65b84b385f]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=4895829689f3bdd2 input=7412feb3910276bf]*/ { int rtn; int strtype; @@ -4263,7 +4263,7 @@ _curses.window.vline Line length. [ - attr: long + attr: attr Attributes for the character. ] / @@ -4274,8 +4274,8 @@ Display a vertical line. static PyObject * _curses_window_vline_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int n, - int group_right_1, long attr) -/*[clinic end generated code: output=287ad1cc8982217f input=1d4aa27ff0309bbc]*/ + int group_right_1, attr_t attr) +/*[clinic end generated code: output=18efd3ea37bb04f6 input=e8678752623197a1]*/ { chtype ch_; #ifdef HAVE_NCURSESW @@ -5228,7 +5228,20 @@ _curses_color_pair_impl(PyObject *module, int pair_number) PyCursesStatefulInitialised(module); PyCursesStatefulInitialisedColor(module); - return PyLong_FromLong(COLOR_PAIR(pair_number)); + /* COLOR_PAIR() packs the pair into a limited field; a pair too large to be + recovered by its inverse PAIR_NUMBER() would be masked to a different + one. Reject pairs that do not round-trip (this assumes only that the two + macros are inverses). color_set()/attr_set()/complexchar can still + display larger pairs. */ + chtype attr = COLOR_PAIR(pair_number); + if (pair_number < 0 || PAIR_NUMBER(attr) != pair_number) { + PyErr_Format(PyExc_OverflowError, + "color pair %d does not fit in a chtype " + "(color_pair() can encode only pairs 0 to %d)", + pair_number, (int)PAIR_NUMBER(A_COLOR)); + return NULL; + } + return PyLong_FromLong(attr); } /*[clinic input] diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h index fc48af636346c27..1bd0d8f70bec14c 100644 --- a/Modules/clinic/_cursesmodule.c.h +++ b/Modules/clinic/_cursesmodule.c.h @@ -220,7 +220,7 @@ PyDoc_STRVAR(_curses_window_addch__doc__, static PyObject * _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int group_right_1, - long attr); + attr_t attr); static PyObject * _curses_window_addch(PyObject *self, PyObject *args) @@ -231,7 +231,7 @@ _curses_window_addch(PyObject *self, PyObject *args) int x = 0; PyObject *ch; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -240,7 +240,7 @@ _curses_window_addch(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:addch", &ch, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:addch", &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -252,7 +252,7 @@ _curses_window_addch(PyObject *self, PyObject *args) group_left_1 = 1; break; case 4: - if (!PyArg_ParseTuple(args, "iiOl:addch", &y, &x, &ch, &attr)) { + if (!PyArg_ParseTuple(args, "iiOO&:addch", &y, &x, &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -292,7 +292,7 @@ PyDoc_STRVAR(_curses_window_addstr__doc__, static PyObject * _curses_window_addstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int group_right_1, - long attr); + attr_t attr); static PyObject * _curses_window_addstr(PyObject *self, PyObject *args) @@ -303,7 +303,7 @@ _curses_window_addstr(PyObject *self, PyObject *args) int x = 0; PyObject *str; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -312,7 +312,7 @@ _curses_window_addstr(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:addstr", &str, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:addstr", &str, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -324,7 +324,7 @@ _curses_window_addstr(PyObject *self, PyObject *args) group_left_1 = 1; break; case 4: - if (!PyArg_ParseTuple(args, "iiOl:addstr", &y, &x, &str, &attr)) { + if (!PyArg_ParseTuple(args, "iiOO&:addstr", &y, &x, &str, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -366,7 +366,7 @@ PyDoc_STRVAR(_curses_window_addnstr__doc__, static PyObject * _curses_window_addnstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int n, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_addnstr(PyObject *self, PyObject *args) @@ -378,7 +378,7 @@ _curses_window_addnstr(PyObject *self, PyObject *args) PyObject *str; int n; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -387,7 +387,7 @@ _curses_window_addnstr(PyObject *self, PyObject *args) } break; case 3: - if (!PyArg_ParseTuple(args, "Oil:addnstr", &str, &n, &attr)) { + if (!PyArg_ParseTuple(args, "OiO&:addnstr", &str, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -399,7 +399,7 @@ _curses_window_addnstr(PyObject *self, PyObject *args) group_left_1 = 1; break; case 5: - if (!PyArg_ParseTuple(args, "iiOil:addnstr", &y, &x, &str, &n, &attr)) { + if (!PyArg_ParseTuple(args, "iiOiO&:addnstr", &y, &x, &str, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -429,7 +429,7 @@ PyDoc_STRVAR(_curses_window_bkgd__doc__, static PyObject * _curses_window_bkgd_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_bkgd(PyObject *self, PyObject *args) @@ -437,7 +437,7 @@ _curses_window_bkgd(PyObject *self, PyObject *args) PyObject *return_value = NULL; PyObject *ch; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -446,7 +446,7 @@ _curses_window_bkgd(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:bkgd", &ch, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:bkgd", &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -471,16 +471,15 @@ PyDoc_STRVAR(_curses_window_attroff__doc__, {"attroff", (PyCFunction)_curses_window_attroff, METH_O, _curses_window_attroff__doc__}, static PyObject * -_curses_window_attroff_impl(PyCursesWindowObject *self, long attr); +_curses_window_attroff_impl(PyCursesWindowObject *self, attr_t attr); static PyObject * _curses_window_attroff(PyObject *self, PyObject *arg) { PyObject *return_value = NULL; - long attr; + attr_t attr; - attr = PyLong_AsLong(arg); - if (attr == -1 && PyErr_Occurred()) { + if (!attr_converter(arg, &attr)) { goto exit; } return_value = _curses_window_attroff_impl((PyCursesWindowObject *)self, attr); @@ -499,16 +498,15 @@ PyDoc_STRVAR(_curses_window_attron__doc__, {"attron", (PyCFunction)_curses_window_attron, METH_O, _curses_window_attron__doc__}, static PyObject * -_curses_window_attron_impl(PyCursesWindowObject *self, long attr); +_curses_window_attron_impl(PyCursesWindowObject *self, attr_t attr); static PyObject * _curses_window_attron(PyObject *self, PyObject *arg) { PyObject *return_value = NULL; - long attr; + attr_t attr; - attr = PyLong_AsLong(arg); - if (attr == -1 && PyErr_Occurred()) { + if (!attr_converter(arg, &attr)) { goto exit; } return_value = _curses_window_attron_impl((PyCursesWindowObject *)self, attr); @@ -527,16 +525,15 @@ PyDoc_STRVAR(_curses_window_attrset__doc__, {"attrset", (PyCFunction)_curses_window_attrset, METH_O, _curses_window_attrset__doc__}, static PyObject * -_curses_window_attrset_impl(PyCursesWindowObject *self, long attr); +_curses_window_attrset_impl(PyCursesWindowObject *self, attr_t attr); static PyObject * _curses_window_attrset(PyObject *self, PyObject *arg) { PyObject *return_value = NULL; - long attr; + attr_t attr; - attr = PyLong_AsLong(arg); - if (attr == -1 && PyErr_Occurred()) { + if (!attr_converter(arg, &attr)) { goto exit; } return_value = _curses_window_attrset_impl((PyCursesWindowObject *)self, attr); @@ -715,7 +712,7 @@ PyDoc_STRVAR(_curses_window_bkgdset__doc__, static PyObject * _curses_window_bkgdset_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_bkgdset(PyObject *self, PyObject *args) @@ -723,7 +720,7 @@ _curses_window_bkgdset(PyObject *self, PyObject *args) PyObject *return_value = NULL; PyObject *ch; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -732,7 +729,7 @@ _curses_window_bkgdset(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:bkgdset", &ch, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:bkgdset", &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1005,7 +1002,7 @@ PyDoc_STRVAR(_curses_window_echochar__doc__, static PyObject * _curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_echochar(PyObject *self, PyObject *args) @@ -1013,7 +1010,7 @@ _curses_window_echochar(PyObject *self, PyObject *args) PyObject *return_value = NULL; PyObject *ch; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -1022,7 +1019,7 @@ _curses_window_echochar(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:echochar", &ch, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:echochar", &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1339,7 +1336,7 @@ PyDoc_STRVAR(_curses_window_hline__doc__, static PyObject * _curses_window_hline_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int n, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_hline(PyObject *self, PyObject *args) @@ -1351,7 +1348,7 @@ _curses_window_hline(PyObject *self, PyObject *args) PyObject *ch; int n; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -1360,7 +1357,7 @@ _curses_window_hline(PyObject *self, PyObject *args) } break; case 3: - if (!PyArg_ParseTuple(args, "Oil:hline", &ch, &n, &attr)) { + if (!PyArg_ParseTuple(args, "OiO&:hline", &ch, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1372,7 +1369,7 @@ _curses_window_hline(PyObject *self, PyObject *args) group_left_1 = 1; break; case 5: - if (!PyArg_ParseTuple(args, "iiOil:hline", &y, &x, &ch, &n, &attr)) { + if (!PyArg_ParseTuple(args, "iiOiO&:hline", &y, &x, &ch, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1410,7 +1407,7 @@ PyDoc_STRVAR(_curses_window_insch__doc__, static PyObject * _curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int group_right_1, - long attr); + attr_t attr); static PyObject * _curses_window_insch(PyObject *self, PyObject *args) @@ -1421,7 +1418,7 @@ _curses_window_insch(PyObject *self, PyObject *args) int x = 0; PyObject *ch; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -1430,7 +1427,7 @@ _curses_window_insch(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:insch", &ch, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:insch", &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1442,7 +1439,7 @@ _curses_window_insch(PyObject *self, PyObject *args) group_left_1 = 1; break; case 4: - if (!PyArg_ParseTuple(args, "iiOl:insch", &y, &x, &ch, &attr)) { + if (!PyArg_ParseTuple(args, "iiOO&:insch", &y, &x, &ch, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1529,7 +1526,7 @@ PyDoc_STRVAR(_curses_window_insstr__doc__, static PyObject * _curses_window_insstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int group_right_1, - long attr); + attr_t attr); static PyObject * _curses_window_insstr(PyObject *self, PyObject *args) @@ -1540,7 +1537,7 @@ _curses_window_insstr(PyObject *self, PyObject *args) int x = 0; PyObject *str; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -1549,7 +1546,7 @@ _curses_window_insstr(PyObject *self, PyObject *args) } break; case 2: - if (!PyArg_ParseTuple(args, "Ol:insstr", &str, &attr)) { + if (!PyArg_ParseTuple(args, "OO&:insstr", &str, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1561,7 +1558,7 @@ _curses_window_insstr(PyObject *self, PyObject *args) group_left_1 = 1; break; case 4: - if (!PyArg_ParseTuple(args, "iiOl:insstr", &y, &x, &str, &attr)) { + if (!PyArg_ParseTuple(args, "iiOO&:insstr", &y, &x, &str, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1605,7 +1602,7 @@ PyDoc_STRVAR(_curses_window_insnstr__doc__, static PyObject * _curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *str, int n, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_insnstr(PyObject *self, PyObject *args) @@ -1617,7 +1614,7 @@ _curses_window_insnstr(PyObject *self, PyObject *args) PyObject *str; int n; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -1626,7 +1623,7 @@ _curses_window_insnstr(PyObject *self, PyObject *args) } break; case 3: - if (!PyArg_ParseTuple(args, "Oil:insnstr", &str, &n, &attr)) { + if (!PyArg_ParseTuple(args, "OiO&:insnstr", &str, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -1638,7 +1635,7 @@ _curses_window_insnstr(PyObject *self, PyObject *args) group_left_1 = 1; break; case 5: - if (!PyArg_ParseTuple(args, "iiOil:insnstr", &y, &x, &str, &n, &attr)) { + if (!PyArg_ParseTuple(args, "iiOiO&:insnstr", &y, &x, &str, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -2216,7 +2213,7 @@ PyDoc_STRVAR(_curses_window_vline__doc__, static PyObject * _curses_window_vline_impl(PyCursesWindowObject *self, int group_left_1, int y, int x, PyObject *ch, int n, - int group_right_1, long attr); + int group_right_1, attr_t attr); static PyObject * _curses_window_vline(PyObject *self, PyObject *args) @@ -2228,7 +2225,7 @@ _curses_window_vline(PyObject *self, PyObject *args) PyObject *ch; int n; int group_right_1 = 0; - long attr = 0; + attr_t attr; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -2237,7 +2234,7 @@ _curses_window_vline(PyObject *self, PyObject *args) } break; case 3: - if (!PyArg_ParseTuple(args, "Oil:vline", &ch, &n, &attr)) { + if (!PyArg_ParseTuple(args, "OiO&:vline", &ch, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -2249,7 +2246,7 @@ _curses_window_vline(PyObject *self, PyObject *args) group_left_1 = 1; break; case 5: - if (!PyArg_ParseTuple(args, "iiOil:vline", &y, &x, &ch, &n, &attr)) { + if (!PyArg_ParseTuple(args, "iiOiO&:vline", &y, &x, &ch, &n, attr_converter, &attr)) { goto exit; } group_right_1 = 1; @@ -5400,4 +5397,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored #ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */ -/*[clinic end generated code: output=7940d7d4775b58fd input=a9049054013a1b77]*/ +/*[clinic end generated code: output=a19d96f9c94cf7eb input=a9049054013a1b77]*/ From e886f9325bebfb60288ddea1b8450a4e59801620 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 26 Jun 2026 20:42:17 +0300 Subject: [PATCH 2/2] gh-152275: Initialize the optional attr argument to zero For methods where attr is an optional argument (addch(), addstr(), insch(), ...) the attr converter left the attr_t variable uninitialized when the argument was omitted, so a garbage value could be applied as character attributes. Give the converter c_ignored_default, as the builtin numeric converters have, so the option-group variable is initialized to zero. Co-Authored-By: Claude Opus 4.8 --- Modules/_cursesmodule.c | 3 ++- Modules/clinic/_cursesmodule.c.h | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index ac9106d52693bab..f0d31e8e680b0e1 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -931,8 +931,9 @@ attr_converter(PyObject *arg, void *ptr) class attr_converter(CConverter): type = 'attr_t' converter = 'attr_converter' + c_ignored_default = '0' [python start generated code]*/ -/*[python end generated code: output=da39a3ee5e6b4b0d input=6132d3d99d3ec25a]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=57b994c97cbd5e80]*/ #ifdef HAVE_NCURSESW /* -------------------------------------------------------*/ diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h index 5d7778c317a963e..f4911e80e85838e 100644 --- a/Modules/clinic/_cursesmodule.c.h +++ b/Modules/clinic/_cursesmodule.c.h @@ -231,7 +231,7 @@ _curses_window_addch(PyObject *self, PyObject *args) int x = 0; PyObject *ch; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -303,7 +303,7 @@ _curses_window_addstr(PyObject *self, PyObject *args) int x = 0; PyObject *str; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -378,7 +378,7 @@ _curses_window_addnstr(PyObject *self, PyObject *args) PyObject *str; int n; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -437,7 +437,7 @@ _curses_window_bkgd(PyObject *self, PyObject *args) PyObject *return_value = NULL; PyObject *ch; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -720,7 +720,7 @@ _curses_window_bkgdset(PyObject *self, PyObject *args) PyObject *return_value = NULL; PyObject *ch; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -1032,7 +1032,7 @@ _curses_window_echochar(PyObject *self, PyObject *args) PyObject *return_value = NULL; PyObject *ch; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -1370,7 +1370,7 @@ _curses_window_hline(PyObject *self, PyObject *args) PyObject *ch; int n; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -1440,7 +1440,7 @@ _curses_window_insch(PyObject *self, PyObject *args) int x = 0; PyObject *ch; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -1559,7 +1559,7 @@ _curses_window_insstr(PyObject *self, PyObject *args) int x = 0; PyObject *str; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 1: @@ -1636,7 +1636,7 @@ _curses_window_insnstr(PyObject *self, PyObject *args) PyObject *str; int n; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -2247,7 +2247,7 @@ _curses_window_vline(PyObject *self, PyObject *args) PyObject *ch; int n; int group_right_1 = 0; - attr_t attr; + attr_t attr = 0; switch (PyTuple_GET_SIZE(args)) { case 2: @@ -5478,4 +5478,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored #ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF #endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */ -/*[clinic end generated code: output=f76340c98e75e824 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3ddb2b89cc5f7f07 input=a9049054013a1b77]*/