ncurses 5 ABI compatibility mode can have spaces replaced with null bytes
Platon <[email protected]> Sun, 19 Jul 2026 14:54:54 +0100
| Newsgroups | gmane.comp.lib.ncurses.bugs |
|---|---|
| Message-ID | <[email protected]> |
Hi!
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=5).
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 (or was allowing) null bytes to mean "blank cell", but in ncurses 6 it doesn't work that way anymore.
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>
int main(void)
{
initscr();
start_color();
if (has_colors()) {
init_pair(6, 3, 0);
}
/* 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) = (long)(6 << 8);
wmove(stdscr, 0, 0);
waddnstr(stdscr, "A D O M", -1);
wrefresh(stdscr);
wgetch(stdscr);
endwin();
return 0;
}
/* compile with gcc repro.c -o repro /usr/lib/libncurses.so.5 */
```
This program will show "ADOM", without spaces between characters. That's because spaces become null bytes in the final output.
The main problem is in this (decompiled) line:
```
*(long *)((char *)stdscr + 0x10) = (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)
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 comes _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.
When ran with a "real" ncurses 5 library (I've managed to find one in Debian's libncurses5), this problem isn't manifesting - the entire 8 bytes are zero from the start.
I would appreciate any opinions on this situation.
Did I guess correctly about changes in handling of null bytes between ncurses 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 type field manually?
Is it possible to adjust ncurses 6 behavior to output null bytes as spaces when compiled with `--with-abi-version=5`, or will it be too difficult?
I'm on Arch Linux, x86_64.
The program in question is the ADOM game (one of the more famous roguelikes, I believe). Unfortunately, the sources are not available for it and last release happened in 2019, so simply recompiling the program for newer ncurses version isn't possible.