NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
lib/60513: lib/curses: wenclose() accepts coordinates one row/column outside the window
>Number: 60513
>Category: lib
>Synopsis: curses: wenclose() is off by one, so wmouse_trafo() transforms coordinates outside the window
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: lib-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Tue Jul 28 15:00:01 +0000 2026
>Originator: Serhiy Storchaka
>Release: NetBSD 10.1
>Environment:
System: NetBSD 10.1 amd64 (GENERIC), libcurses.so.9.1
>Description:
wenclose() returns true for the row just below a window and for the
column just to its right, so a coordinate outside the window is
reported as enclosed by it. wmouse_trafo() tests the coordinates with
wenclose(), so it transforms and accepts those coordinates too instead
of returning false.
curses_mouse.3 says wenclose() "returns true if the screen relative x
and y co-ordinates are enclosed by the window win, otherwise false",
and the row below a window is not enclosed by it. ncurses, whose
extension this is, documents the same and returns false there.
The cause is a field with a different meaning in the two
implementations. The test in libcurses/mouse.c is the one ncurses
uses in ncurses/base/lib_mouse.c:
/* NetBSD, libcurses/mouse.c */
if (y < win->begy || y > win->begy + win->maxy ||
x < win->begx || x > win->begx + win->maxx)
return false;
/* ncurses, ncurses/base/lib_mouse.c */
result = ((win->_begy <= y && win->_begx <= x &&
(win->_begx + win->_maxx) >= x &&
(win->_begy + win->_maxy) >= y) ? TRUE : FALSE);
but ncurses' _maxy is the largest valid coordinate ("maximums of x and
y, NOT window size" in its curses.h), while NetBSD's maxy is the
number of lines (newwin.c: "win->maxy = nlines"). So begy + maxy is
the last row in ncurses and one past the last row here. The rest of
libcurses uses maxy as a count, for example chgat.c "if (x >=
win->maxx || y >= win->maxy)" and move.c "if (x > win->maxx || y >=
win->maxy)"; mouse.c is the only place that reads it the ncurses way.
The code is the same in -current (mouse.c has not changed since the
NULL-pointer checks of 2024-12-23).
>How-To-Repeat:
/* cc wenclose_off.c -lcurses && TERM=xterm ./a.out */
#include <curses.h>
#include <stdio.h>
int main(void) {
FILE *f = fopen("/dev/null", "w+");
SCREEN *sp = newterm("xterm", f, f);
/* 5 rows x 15 columns at (2,5): rows 2..6, columns 5..19 */
WINDOW *w = newwin(5, 15, 2, 5);
printf("wenclose(win, 6, 19) = %d (expected 1)\n", wenclose(w, 6, 19));
printf("wenclose(win, 7, 19) = %d (expected 0)\n", wenclose(w, 7, 19));
printf("wenclose(win, 6, 20) = %d (expected 0)\n", wenclose(w, 6, 20));
endwin(); delscreen(sp);
return 0;
}
prints
wenclose(win, 6, 19) = 1 (expected 1)
wenclose(win, 7, 19) = 1 (expected 0)
wenclose(win, 6, 20) = 1 (expected 0)
The same program against ncurses 6.6 prints 1, 0, 0.
Found through Python's curses module, whose test suite checks these
boundaries (test_curses: test_enclose and test_mouse_trafo).
>Fix:
--- a/libcurses/mouse.c
+++ b/libcurses/mouse.c
@@
- if (y < win->begy || y > win->begy + win->maxy ||
- x < win->begx || x > win->begx + win->maxx)
+ if (y < win->begy || y >= win->begy + win->maxy ||
+ x < win->begx || x >= win->begx + win->maxx)
return false;
return true;
so that the comparison uses maxy/maxx as the counts they are here.
This should make the reproducer print 1, 0, 0 and wmouse_trafo()
reject the same coordinates; I have not rebuilt libcurses to confirm
it.
>Unformatted:
Home |
Main Index |
Thread Index |
Old Index