mirror of
https://github.com/neovim/neovim.git
synced 2026-09-02 04:13:48 +00:00
Problem: arbitrary Ex command execution during C omni-completion via
tag file names (Yazan Balawneh)
Solution: Escape the | for all returned tag files
Github Security Advisory:
https://github.com/vim/vim/security/advisories/GHSA-r77m-8m55-rpr6
331d5d6702
Co-authored-by: Christian Brabandt <cb@256bit.org>
119 lines
3.7 KiB
VimL
119 lines
3.7 KiB
VimL
" Tests for the C omni-completion plugin (runtime/autoload/ccomplete.vim).
|
|
|
|
func s:WriteTags(lines)
|
|
" Mark unsorted so lookup is a linear scan regardless of entry order.
|
|
let tagsfile = tempname()
|
|
call writefile(["!_TAG_FILE_SORTED\t0\t/0/"] + a:lines, tagsfile)
|
|
return tagsfile
|
|
endfunc
|
|
|
|
" A crafted typeref field is interpolated into the :vimgrep pattern in
|
|
" StructMembers(). Without escaping, "/" closes the pattern and "|" starts a
|
|
" new Ex command, so the field runs as an Ex command during completion.
|
|
func Test_ccomplete_no_exec_via_typeref()
|
|
unlet! g:ccomplete_injected
|
|
let tagsfile = s:WriteTags([
|
|
\ "myvar\tmain.c\t/^x$/;\"\tv\ttyperef:x/|let g:ccomplete_injected = 1|\"",
|
|
\ ])
|
|
|
|
let save_tags = &tags
|
|
let &tags = tagsfile
|
|
|
|
new
|
|
call ccomplete#Complete(1, '')
|
|
call ccomplete#Complete(0, 'myvar.x')
|
|
|
|
call assert_false(exists('g:ccomplete_injected'),
|
|
\ 'typeref field was executed as an Ex command during omni-completion')
|
|
|
|
bwipe!
|
|
let &tags = save_tags
|
|
unlet! g:ccomplete_injected
|
|
endfunc
|
|
|
|
" Escaping "/" and "\" is not enough: with no "/" in the payload, an unclosed
|
|
" "[" makes vimgrep's pattern skipping fail, and the command parser then treats
|
|
" the first "|" as a command separator. The typeref must be matched literally.
|
|
func Test_ccomplete_no_exec_via_typeref_bracket()
|
|
CheckUnix
|
|
let sentinel = tempname()
|
|
call delete(sentinel)
|
|
let tagsfile = s:WriteTags([
|
|
\ "myvar\tmain.c\t/^x$/;\"\tv\ttyperef:struct:[|call system('touch " .. sentinel .. "')|####",
|
|
\ ])
|
|
|
|
let save_tags = &tags
|
|
let &tags = tagsfile
|
|
|
|
new
|
|
call ccomplete#Complete(1, '')
|
|
call ccomplete#Complete(0, 'myvar.x')
|
|
|
|
call assert_false(filereadable(sentinel),
|
|
\ 'typeref field was executed as an Ex command during omni-completion')
|
|
|
|
bwipe!
|
|
let &tags = save_tags
|
|
call delete(sentinel)
|
|
endfunc
|
|
|
|
" A legitimate typeref must still drive struct-member completion: escaping the
|
|
" field value must not break the normal path.
|
|
func Test_ccomplete_typeref_completion_still_works()
|
|
let tagsfile = s:WriteTags([
|
|
\ "myvar\tmain.c\t/^x$/;\"\tv\ttyperef:struct:mystruct",
|
|
\ "alpha\tmain.c\t/^x$/;\"\tm\tstruct:mystruct",
|
|
\ "beta\tmain.c\t/^x$/;\"\tm\tstruct:mystruct",
|
|
\ ])
|
|
|
|
let save_tags = &tags
|
|
let &tags = tagsfile
|
|
|
|
new
|
|
call ccomplete#Complete(1, '')
|
|
let items = ccomplete#Complete(0, 'myvar.')
|
|
|
|
call assert_equal(type([]), type(items),
|
|
\ 'ccomplete#Complete did not return a list')
|
|
let names = map(copy(items), 'v:val.word')
|
|
call assert_true(index(names, 'alpha') >= 0,
|
|
\ 'struct member "alpha" missing from completion: ' . string(names))
|
|
call assert_true(index(names, 'beta') >= 0,
|
|
\ 'struct member "beta" missing from completion: ' . string(names))
|
|
|
|
bwipe!
|
|
let &tags = save_tags
|
|
endfunc
|
|
|
|
" The tags file names from tagfiles() are executed
|
|
" via a single vimgrep command and trailing | does
|
|
" not cause code execution
|
|
func Test_ccomplete_no_exec_via_tagfile_name()
|
|
CheckUnix
|
|
let dir = "Xcc|&titlestring\t=\t'INJECTED'|ls\t"
|
|
call mkdir(dir, 'pR')
|
|
let tagsfile = dir .. '/tags'
|
|
call writefile(["!_TAG_FILE_SORTED\t0\t/0/",
|
|
\ "myvar\tmain.c\t/^x$/;\"\tv\ttyperef:struct:mystruct",
|
|
\ "alpha\tmain.c\t/^x$/;\"\tm\tstruct:mystruct",
|
|
\ ], tagsfile)
|
|
|
|
let save_tags = &tags
|
|
let save_title = &titlestring
|
|
let &tags = fnamemodify(tagsfile, ':p')
|
|
set titlestring=orig
|
|
|
|
new
|
|
call ccomplete#Complete(1, '')
|
|
call ccomplete#Complete(0, 'myvar.x')
|
|
|
|
call assert_equal('orig', &titlestring,
|
|
\ 'tags file name was executed as an Ex command during omni-completion')
|
|
|
|
bwipe!
|
|
let &tags = save_tags
|
|
let &titlestring = save_title
|
|
endfunc
|
|
|
|
" vim: shiftwidth=2 sts=2 expandtab
|