From 5671b61327bf570ccc232b4a6f164c077eaa5bcb Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 17 Jul 2025 16:48:56 +0800 Subject: [PATCH 1/4] vim-patch:partial:9.0.0877: using freed memory with :comclear while listing commands Problem: Using freed memory with :comclear while listing commands. Solution: Bail out when the command list has changed. (closes vim/vim#11440) https://github.com/vim/vim/commit/cf2594fbf34d9a6776bd9d33f845cb8ceb1e1cd0 Co-authored-by: Bram Moolenaar --- test/old/testdir/test_usercommands.vim | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/old/testdir/test_usercommands.vim b/test/old/testdir/test_usercommands.vim index c4bc45aa3d..046e0b1874 100644 --- a/test/old/testdir/test_usercommands.vim +++ b/test/old/testdir/test_usercommands.vim @@ -1,5 +1,8 @@ " Tests for user defined commands +source check.vim +source screendump.vim + " Test for in user defined commands function Test_cmdmods() let g:mods = '' @@ -362,6 +365,14 @@ func Test_CmdCompletion() call feedkeys(":com MyCmd chist\\\"\", 'tx') call assert_equal("\"com MyCmd chistory", @:) + " delete the Check commands to avoid them showing up + call feedkeys(":com Check\\\"\", 'tx') + let cmds = substitute(@:, '"com ', '', '')->split() + for cmd in cmds + exe 'delcommand ' .. cmd + endfor + delcommand MissingFeature + command! DoCmd1 : command! DoCmd2 : call feedkeys(":com \\\"\", 'tx') @@ -715,7 +726,7 @@ func Test_recursive_define() call DefCmd('Command') let name = 'Command' - while len(name) < 30 + while len(name) <= 30 exe 'delcommand ' .. name let name ..= 'x' endwhile From 2efc84d005bbfbb65731edc87495278cdb94b778 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 17 Jul 2025 16:23:13 +0800 Subject: [PATCH 2/4] vim-patch:a250738: runtime(tar): use readblob() instead of shelling out to file(1) fixes: #vim/vim#16761 closes: vim/vim#16769 https://github.com/vim/vim/commit/a250738303f85132335d69fd1936501b189eebce Co-authored-by: Christian Brabandt Co-authored-by: Jim Zhou --- runtime/autoload/tar.vim | 59 ++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/runtime/autoload/tar.vim b/runtime/autoload/tar.vim index 8bc0dbf017..8de0ae8708 100644 --- a/runtime/autoload/tar.vim +++ b/runtime/autoload/tar.vim @@ -12,6 +12,8 @@ " 2025 Feb 28 by Vim Project: add support for bzip3 (#16755) " 2025 Mar 01 by Vim Project: fix syntax error in tar#Read() " 2025 Mar 02 by Vim Project: escape the filename before using :read +" 2025 Mar 02 by Vim Project: determine the compression using readblob() +" instead of shelling out to file(1) " " Contains many ideas from Michael Toren's " @@ -161,23 +163,19 @@ fun! tar#Browse(tarfile) elseif tarfile =~# '\.\(tgz\)$' || tarfile =~# '\.\(tbz\)$' || tarfile =~# '\.\(txz\)$' || \ tarfile =~# '\.\(tzst\)$' || tarfile =~# '\.\(tlz4\)$' - if has("unix") && executable("file") - let filekind= system("file ".shellescape(tarfile,1)) - else - let filekind= "" - endif + let header= s:Header(tarfile) - if filekind =~ "bzip2" + if header =~? 'bzip2' exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " - elseif filekind =~ "bzip3" + elseif header =~? 'bzip3' exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " - elseif filekind =~ "XZ" + elseif header =~? 'xz' exe "sil! r! xz -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " - elseif filekind =~ "Zstandard" + elseif header =~? 'zstd' exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " - elseif filekind =~ "LZ4" + elseif header =~? 'lz4' exe "sil! r! lz4 --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " - else + elseif header =~? 'gzip' exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_browseoptions." - " endif @@ -372,24 +370,20 @@ fun! tar#Read(fname,mode) exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif tarfile =~# '\(\.tgz\|\.tbz\|\.txz\)' - if has("unix") && executable("file") - let filekind= system("file ".shellescape(tarfile,1)) - else - let filekind= "" - endif - if filekind =~ "bzip2" + let filekind= s:Header(tarfile) + if filekind =~? "bzip2" exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif filekind =~ "bzip3" exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp exe "read ".escape_file - elseif filekind =~ "XZ" + elseif filekind =~? "xz" exe "sil! r! xz -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp exe "read ".escape_file - elseif filekind =~ "Zstandard" + elseif filekind =~? "zstd" exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp exe "read ".escape_file - else + elseif filekind =~? "gzip" exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp exe "read ".escape_file endif @@ -501,6 +495,7 @@ fun! tar#Write(fname) let tarfile = substitute(tarfile,'\.lzma','','e') let compress= "lzma -- ".shellescape(tarfile,0) endif + " Note: no support for name.tar.tbz/.txz/.tgz/.tlz4/.tzst if v:shell_error != 0 redraw! @@ -749,6 +744,30 @@ fun! s:Rmdir(fname) endif endfun +" s:FileHeader: {{{2 +fun! s:Header(fname) + let header= readblob(a:fname, 0, 6) + " Nvim: see https://github.com/neovim/neovim/pull/34968 + if header[0:2] == 0z425A68 " bzip2 header + return "bzip2" + elseif header[0:2] == 0z425A33 " bzip3 header + return "bzip3" + elseif header == 0zFD377A58.5A00 " xz header + return "xz" + elseif header[0:3] == 0z28B52FFD " zstd header + return "zstd" + elseif header[0:3] == 0z04224D18 " lz4 header + return "lz4" + elseif (header[0:1] == 0z1F9D || + \ header[0:1] == 0z1F8B || + \ header[0:1] == 0z1F9E || + \ header[0:1] == 0z1FA0 || + \ header[0:1] == 0z1F1E) + return "gzip" + endif + return "unknown" +endfun + " ===================================================================== " Modelines And Restoration: {{{1 let &cpo= s:keepcpo From ad0c21a445d1d022741d464d3023629cb6b95bf1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 17 Jul 2025 16:24:10 +0800 Subject: [PATCH 3/4] vim-patch:470317f: runtime(tar): remove dependency on netrw#WinPath, include mapping doc related: vim/vim#17124 https://github.com/vim/vim/commit/470317f78b110b4559cecb26039b5f93447c1bf0 Co-authored-by: Christian Brabandt --- runtime/autoload/tar.vim | 24 ++++++++++++++++++++++-- runtime/doc/pi_tar.txt | 9 +++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/runtime/autoload/tar.vim b/runtime/autoload/tar.vim index 8de0ae8708..a6fd453a6a 100644 --- a/runtime/autoload/tar.vim +++ b/runtime/autoload/tar.vim @@ -14,6 +14,7 @@ " 2025 Mar 02 by Vim Project: escape the filename before using :read " 2025 Mar 02 by Vim Project: determine the compression using readblob() " instead of shelling out to file(1) +" 2025 Apr 16 by Vim Project: decouple from netrw by adding s:WinPath() " " Contains many ideas from Michael Toren's " @@ -146,7 +147,7 @@ fun! tar#Browse(tarfile) let lastline= line("$") call setline(lastline+1,'" tar.vim version '.g:loaded_tar) call setline(lastline+2,'" Browsing tarfile '.a:tarfile) - call setline(lastline+3,'" Select a file with cursor and press ENTER') + call setline(lastline+3,'" Select a file with cursor and press ENTER, "x" to extract a file') keepj $put ='' keepj sil! 0d keepj $ @@ -617,7 +618,7 @@ fun! tar#Extract() let tarball = expand("%") let tarbase = substitute(tarball,'\..*$','','') - let extractcmd= netrw#WinPath(g:tar_extractcmd) + let extractcmd= s:WinPath(g:tar_extractcmd) if filereadable(tarbase.".tar") call system(extractcmd." ".shellescape(tarbase).".tar ".shellescape(fname)) if v:shell_error != 0 @@ -768,6 +769,25 @@ fun! s:Header(fname) return "unknown" endfun +" --------------------------------------------------------------------- +" s:WinPath: {{{2 +fun! s:WinPath(path) + if (!g:netrw_cygwin || &shell !~ '\%(\\|\\)\%(\.exe\)\=$') && has("win32") + " remove cygdrive prefix, if present + let path = substitute(a:path, '/cygdrive/\(.\)', '\1:', '') + " remove trailing slash (Win95) + let path = substitute(path, '\(\\\|/\)$', '', 'g') + " remove escaped spaces + let path = substitute(path, '\ ', ' ', 'g') + " convert slashes to backslashes + let path = substitute(path, '/', '\', 'g') + else + let path = a:path + endif + + return path +endfun + " ===================================================================== " Modelines And Restoration: {{{1 let &cpo= s:keepcpo diff --git a/runtime/doc/pi_tar.txt b/runtime/doc/pi_tar.txt index 96b26d92e7..296bf5ae54 100644 --- a/runtime/doc/pi_tar.txt +++ b/runtime/doc/pi_tar.txt @@ -57,6 +57,15 @@ Copyright 2005-2017: *tar-copyright* let g:loaded_tarPlugin= 1 let g:loaded_tar = 1 < + *tar-mappings* + MAPPINGS~ + + The following (buffer-local) mappings are available in a tar buffer: + + Open selected file for editing, any changes will be + written back to the archive. + same as + x Extract selected file. ============================================================================== 3. Options *tar-options* From 77c6cae25bc3354d83b6a97bc9192e2bfd13c28b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 17 Jul 2025 16:28:30 +0800 Subject: [PATCH 4/4] vim-patch:9.1.1552: [security]: path traversal issue in tar.vim Problem: [security]: path traversal issue in tar.vim (@ax) Solution: warn the user for such things, drop leading /, don't forcefully overwrite files when writing temporary files, refactor autoload/tar.vim tar.vim: drop leading / in path names A tar archive containing files with leading `/` may cause confusions as to where the content is extracted. Let's make sure we drop the leading `/` and use a relative path instead. Also while at it, had to refactor it quite a bit and increase the minimum supported Vim version to v9. Also add a test for some basic tar functionality closes: vim/vim#17733 https://github.com/vim/vim/commit/87757c6b0a4b2c1f71c72ea8e1438b8fb116b239 Co-authored-by: Christian Brabandt --- runtime/autoload/tar.vim | 228 +++++++++++++-------------- runtime/doc/pi_tar.txt | 28 ++-- runtime/plugin/tarPlugin.vim | 8 +- test/old/testdir/runtest.vim | 3 + test/old/testdir/samples/evil.tar | Bin 0 -> 10240 bytes test/old/testdir/samples/sample.tar | Bin 0 -> 10240 bytes test/old/testdir/test_plugin_tar.vim | 127 +++++++++++++++ 7 files changed, 254 insertions(+), 140 deletions(-) create mode 100644 test/old/testdir/samples/evil.tar create mode 100644 test/old/testdir/samples/sample.tar create mode 100644 test/old/testdir/test_plugin_tar.vim diff --git a/runtime/autoload/tar.vim b/runtime/autoload/tar.vim index a6fd453a6a..6695a4d22d 100644 --- a/runtime/autoload/tar.vim +++ b/runtime/autoload/tar.vim @@ -15,6 +15,8 @@ " 2025 Mar 02 by Vim Project: determine the compression using readblob() " instead of shelling out to file(1) " 2025 Apr 16 by Vim Project: decouple from netrw by adding s:WinPath() +" 2025 May 19 by Vim Project: restore working directory after read/write +" 2025 Jul 13 by Vim Project: warn with path traversal attacks " " Contains many ideas from Michael Toren's " @@ -33,9 +35,9 @@ if &cp || exists("g:loaded_tar") finish endif let g:loaded_tar= "v32b" -if v:version < 702 +if !has('nvim-0.12') && v:version < 900 echohl WarningMsg - echo "***warning*** this version of tar needs vim 7.2" + echo "***warning*** this version of tar needs vim 9.0" echohl Normal finish endif @@ -45,10 +47,10 @@ set cpo&vim " --------------------------------------------------------------------- " Default Settings: {{{1 if !exists("g:tar_browseoptions") - let g:tar_browseoptions= "Ptf" + let g:tar_browseoptions= "tf" endif if !exists("g:tar_readoptions") - let g:tar_readoptions= "pPxf" + let g:tar_readoptions= "pxf" endif if !exists("g:tar_cmd") let g:tar_cmd= "tar" @@ -57,6 +59,7 @@ if !exists("g:tar_writeoptions") let g:tar_writeoptions= "uf" endif if !exists("g:tar_delfile") + " Note: not supported on BSD let g:tar_delfile="--delete -f" endif if !exists("g:netrw_cygwin") @@ -105,10 +108,26 @@ if !exists("g:tar_shq") endif endif +let g:tar_secure=' -- ' +let g:tar_leading_pat='^\%([.]\{,2\}/\)\+' + " ---------------- " Functions: {{{1 " ---------------- +" --------------------------------------------------------------------- +" s:Msg: {{{2 +fun! s:Msg(func, severity, msg) + redraw! + if a:severity =~? 'error' + echohl Error + else + echohl WarningMsg + endif + echo $"***{a:severity}*** ({a:func}) {a:msg}" + echohl None +endfunc + " --------------------------------------------------------------------- " tar#Browse: {{{2 fun! tar#Browse(tarfile) @@ -117,16 +136,14 @@ fun! tar#Browse(tarfile) " sanity checks if !executable(g:tar_cmd) - redraw! - echohl Error | echo '***error*** (tar#Browse) "'.g:tar_cmd.'" not available on your system' + call s:Msg('tar#Browse', 'error', $"{g:tar_cmd} not available on your system") let &report= repkeep return endif if !filereadable(a:tarfile) if a:tarfile !~# '^\a\+://' " if it's an url, don't complain, let url-handlers such as vim do its thing - redraw! - echohl Error | echo "***error*** (tar#Browse) File not readable<".a:tarfile.">" | echohl None + call s:Msg('tar#Browse', 'error', $"File not readable<{a:tarfile}>") endif let &report= repkeep return @@ -202,28 +219,18 @@ fun! tar#Browse(tarfile) exe "sil! r! ".g:tar_cmd." -".g:tar_browseoptions." ".shellescape(tarfile,1) endif if v:shell_error != 0 - redraw! - echohl WarningMsg | echo "***warning*** (tar#Browse) please check your g:tar_browseoptions<".g:tar_browseoptions.">" + call s:Msg('tar#Browse', 'warning', $"please check your g:tar_browseoptions '<{g:tar_browseoptions}>'") return endif - " - " The following should not be neccessary, since in case of errors the - " previous if statement should have caught the problem (because tar exited - " with a non-zero exit code). - " if line("$") == curlast || ( line("$") == (curlast + 1) && - " \ getline("$") =~# '\c\<\%(warning\|error\|inappropriate\|unrecognized\)\>' && - " \ getline("$") =~ '\s' ) - " redraw! - " echohl WarningMsg | echo "***warning*** (tar#Browse) ".a:tarfile." doesn't appear to be a tar file" | echohl None - " keepj sil! %d - " let eikeep= &ei - " set ei=BufReadCmd,FileReadCmd - " exe "r ".fnameescape(a:tarfile) - " let &ei= eikeep - " keepj sil! 1d - " call Dret("tar#Browse : a:tarfile<".a:tarfile.">") - " return - " endif + + " remove tar: Removing leading '/' from member names + " Note: the message could be localized + if search('^tar: ') > 0 || search(g:tar_leading_pat) > 0 + call append(3,'" Note: Path Traversal Attack detected!') + let b:leading_slash = 1 + " remove the message output + sil g/^tar: /d + endif " set up maps supported for tar setlocal noma nomod ro @@ -242,12 +249,7 @@ fun! s:TarBrowseSelect() let repkeep= &report set report=10 let fname= getline(".") - - if !exists("g:tar_secure") && fname =~ '^\s*-\|\s\+-' - redraw! - echohl WarningMsg | echo '***warning*** (tar#BrowseSelect) rejecting tarfile member<'.fname.'> because of embedded "-"' - return - endif + let ls= get(b:, 'leading_slash', 0) " sanity check if fname =~ '^"' @@ -269,7 +271,8 @@ fun! s:TarBrowseSelect() wincmd _ endif let s:tblfile_{winnr()}= curfile - call tar#Read("tarfile:".tarfile.'::'.fname,1) + let b:leading_slash= ls + call tar#Read("tarfile:".tarfile.'::'.fname) filetype detect set nomod exe 'com! -buffer -nargs=? -complete=file TarDiff :call tar#Diff(,"'.fnameescape(fname).'")' @@ -279,26 +282,18 @@ endfun " --------------------------------------------------------------------- " tar#Read: {{{2 -fun! tar#Read(fname,mode) +fun! tar#Read(fname) let repkeep= &report set report=10 let tarfile = substitute(a:fname,'tarfile:\(.\{-}\)::.*$','\1','') let fname = substitute(a:fname,'tarfile:.\{-}::\(.*\)$','\1','') " be careful not to execute special crafted files - let escape_file = fname->fnameescape() - - " changing the directory to the temporary earlier to allow tar to extract the file with permissions intact - if !exists("*mkdir") - redraw! - echohl Error | echo "***error*** (tar#Write) sorry, mkdir() doesn't work on your system" | echohl None - let &report= repkeep - return - endif + let escape_file = fname->substitute(g:tar_leading_pat, '', '')->fnameescape() let curdir= getcwd() + let b:curdir= curdir let tmpdir= tempname() - let b:curdir= tmpdir - let b:tmpdir= curdir + let b:tmpdir= tmpdir if tmpdir =~ '\.' let tmpdir= substitute(tmpdir,'\.[^.]*$','','e') endif @@ -308,8 +303,7 @@ fun! tar#Read(fname,mode) try exe "lcd ".fnameescape(tmpdir) catch /^Vim\%((\a\+)\)\=:E344/ - redraw! - echohl Error | echo "***error*** (tar#Write) cannot lcd to temporary directory" | Echohl None + call s:Msg('tar#Read', 'error', "cannot lcd to temporary directory") let &report= repkeep return endtry @@ -332,7 +326,7 @@ fun! tar#Read(fname,mode) elseif fname =~ '\.bz3$' && executable("bz3cat") let decmp= "|bz3cat" let doro = 1 - elseif fname =~ '\.t\=gz$' && executable("zcat") + elseif fname =~ '\.t\=gz$' && executable("zcat") let decmp= "|zcat" let doro = 1 elseif fname =~ '\.lzma$' && executable("lzcat") @@ -355,68 +349,66 @@ fun! tar#Read(fname,mode) endif endif - if exists("g:tar_secure") - let tar_secure= " -- " - else - let tar_secure= " " - endif if tarfile =~# '\.bz2$' - exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif tarfile =~# '\.bz3$' - exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif tarfile =~# '\.\(gz\)$' - exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif tarfile =~# '\(\.tgz\|\.tbz\|\.txz\)' let filekind= s:Header(tarfile) if filekind =~? "bzip2" - exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! bzip2 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif filekind =~ "bzip3" - exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! bzip3 -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif filekind =~? "xz" - exe "sil! r! xz -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! xz -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif filekind =~? "zstd" - exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! zstd --decompress --stdout -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif filekind =~? "gzip" - exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! gzip -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file endif elseif tarfile =~# '\.lrp$' - exe "sil! r! cat -- ".shellescape(tarfile,1)." | gzip -d -c - | ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! cat -- ".shellescape(tarfile,1)." | gzip -d -c - | ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif tarfile =~# '\.lzma$' - exe "sil! r! lzma -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! lzma -d -c -- ".shellescape(tarfile,1)."| ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif tarfile =~# '\.\(xz\|txz\)$' - exe "sil! r! xz --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! xz --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file elseif tarfile =~# '\.\(lz4\|tlz4\)$' - exe "sil! r! lz4 --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_readoptions." - ".tar_secure.shellescape(fname,1).decmp + exe "sil! r! lz4 --decompress --stdout -- ".shellescape(tarfile,1)." | ".g:tar_cmd." -".g:tar_readoptions." - ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file else if tarfile =~ '^\s*-' " A file name starting with a dash is taken as an option. Prepend ./ to avoid that. let tarfile = substitute(tarfile, '-', './-', '') endif - exe "silent r! ".g:tar_cmd." -".g:tar_readoptions.shellescape(tarfile,1)." ".tar_secure.shellescape(fname,1).decmp + exe "silent r! ".g:tar_cmd." -".g:tar_readoptions.shellescape(tarfile,1)." ".g:tar_secure.shellescape(fname,1).decmp exe "read ".escape_file endif + if get(b:, 'leading_slash', 0) + sil g/^tar: /d + endif redraw! -if v:shell_error != 0 + if v:shell_error != 0 lcd .. call s:Rmdir("_ZIPVIM_") exe "lcd ".fnameescape(curdir) - echohl Error | echo "***error*** (tar#Read) sorry, unable to open or extract ".tarfile." with ".fname | echohl None + call s:Msg('tar#Read', 'error', $"sorry, unable to open or extract {tarfile} with {fname}") endif if doro @@ -425,7 +417,6 @@ if v:shell_error != 0 endif let b:tarfile= a:fname - exe "file tarfile::".fnameescape(fname) " cleanup keepj sil! 0d @@ -433,7 +424,7 @@ if v:shell_error != 0 let &report= repkeep exe "lcd ".fnameescape(curdir) - silent exe "file tarfile::".escape_file + silent exe "file tarfile::". fname->fnameescape() endfun " --------------------------------------------------------------------- @@ -445,22 +436,35 @@ fun! tar#Write(fname) let curdir= b:curdir let tmpdir= b:tmpdir - if !exists("g:tar_secure") && a:fname =~ '^\s*-\|\s\+-' - redraw! - echohl WarningMsg | echo '***warning*** (tar#Write) rejecting tarfile member<'.a:fname.'> because of embedded "-"' - return - endif - " sanity checks if !executable(g:tar_cmd) redraw! let &report= repkeep return endif - let tarfile = substitute(b:tarfile,'tarfile:\(.\{-}\)::.*$','\1','') let fname = substitute(b:tarfile,'tarfile:.\{-}::\(.*\)$','\1','') + if get(b:, 'leading_slash', 0) + call s:Msg('tar#Write', 'error', $"sorry, not attempting to update {tarfile} with {fname}") + let &report= repkeep + return + endif + + if !isdirectory(fnameescape(tmpdir)) + call mkdir(fnameescape(tmpdir), 'p') + endif + exe $"lcd {fnameescape(tmpdir)}" + if isdirectory("_ZIPVIM_") + call s:Rmdir("_ZIPVIM_") + endif + call mkdir("_ZIPVIM_") + lcd _ZIPVIM_ + let dir = fnamemodify(fname, ':p:h') + if dir !~# '_ZIPVIM_$' + call mkdir(dir) + endif + " handle compressed archives if tarfile =~# '\.bz2' call system("bzip2 -d -- ".shellescape(tarfile,0)) @@ -499,8 +503,7 @@ fun! tar#Write(fname) " Note: no support for name.tar.tbz/.txz/.tgz/.tlz4/.tzst if v:shell_error != 0 - redraw! - echohl Error | echo "***error*** (tar#Write) sorry, unable to update ".tarfile." with ".fname | echohl None + call s:Msg('tar#Write', 'error', $"sorry, unable to update {tarfile} with {fname}") else if fname =~ '/' @@ -518,28 +521,22 @@ fun! tar#Write(fname) let tarfile = substitute(tarfile, '-', './-', '') endif - if exists("g:tar_secure") - let tar_secure= " -- " - else - let tar_secure= " " - endif - exe "w! ".fnameescape(fname) + " don't overwrite a file forcefully + exe "w ".fnameescape(fname) if has("win32unix") && executable("cygpath") let tarfile = substitute(system("cygpath ".shellescape(tarfile,0)),'\n','','e') endif " delete old file from tarfile - call system(g:tar_cmd." ".g:tar_delfile." ".shellescape(tarfile,0).tar_secure.shellescape(fname,0)) + " Note: BSD tar does not support --delete flag + call system(g:tar_cmd." ".g:tar_delfile." ".shellescape(tarfile,0).g:tar_secure.shellescape(fname,0)) if v:shell_error != 0 - redraw! - echohl Error | echo "***error*** (tar#Write) sorry, unable to update ".fnameescape(tarfile)." with ".fnameescape(fname) | echohl None + call s:Msg('tar#Write', 'error', $"sorry, unable to update {fnameescape(tarfile)} with {fnameescape(fname)} --delete not supported?") else - " update tarfile with new file - call system(g:tar_cmd." -".g:tar_writeoptions." ".shellescape(tarfile,0).tar_secure.shellescape(fname,0)) + call system(g:tar_cmd." -".g:tar_writeoptions." ".shellescape(tarfile,0).g:tar_secure.shellescape(fname,0)) if v:shell_error != 0 - redraw! - echohl Error | echo "***error*** (tar#Write) sorry, unable to update ".fnameescape(tarfile)." with ".fnameescape(fname) | echohl None + call s:Msg('tar#Write', 'error', $"sorry, unable to update {fnameescape(tarfile)} with {fnameescape(fname)}") elseif exists("compress") call system(compress) if exists("tgz") @@ -580,6 +577,7 @@ fun! tar#Diff(userfname,fname) if a:userfname != "" let fname= a:userfname endif + exe "lcd ".fnameescape(b:tmpdir). '/_ZIPVIM_' if filereadable(fname) " sets current file (from tarball) for diff'ing " splits window vertically @@ -603,12 +601,6 @@ fun! tar#Extract() set report=10 let fname= getline(".") - if !exists("g:tar_secure") && fname =~ '^\s*-\|\s\+-' - redraw! - echohl WarningMsg | echo '***warning*** (tar#BrowseSelect) rejecting tarfile member<'.fname.'> because of embedded "-"' - return - endif - " sanity check if fname =~ '^"' let &report= repkeep @@ -622,16 +614,16 @@ fun! tar#Extract() if filereadable(tarbase.".tar") call system(extractcmd." ".shellescape(tarbase).".tar ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tar ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar {fname}: failed!") else - echo "***note*** successfully extracted ".fname + echo "***note*** successfully extracted ". fname endif elseif filereadable(tarbase.".tgz") let extractcmd= substitute(extractcmd,"-","-z","") call system(extractcmd." ".shellescape(tarbase).".tgz ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tgz ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tgz {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -640,7 +632,7 @@ fun! tar#Extract() let extractcmd= substitute(extractcmd,"-","-z","") call system(extractcmd." ".shellescape(tarbase).".tar.gz ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tar.gz ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.gz {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -649,7 +641,7 @@ fun! tar#Extract() let extractcmd= substitute(extractcmd,"-","-j","") call system(extractcmd." ".shellescape(tarbase).".tbz ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd."j ".tarbase.".tbz ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tbz {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -658,7 +650,7 @@ fun! tar#Extract() let extractcmd= substitute(extractcmd,"-","-j","") call system(extractcmd." ".shellescape(tarbase).".tar.bz2 ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd."j ".tarbase.".tar.bz2 ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.bz2 {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -667,7 +659,7 @@ fun! tar#Extract() let extractcmd= substitute(extractcmd,"-","-j","") call system(extractcmd." ".shellescape(tarbase).".tar.bz3 ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd."j ".tarbase.".tar.bz3 ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.bz3 {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -676,7 +668,7 @@ fun! tar#Extract() let extractcmd= substitute(extractcmd,"-","-J","") call system(extractcmd." ".shellescape(tarbase).".txz ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd." ".tarbase.".txz ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.txz {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -685,7 +677,7 @@ fun! tar#Extract() let extractcmd= substitute(extractcmd,"-","-J","") call system(extractcmd." ".shellescape(tarbase).".tar.xz ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tar.xz ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.xz {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -694,7 +686,7 @@ fun! tar#Extract() let extractcmd= substitute(extractcmd,"-","--zstd","") call system(extractcmd." ".shellescape(tarbase).".tzst ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tzst ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tzst {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -703,7 +695,7 @@ fun! tar#Extract() let extractcmd= substitute(extractcmd,"-","--zstd","") call system(extractcmd." ".shellescape(tarbase).".tar.zst ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tar.zst ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.zst {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -712,7 +704,7 @@ fun! tar#Extract() let extractcmd= substitute(extractcmd,"-","-I lz4","") call system(extractcmd." ".shellescape(tarbase).".tlz4 ".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tlz4 ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tlz4 {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -721,7 +713,7 @@ fun! tar#Extract() let extractcmd= substitute(extractcmd,"-","-I lz4","") call system(extractcmd." ".shellescape(tarbase).".tar.lz4".shellescape(fname)) if v:shell_error != 0 - echohl Error | echo "***error*** ".extractcmd." ".tarbase.".tar.lz4 ".fname.": failed!" | echohl NONE + call s:Msg('tar#Extract', 'error', $"{extractcmd} {tarbase}.tar.lz4 {fname}: failed!") else echo "***note*** successfully extracted ".fname endif @@ -734,15 +726,7 @@ endfun " --------------------------------------------------------------------- " s:Rmdir: {{{2 fun! s:Rmdir(fname) - if has("unix") - call system("/bin/rm -rf -- ".shellescape(a:fname,0)) - elseif has("win32") || has("win95") || has("win64") || has("win16") - if &shell =~? "sh$" - call system("/bin/rm -rf -- ".shellescape(a:fname,0)) - else - call system("del /S ".shellescape(a:fname,0)) - endif - endif + call delete(a:fname, 'rf') endfun " s:FileHeader: {{{2 diff --git a/runtime/doc/pi_tar.txt b/runtime/doc/pi_tar.txt index 296bf5ae54..8e90b5a525 100644 --- a/runtime/doc/pi_tar.txt +++ b/runtime/doc/pi_tar.txt @@ -4,8 +4,7 @@ | Tar File Interface | +====================+ -Author: Charles E. Campbell - (remove NOSPAM from Campbell's email first) +Original Author: Charles E. Campbell Copyright 2005-2017: *tar-copyright* The VIM LICENSE (see |copyright|) applies to the files in this package, including tarPlugin.vim, tar.vim, and pi_tar.txt. Like @@ -78,24 +77,25 @@ Copyright 2005-2017: *tar-copyright* *g:tar_readoptions* "OPxf" used to extract a file from a tarball *g:tar_cmd* "tar" the name of the tar program *g:tar_nomax* 0 if true, file window will not be maximized - *g:tar_secure* undef if exists: - "--"s will be used to prevent unwanted - option expansion in tar commands. - Please be sure that your tar command - accepts "--"; Posix compliant tar - utilities do accept them. - if not exists: - The tar plugin will reject any tar - files or member files that begin with - "-" - Not all tar's support the "--" which is why - it isn't default. *g:tar_writeoptions* "uf" used to update/replace a file ============================================================================== 4. History *tar-history* + unreleased: + Jul 13, 2025 * drop leading / + May 19, 2025 * restore working directory after read/write + Apr 16, 2025 * decouple from netrw by adding s:WinPath() + instead of shelling out to file(1) + Mar 02, 2025 * determine the compression using readblob() + Mar 02, 2025 * escape the filename before using :read + Mar 01, 2025 * fix syntax error in tar#Read() + Feb 28, 2025 * add support for bzip3 (#16755) + Feb 06, 2025 * add support for lz4 (#16591) + Nov 11, 2024 * support permissions (#7379) + Feb 19, 2024 * announce adoption + Jan 08, 2024 * fix a few problems (#138331, #12637, #8109) v31 Apr 02, 2017 * (klartext) reported that browsing encrypted files in a zip archive created unencrypted swap files. I am applying a similar fix diff --git a/runtime/plugin/tarPlugin.vim b/runtime/plugin/tarPlugin.vim index a337c91214..d661ec64c2 100644 --- a/runtime/plugin/tarPlugin.vim +++ b/runtime/plugin/tarPlugin.vim @@ -23,14 +23,14 @@ set cpo&vim " Public Interface: {{{1 augroup tar au! - au BufReadCmd tarfile::* call tar#Read(expand(""), 1) - au FileReadCmd tarfile::* call tar#Read(expand(""), 0) + au BufReadCmd tarfile::* call tar#Read(expand("")) + au FileReadCmd tarfile::* call tar#Read(expand("")) au BufWriteCmd tarfile::* call tar#Write(expand("")) au FileWriteCmd tarfile::* call tar#Write(expand("")) if has("unix") - au BufReadCmd tarfile::*/* call tar#Read(expand(""), 1) - au FileReadCmd tarfile::*/* call tar#Read(expand(""), 0) + au BufReadCmd tarfile::*/* call tar#Read(expand("")) + au FileReadCmd tarfile::*/* call tar#Read(expand("")) au BufWriteCmd tarfile::*/* call tar#Write(expand("")) au FileWriteCmd tarfile::*/* call tar#Write(expand("")) endif diff --git a/test/old/testdir/runtest.vim b/test/old/testdir/runtest.vim index eb2d0c6d8d..7a1d7304ea 100644 --- a/test/old/testdir/runtest.vim +++ b/test/old/testdir/runtest.vim @@ -99,6 +99,9 @@ source setup.vim " Needed for RunningWithValgrind(). source shared.vim +" Needed for the various Check commands +source check.vim + " For consistency run all tests with 'nocompatible' set. " This also enables use of line continuation. set nocp viminfo+=nviminfo diff --git a/test/old/testdir/samples/evil.tar b/test/old/testdir/samples/evil.tar new file mode 100644 index 0000000000000000000000000000000000000000..8cbc061fdf650a5182f29d18cfbfa2ab6cbfa05f GIT binary patch literal 10240 zcmdN-ElJi-tk5ke&tsq!FfcGMFf%h@FfcGMG&eOsra@8$28KoqhNcF_rY5Flrp87L z28PB)MkWjj2DEYzvbm+jC5c4}3Jl2^MVZA(MTy9A$ZSIL$ZCfWyEs2LwIm}mFP&=$ zIeOHoqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? v8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UhRelFf=zcK&C-bAk1KBYG70= z&Y)mQLpQ;VE-fxeEK*QlNX{tAEKVv)giD|?2+5-" + call assert_equal("X.tar", @%) + + "## Check ENTER on file + :6 + exe ":normal \" + call assert_equal("tarfile::testtar/file1.txt", @%) + + + "## Check editing file + "## Note: deleting entries not supported on BSD + if has("mac") + return + endif + if has("bsd") + return + endif + s/.*/some-content/ + call assert_equal("some-content", getline(1)) + w! + call assert_equal("tarfile::testtar/file1.txt", @%) + bw! + close + bw! + + e X.tar + :6 + exe "normal \" + call assert_equal("some-content", getline(1)) + bw! + close + + "## Check extracting file + :5 + normal x + call assert_true(filereadable("./testtar/file1.txt")) + bw! +endfunc + +func Test_tar_evil() + call s:CopyFile("evil.tar") + defer delete("X.tar") + defer delete("./etc", 'rf') + e X.tar + + "## Check header + call assert_match('^" tar\.vim version v\d\+', getline(1)) + call assert_match('^" Browsing tarfile .*/X.tar', getline(2)) + call assert_match('^" Select a file with cursor and press ENTER, "x" to extract a file', getline(3)) + call assert_match('^" Note: Path Traversal Attack detected', getline(4)) + call assert_match('^$', getline(5)) + call assert_match('/etc/ax-pwn', getline(6)) + + "## Check ENTER on header + :1 + exe ":normal \" + call assert_equal("X.tar", @%) + call assert_equal(1, b:leading_slash) + + "## Check ENTER on file + :6 + exe ":normal \" + call assert_equal(1, b:leading_slash) + call assert_equal("tarfile::/etc/ax-pwn", @%) + + + "## Check editing file + "## Note: deleting entries not supported on BSD + if has("mac") + return + endif + if has("bsd") + return + endif + s/.*/none/ + call assert_equal("none", getline(1)) + w! + call assert_equal(1, b:leading_slash) + call assert_equal("tarfile::/etc/ax-pwn", @%) + bw! + close + bw! + + " Writing was aborted + e X.tar + call assert_match('^" Note: Path Traversal Attack detected', getline(4)) + :6 + exe "normal \" + call assert_equal("something", getline(1)) + bw! + close + + "## Check extracting file + :5 + normal x + call assert_true(filereadable("./etc/ax-pwn")) + + bw! +endfunc