Skip to content

curses: buffered reads assume ncurses' reading of the size argument #154855

Description

@serhiy-storchaka

Bug report

window.instr(), in_wstr(), in_wchstr(), getstr() and get_wstr() pass n to the library and allocate n + 1 elements for the result. That encodes ncurses' reading of n: it stores n characters and adds a terminator, writing n + 1 elements in all.

X/Open does not say that. For innstr() it says the functions "store at most n bytes in the string pointed to by str", for innwstr() "at most n characters in the array", for in_wchnstr() "at most n cchar_t elements" -- and none of the three mentions a terminating null at all. NetBSD curses reads that limit as covering the terminator, so it stores n - 1 characters, and every one of these methods returns one character too few there. Window containing "abcde", n = 5: ncurses gives "abcde", NetBSD gives "abcd".

Both readings are defensible, and NetBSD documents its own (curses_inch.3: read "until either n - 1 characters are read"). The problem is ours: we ask for n and assume ncurses' answer.

Asking a library that counts the terminator for n + 1 gives the n characters ncurses returns:

#if defined(NCURSES_VERSION) || defined(PDCURSES)
#  define CURSES_STR_EXTRA  0
#else
#  define CURSES_STR_EXTRA  1
#endif

PDCurses follows ncurses (measured), so only the unknown case is bumped, and the buffers become n + 1 + CURSES_STR_EXTRA so that such a library cannot overrun them. Note that the extra element does not always add exactly one character: X/Open requires these functions to store only whole characters ("If the array is not large enough to contain any complete characters, the function fails"), so with multi-byte or combining characters the last cell is admitted as a whole or not at all.

test_curses against NetBSD native curses goes from 34 to 22 failures on main; builds against ncurses are unchanged.

The strlen() side of this is gh-154781: in_wstr() scans for a terminator that winnwstr() need not have written. X/Open supports the fix suggested there -- it promises no terminator for the innstr() family and specifies that these functions return the number of characters actually read, so the return value is the length. The same holds for instr(), which gh-154781 reports as unaffected: it works only because both libraries happen to terminate. getstr() and get_wstr() are the opposite case -- getstr() "is then terminated with a null byte", while getnstr() returns OK, not a count -- so they must keep scanning for the terminator.

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.13bugs and security fixes3.14bugs and security fixes3.15pre-release feature fixes, bugs and security fixes3.16new features, bugs and security fixesextension-modulesC modules in the Modules dirtype-bugAn unexpected behavior, bug, or error

    Projects

    Status
    Bugs

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions