From c02f9326417c9aae6ac19ffd94276cc06a3c908e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 24 Sep 2026 18:49:35 +0530 Subject: [PATCH] Fix an out of bounds memory access when drawing a multi line character with cursor below bottom margin --- docs/changelog.rst | 6 ++++++ kitty/screen.c | 18 +++++++++++++----- kitty_tests/multicell.py | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2566bfde0..0f5dae0a0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -199,6 +199,12 @@ you use a decent Wayland compositor. Detailed list of changes ------------------------------------- +0.49.2 [future] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Fix an out-of-bounds memory access when drawing a multi-line text sized + character with the cursor below the bottom margin of the scroll region + 0.49.1 [2026-09-24] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/screen.c b/kitty/screen.c index b347c5a6f..908961599 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1568,11 +1568,19 @@ handle_fixed_width_multicell_command(Screen *self, CPUCell mcd, ListOfChars *lc) cell_set_chars(&mcd, self->text_cache, lc); move_cursor_past_multicell(self, width); if (height > 1) { - index_type available_height = self->margin_bottom - self->cursor->y + 1; - if (height > available_height) { - index_type extra_lines = height - available_height; - screen_scroll(self, extra_lines); - self->cursor->y -= extra_lines; + if (self->cursor->y <= self->margin_bottom) { + index_type available_height = self->margin_bottom - self->cursor->y + 1; + if (height > available_height) { + index_type extra_lines = height - available_height; + screen_scroll(self, extra_lines); + self->cursor->y -= extra_lines; + } + } else { + // Cursor is below the scroll region, which cannot be scrolled, so + // move the cursor up until the cell fits on screen. height <= + // max_height <= lines so this cannot underflow. + index_type available_height = self->lines - self->cursor->y; + if (height > available_height) self->cursor->y -= height - available_height; } } if (self->modes.mIRM) { diff --git a/kitty_tests/multicell.py b/kitty_tests/multicell.py index c9308f7e3..2a62827e9 100644 --- a/kitty_tests/multicell.py +++ b/kitty_tests/multicell.py @@ -25,6 +25,21 @@ class TestMulticell(BaseTest): s.draw('好好') # two 2-cell wide chars covering columns 0..3 s.test_draw_overlay_line('xy', xstart, 0) # would SIGSEGV without fix + def test_multicell_below_bottom_margin(self): + # Regression: with the cursor below the bottom margin, the available + # height computation underflowed and cells were written past the end + # of the line buffer. + s = self.create_screen(cols=10, lines=6) + s.set_margins(1, 3) # 1-based indexing + s.cursor.x, s.cursor.y = 0, s.lines - 1 + multicell(s, 'A', scale=2) + self.ae((s.cursor.x, s.cursor.y), (2, s.lines - 2)) + for y in (s.lines - 2, s.lines - 1): + for x in range(2): + c = s.cpu_cells(y, x) + self.assertIsNotNone(c['mcd']) + self.ae((c['x'], c['y'], c['mcd']['scale']), (x, y - s.lines + 2, 2)) + def test_multicell(self: TestMulticell) -> None: from kitty.tab_bar import as_rgb