]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0845: [security]: arbitrary Ex command execution during C omni-completion v9.2.0845
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Fri, 17 Jul 2026 00:11:42 +0000 (09:11 +0900)
committerChristian Brabandt <cb@256bit.org>
Fri, 24 Jul 2026 18:17:41 +0000 (18:17 +0000)
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 <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/autoload/ccomplete.vim
src/testdir/test_plugin_ccomplete.vim
src/version.c

index dc3388b5247476280bb5ba8895e7ebc0f39864a2..593789a84f860ee95ba289e6a3349cecbfc6aaf2 100644 (file)
@@ -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()
index a635bd50bd43930e3647078ff39775c09a8cda7b..c1754d17c18d39a6f0c1839a68c1b6ea20c001eb 100644 (file)
@@ -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()
index 4bd76c0f5001c308bc41d84793894990177528cf..a93ef0384262ed93f5f17d7522cd20ca57f5e00f 100644 (file)
@@ -758,6 +758,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    845,
 /**/
     844,
 /**/