diff --git a/0010-fix-CVE-2025-53905.patch b/0010-fix-CVE-2025-53905.patch new file mode 100644 index 0000000000000000000000000000000000000000..236ca5b69d81346cf2d57f5b1953273fdf5967e8 --- /dev/null +++ b/0010-fix-CVE-2025-53905.patch @@ -0,0 +1,1304 @@ +From 87757c6b0a4b2c1f71c72ea8e1438b8fb116b239 Mon Sep 17 00:00:00 2001 +From: Christian Brabandt +Date: Tue, 15 Jul 2025 21:54:00 +0200 +Subject: [PATCH] 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: #17733 +--- + Filelist | 2 + + runtime/autoload/tar.vim | 227 +++++++++++++++----------------- + runtime/doc/pi_tar.txt | 32 ++--- + runtime/doc/tags | 1 - + runtime/plugin/tarPlugin.vim | 8 +- + src/po/vim.pot | 146 ++++++++++---------- + src/testdir/Make_all.mak | 2 + + src/testdir/samples/evil.tar | Bin 0 -> 10240 bytes + src/testdir/samples/sample.tar | Bin 0 -> 10240 bytes + src/testdir/test_plugin_tar.vim | 128 ++++++++++++++++++ + src/version.c | 2 + + 11 files changed, 332 insertions(+), 216 deletions(-) + create mode 100644 src/testdir/samples/evil.tar + create mode 100644 src/testdir/samples/sample.tar + create mode 100644 src/testdir/test_plugin_tar.vim + +diff --git a/Filelist b/Filelist +index 3c9f783018f5a7..41eba31075bde6 100644 +--- a/Filelist ++++ b/Filelist +@@ -213,7 +213,9 @@ SRC_ALL = \ + src/testdir/samples/*.txt \ + src/testdir/samples/*.vim \ + src/testdir/samples/evil.zip \ ++ src/testdir/samples/evil.tar \ + src/testdir/samples/poc.zip \ ++ src/testdir/samples/sample.tar \ + src/testdir/samples/test.zip \ + src/testdir/samples/test000 \ + src/testdir/samples/test_undo.txt.undo \ +diff --git a/runtime/autoload/tar.vim b/runtime/autoload/tar.vim +index 7c1cefa63eaad7..1a0d4f8a3d28e4 100644 +--- a/runtime/autoload/tar.vim ++++ b/runtime/autoload/tar.vim +@@ -16,6 +16,7 @@ + " 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 + " +@@ -34,9 +35,9 @@ if &cp || exists("g:loaded_tar") + finish + endif + let g:loaded_tar= "v32b" +-if v:version < 702 ++if 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 +@@ -46,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" +@@ -58,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") +@@ -106,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) +@@ -118,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 +@@ -203,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 +@@ -243,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 =~ '^"' +@@ -270,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).'")' +@@ -280,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 +@@ -309,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 +@@ -333,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") +@@ -356,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 +@@ -426,7 +417,6 @@ if v:shell_error != 0 + endif + + let b:tarfile= a:fname +- exe "file tarfile::".fnameescape(fname) + + " cleanup + keepj sil! 0d +@@ -434,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 + + " --------------------------------------------------------------------- +@@ -446,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)) +@@ -500,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 =~ '/' +@@ -519,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") +@@ -581,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 +@@ -604,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 +@@ -623,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 +@@ -641,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 +@@ -650,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 +@@ -659,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 +@@ -668,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 +@@ -677,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 +@@ -686,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 +@@ -695,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 +@@ -704,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 +@@ -713,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 +@@ -722,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 +@@ -735,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 6d49928dcbc85f..52706e93efe1e6 100644 +--- a/runtime/doc/pi_tar.txt ++++ b/runtime/doc/pi_tar.txt +@@ -1,11 +1,10 @@ +-*pi_tar.txt* For Vim version 9.1. Last change: 2025 Mar 16 ++*pi_tar.txt* For Vim version 9.1. Last change: 2025 Jul 15 + + +====================+ + | 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 +@@ -61,7 +60,7 @@ Copyright 2005-2017: *tar-copyright* + the file mentioned in the tarball. If the current directory is not + correct for that path, :TarDiff will fail to find the associated file. + +- If the [filename] is given, that that filename (and path) will be used ++ If the [filename] is given, that filename (and path) will be used + to specify the associated file. + + +@@ -95,24 +94,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/doc/tags b/runtime/doc/tags +index 384b27aa6257a9..8251c73d412583 100644 +--- a/runtime/doc/tags ++++ b/runtime/doc/tags +@@ -7857,7 +7857,6 @@ g:tar_copycmd pi_tar.txt /*g:tar_copycmd* + g:tar_extractcmd pi_tar.txt /*g:tar_extractcmd* + g:tar_nomax pi_tar.txt /*g:tar_nomax* + g:tar_readoptions pi_tar.txt /*g:tar_readoptions* +-g:tar_secure pi_tar.txt /*g:tar_secure* + g:tar_writeoptions pi_tar.txt /*g:tar_writeoptions* + g:termdebug_config terminal.txt /*g:termdebug_config* + g:termdebugger terminal.txt /*g:termdebugger* +diff --git a/runtime/plugin/tarPlugin.vim b/runtime/plugin/tarPlugin.vim +index 825b7ae17fb4f8..e55a367854857c 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/src/po/vim.pot b/src/po/vim.pot +index 7d28c12df7a4ee..4a9a20bcde002a 100644 +--- a/src/po/vim.pot ++++ b/src/po/vim.pot +@@ -8,7 +8,7 @@ msgid "" + msgstr "" + "Project-Id-Version: PACKAGE VERSION\n" + "Report-Msgid-Bugs-To: \n" +-"POT-Creation-Date: 2025-07-15 21:42+0200\n" ++"POT-Creation-Date: 2025-07-15 21:50+0200\n" + "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" + "Last-Translator: FULL NAME \n" + "Language-Team: LANGUAGE \n" +@@ -4257,327 +4257,327 @@ msgstr "" + msgid "%s (%s, compiled %s)" + msgstr "" + +-#: ../version.c:4036 ++#: ../version.c:4038 + msgid "" + "\n" + "MS-Windows ARM64 GUI/console version" + msgstr "" + +-#: ../version.c:4038 ++#: ../version.c:4040 + msgid "" + "\n" + "MS-Windows 64-bit GUI/console version" + msgstr "" + +-#: ../version.c:4041 ++#: ../version.c:4043 + msgid "" + "\n" + "MS-Windows 32-bit GUI/console version" + msgstr "" + +-#: ../version.c:4046 ++#: ../version.c:4048 + msgid "" + "\n" + "MS-Windows ARM64 GUI version" + msgstr "" + +-#: ../version.c:4048 ++#: ../version.c:4050 + msgid "" + "\n" + "MS-Windows 64-bit GUI version" + msgstr "" + +-#: ../version.c:4051 ++#: ../version.c:4053 + msgid "" + "\n" + "MS-Windows 32-bit GUI version" + msgstr "" + +-#: ../version.c:4055 ++#: ../version.c:4057 + msgid " with OLE support" + msgstr "" + +-#: ../version.c:4060 ++#: ../version.c:4062 + msgid "" + "\n" + "MS-Windows ARM64 console version" + msgstr "" + +-#: ../version.c:4062 ++#: ../version.c:4064 + msgid "" + "\n" + "MS-Windows 64-bit console version" + msgstr "" + +-#: ../version.c:4065 ++#: ../version.c:4067 + msgid "" + "\n" + "MS-Windows 32-bit console version" + msgstr "" + +-#: ../version.c:4071 ++#: ../version.c:4073 + msgid "" + "\n" + "macOS version" + msgstr "" + +-#: ../version.c:4073 ++#: ../version.c:4075 + msgid "" + "\n" + "macOS version w/o darwin feat." + msgstr "" + +-#: ../version.c:4083 ++#: ../version.c:4085 + msgid "" + "\n" + "OpenVMS version" + msgstr "" + +-#: ../version.c:4098 ++#: ../version.c:4100 + msgid "" + "\n" + "Included patches: " + msgstr "" + +-#: ../version.c:4123 ++#: ../version.c:4125 + msgid "" + "\n" + "Extra patches: " + msgstr "" + +-#: ../version.c:4135 ../version.c:4446 ++#: ../version.c:4137 ../version.c:4448 + msgid "Modified by " + msgstr "" + +-#: ../version.c:4142 ++#: ../version.c:4144 + msgid "" + "\n" + "Compiled " + msgstr "" + +-#: ../version.c:4145 ++#: ../version.c:4147 + msgid "by " + msgstr "" + +-#: ../version.c:4157 ++#: ../version.c:4159 + msgid "" + "\n" + "Huge version " + msgstr "" + +-#: ../version.c:4159 ++#: ../version.c:4161 + msgid "" + "\n" + "Normal version " + msgstr "" + +-#: ../version.c:4161 ++#: ../version.c:4163 + msgid "" + "\n" + "Tiny version " + msgstr "" + +-#: ../version.c:4164 ++#: ../version.c:4166 + msgid "without GUI." + msgstr "" + +-#: ../version.c:4167 ++#: ../version.c:4169 + msgid "with GTK3 GUI." + msgstr "" + +-#: ../version.c:4169 ++#: ../version.c:4171 + msgid "with GTK2-GNOME GUI." + msgstr "" + +-#: ../version.c:4171 ++#: ../version.c:4173 + msgid "with GTK2 GUI." + msgstr "" + +-#: ../version.c:4174 ++#: ../version.c:4176 + msgid "with X11-Motif GUI." + msgstr "" + +-#: ../version.c:4176 ++#: ../version.c:4178 + msgid "with Haiku GUI." + msgstr "" + +-#: ../version.c:4178 ++#: ../version.c:4180 + msgid "with Photon GUI." + msgstr "" + +-#: ../version.c:4180 ++#: ../version.c:4182 + msgid "with GUI." + msgstr "" + +-#: ../version.c:4182 ++#: ../version.c:4184 + msgid " Features included (+) or not (-):\n" + msgstr "" + +-#: ../version.c:4189 ++#: ../version.c:4191 + msgid " system vimrc file: \"" + msgstr "" + +-#: ../version.c:4194 ++#: ../version.c:4196 + msgid " user vimrc file: \"" + msgstr "" + +-#: ../version.c:4199 ++#: ../version.c:4201 + msgid " 2nd user vimrc file: \"" + msgstr "" + +-#: ../version.c:4204 ../version.c:4211 ../version.c:4215 ++#: ../version.c:4206 ../version.c:4213 ../version.c:4217 + msgid " 3rd user vimrc file: \"" + msgstr "" + +-#: ../version.c:4207 ++#: ../version.c:4209 + msgid " 4th user vimrc file: \"" + msgstr "" + +-#: ../version.c:4220 ++#: ../version.c:4222 + msgid " user exrc file: \"" + msgstr "" + +-#: ../version.c:4225 ++#: ../version.c:4227 + msgid " 2nd user exrc file: \"" + msgstr "" + +-#: ../version.c:4231 ++#: ../version.c:4233 + msgid " system gvimrc file: \"" + msgstr "" + +-#: ../version.c:4235 ++#: ../version.c:4237 + msgid " user gvimrc file: \"" + msgstr "" + +-#: ../version.c:4239 ++#: ../version.c:4241 + msgid "2nd user gvimrc file: \"" + msgstr "" + +-#: ../version.c:4244 ++#: ../version.c:4246 + msgid "3rd user gvimrc file: \"" + msgstr "" + +-#: ../version.c:4249 ++#: ../version.c:4251 + msgid " defaults file: \"" + msgstr "" + +-#: ../version.c:4254 ++#: ../version.c:4256 + msgid " system menu file: \"" + msgstr "" + +-#: ../version.c:4262 ++#: ../version.c:4264 + msgid " fall-back for $VIM: \"" + msgstr "" + +-#: ../version.c:4268 ++#: ../version.c:4270 + msgid " f-b for $VIMRUNTIME: \"" + msgstr "" + +-#: ../version.c:4272 ++#: ../version.c:4274 + msgid "Compilation: " + msgstr "" + +-#: ../version.c:4278 ++#: ../version.c:4280 + msgid "Compiler: " + msgstr "" + +-#: ../version.c:4283 ++#: ../version.c:4285 + msgid "Linking: " + msgstr "" + +-#: ../version.c:4288 ++#: ../version.c:4290 + msgid " DEBUG BUILD" + msgstr "" + +-#: ../version.c:4324 ++#: ../version.c:4326 + msgid "VIM - Vi IMproved" + msgstr "" + +-#: ../version.c:4326 ++#: ../version.c:4328 + msgid "version " + msgstr "" + +-#: ../version.c:4327 ++#: ../version.c:4329 + msgid "by Bram Moolenaar et al." + msgstr "" + +-#: ../version.c:4331 ++#: ../version.c:4333 + msgid "Vim is open source and freely distributable" + msgstr "" + +-#: ../version.c:4333 ++#: ../version.c:4335 + msgid "Help poor children in Uganda!" + msgstr "" + +-#: ../version.c:4334 ++#: ../version.c:4336 + msgid "type :help iccf for information " + msgstr "" + +-#: ../version.c:4336 ++#: ../version.c:4338 + msgid "type :q to exit " + msgstr "" + +-#: ../version.c:4337 ++#: ../version.c:4339 + msgid "type :help or for on-line help" + msgstr "" + +-#: ../version.c:4338 ++#: ../version.c:4340 + msgid "type :help version9 for version info" + msgstr "" + +-#: ../version.c:4341 ++#: ../version.c:4343 + msgid "Running in Vi compatible mode" + msgstr "" + +-#: ../version.c:4342 ++#: ../version.c:4344 + msgid "type :set nocp for Vim defaults" + msgstr "" + +-#: ../version.c:4343 ++#: ../version.c:4345 + msgid "type :help cp-default for info on this" + msgstr "" + +-#: ../version.c:4358 ++#: ../version.c:4360 + msgid "menu Help->Orphans for information " + msgstr "" + +-#: ../version.c:4360 ++#: ../version.c:4362 + msgid "Running modeless, typed text is inserted" + msgstr "" + +-#: ../version.c:4361 ++#: ../version.c:4363 + msgid "menu Edit->Global Settings->Toggle Insert Mode " + msgstr "" + +-#: ../version.c:4362 ++#: ../version.c:4364 + msgid " for two modes " + msgstr "" + +-#: ../version.c:4366 ++#: ../version.c:4368 + msgid "menu Edit->Global Settings->Toggle Vi Compatible" + msgstr "" + +-#: ../version.c:4367 ++#: ../version.c:4369 + msgid " for Vim defaults " + msgstr "" + +-#: ../version.c:4408 ++#: ../version.c:4410 + msgid "Sponsor Vim development!" + msgstr "" + +-#: ../version.c:4409 ++#: ../version.c:4411 + msgid "Become a registered Vim user!" + msgstr "" + +-#: ../version.c:4412 ++#: ../version.c:4414 + msgid "type :help sponsor for information " + msgstr "" + +-#: ../version.c:4413 ++#: ../version.c:4415 + msgid "type :help register for information " + msgstr "" + +-#: ../version.c:4415 ++#: ../version.c:4417 + msgid "menu Help->Sponsor/Register for information " + msgstr "" + +diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak +index c0e3e60d31b809..e61bf2b50e8b59 100644 +--- a/src/testdir/Make_all.mak ++++ b/src/testdir/Make_all.mak +@@ -245,6 +245,7 @@ NEW_TESTS = \ + test_plugin_helptoc \ + test_plugin_man \ + test_plugin_matchparen \ ++ test_plugin_tar \ + test_plugin_termdebug \ + test_plugin_tohtml \ + test_plugin_tutor \ +@@ -517,6 +518,7 @@ NEW_TESTS_RES = \ + test_plugin_helptoc.res \ + test_plugin_man.res \ + test_plugin_matchparen.res \ ++ test_plugin_tar.res \ + test_plugin_termdebug.res \ + test_plugin_tohtml.res \ + test_plugin_tutor.res \ +diff --git a/src/testdir/samples/evil.tar b/src/testdir/samples/evil.tar +new file mode 100644 +index 0000000000000000000000000000000000000000..8cbc061fdf650a5182f29d18cfbfa2ab6cbfa05f +GIT binary patch +literal 10240 +zcmeIuJr2S!42EIPoFW%M%|GV}RYg0rMM)4GpRi?P>QL$RV#{9?S&%{j^9|TRXc*mHvugrUAPJ;{If~ZDPj7(=(wm3^GO<( +z&6QG=ytowYb}g+^;-c+hbUt)Rtn(P%T|GqJDxy{pZo`fcrjdbN}3f(#p5ORr!a|TQRo5EW0DgUrY9f9SkoJKmY**5I_I{ +o1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILK;YK}UM5a66aWAK + +literal 0 +HcmV?d00001 + +diff --git a/src/testdir/test_plugin_tar.vim b/src/testdir/test_plugin_tar.vim +new file mode 100644 +index 00000000000000..ebf74d7daab1ea +--- /dev/null ++++ b/src/testdir/test_plugin_tar.vim +@@ -0,0 +1,128 @@ ++vim9script ++ ++CheckExecutable tar ++CheckNotMSWindows ++ ++runtime plugin/tarPlugin.vim ++ ++def CopyFile(source: string) ++ if !filecopy($"samples/{source}", "X.tar") ++ assert_report($"Can't copy samples/{source}") ++ endif ++enddef ++ ++def g:Test_tar_basic() ++ CopyFile("sample.tar") ++ defer delete("X.tar") ++ defer delete("./testtar", 'rf') ++ e X.tar ++ ++ ### Check header ++ assert_match('^" tar\.vim version v\d\+', getline(1)) ++ assert_match('^" Browsing tarfile .*/X.tar', getline(2)) ++ assert_match('^" Select a file with cursor and press ENTER, "x" to extract a file', getline(3)) ++ assert_match('^$', getline(4)) ++ assert_match('testtar/', getline(5)) ++ assert_match('testtar/file1.txt', getline(6)) ++ ++ ### Check ENTER on header ++ :1 ++ exe ":normal \" ++ assert_equal("X.tar", @%) ++ ++ ### Check ENTER on file ++ :6 ++ exe ":normal \" ++ 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/ ++ assert_equal("some-content", getline(1)) ++ w! ++ assert_equal("tarfile::testtar/file1.txt", @%) ++ bw! ++ close ++ bw! ++ ++ e X.tar ++ :6 ++ exe "normal \" ++ assert_equal("some-content", getline(1)) ++ bw! ++ close ++ ++ ### Check extracting file ++ :5 ++ normal x ++ assert_true(filereadable("./testtar/file1.txt")) ++ bw! ++enddef ++ ++def g:Test_tar_evil() ++ CopyFile("evil.tar") ++ defer delete("X.tar") ++ defer delete("./etc", 'rf') ++ e X.tar ++ ++ ### Check header ++ assert_match('^" tar\.vim version v\d\+', getline(1)) ++ assert_match('^" Browsing tarfile .*/X.tar', getline(2)) ++ assert_match('^" Select a file with cursor and press ENTER, "x" to extract a file', getline(3)) ++ assert_match('^" Note: Path Traversal Attack detected', getline(4)) ++ assert_match('^$', getline(5)) ++ assert_match('/etc/ax-pwn', getline(6)) ++ ++ ### Check ENTER on header ++ :1 ++ exe ":normal \" ++ assert_equal("X.tar", @%) ++ assert_equal(1, b:leading_slash) ++ ++ ### Check ENTER on file ++ :6 ++ exe ":normal \" ++ assert_equal(1, b:leading_slash) ++ 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/ ++ assert_equal("none", getline(1)) ++ w! ++ assert_equal(1, b:leading_slash) ++ assert_equal("tarfile::/etc/ax-pwn", @%) ++ bw! ++ close ++ bw! ++ ++ # Writing was aborted ++ e X.tar ++ assert_match('^" Note: Path Traversal Attack detected', getline(4)) ++ :6 ++ exe "normal \" ++ assert_equal("something", getline(1)) ++ bw! ++ close ++ ++ ### Check extracting file ++ :5 ++ normal x ++ assert_true(filereadable("./etc/ax-pwn")) ++ ++ bw! ++enddef +diff --git a/src/version.c b/src/version.c +index 40c383a4b13c33..98c397cb610cc5 100644 +--- a/src/version.c ++++ b/src/version.c +@@ -719,6 +719,8 @@ static char *(features[]) = + + static int included_patches[] = + { /* Add new patch number below this line */ ++/**/ ++ 1552, + /**/ + 1551, + /**/ diff --git a/0011-fix-CVE-2025-53906.patch b/0011-fix-CVE-2025-53906.patch new file mode 100644 index 0000000000000000000000000000000000000000..276e0430e615f182dfbf0740e92d4e9ea00815c8 --- /dev/null +++ b/0011-fix-CVE-2025-53906.patch @@ -0,0 +1,787 @@ +From 586294a04179d855c3d1d4ee5ea83931963680b8 Mon Sep 17 00:00:00 2001 +From: Christian Brabandt +Date: Tue, 15 Jul 2025 21:43:01 +0200 +Subject: [PATCH] patch 9.1.1551: [security]: path traversal issue in zip.vim + +Problem: [security]: path traversal issue in zip.vim (@ax) +Solution: drop leading ../ on write of zipfiles, don't forcefully + overwrite existing files + +A zip plugin which contains filenames with leading '../' may cause +confusion as to where the content will be extracted. Let's drop such +things and make sure we use a relative filename instead and don't +forcefully overwrite temporary files. Also, warn the user of such +things. + +related: #17733 + +Signed-off-by: Christian Brabandt +--- + Filelist | 1 + + runtime/autoload/zip.vim | 94 ++++++++++---------- + runtime/doc/pi_zip.txt | 14 ++- + src/po/vim.pot | 146 ++++++++++++++++---------------- + src/testdir/samples/evil.zip | Bin 0 -> 148 bytes + src/testdir/test_plugin_zip.vim | 56 +++++++----- + src/version.c | 2 + + 7 files changed, 177 insertions(+), 136 deletions(-) + create mode 100644 src/testdir/samples/evil.zip + +diff --git a/Filelist b/Filelist +index 1d5a1e6de5a017..3c9f783018f5a7 100644 +--- a/Filelist ++++ b/Filelist +@@ -212,6 +212,7 @@ SRC_ALL = \ + src/testdir/samples/*.html \ + src/testdir/samples/*.txt \ + src/testdir/samples/*.vim \ ++ src/testdir/samples/evil.zip \ + src/testdir/samples/poc.zip \ + src/testdir/samples/test.zip \ + src/testdir/samples/test000 \ +diff --git a/runtime/autoload/zip.vim b/runtime/autoload/zip.vim +index dae4ddeb9921ee..c46ec4470836ce 100644 +--- a/runtime/autoload/zip.vim ++++ b/runtime/autoload/zip.vim +@@ -15,6 +15,7 @@ + " 2024 Aug 18 by Vim Project: correctly handle special globbing chars + " 2024 Aug 21 by Vim Project: simplify condition to detect MS-Windows + " 2025 Mar 11 by Vim Project: handle filenames with leading '-' correctly ++" 2025 Jul 12 by Vim Project: drop ../ on write to prevent path traversal attacks + " License: Vim License (see vim's :help license) + " Copyright: Copyright (C) 2005-2019 Charles E. Campbell {{{1 + " Permission is hereby granted to use and distribute this code, +@@ -236,59 +237,62 @@ endfun + " zip#Write: {{{2 + fun! zip#Write(fname) + let dict = s:SetSaneOpts() ++ let need_rename = 0 + defer s:RestoreOpts(dict) + + " sanity checks + if !executable(substitute(g:zip_zipcmd,'\s\+.*$','','')) +- call s:Mess('Error', "***error*** (zip#Write) sorry, your system doesn't appear to have the ".g:zip_zipcmd." program") +- return +- endif +- if !exists("*mkdir") +- call s:Mess('Error', "***error*** (zip#Write) sorry, mkdir() doesn't work on your system") +- return ++ call s:Mess('Error', "***error*** (zip#Write) sorry, your system doesn't appear to have the ".g:zip_zipcmd." program") ++ return + endif + + let curdir= getcwd() + let tmpdir= tempname() + if tmpdir =~ '\.' +- let tmpdir= substitute(tmpdir,'\.[^.]*$','','e') ++ let tmpdir= substitute(tmpdir,'\.[^.]*$','','e') + endif + call mkdir(tmpdir,"p") + + " attempt to change to the indicated directory + if s:ChgDir(tmpdir,s:ERROR,"(zip#Write) cannot cd to temporary directory") +- return ++ return + endif + + " place temporary files under .../_ZIPVIM_/ + if isdirectory("_ZIPVIM_") +- call delete("_ZIPVIM_", "rf") ++ call delete("_ZIPVIM_", "rf") + endif + call mkdir("_ZIPVIM_") + cd _ZIPVIM_ + + if has("unix") +- let zipfile = substitute(a:fname,'zipfile://\(.\{-}\)::[^\\].*$','\1','') +- let fname = substitute(a:fname,'zipfile://.\{-}::\([^\\].*\)$','\1','') ++ let zipfile = substitute(a:fname,'zipfile://\(.\{-}\)::[^\\].*$','\1','') ++ let fname = substitute(a:fname,'zipfile://.\{-}::\([^\\].*\)$','\1','') + else +- let zipfile = substitute(a:fname,'^.\{-}zipfile://\(.\{-}\)::[^\\].*$','\1','') +- let fname = substitute(a:fname,'^.\{-}zipfile://.\{-}::\([^\\].*\)$','\1','') ++ let zipfile = substitute(a:fname,'^.\{-}zipfile://\(.\{-}\)::[^\\].*$','\1','') ++ let fname = substitute(a:fname,'^.\{-}zipfile://.\{-}::\([^\\].*\)$','\1','') ++ endif ++ if fname =~ '^[.]\{1,2}/' ++ call system(g:zip_zipcmd." -d ".s:Escape(fnamemodify(zipfile,":p"),0)." ".s:Escape(fname,0)) ++ let fname = fname->substitute('^\([.]\{1,2}/\)\+', '', 'g') ++ let need_rename = 1 + endif + + if fname =~ '/' +- let dirpath = substitute(fname,'/[^/]\+$','','e') +- if has("win32unix") && executable("cygpath") ++ let dirpath = substitute(fname,'/[^/]\+$','','e') ++ if has("win32unix") && executable("cygpath") + let dirpath = substitute(system("cygpath ".s:Escape(dirpath,0)),'\n','','e') +- endif +- call mkdir(dirpath,"p") ++ endif ++ call mkdir(dirpath,"p") + endif + if zipfile !~ '/' +- let zipfile= curdir.'/'.zipfile ++ let zipfile= curdir.'/'.zipfile + endif + +- exe "w! ".fnameescape(fname) ++ " don't overwrite files forcefully ++ exe "w ".fnameescape(fname) + if has("win32unix") && executable("cygpath") +- let zipfile = substitute(system("cygpath ".s:Escape(zipfile,0)),'\n','','e') ++ let zipfile = substitute(system("cygpath ".s:Escape(zipfile,0)),'\n','','e') + endif + + if (has("win32") || has("win95") || has("win64") || has("win16")) && &shell !~? 'sh$' +@@ -297,21 +301,24 @@ fun! zip#Write(fname) + + call system(g:zip_zipcmd." -u ".s:Escape(fnamemodify(zipfile,":p"),0)." ".s:Escape(fname,0)) + if v:shell_error != 0 +- call s:Mess('Error', "***error*** (zip#Write) sorry, unable to update ".zipfile." with ".fname) ++ call s:Mess('Error', "***error*** (zip#Write) sorry, unable to update ".zipfile." with ".fname) + + elseif s:zipfile_{winnr()} =~ '^\a\+://' +- " support writing zipfiles across a network +- let netzipfile= s:zipfile_{winnr()} +- 1split|enew +- let binkeep= &binary +- let eikeep = &ei +- set binary ei=all +- exe "noswapfile e! ".fnameescape(zipfile) +- call netrw#NetWrite(netzipfile) +- let &ei = eikeep +- let &binary = binkeep +- q! +- unlet s:zipfile_{winnr()} ++ " support writing zipfiles across a network ++ let netzipfile= s:zipfile_{winnr()} ++ 1split|enew ++ let binkeep= &binary ++ let eikeep = &ei ++ set binary ei=all ++ exe "noswapfile e! ".fnameescape(zipfile) ++ call netrw#NetWrite(netzipfile) ++ let &ei = eikeep ++ let &binary = binkeep ++ q! ++ unlet s:zipfile_{winnr()} ++ elseif need_rename ++ exe $"sil keepalt file {fnameescape($"zipfile://{zipfile}::{fname}")}" ++ call s:Mess('Warning', "***error*** (zip#Browse) Path Traversal Attack detected, dropping relative path") + endif + + " cleanup and restore current directory +@@ -320,7 +327,6 @@ fun! zip#Write(fname) + call s:ChgDir(curdir,s:WARNING,"(zip#Write) unable to return to ".curdir."!") + call delete(tmpdir, "rf") + setlocal nomod +- + endfun + + " --------------------------------------------------------------------- +@@ -333,15 +339,18 @@ fun! zip#Extract() + + " sanity check + if fname =~ '^"' +- return ++ return + endif + if fname =~ '/$' +- call s:Mess('Error', "***error*** (zip#Extract) Please specify a file, not a directory") +- return ++ call s:Mess('Error', "***error*** (zip#Extract) Please specify a file, not a directory") ++ return ++ elseif fname =~ '^[.]\?[.]/' ++ call s:Mess('Error', "***error*** (zip#Browse) Path Traversal Attack detected, not extracting!") ++ return + endif + if filereadable(fname) +- call s:Mess('Error', "***error*** (zip#Extract) <" .. fname .."> already exists in directory, not overwriting!") +- return ++ call s:Mess('Error', "***error*** (zip#Extract) <" .. fname .."> already exists in directory, not overwriting!") ++ return + endif + let target = fname->substitute('\[', '[[]', 'g') + " unzip 6.0 does not support -- to denote end-of-arguments +@@ -363,13 +372,12 @@ fun! zip#Extract() + " extract the file mentioned under the cursor + call system($"{g:zip_extractcmd} -o {shellescape(b:zipfile)} {target}") + if v:shell_error != 0 +- call s:Mess('Error', "***error*** ".g:zip_extractcmd." ".b:zipfile." ".fname.": failed!") ++ call s:Mess('Error', "***error*** ".g:zip_extractcmd." ".b:zipfile." ".fname.": failed!") + elseif !filereadable(fname) +- call s:Mess('Error', "***error*** attempted to extract ".fname." but it doesn't appear to be present!") ++ call s:Mess('Error', "***error*** attempted to extract ".fname." but it doesn't appear to be present!") + else +- echomsg "***note*** successfully extracted ".fname ++ echomsg "***note*** successfully extracted ".fname + endif +- + endfun + + " --------------------------------------------------------------------- +diff --git a/runtime/doc/pi_zip.txt b/runtime/doc/pi_zip.txt +index afc2d0eebe240f..0f7ef4ec3be3e4 100644 +--- a/runtime/doc/pi_zip.txt ++++ b/runtime/doc/pi_zip.txt +@@ -1,4 +1,4 @@ +-*pi_zip.txt* For Vim version 9.1. Last change: 2025 Apr 02 ++*pi_zip.txt* For Vim version 9.1. Last change: 2025 Jul 15 + + +====================+ + | Zip File Interface | +@@ -111,6 +111,18 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright* + + ============================================================================== + 4. History *zip-history* {{{1 ++ unreleased: ++ Jul 12, 2025 * drop ../ on write to prevent path traversal attacks ++ Mar 11, 2025 * handle filenames with leading '-' correctly ++ Aug 21, 2024 * simplify condition to detect MS-Windows ++ Aug 18, 2024 * correctly handle special globbing chars ++ Aug 05, 2024 * clean-up and make it work with shellslash on Windows ++ Aug 05, 2024 * workaround for the FreeBSD's unzip ++ Aug 04, 2024 * escape '[' in name of file to be extracted ++ Jul 30, 2024 * fix opening remote zipfile ++ Jul 24, 2024 * use delete() function ++ Jul 23, 2024 * fix 'x' command ++ Jun 16, 2024 * handle whitespace on Windows properly (#14998) + v33 Dec 07, 2021 * *.xlam mentioned twice in zipPlugin + v32 Oct 22, 2021 * to avoid an issue with a vim 8.2 patch, zipfile: has + been changed to zipfile:// . This often shows up +diff --git a/src/po/vim.pot b/src/po/vim.pot +index 6bbe365910375e..7d28c12df7a4ee 100644 +--- a/src/po/vim.pot ++++ b/src/po/vim.pot +@@ -8,7 +8,7 @@ msgid "" + msgstr "" + "Project-Id-Version: PACKAGE VERSION\n" + "Report-Msgid-Bugs-To: \n" +-"POT-Creation-Date: 2025-07-15 21:26+0200\n" ++"POT-Creation-Date: 2025-07-15 21:42+0200\n" + "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" + "Last-Translator: FULL NAME \n" + "Language-Team: LANGUAGE \n" +@@ -4257,327 +4257,327 @@ msgstr "" + msgid "%s (%s, compiled %s)" + msgstr "" + +-#: ../version.c:4034 ++#: ../version.c:4036 + msgid "" + "\n" + "MS-Windows ARM64 GUI/console version" + msgstr "" + +-#: ../version.c:4036 ++#: ../version.c:4038 + msgid "" + "\n" + "MS-Windows 64-bit GUI/console version" + msgstr "" + +-#: ../version.c:4039 ++#: ../version.c:4041 + msgid "" + "\n" + "MS-Windows 32-bit GUI/console version" + msgstr "" + +-#: ../version.c:4044 ++#: ../version.c:4046 + msgid "" + "\n" + "MS-Windows ARM64 GUI version" + msgstr "" + +-#: ../version.c:4046 ++#: ../version.c:4048 + msgid "" + "\n" + "MS-Windows 64-bit GUI version" + msgstr "" + +-#: ../version.c:4049 ++#: ../version.c:4051 + msgid "" + "\n" + "MS-Windows 32-bit GUI version" + msgstr "" + +-#: ../version.c:4053 ++#: ../version.c:4055 + msgid " with OLE support" + msgstr "" + +-#: ../version.c:4058 ++#: ../version.c:4060 + msgid "" + "\n" + "MS-Windows ARM64 console version" + msgstr "" + +-#: ../version.c:4060 ++#: ../version.c:4062 + msgid "" + "\n" + "MS-Windows 64-bit console version" + msgstr "" + +-#: ../version.c:4063 ++#: ../version.c:4065 + msgid "" + "\n" + "MS-Windows 32-bit console version" + msgstr "" + +-#: ../version.c:4069 ++#: ../version.c:4071 + msgid "" + "\n" + "macOS version" + msgstr "" + +-#: ../version.c:4071 ++#: ../version.c:4073 + msgid "" + "\n" + "macOS version w/o darwin feat." + msgstr "" + +-#: ../version.c:4081 ++#: ../version.c:4083 + msgid "" + "\n" + "OpenVMS version" + msgstr "" + +-#: ../version.c:4096 ++#: ../version.c:4098 + msgid "" + "\n" + "Included patches: " + msgstr "" + +-#: ../version.c:4121 ++#: ../version.c:4123 + msgid "" + "\n" + "Extra patches: " + msgstr "" + +-#: ../version.c:4133 ../version.c:4444 ++#: ../version.c:4135 ../version.c:4446 + msgid "Modified by " + msgstr "" + +-#: ../version.c:4140 ++#: ../version.c:4142 + msgid "" + "\n" + "Compiled " + msgstr "" + +-#: ../version.c:4143 ++#: ../version.c:4145 + msgid "by " + msgstr "" + +-#: ../version.c:4155 ++#: ../version.c:4157 + msgid "" + "\n" + "Huge version " + msgstr "" + +-#: ../version.c:4157 ++#: ../version.c:4159 + msgid "" + "\n" + "Normal version " + msgstr "" + +-#: ../version.c:4159 ++#: ../version.c:4161 + msgid "" + "\n" + "Tiny version " + msgstr "" + +-#: ../version.c:4162 ++#: ../version.c:4164 + msgid "without GUI." + msgstr "" + +-#: ../version.c:4165 ++#: ../version.c:4167 + msgid "with GTK3 GUI." + msgstr "" + +-#: ../version.c:4167 ++#: ../version.c:4169 + msgid "with GTK2-GNOME GUI." + msgstr "" + +-#: ../version.c:4169 ++#: ../version.c:4171 + msgid "with GTK2 GUI." + msgstr "" + +-#: ../version.c:4172 ++#: ../version.c:4174 + msgid "with X11-Motif GUI." + msgstr "" + +-#: ../version.c:4174 ++#: ../version.c:4176 + msgid "with Haiku GUI." + msgstr "" + +-#: ../version.c:4176 ++#: ../version.c:4178 + msgid "with Photon GUI." + msgstr "" + +-#: ../version.c:4178 ++#: ../version.c:4180 + msgid "with GUI." + msgstr "" + +-#: ../version.c:4180 ++#: ../version.c:4182 + msgid " Features included (+) or not (-):\n" + msgstr "" + +-#: ../version.c:4187 ++#: ../version.c:4189 + msgid " system vimrc file: \"" + msgstr "" + +-#: ../version.c:4192 ++#: ../version.c:4194 + msgid " user vimrc file: \"" + msgstr "" + +-#: ../version.c:4197 ++#: ../version.c:4199 + msgid " 2nd user vimrc file: \"" + msgstr "" + +-#: ../version.c:4202 ../version.c:4209 ../version.c:4213 ++#: ../version.c:4204 ../version.c:4211 ../version.c:4215 + msgid " 3rd user vimrc file: \"" + msgstr "" + +-#: ../version.c:4205 ++#: ../version.c:4207 + msgid " 4th user vimrc file: \"" + msgstr "" + +-#: ../version.c:4218 ++#: ../version.c:4220 + msgid " user exrc file: \"" + msgstr "" + +-#: ../version.c:4223 ++#: ../version.c:4225 + msgid " 2nd user exrc file: \"" + msgstr "" + +-#: ../version.c:4229 ++#: ../version.c:4231 + msgid " system gvimrc file: \"" + msgstr "" + +-#: ../version.c:4233 ++#: ../version.c:4235 + msgid " user gvimrc file: \"" + msgstr "" + +-#: ../version.c:4237 ++#: ../version.c:4239 + msgid "2nd user gvimrc file: \"" + msgstr "" + +-#: ../version.c:4242 ++#: ../version.c:4244 + msgid "3rd user gvimrc file: \"" + msgstr "" + +-#: ../version.c:4247 ++#: ../version.c:4249 + msgid " defaults file: \"" + msgstr "" + +-#: ../version.c:4252 ++#: ../version.c:4254 + msgid " system menu file: \"" + msgstr "" + +-#: ../version.c:4260 ++#: ../version.c:4262 + msgid " fall-back for $VIM: \"" + msgstr "" + +-#: ../version.c:4266 ++#: ../version.c:4268 + msgid " f-b for $VIMRUNTIME: \"" + msgstr "" + +-#: ../version.c:4270 ++#: ../version.c:4272 + msgid "Compilation: " + msgstr "" + +-#: ../version.c:4276 ++#: ../version.c:4278 + msgid "Compiler: " + msgstr "" + +-#: ../version.c:4281 ++#: ../version.c:4283 + msgid "Linking: " + msgstr "" + +-#: ../version.c:4286 ++#: ../version.c:4288 + msgid " DEBUG BUILD" + msgstr "" + +-#: ../version.c:4322 ++#: ../version.c:4324 + msgid "VIM - Vi IMproved" + msgstr "" + +-#: ../version.c:4324 ++#: ../version.c:4326 + msgid "version " + msgstr "" + +-#: ../version.c:4325 ++#: ../version.c:4327 + msgid "by Bram Moolenaar et al." + msgstr "" + +-#: ../version.c:4329 ++#: ../version.c:4331 + msgid "Vim is open source and freely distributable" + msgstr "" + +-#: ../version.c:4331 ++#: ../version.c:4333 + msgid "Help poor children in Uganda!" + msgstr "" + +-#: ../version.c:4332 ++#: ../version.c:4334 + msgid "type :help iccf for information " + msgstr "" + +-#: ../version.c:4334 ++#: ../version.c:4336 + msgid "type :q to exit " + msgstr "" + +-#: ../version.c:4335 ++#: ../version.c:4337 + msgid "type :help or for on-line help" + msgstr "" + +-#: ../version.c:4336 ++#: ../version.c:4338 + msgid "type :help version9 for version info" + msgstr "" + +-#: ../version.c:4339 ++#: ../version.c:4341 + msgid "Running in Vi compatible mode" + msgstr "" + +-#: ../version.c:4340 ++#: ../version.c:4342 + msgid "type :set nocp for Vim defaults" + msgstr "" + +-#: ../version.c:4341 ++#: ../version.c:4343 + msgid "type :help cp-default for info on this" + msgstr "" + +-#: ../version.c:4356 ++#: ../version.c:4358 + msgid "menu Help->Orphans for information " + msgstr "" + +-#: ../version.c:4358 ++#: ../version.c:4360 + msgid "Running modeless, typed text is inserted" + msgstr "" + +-#: ../version.c:4359 ++#: ../version.c:4361 + msgid "menu Edit->Global Settings->Toggle Insert Mode " + msgstr "" + +-#: ../version.c:4360 ++#: ../version.c:4362 + msgid " for two modes " + msgstr "" + +-#: ../version.c:4364 ++#: ../version.c:4366 + msgid "menu Edit->Global Settings->Toggle Vi Compatible" + msgstr "" + +-#: ../version.c:4365 ++#: ../version.c:4367 + msgid " for Vim defaults " + msgstr "" + +-#: ../version.c:4406 ++#: ../version.c:4408 + msgid "Sponsor Vim development!" + msgstr "" + +-#: ../version.c:4407 ++#: ../version.c:4409 + msgid "Become a registered Vim user!" + msgstr "" + +-#: ../version.c:4410 ++#: ../version.c:4412 + msgid "type :help sponsor for information " + msgstr "" + +-#: ../version.c:4411 ++#: ../version.c:4413 + msgid "type :help register for information " + msgstr "" + +-#: ../version.c:4413 ++#: ../version.c:4415 + msgid "menu Help->Sponsor/Register for information " + msgstr "" + +diff --git a/src/testdir/samples/evil.zip b/src/testdir/samples/evil.zip +new file mode 100644 +index 0000000000000000000000000000000000000000..e0a7f96141eed3ec5de68babd9d8bef7c72e5845 +GIT binary patch +literal 148 +zcmWIWW@Zs#00E(lH&MUiHn*|?*&r+i#Cm%AaFkk-te;q+TTq?{M5!rU0p5&EBFwn; +h0=0v|mPQZ-HwMT+m=xg63bKlU5eQv?v=xZM005CM8qELz + +literal 0 +HcmV?d00001 + +diff --git a/src/testdir/test_plugin_zip.vim b/src/testdir/test_plugin_zip.vim +index 516f54e3a9d7d6..08f8223b60a649 100644 +--- a/src/testdir/test_plugin_zip.vim ++++ b/src/testdir/test_plugin_zip.vim +@@ -1,21 +1,23 @@ ++vim9script ++ + CheckExecutable unzip + +-if 0 " Find uncovered line ++if 0 # Find uncovered line + profile start zip_profile + profile! file */zip*.vim + endif + + runtime plugin/zipPlugin.vim + +-def Test_zip_basic() +- +- ### get our zip file +- if !filecopy("samples/test.zip", "X.zip") +- assert_report("Can't copy samples/test.zip") +- return ++def CopyZipFile(source: string) ++ if !filecopy($"samples/{source}", "X.zip") ++ assert_report($"Can't copy samples/{source}.zip") + endif +- defer delete("X.zip") ++enddef + ++def g:Test_zip_basic() ++ CopyZipFile("test.zip") ++ defer delete("X.zip") + e X.zip + + ### Check header +@@ -136,15 +138,11 @@ def Test_zip_basic() + bw + enddef + +-def Test_zip_glob_fname() ++def g:Test_zip_glob_fname() + CheckNotMSWindows + # does not work on Windows, why? + +- ### copy sample zip file +- if !filecopy("samples/testa.zip", "X.zip") +- assert_report("Can't copy samples/testa.zip") +- return +- endif ++ CopyZipFile("testa.zip") + defer delete("X.zip") + defer delete('zipglob', 'rf') + +@@ -234,14 +232,11 @@ def Test_zip_glob_fname() + bw + enddef + +-def Test_zip_fname_leading_hyphen() ++def g:Test_zip_fname_leading_hyphen() + CheckNotMSWindows + + ### copy sample zip file +- if !filecopy("samples/poc.zip", "X.zip") +- assert_report("Can't copy samples/poc.zip") +- return +- endif ++ CopyZipFile("poc.zip") + defer delete("X.zip") + defer delete('-d', 'rf') + defer delete('/tmp/pwned', 'rf') +@@ -256,3 +251,26 @@ def Test_zip_fname_leading_hyphen() + assert_false(filereadable('/tmp/pwned')) + bw + enddef ++ ++def g:Test_zip_fname_evil_path() ++ CheckNotMSWindows ++ # needed for writing the zip file ++ CheckExecutable zip ++ ++ CopyZipFile("evil.zip") ++ defer delete("X.zip") ++ e X.zip ++ ++ :1 ++ var fname = 'pwn' ++ search('\V' .. fname) ++ normal x ++ assert_false(filereadable('/etc/ax-pwn')) ++ var mess = execute(':mess') ++ assert_match('Path Traversal Attack', mess) ++ ++ exe ":normal \" ++ :w ++ assert_match('zipfile://.*::etc/ax-pwn', @%) ++ bw ++enddef +diff --git a/src/version.c b/src/version.c +index 9fd90fa34a45d8..40c383a4b13c33 100644 +--- a/src/version.c ++++ b/src/version.c +@@ -719,6 +719,8 @@ static char *(features[]) = + + static int included_patches[] = + { /* Add new patch number below this line */ ++/**/ ++ 1551, + /**/ + 1550, + /**/ diff --git a/vim.spec b/vim.spec index 84e64924cb7e29f07a2b4e5c5960223202c990e5..e94d80a282f77e4a2e25baaeb132ca8d9fd23740 100644 --- a/vim.spec +++ b/vim.spec @@ -1,4 +1,4 @@ -%define anolis_release 6 +%define anolis_release 7 %bcond_without gui %bcond_with default_editor @@ -819,6 +819,9 @@ touch %{buildroot}/%{data_dir}/vimfiles/doc/tags %endif %changelog +* Thu Jul 24 2025 zjl002254423 -3:9.0.2092-7 +- Add patch to fix CVE-2025-53905,CVE-2025-53906 + * Mon Jul 07 2025 wenxin - 3:9.0.2092-6 - Add patch to Fix CVE-2024-43802, CVE-2024-43374