Re: winsch() recurses without bound when wcwidth() returns -1

Thomas Dickey <[email protected]> Thu, 30 Jul 2026 15:31:53 -0400
Newsgroups gmane.comp.lib.ncurses.bugs
Message-ID <[email protected]>
--Qhqucl5WIQQcm4FU
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

On Thu, Jul 30, 2026 at 07:19:10PM +0300, Serhiy Storchaka wrote:
> ncurses/widechar/lib_ins_wch.c:
>=20
>     int cells =3D _nc_wacs_width(CharOf(CHDEREF(wch)));
>=20
>     if (cells < 0) {
> 	code =3D winsch(win, (chtype) CharOf(CHDEREF(wch)));
>     } else {
>=20
> winsch() calls _nc_insert_ch(), which passes a non-control character
> to its wide branch, where _nc_build_wch() builds the same character
> and gives it back to _nc_insert_wch(), which finds the same negative
> width:
>=20
>     frame #0: _nc_build_wch + 197
>     frame #1: _nc_insert_ch + 395
>     frame #2: winsch + 41
>     frame #3: _nc_insert_ch + 415
>     frame #4: winsch + 41
>     ...
>=20
> To repeat:
>=20
>     /* cc winsch_recurse.c -lncursesw -o winsch_recurse
>        LC_ALL=3Den_US.ISO8859-15 TERM=3Dxterm ./winsch_recurse 0xA4 */
>     #include <curses.h>
>     #include <locale.h>
>     #include <stdio.h>
>=20
>     int main(void)
>     {
>         FILE *f =3D fopen("/dev/null", "w+");
>=20
>         setlocale(LC_ALL, "");
>         newterm("xterm", f, f);
>         winsch(stdscr, (chtype) 0xA4);
>         fprintf(stderr, "ok\n");
>         endwin();
>         return 0;
>     }
>=20
> It takes a byte that is not a control character and whose wcwidth() is
> negative.  FreeBSD 15.1 has 57 of them in en_US.ISO8859-15 and 54 in
> uk_UA.KOI8-U, because there wchar_t holds the locale value rather than
> a code point; each crashes with the system libncursesw.so.9
> (6.6.20251230) and with the 2026-07-19 snapshot.  On glibc such a byte
> does not seem to exist, and the same snapshot does not crash in any
> locale I tried.  UTF-8 locales are unaffected on both.

yes... the BSDs differ from glibc (iirc, {Free,Net,Open}BSD don't match
each other, either)

thanks
=20
> Found through Python's curses module, whose test suite calls insstr().
>=20
> A character that the locale cannot represent has no width to insert,
> so returning ERR ends the recursion:
>=20
> --- a/ncurses/widechar/lib_ins_wch.c
> +++ b/ncurses/widechar/lib_ins_wch.c
> @@
>      if (cells < 0) {
> -	code =3D winsch(win, (chtype) CharOf(CHDEREF(wch)));
> +	code =3D ERR;
>      } else {
>=20
> With that the reproducer returns for every byte and locale I tried,
> and a byte with a positive width is unaffected.  I do not know whether
> ERR is the right return there, or whether such a character should be
> treated as one cell.

probably it should be stored according to its representation with unctrl
(two cells) since this is similar to waddch.

--=20
Thomas E. Dickey <[email protected]>
https://invisible-island.net

--Qhqucl5WIQQcm4FU
Content-Type: application/pgp-signature; name="signature.asc"

-----BEGIN PGP SIGNATURE-----

iQGzBAABCgAdFiEEGYgtkt2kxADCLA1WzCr0RyFnvgMFAmprpqUACgkQzCr0RyFn
vgONnAwAqHPgljbpSEhudcrTfUma81BIDHmx9Q43/hoQ1DRlsCL2VzYJ/I3PgEAx
SLfUSAixIuIgkkWuzdOYwxlbsUKsJt8mXQP750vxPgK6ZS4RUFWjFxWH6h7ZjfCA
5zNfPNMTqGHhcCuaq9PGldj5M9riGexkH3z1qsKGEMN+KHAgLmnj7fnZ300a1mAK
lQP86kQ+Yz/j+pvDYK+CvBZmoB7PMpUFpRYNXcoTezPxRTbE7qay+lowjqAwrprE
iJVFEn432nu6/PQfPfjd7qEDmCC2WkwtHKhbUECNX5ChPC/6zHQ7WyDZmK6t4aSV
suuVQS9TascvaPX4FDMiaxqMcyPBc95wywu3d0WLnLO4/PAIz0igJPqPGrWsTTaW
+cnF2hUhRiIaxUhXyco8UsCpmwdtY/pPvsWXj82QPFQgGTkgPIt3PVbAHgIm9txj
DnAnSMvCcErJpbCSCj8GeAi9TxA3KKoljdeY6NP5NoFvpQIVlw5/gL1CjbVtWDL3
z0E78eTB
=D3sq
-----END PGP SIGNATURE-----

--Qhqucl5WIQQcm4FU--