Server aborts (fatalx "x too big") in tty_clamp_area on resize while a pane redraws
Issue description
The server calls fatalx() and exits, killing every session at once, from the assertion at the end of tty_clamp_area() in tty.c:
if (*rx > nx)
fatalx("%s: x too big, %u > %u", __func__, *rx, nx);
It happens while a pane is redrawing (a full-screen curses program clearing its screen) at the same moment the window is resized. In my case the window is attached by two clients of different sizes, so the window is bigger than one client (TTY_CTX_WINDOW_BIGGER), and a compositor keybind resizes one client rapidly.
The code is identical on master and on the 3.7c tag, so this is not already fixed.
Reproduced on
master (verified tty_clamp_area is byte-identical to the 3.7c build I run).
tmux version
tmux 3.7c (Arch/CachyOS package tmux 3.7_c-1.1).
TERM
- Outside tmux:
xterm-ghostty - Inside tmux:
tmux-256color
Platform
Linux (CachyOS, kernel 7.2.x), x86-64. Wayland compositor (Hyprland). The tmux server runs under a systemd user service; clients attach from terminal windows.
Backtrace
Captured with gdb attached to the live server, breakpoint on exit, symbols from the distribution debug package. Innermost first:
#0 exit
#1 fatalx
#2 tty_clamp_area tty.c:1284 ("x too big") [inlined into tty_clear_pane_area]
#3 tty_clear_pane_area
#4 screen_write_clearscreen
#5 input_csi_dispatch
#6 input_parse
#7 input_parse_buffer
#8 window_pane_read_callback
... bufferevent_run_readcb_ -> event_base_loop (libevent)
#9 proc_loop
So: a pane emits a clear-screen sequence; screen_write_clearscreen draws it straight to the client through tty_clear_pane_area -> tty_clamp_area; the clamp computes *rx > nx and the server aborts.
A possible lead
tty_clamp_area computes its offset unsigned:
u_int xoff = ctx->rxoff + px, yoff = ctx->ryoff + py;
...
if (xoff >= ctx->wox && xoff + nx <= ctx->wox + ctx->wsx) {
The sibling tty_clamp_line computes the same offset signed and casts the window fields:
int xoff = ctx->rxoff + px;
...
if (xoff >= (int)ctx->wox && xoff + nx <= ctx->wox + ctx->wsx) {
ctx->rxoff / ctx->ryoff are int (they can be negative). In tty_clamp_area a negative offset, or a branch chosen from rxoff while the width is computed from ctx->xoff, makes *rx exceed nx, and the assertion aborts the whole server rather than skipping the draw.
What I can provide
I have a machine that hits this every few days under normal use, plus a standing gdb trap that captures the backtrace and registers on each occurrence. I do not yet have a minimal reproducer. I am happy to run an instrumented build, add logging around tty_clamp_area, or test a patch. If a defensive clamp (skip the draw instead of fatalx) is acceptable as an interim guard, I can send it.
Source: tmux/tmux