128 lines
4.4 KiB
Diff
128 lines
4.4 KiB
Diff
From 6c3ee4e150433e9a19858e14308ba87560640d3f Mon Sep 17 00:00:00 2001
|
|
From: Hirohito Higashi <h.east.727@gmail.com>
|
|
Date: Fri, 26 Jun 2026 15:41:24 +0900
|
|
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).
|
|
|
|
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 | 2 +-
|
|
src/testdir/Make_all.mak | 2 +
|
|
src/testdir/test_plugin_ccomplete.vim | 62 +++++++++++++++++++++++++++
|
|
3 files changed, 65 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 7096dcf4a..fa69f57eb 100644
|
|
--- a/runtime/autoload/ccomplete.vim
|
|
+++ b/runtime/autoload/ccomplete.vim
|
|
@@ -560,7 +560,7 @@ def StructMembers( # {{{1
|
|
if !cached
|
|
while 1
|
|
execute 'silent! keepjumps noautocmd '
|
|
- .. n .. 'vimgrep ' .. '/\t' .. typename .. '\(\t\|$\)/j '
|
|
+ .. n .. 'vimgrep ' .. '/\t' .. escape(typename, '/\') .. '\(\t\|$\)/j '
|
|
.. fnames
|
|
|
|
qflist = getqflist()
|
|
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
|
|
index 66e94c0a2..49c034fb3 100644
|
|
--- a/src/testdir/Make_all.mak
|
|
+++ b/src/testdir/Make_all.mak
|
|
@@ -227,6 +227,7 @@ NEW_TESTS = \
|
|
test_partial \
|
|
test_paste \
|
|
test_perl \
|
|
+ test_plugin_ccomplete \
|
|
test_plugin_netrw \
|
|
test_plugin_python3complete \
|
|
test_plugin_tar \
|
|
@@ -479,6 +480,7 @@ NEW_TESTS_RES = \
|
|
test_partial.res \
|
|
test_paste.res \
|
|
test_perl.res \
|
|
+ test_plugin_ccomplete.res \
|
|
test_plugin_netrw.res \
|
|
test_plugin_python3complete.res \
|
|
test_plugin_tar.res \
|
|
diff --git a/src/testdir/test_plugin_ccomplete.vim b/src/testdir/test_plugin_ccomplete.vim
|
|
new file mode 100644
|
|
index 000000000..a635bd50b
|
|
--- /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
|