From: Yasuhiro Matsumoto Date: Fri, 17 Jul 2026 00:11:42 +0000 (+0900) Subject: patch 9.2.0845: [security]: arbitrary Ex command execution during C omni-completion X-Git-Tag: v9.2.0845^0 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=2f628d8104958fa7421664f792ca6d4f7a39a10f;p=thirdparty%2Fvim.git patch 9.2.0845: [security]: arbitrary Ex command execution during C omni-completion Problem: [security]: arbitrary Ex command execution during C omni-completion (Threonine) Solution: Match tags typeref literally to block Ex command injection (Yasuhiro Matsumoto). Escaping only "/" and "\" left the typeref able to break out of the :vimgrep pattern without a "/": an unclosed "[" makes vimgrep's pattern skipping fail, and the parser then treats a following "|" as a command separator, so the tag value runs as Ex commands during C omni-completion. Match the field literally with \V so no regex metacharacter can affect pattern parsing. Github Security Advisory: https://github.com/vim/vim/security/advisories/GHSA-cx73-phcg-3j5g Signed-off-by: Yasuhiro Matsumoto Signed-off-by: Christian Brabandt --- diff --git a/runtime/autoload/ccomplete.vim b/runtime/autoload/ccomplete.vim index dc3388b524..593789a84f 100644 --- a/runtime/autoload/ccomplete.vim +++ b/runtime/autoload/ccomplete.vim @@ -599,8 +599,11 @@ def StructMembers( # {{{1 if complete_check() return [] endif + # Match "typename" literally (\V): escaping alone is not enough, as e.g. + # an unclosed "[" makes vimgrep's pattern skipping fail and the rest of + # the tag value is then parsed as Ex commands. execute 'silent! keepjumps noautocmd ' - .. n .. 'vimgrep ' .. '/\t' .. escape(typename, '/\') .. '\(\t\|$\)/j ' + .. n .. 'vimgrep ' .. '/\t\V' .. escape(typename, '/\') .. '\m\(\t\|$\)/j ' .. fnames qflist = getqflist() diff --git a/src/testdir/test_plugin_ccomplete.vim b/src/testdir/test_plugin_ccomplete.vim index a635bd50bd..c1754d17c1 100644 --- a/src/testdir/test_plugin_ccomplete.vim +++ b/src/testdir/test_plugin_ccomplete.vim @@ -31,6 +31,32 @@ func Test_ccomplete_no_exec_via_typeref() 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() diff --git a/src/version.c b/src/version.c index 4bd76c0f50..a93ef03842 100644 --- a/src/version.c +++ b/src/version.c @@ -758,6 +758,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 845, /**/ 844, /**/