Commit: patch 9.2.0847: [security]: vimball: code execution via .VimballRecord file

Christian Brabandt <[email protected]>
Newsgroups gmane.editors.vim.devel
Message-ID <[email protected]>
patch 9.2.0847: [security]: vimball: code execution via .VimballRecord file

Commit: https://github.com/vim/vim/commit/581a2f3ac9c6f96a26324f6b2c8c11415fd0d452
Author: Christian Brabandt <[email protected]>
Date:   Fri Jul 24 17:43:51 2026 +0200

    patch 9.2.0847: [security]: vimball: code execution via .VimballRecord file
    
    Problem:  [security]: vimball: code execution via .VimballRecord file
              (tdjackey)
    Solution: Forbid arbitrary commands, fix broken directory deletion code,
              refactor code
    
    Github Security Advisory:
    https://github.com/vim/vim/security/advisories/GHSA-r22p-fhw4-84p2
    
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/autoload/vimball.vim b/runtime/autoload/vimball.vim
index a1b2e7ffc..d4ea66cd1 100644
--- a/runtime/autoload/vimball.vim
+++ b/runtime/autoload/vimball.vim
@@ -1,7 +1,7 @@
 " vimball.vim : construct a file containing both paths and files
 " Maintainer: This runtime file is looking for a new maintainer.
 " Original Author:	Charles E. Campbell
-" Date:			Jun 29, 2026
+" Date:			Jul 23, 2026
 " Version:	37 (with modifications from the Vim Project)
 " GetLatestVimScripts: 1502 1 :AutoInstall: vimball.vim
 " Copyright: (c) 2004-2011 by Charles E. Campbell
@@ -16,9 +16,9 @@ if &cp || exists("g:loaded_vimball")
  finish
 endif
 let g:loaded_vimball = "v37"
-if v:version < 704
+if v:version < 900
  echohl WarningMsg
- echo "***warning*** this version of vimball needs vim 7.4"
+ echo "***warning*** this version of vimball needs vim 9.0"
  echohl Normal
  finish
 endif
@@ -245,6 +245,12 @@ fun! vimball#Vimball(really,...)
      bw! Vimball
      call s:ChgDir(curdir)
      return
+   elseif fname =~? '\%(^\|/\)\.VimballRecord$'
+     echomsg "(Vimball) Forbidding .VimballRecord filename, aborting..."
+     exe "tabn ".curtabnr
+     bw! Vimball
+     call s:ChgDir(curdir)
+     return
    endif
 
    if a:really
@@ -272,7 +278,7 @@ fun! vimball#Vimball(really,...)
      let fnamebuf = substitute(fnamebuf,'^.\{-}/\(.*\)$',' ','')
      if !isdirectory(dirname)
       call mkdir(dirname)
-      call s:RecordInVar(home,"rmdir('".dirname."')")
+      call s:RecordDirInVar(dirname)
      endif
     endwhile
    endif
@@ -303,7 +309,7 @@ fun! vimball#Vimball(really,...)
       exe "silent w! ".fnameescape(fnamepath)
     endif
     echo "wrote ".fnameescape(fnamepath)
-    call s:RecordInVar(home,"call delete('".escape(fnamepath, '"''|')."')")
+    call s:RecordInVar(fnamepath)
     endif
 
     " return to tab with vimball
@@ -402,10 +408,17 @@ fun! vimball#RmVimball(...)
     endif
     let s:VBRstring= substitute(exestring,'call delete(','','g')
     let s:VBRstring= substitute(s:VBRstring,"[')]",'','g')
-    sil! keepalt keepjumps exe exestring
+    let nr_files= 0
+    for line in split(exestring, '|')
+      if line !~ '^call delete(''[^'']\{-}''\(,"d"\)\?)$'
+        echomsg "ignoring .VimballRecord entry: " line
+      else
+        sil! keepalt keepjumps exe line
+        let nr_files+= 1
+      endif
+    endfor
     sil! keepalt keepjumps d
-    let exestring= strlen(substitute(exestring,'call delete(.\{-})|\=',"D","g"))
-    echomsg "removed ".exestring." files"
+    echomsg "removed ".nr_files." files"
    else
     let s:VBRstring= ''
     let curfile    = substitute(curfile,'\.vmb','','')
@@ -547,13 +560,20 @@ fun! s:ChgDir(newdir)
 endfun
 
 " ---------------------------------------------------------------------
-" s:RecordInVar: record a un-vimball command in the .VimballRecord file {{{2
-fun! s:RecordInVar(home,cmd)
+" s:RecordInVar: record a un-vimball file deletion in the .VimballRecord file {{{2
+fun! s:RecordInVar(file)
   if !exists("s:recordfile")
-   let s:recordfile= a:cmd
-  else
-   let s:recordfile= s:recordfile."|".a:cmd
+    let s:recordfile=[]
+  endif
+  call add(s:recordfile, $'call delete({string(a:file)})')
+endfun
+
+" s:RecordDirInVar: record a un-vimball dir deletion in the .VimballRecord file {{{2
+fun! s:RecordDirInVar(dir)
+  if !exists("s:recorddir")
+    let s:recorddir = []
   endif
+  call add(s:recorddir, $'call delete({string(a:dir)},"d")')
 endfun
 
 " ---------------------------------------------------------------------
@@ -574,11 +594,11 @@ fun! s:RecordInFile(home)
    setlocal ma
    $
    if exists("s:recordfile") && exists("s:recorddir")
-    let cmd= cmd.s:recordfile."|".s:recorddir
+    let cmd= cmd.join(s:recordfile, '|')."|".join(s:recorddir, '|')
    elseif exists("s:recorddir")
-    let cmd= cmd.s:recorddir
+    let cmd= cmd.join(s:recorddir, '|')
    elseif exists("s:recordfile")
-    let cmd= cmd.s:recordfile
+    let cmd= cmd.join(s:recordfile, '|')
    else
     return
    endif
diff --git a/src/testdir/test_plugin_vimball.vim b/src/testdir/test_plugin_vimball.vim
index 30093ef71..156b4c843 100644
--- a/src/testdir/test_plugin_vimball.vim
+++ b/src/testdir/test_plugin_vimball.vim
@@ -65,7 +65,7 @@ func Test_vimball_basic()
   call assert_true(filereadable('.VimballRecord'))
   let record = readfile('.VimballRecord')
   call assert_equal(1, record->len())
-  call assert_match('^Xtest.vmb: rmdir.*call delete(', record[0])
+  call assert_match('^Xtest.vmb: call delete(''.\{-}'')|call delete(''.\{-}'',"d")$', record[0])
   call s:teardown()
 endfunc
 
@@ -110,3 +110,17 @@ func Test_vimball_evil_filenames()
   call assert_match('(Vimball) Forbidding strange filename:.* aborting\.\.\.', mess)
   call s:teardown()
 endfunc
+
+func Test_vimball_VimballRecord_filenames()
+  call s:Mkvimball()
+  call delete('XVimball', 'rf')
+  sp Xtest.vmb
+  4s#.*\ze	#.VimballRecord#
+  so %
+  call feedkeys("\<cr>", "it")
+
+  let mess = execute(':mess')->split('
')[-1]
+  call assert_match('(Vimball) Forbidding .VimballRecord filename.* aborting\.\.\.', mess)
+  call assert_false(filereadable('.VimballRecord'))
+  call s:teardown()
+endfunc
diff --git a/src/version.c b/src/version.c
index 5547cf4d5..a0b96e2eb 100644
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    847,
 /**/
     846,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups "vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1wnLpP-00C3oX-M8%40256bit.org.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.