]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0735: [security]: arbitrary Ex command execution during C omni-completion v9.2.0735
authorHirohito Higashi <h.east.727@gmail.com>
Fri, 26 Jun 2026 06:41:24 +0000 (15:41 +0900)
committerChristian Brabandt <cb@256bit.org>
Fri, 26 Jun 2026 22:02:46 +0000 (22:02 +0000)
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).

Github Security Advisory:
https://github.com/vim/vim/security/advisories/GHSA-mf92-v4xw-j45x

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/autoload/ccomplete.vim
src/testdir/Make_all.mak
src/testdir/test_plugin_ccomplete.vim [new file with mode: 0644]
src/version.c

index 51237be98b4ef7dfd37a9b0295bc5d8377a60449..dc3388b5247476280bb5ba8895e7ebc0f39864a2 100644 (file)
@@ -600,7 +600,7 @@ def StructMembers( # {{{1
         return []
       endif
       execute 'silent! keepjumps noautocmd '
-        .. n .. 'vimgrep ' .. '/\t' .. typename .. '\(\t\|$\)/j '
+        .. n .. 'vimgrep ' .. '/\t' .. escape(typename, '/\') .. '\(\t\|$\)/j '
         .. fnames
 
       qflist = getqflist()
index b81e7e6bf6e882a799cf571c05ad2f590c828da4..dd0708c5ca66e6112e9d74f42df674f8d9677788 100644 (file)
@@ -243,6 +243,7 @@ NEW_TESTS = \
        test_partial \
        test_paste \
        test_perl \
+       test_plugin_ccomplete \
        test_plugin_comment \
        test_plugin_glvs \
        test_plugin_helpcurwin \
@@ -523,6 +524,7 @@ NEW_TESTS_RES = \
        test_partial.res \
        test_paste.res \
        test_perl.res \
+       test_plugin_ccomplete.res \
        test_plugin_comment.res \
        test_plugin_glvs.res \
        test_plugin_helpcurwin.res \
diff --git a/src/testdir/test_plugin_ccomplete.vim b/src/testdir/test_plugin_ccomplete.vim
new file mode 100644 (file)
index 0000000..a635bd5
--- /dev/null
@@ -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
index 85bb6d17e139933b73de43b1560d704a63abf478..5a976ac5775f8c5ad1113b93f759817eb9d128b8 100644 (file)
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    735,
 /**/
     734,
 /**/