Re: ncurses 5 ABI compatibility mode can have spaces replaced with null bytes
Thomas Dickey <[email protected]> Sun, 19 Jul 2026 19:16:57 -0400
| Newsgroups | gmane.comp.lib.ncurses.bugs |
|---|---|
| Message-ID | <[email protected]> |
--YDQRr2p1JhIF0aQN
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
On Sun, Jul 19, 2026 at 02:54:54PM +0100, Platon wrote:
> Hi!
>=20
> I've encountered an interesting issue when running a program compiled for=
ncurses 5 when linked to ncurses 6 built in compatibility mode (--with-abi=
-version=3D5).
> If a program tries to output colored text, then all spaces between words =
on screen vanish. Looks like it's because internally ncurses 5 was using (o=
r was allowing) null bytes to mean "blank cell", but in ncurses 6 it doesn'=
t work that way anymore.
>=20
> There are no sources for this program, so I had to dig into assembler to =
figure out what was going on. I managed to make a reproducer that showcases=
the issue:
> ```
> #include <ncurses.h>
> #include <stdio.h>
>=20
> int main(void)
> {
> initscr();
>=20
> start_color();
> if (has_colors()) {
> init_pair(6, 3, 0);
> }
>=20
> /* I suspect it should have been something like `attron(COLOR_PAIR(6)=
)`, */
> /* but without access to source code it's hard to tell. */
> *(long *)((char *)stdscr + 0x10) =3D (long)(6 << 8);
use the source (ncurses.h declaration of WINDOW):
----------------------------------------------------------------------
struct _win_st
{
NCURSES_SIZE_T _cury, _curx; /* current cursor position */
/* window location and size */
NCURSES_SIZE_T _maxy, _maxx; /* maximums of x and y, NOT window size */
NCURSES_SIZE_T _begy, _begx; /* screen coords of upper-left-hand corner */
short _flags; /* window state flags */
/* attribute tracking */
attr_t _attrs; /* current attribute for non-space character */
chtype _bkgd; /* current background char/attribute pair */
----------------------------------------------------------------------=20
It looks as if your assignment is overwriting the background character,
which will do what you're describing.
Using the API, that's checked to ensure it's not null
----------------------------------------------------------------------
bkgd(3NCURSES) Library calls bkgd(3NCUR=
SES)
NAME
bkgdset, wbkgdset, bkgd, wbkgd, getbkgd - manipulate background =
of a
curses window of characters
SYNOPSIS
#include <curses.h>
int bkgd(chtype ch);
int wbkgd(WINDOW *win, chtype ch);
void bkgdset(chtype ch);
void wbkgdset(WINDOW *win, chtype ch);
chtype getbkgd(WINDOW *win);
----------------------------------------------------------------------
> wmove(stdscr, 0, 0);
> waddnstr(stdscr, "A D O M", -1);
> wrefresh(stdscr);
>=20
> wgetch(stdscr);
> endwin();
> return 0;
> }
> /* compile with gcc repro.c -o repro /usr/lib/libncurses.so.5 */
> ```
>=20
> This program will show "ADOM", without spaces between characters. That's =
because spaces become null bytes in the final output.
>=20
> The main problem is in this (decompiled) line:
> ```
> *(long *)((char *)stdscr + 0x10) =3D (long)(6 << 8);
> ```
> Here's the assembly for reference:
> ```
> 00298cd5 8b 05 d1 MOV EAX,dword ptr [DAT_008e69ac]
> dc 64 00
> 00298cdb c1 e0 04 SHL EAX,0x4
> 00298cde 01 c7 ADD EDI,EAX
> 00298ce0 48 63 ff MOVSXD RDI,EDI
> 00298ce3 48 c1 e7 08 SHL RDI,0x8
> 00298ce7 48 89 7a 10 MOV qword ptr [RDX + 0x10],RDI
> ```
> (DAT_008e69ac contains 0 here, EDI contains 6)
>=20
> According to the _win_st struct definition, it looks like offset 0x10 is =
_attrs field. However, _attrs field is only 4 bytes (I think), after it com=
es _bkgd field, which contains 0x20 on startup.
> And so writing 8 bytes to (stdscr + 0x10) overwrites the _bkgd with zeros=
, replacing space with null byte, which is used for output going forward.
>=20
> When ran with a "real" ncurses 5 library (I've managed to find one in Deb=
ian's libncurses5), this problem isn't manifesting - the entire 8 bytes are=
zero from the start.
>=20
> I would appreciate any opinions on this situation.
> Did I guess correctly about changes in handling of null bytes between ncu=
rses 5 and 6?
> Could this construct (writing 8 bytes to _attrs) be a result from normal =
ncurses 5 API usage, or did the program author access undocumented opaque t=
ype field manually?
> Is it possible to adjust ncurses 6 behavior to output null bytes as space=
s when compiled with `--with-abi-version=3D5`, or will it be too difficult?
>=20
> I'm on Arch Linux, x86_64.
> The program in question is the ADOM game (one of the more famous roguelik=
es, I believe). Unfortunately, the sources are not available for it and las=
t release happened in 2019, so simply recompiling the program for newer ncu=
rses version isn't possible.
>=20
>=20
--=20
Thomas E. Dickey <[email protected]>
https://invisible-island.net
--YDQRr2p1JhIF0aQN
Content-Type: application/pgp-signature; name="signature.asc"
-----BEGIN PGP SIGNATURE-----
iQGzBAABCgAdFiEEGYgtkt2kxADCLA1WzCr0RyFnvgMFAmpdWuUACgkQzCr0RyFn
vgOzbAwA8kFesJYGFHko0TE+1IBdbVSyo5ZHxBGiZT1uUipXIDbqzjKFSbY70IjY
LDjMaOBhDpRIlzdxCAUXytCVkwXSaT+FI7E5sj9cmS9mF5uuElxxIOJ6DNpChYFg
ywFHLzAlzHLXnb3MF0wvHbvqd1Fk2+4snow78WjiNdYNXxJMbVbcBhmyQZVvT9YT
7izCfjFHKuVavnGWdcYPvejDBfAW5o333VMgLaxl8rxyRz5AXxnMmTItkSBBM/9p
0Rr+iNWqaDRozoAmXuP6q4UJ5suwSG3YAxKx/9Ebr6F6qdYaQnOolrZbImY9Xve2
hpQ7TDPLD9ku1ti2CA6RLjXxJcUOZAOdsgV9LVxTUlAtW3V69lCHn0eGiT/p0Hh8
D8X+XQmozG78sqyqT7g/kS9IICvcI5e4d7j6E7BeXlqkIDj8dgrSMXTWN1pKTNaC
OHPYRIooYDwVC06vj7Gvv8juL8S5WtyjuFkr7AN387b5sfFAahJSN0GNk+TOMwxT
HpEAdy9o
=nyB4
-----END PGP SIGNATURE-----
--YDQRr2p1JhIF0aQN--