From 63680c6d3d52477817b49cd1a66e7aabe8a7aa19 Mon Sep 17 00:00:00 2001 From: Christian Brabandt Date: Sat, 30 May 2026 16:34:40 +0000 Subject: [PATCH] patch 9.2.0565: [security]: out-of-bounds read in update_snapshot() Problem: Out-of-bounds read in update_snapshot() when a terminal cell fills all VTERM_MAX_CHARS_PER_CELL slots (a base character plus five combining marks): the loop over cell.chars[] has no upper bound and libvterm leaves the array unterminated when full, so it reads past the array and appends out-of-bounds values to a buffer sized for only VTERM_MAX_CHARS_PER_CELL characters. Solution: Bound the loop with i < VTERM_MAX_CHARS_PER_CELL, mirroring the loop in handle_pushline() (Christian Brabandt). Signed-off-by: Christian Brabandt --- src/terminal.c | 3 +- src/testdir/samples/combining_chars.txt | 200 ++++++++++++++++++++++++ src/testdir/test_terminal3.vim | 15 ++ src/version.c | 2 + 4 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/testdir/samples/combining_chars.txt diff --git a/src/terminal.c b/src/terminal.c index b843f22da3..b748ed35f1 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -2265,7 +2265,8 @@ update_snapshot(term_T *term) int i; int c; - for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i) + for (i = 0; i < VTERM_MAX_CHARS_PER_CELL && + ((c = cell.chars[i]) > 0 || i == 0); ++i) ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c, (char_u *)ga.ga_data + ga.ga_len); } diff --git a/src/testdir/samples/combining_chars.txt b/src/testdir/samples/combining_chars.txt new file mode 100644 index 0000000000..d9a3c171fb --- /dev/null +++ b/src/testdir/samples/combining_chars.txt @@ -0,0 +1,200 @@ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ +á́́́́ጁ +á́́́́ጁ +á́́́́ጁ +á́́́́ጁ +á́́́́ጁ +á́́́́ጁ +á́́́́ጁ +á́́́́ጁ +á́́́́ጁ +á́́́́ጁ diff --git a/src/testdir/test_terminal3.vim b/src/testdir/test_terminal3.vim index 04c7c925e3..738a4c6284 100644 --- a/src/testdir/test_terminal3.vim +++ b/src/testdir/test_terminal3.vim @@ -1241,4 +1241,19 @@ func Test_terminal_csi_args_overflow() call StopVimInTerminal(buf) endfunc +func Test_terminal_output_combining_chars() + CheckUnix + new + let cmd = "cat samples/combining_chars.txt" + let buf = term_start(cmd, {'curwin': 1, 'term_finish': 'open', 'term_rows': 10, 'term_cols': 30}) + call WaitForAssert({-> assert_match('finished', term_getstatus(buf))}) + call TermWait(buf) + let lines = getbufline(buf, 1, '$') + " get byte lengths to confirm combining chars present + let lens = map(copy(lines), 'len(v:val)') + let expected = repeat([11], 190) + repeat([14], 10) + call assert_equal(expected, lens) + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index 31d5a23059..7c9c18a7db 100644 --- a/src/version.c +++ b/src/version.c @@ -729,6 +729,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 565, /**/ 564, /**/