]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0642: statusline: buffer overflow with item groups v9.2.0642
authorSébastien Hoffmann <contact@shoffmann.dev>
Sun, 14 Jun 2026 14:50:14 +0000 (14:50 +0000)
committerChristian Brabandt <cb@256bit.org>
Sun, 14 Jun 2026 14:50:14 +0000 (14:50 +0000)
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 <contact@shoffmann.dev>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/buffer.c
src/testdir/test_statusline.vim
src/version.c

index d28718329318f3179db7f267a3a83c185b7b70f6..48a6bdc3db83a3ca394a3f5db164a146b6c01999 100644 (file)
@@ -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;
index d4267ee929432f8e3065b4c5c53419ec2e3df866..c00582d5fdd145eb525b99c04136375710a29bad 100644 (file)
@@ -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".
index 83d1e805840881f3679a24f11b6bf6009cf4072c..4611e8488ab493232a6c1ec5f77996e454486c12 100644 (file)
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    642,
 /**/
     641,
 /**/