From 3670c2d9794931e2c3f01dcc2b26165f31306b90 Mon Sep 17 00:00:00 2001 From: RHEL Packaging Agent Date: Tue, 14 Jul 2026 08:48:36 +0000 Subject: [PATCH] patch 9.2.0735: [security]: arbitrary Ex command execution during C omni-completion Problem: [security]: With C omni-completion, a crafted tags file can execute arbitrary Ex commands when completing a struct/union member (cipher-creator) Solution: Escape the type field before inserting it into the :vimgrep pattern so it cannot close the pattern and start a new command (Hirohito Higashi). Adapted for vim 8.0: the ccomplete.vim in 8.0 uses legacy Vim script syntax (exe with dot concatenation) instead of vim9script (execute with .. operator). The same escape() call is applied to the typename variable. Stripped src/version.c hunk as per downstream policy. --- runtime/autoload/ccomplete.vim | 2 +- src/testdir/Make_all.mak | 1 + src/testdir/test_plugin_ccomplete.vim | 62 +++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/testdir/test_plugin_ccomplete.vim diff --git a/runtime/autoload/ccomplete.vim b/runtime/autoload/ccomplete.vim index d5bfa07..cdbb1ca 100644 --- a/runtime/autoload/ccomplete.vim +++ b/runtime/autoload/ccomplete.vim @@ -500,7 +500,7 @@ function! s:StructMembers(typename, items, all) endif if !cached while 1 - exe 'silent! keepj noautocmd ' . n . 'vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames + exe 'silent! keepj noautocmd ' . n . 'vimgrep /\t' . escape(typename, '/\') . '\(\t\|$\)/j ' . fnames let qflist = getqflist() if len(qflist) > 0 || match(typename, "::") < 0 diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index ed348a4..xxxxxxx 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -146,5 +146,6 @@ NEW_TESTS = test_arabic.res \ test_paste.res \ test_perl.res \ + test_plugin_ccomplete.res \ test_plugin_netrw.res \ test_plugin_python3complete.res \ test_plus_arg_edit.res \ diff --git a/src/testdir/test_plugin_ccomplete.vim b/src/testdir/test_plugin_ccomplete.vim new file mode 100644 index 0000000..a635bd5 --- /dev/null +++ b/src/testdir/test_plugin_ccomplete.vim @@ -0,0 +1,62 @@ +" 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 + +" 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 + +" vim: shiftwidth=2 sts=2 expandtab