Commit: patch 9.2.0642: statusline: buffer overflow with item groups

0 views
Skip to first unread message

Christian Brabandt

unread,
11:15 AM (1 hour ago) 11:15 AM
to vim...@googlegroups.com
patch 9.2.0642: statusline: buffer overflow with item groups

Commit: https://github.com/vim/vim/commit/d249884340ca7d0e5edb02445101e88a280dac68
Author: Sébastien Hoffmann <con...@shoffmann.dev>
Date: Sun Jun 14 14:50:14 2026 +0000

patch 9.2.0642: statusline: buffer overflow with item groups

Problem: statusline: buffer overflow with item groups
Solution: Fix the issues (see below) (Sébastien Hoffmann)

Fix various buffer overflow bugs (examples assume MAXPATHL==4096):
- truncated item groups where minwid>maxwid:
vim --clean +"set ls=2 stl=%<%{%repeat('x',4096-11)%}%50.5(12🙂345%)"
leads to fillchars spilling over the end of the group/buffer while trying to
compensate for truncating at a multicell character because minwid<=maxwid is assumed
- left-aligned item groups with multi-byte fillchar:
vim --clean +"set ls=2 fillchars+=stl:∙ stl=%<%{%repeat('x',4096-3)%}%-2(X%)"
wrongly leads to padding at the end of the statusline and `p-out==4097`
because the bounds check assumes a 1-byte fillchar
- right-aligned item groups with 1-byte fillchar:
vim --clean +"set ls=2 stl=%<%{%repeat('x',4096-4)%}%4(XY%)"
leads to "YX" instead of "XY" at the end of the statusline
because `memmove` is done before adjusting the offset
- right-aligned item groups with multi-byte fillchar:
vim --clean +"set ls=2 fillchars+=stl:∙ stl=%5(X%)"
leads to "∙∙∙∙<e2>", i.e. the fillchar is being written over the group contents
and eventually being overwritten itself at the second byte with the final NUL,
because the padding counter assumes a 1-byte fillchar; to crash vim,
vim --clean +"set ls=2 fillchars+=stl:∙ stl=%<%{%repeat('x',4096-149)%}%50(X%)"

related: neovim/neovim#40219
closes: #20522

Signed-off-by: Sébastien Hoffmann <con...@shoffmann.dev>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/buffer.c b/src/buffer.c
index d28718329..48a6bdc3d 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -4864,7 +4864,9 @@ build_stl_str_hl_local(
p = p - n + 1;

// Fill up space left over by half a double-wide char.
- while (++l < stl_items[stl_groupitem[groupdepth]].stl_minwid)
+ int minwid_fixed = MIN(stl_items[stl_groupitem[groupdepth]].stl_minwid,
+ stl_items[stl_groupitem[groupdepth]].stl_maxwid);
+ while (++l < minwid_fixed)
MB_CHAR2BYTES(fillchar, p);

// correct the start of the items for the truncation
@@ -4880,25 +4882,30 @@ build_stl_str_hl_local(
{
// fill
n = stl_items[stl_groupitem[groupdepth]].stl_minwid;
+ int fillchar_len = MB_CHAR2LEN(fillchar);
if (n < 0)
{
// fill by appending characters
n = 0 - n;
- while (l++ < n && p + 1 < out + outlen)
+ while (l++ < n && p + fillchar_len < out + outlen)
MB_CHAR2BYTES(fillchar, p);
}
else
{
// fill by inserting characters
- l = (n - l) * MB_CHAR2LEN(fillchar);
- mch_memmove(t + l, t, (size_t)(p - t));
+ n = n - l;
+ l = n * fillchar_len;
if (p + l >= out + outlen)
- l = (long)((out + outlen) - p - 1);
+ {
+ n = (long)((out + outlen) - p - 1) / fillchar_len;
+ l = n * fillchar_len;
+ }
+ mch_memmove(t + l, t, (size_t)(p - t));
p += l;
+ for ( ; n > 0; n--)
+ MB_CHAR2BYTES(fillchar, t);
for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++)
stl_items[n].stl_start += l;
- for ( ; l > 0; l--)
- MB_CHAR2BYTES(fillchar, t);
}
}
continue;
diff --git a/src/testdir/test_statusline.vim b/src/testdir/test_statusline.vim
index d4267ee92..c00582d5f 100644
--- a/src/testdir/test_statusline.vim
+++ b/src/testdir/test_statusline.vim
@@ -120,10 +120,30 @@ func Test_statusline()
call assert_match('^ Xstatusline\s*$', s:get_statusline())
set statusline=%.6(%f%)
call assert_match('^<sline\s*$', s:get_statusline())
+ set statusline=%.5(1234567%),%2.5(1234567%),%5.5(1234567%),%50.5(1234567%)
+ call assert_match('^<4567,<4567,<4567,<4567\s*$', s:get_statusline())
set statusline=%14f
call assert_match('^ Xstatusline\s*$', s:get_statusline())
set statusline=%.4L
call assert_match('^10>3\s*$', s:get_statusline())
+ for filler in ['-', '∙']
+ exec 'set fillchars+=stl:'..filler
+ set statusline=%-5(x%),%-5.(x%),%-5.5(x%),%-5.10(x%);
+ call assert_match(substitute('^x____,x____,x____,x____;_*$', '_', filler, 'g'), s:get_statusline())
+ set statusline=%5(x%),%5.(x%),%5.5(x%),%5.10(x%);
+ call assert_match(substitute('^____x,____x,____x,____x;_*$', '_', filler, 'g'), s:get_statusline())
+ set statusline=%.5(12🙂345%),%4.5(12🙂345%),%5.5(12🙂345%),%50.5(12🙂345%);
+ call assert_match(substitute('^<345,<345,<345_,<345_;_*$', '_', filler, 'g'), s:get_statusline())
+ endfor
+ if has('linux')
+ " This assumes MAXPATHL is 4096 bytes.
+ set stl=%{%repeat('x',4096-6)%}%10(X%)
+ set fillchars+=stl:-
+ call assert_match('^<x\+----X$', s:get_statusline())
+ set fillchars+=stl:∙
+ call assert_match('^<x\+∙X$', s:get_statusline())
+ endif
+ set fillchars&

" %h: Help buffer flag, text is "[help]".
" %H: Help buffer flag, text is ",HLP".
diff --git a/src/version.c b/src/version.c
index 83d1e8058..4611e8488 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 642,
/**/
641,
/**/
Reply all
Reply to author
Forward
0 new messages