mirror of
https://github.com/neovim/neovim.git
synced 2026-07-18 15:11:40 +00:00
vim-patch:9.2.0678: [security]: potential powershell code execution in zip.vim (#40343)
Problem: [security]: potential powershell code execution in zip.vim
(DDugs)
Solution: Cleanup zip.vim, introduce PSEscape() to escape() potential powershell code,
use consistent s:Escape() in the various PowerShell functions
Github Security Advisory:
https://github.com/vim/vim/security/advisories/GHSA-x5fg-h5w9-9frf
b2cc9be119
Co-authored-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
" 2026 Apr 05 by Vim Project: Detect more path traversal attacks
|
||||
" 2026 Apr 14 by Vim Project: Detect more path traversal attacks on Windows
|
||||
" 2026 Apr 15 by Vim Project: Detect more path traversal attacks on Windows
|
||||
" 2026 Jun 20 by Vim Project: Fix wrong escaping for the powershell calls
|
||||
" 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,
|
||||
@@ -51,15 +52,6 @@ let s:NOTE = 0
|
||||
|
||||
" ---------------------------------------------------------------------
|
||||
" Global Values: {{{1
|
||||
if !exists("g:zip_shq")
|
||||
if &shq != ""
|
||||
let g:zip_shq= &shq
|
||||
elseif has("unix")
|
||||
let g:zip_shq= "'"
|
||||
else
|
||||
let g:zip_shq= '"'
|
||||
endif
|
||||
endif
|
||||
if !exists("g:zip_zipcmd")
|
||||
let g:zip_zipcmd= "zip"
|
||||
endif
|
||||
@@ -135,7 +127,7 @@ function! s:ZipBrowsePS(zipfile)
|
||||
" Browse the contents of a zip file using PowerShell's
|
||||
" Equivalent `unzip -Z1 -- zipfile`
|
||||
let cmds = [
|
||||
\ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:Escape(a:zipfile, 1) . ');',
|
||||
\ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:PSEscape(a:zipfile) . ');',
|
||||
\ '$zip.Entries | ForEach-Object { $_.FullName };',
|
||||
\ '$zip.Dispose()'
|
||||
\ ]
|
||||
@@ -149,16 +141,16 @@ function! s:ZipReadPS(zipfile, fname, tempfile)
|
||||
call s:Mess('WarningMsg', "***warning*** PowerShell can display, but cannot update, files in archive subfolders")
|
||||
endif
|
||||
let cmds = [
|
||||
\ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:Escape(a:zipfile, 1) . ');',
|
||||
\ '$fileEntry = $zip.Entries | Where-Object { $_.FullName -eq ' . s:Escape(a:fname, 1) . ' };',
|
||||
\ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:PSEscape(a:zipfile) . ');',
|
||||
\ '$fileEntry = $zip.Entries | Where-Object { $_.FullName -eq ' . s:PSEscape(a:fname) . ' };',
|
||||
\ '$stream = $fileEntry.Open();',
|
||||
\ '$fileStream = [System.IO.File]::Create(' . s:Escape(a:tempfile, 1) . ');',
|
||||
\ '$fileStream = [System.IO.File]::Create(' . s:PSEscape(a:tempfile) . ');',
|
||||
\ '$stream.CopyTo($fileStream);',
|
||||
\ '$fileStream.Close();',
|
||||
\ '$stream.Close();',
|
||||
\ '$zip.Dispose()'
|
||||
\ ]
|
||||
return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1)
|
||||
return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '))
|
||||
endfunction
|
||||
|
||||
function! s:ZipUpdatePS(zipfile, fname)
|
||||
@@ -168,7 +160,7 @@ function! s:ZipUpdatePS(zipfile, fname)
|
||||
call s:Mess('Error', "***error*** PowerShell cannot update files in archive subfolders")
|
||||
return ':'
|
||||
endif
|
||||
return 'Compress-Archive -Path ' . a:fname . ' -Update -DestinationPath ' . a:zipfile
|
||||
return 'Compress-Archive -Path ' . s:PSEscape(a:fname) . ' -Update -DestinationPath ' . s:PSEscape(a:zipfile)
|
||||
endfunction
|
||||
|
||||
function! s:ZipExtractFilePS(zipfile, fname)
|
||||
@@ -179,16 +171,16 @@ function! s:ZipExtractFilePS(zipfile, fname)
|
||||
return ':'
|
||||
endif
|
||||
let cmds = [
|
||||
\ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:Escape(a:zipfile, 1) . ');',
|
||||
\ '$fileEntry = $zip.Entries | Where-Object { $_.FullName -eq ' . a:fname . ' };',
|
||||
\ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:PSEscape(a:zipfile) . ');',
|
||||
\ '$fileEntry = $zip.Entries | Where-Object { $_.FullName -eq ' . s:PSEscape(a:fname) . ' };',
|
||||
\ '$stream = $fileEntry.Open();',
|
||||
\ '$fileStream = [System.IO.File]::Create(' . a:fname . ');',
|
||||
\ '$fileStream = [System.IO.File]::Create(' . s:PSEscape(a:fname) . ');',
|
||||
\ '$stream.CopyTo($fileStream);',
|
||||
\ '$fileStream.Close();',
|
||||
\ '$stream.Close();',
|
||||
\ '$zip.Dispose()'
|
||||
\ ]
|
||||
return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1)
|
||||
return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '))
|
||||
endfunction
|
||||
|
||||
function! s:ZipDeleteFilePS(zipfile, fname)
|
||||
@@ -196,12 +188,12 @@ function! s:ZipDeleteFilePS(zipfile, fname)
|
||||
" Equivalent to `zip -d zipfile fname`
|
||||
let cmds = [
|
||||
\ 'Add-Type -AssemblyName System.IO.Compression.FileSystem;',
|
||||
\ '$zip = [System.IO.Compression.ZipFile]::Open(' . s:Escape(a:zipfile, 1) . ', ''Update'');',
|
||||
\ '$entry = $zip.Entries | Where-Object { $_.Name -eq ' . s:Escape(a:fname, 1) . ' };',
|
||||
\ '$zip = [System.IO.Compression.ZipFile]::Open(' . s:PSEscape(a:zipfile) . ', ''Update'');',
|
||||
\ '$entry = $zip.Entries | Where-Object { $_.Name -eq ' . s:PSEscape(a:fname) . ' };',
|
||||
\ 'if ($entry) { $entry.Delete(); $zip.Dispose() }',
|
||||
\ 'else { $zip.Dispose() }'
|
||||
\ ]
|
||||
return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1)
|
||||
return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '))
|
||||
endfunction
|
||||
|
||||
" ----------------
|
||||
@@ -341,9 +333,9 @@ fun! zip#Read(fname,mode)
|
||||
let temp = tempname()
|
||||
let fn = expand('%:p')
|
||||
|
||||
let gnu_cmd = g:zip_unzipcmd . ' -p -- ' . s:Escape(zipfile, 0) . ' ' . s:Escape(fname, 0) . ' > ' . s:Escape(temp, 0)
|
||||
let gnu_cmd = 'call system(''' . substitute(gnu_cmd, "'", "''", 'g') . ''')'
|
||||
let ps_cmd = 'sil !' . s:ZipReadPS(zipfile, fname, temp)
|
||||
let gnu_cmd = g:zip_unzipcmd . ' -p -- ' . s:Escape(zipfile) . ' ' . s:Escape(fname) . ' > ' . s:Escape(temp)
|
||||
let gnu_cmd = 'call system(' . string(gnu_cmd) . ')'
|
||||
let ps_cmd = 'call system(' . string(s:ZipReadPS(zipfile, fname, temp)) . ')'
|
||||
call s:TryExecGnuFallBackToPs(g:zip_unzipcmd, gnu_cmd, ps_cmd)
|
||||
|
||||
sil exe 'keepalt file '.temp
|
||||
@@ -415,9 +407,9 @@ fun! zip#Write(fname)
|
||||
endif
|
||||
endif
|
||||
if fname =~ '^[.]\{1,2}/'
|
||||
let gnu_cmd = g:zip_zipcmd . ' -d ' . s:Escape(fnamemodify(zipfile,":p"),0) . ' ' . s:Escape(fname,0)
|
||||
let gnu_cmd = 'call system(''' . substitute(gnu_cmd, "'", "''", 'g') . ''')'
|
||||
let ps_cmd = $"call system({s:Escape(s:ZipDeleteFilePS(zipfile, fname), 1)})"
|
||||
let gnu_cmd = g:zip_zipcmd . ' -d ' . s:Escape(fnamemodify(zipfile,":p")) . ' ' . s:Escape(fname)
|
||||
let gnu_cmd = 'call system(' . string(gnu_cmd) . ')'
|
||||
let ps_cmd = $"call system({string(s:ZipDeleteFilePS(zipfile, fname))})"
|
||||
call s:TryExecGnuFallBackToPs(g:zip_zipcmd, gnu_cmd, ps_cmd)
|
||||
let fname = fname->substitute('^\([.]\{1,2}/\)\+', '', 'g')
|
||||
let need_rename = 1
|
||||
@@ -426,7 +418,7 @@ fun! zip#Write(fname)
|
||||
if fname =~ '/'
|
||||
let dirpath = substitute(fname,'/[^/]\+$','','e')
|
||||
if has("win32unix") && executable("cygpath")
|
||||
let dirpath = substitute(system("cygpath ".s:Escape(dirpath,0)),'\n','','e')
|
||||
let dirpath = substitute(system("cygpath ".s:Escape(dirpath)),'\n','','e')
|
||||
endif
|
||||
call mkdir(dirpath,"p")
|
||||
endif
|
||||
@@ -437,16 +429,17 @@ fun! zip#Write(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)),'\n','','e')
|
||||
endif
|
||||
|
||||
if (has("win32") || has("win95") || has("win64") || has("win16")) && &shell !~? 'sh$'
|
||||
let fname = substitute(fname, '[', '[[]', 'g')
|
||||
endif
|
||||
|
||||
let gnu_cmd = g:zip_zipcmd . ' -u '. s:Escape(fnamemodify(zipfile,":p"),0) . ' ' . s:Escape(fname,0)
|
||||
let gnu_cmd = g:zip_zipcmd . ' -u '. s:Escape(fnamemodify(zipfile,":p")) . ' ' . s:Escape(fname)
|
||||
let gnu_cmd = 'call system(''' . substitute(gnu_cmd, "'", "''", 'g') . ''')'
|
||||
let ps_cmd = s:ZipUpdatePS(s:Escape(fnamemodify(zipfile, ':p'), 0), s:Escape(fname, 0))
|
||||
let zip = fnamemodify(zipfile, ':p')
|
||||
let ps_cmd = s:ZipUpdatePS(zip, fname)
|
||||
let ps_cmd = 'call system(''' . substitute(ps_cmd, "'", "''", 'g') . ''')'
|
||||
call s:TryExecGnuFallBackToPs(g:zip_zipcmd, gnu_cmd, ps_cmd)
|
||||
if &shell =~ 'pwsh'
|
||||
@@ -541,8 +534,8 @@ fun! zip#Extract()
|
||||
|
||||
" extract the file mentioned under the cursor
|
||||
let gnu_cmd = g:zip_extractcmd . ' -o '. shellescape(b:zipfile) . ' ' . target
|
||||
let gnu_cmd = 'call system(''' . substitute(gnu_cmd, "'", "''", 'g') . ''')'
|
||||
let ps_cmd = $"call system({s:Escape(s:ZipExtractFilePS(b:zipfile, target), 1)})"
|
||||
let gnu_cmd = 'call system(' . string(gnu_cmd) . ')'
|
||||
let ps_cmd = 'call system(' . string(s:ZipExtractFilePS(b:zipfile, fname)) . ')'
|
||||
call s:TryExecGnuFallBackToPs(g:zip_extractcmd, gnu_cmd, ps_cmd)
|
||||
|
||||
if v:shell_error != 0
|
||||
@@ -556,19 +549,20 @@ endfun
|
||||
|
||||
" ---------------------------------------------------------------------
|
||||
" s:Escape: {{{2
|
||||
fun! s:Escape(fname,isfilt)
|
||||
if exists("*shellescape")
|
||||
if a:isfilt
|
||||
let qnameq= shellescape(a:fname,1)
|
||||
else
|
||||
let qnameq= shellescape(a:fname)
|
||||
endif
|
||||
fun! s:Escape(fname, isfilt = 0)
|
||||
if a:isfilt
|
||||
let qnameq = shellescape(a:fname, 1)
|
||||
else
|
||||
let qnameq= g:zip_shq.escape(a:fname,g:zip_shq).g:zip_shq
|
||||
let qnameq = shellescape(a:fname)
|
||||
endif
|
||||
return qnameq
|
||||
endfun
|
||||
|
||||
" s:PSEscape: Escape a string for Powershell, shellescape() does not work here {{{2
|
||||
fun! s:PSEscape(str)
|
||||
return "'" .. substitute(a:str, "'", "''", 'g') .. "'"
|
||||
endfun
|
||||
|
||||
" ---------------------------------------------------------------------
|
||||
" s:ChgDir: {{{2
|
||||
fun! s:ChgDir(newdir,errlvl,errmsg)
|
||||
|
||||
@@ -48,16 +48,6 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright*
|
||||
If this variable exists and is true, the file window will not be
|
||||
automatically maximized when opened.
|
||||
|
||||
*g:zip_shq*
|
||||
Different operating systems may use one or more shells to execute
|
||||
commands. Zip will try to guess the correct quoting mechanism to
|
||||
allow spaces and whatnot in filenames; however, if it is incorrectly
|
||||
guessing the quote to use for your setup, you may use >
|
||||
g:zip_shq
|
||||
< which by default is a single quote under Unix (') and a double quote
|
||||
under Windows ("). If you'd rather have no quotes, simply set
|
||||
g:zip_shq to the empty string (let g:zip_shq= "") in your <.vimrc>.
|
||||
|
||||
*g:zip_unzipcmd*
|
||||
Use this option to specify the program which does the duty of "unzip".
|
||||
It's used during browsing. By default: >
|
||||
|
||||
Reference in New Issue
Block a user