Fix CVE-2026-59858: arbitrary Ex command execution in C omni-completion
Backport upstream fix for CVE-2026-59858 from commit 6b611b0d15603c52ebdad17172b0232b4f65704e. A crafted tags file could execute arbitrary Ex commands when completing struct/union members via C omni-completion. The fix escapes the typename field before interpolating it into the :vimgrep pattern in StructMembers(), preventing command injection. A new test file validates both the security fix and that normal struct-member completion continues to work. CVE: CVE-2026-59858 Upstream patches: -6b611b0d15.patch -d9ec676911Resolves: RHEL-203886
This commit is contained in:
parent
dd7bfa6827
commit
636b8cbcfc
127
0001-patch-9.2.0735-security-arbitrary-Ex-command-executi.patch
Normal file
127
0001-patch-9.2.0735-security-arbitrary-Ex-command-executi.patch
Normal file
@ -0,0 +1,127 @@
|
||||
From 65007bcf94a8c32ce99fbbb69c71124e4a06f580 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 c4df6ec27..abcdef123 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_phpcomplete \
|
||||
test_plugin_tar \
|
||||
test_plus_arg_edit \
|
||||
@@ -479,6 +480,7 @@ NEW_TESTS_RES = \
|
||||
test_partial.res \
|
||||
test_paste.res \
|
||||
test_perl.res \
|
||||
+ test_plugin_ccomplete.res \
|
||||
test_plugin_phpcomplete.res \
|
||||
test_plugin_tar.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 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
|
||||
30
0001-runtime-ccomplete-fix-type-mismatch-error.patch
Normal file
30
0001-runtime-ccomplete-fix-type-mismatch-error.patch
Normal file
@ -0,0 +1,30 @@
|
||||
From d9ec67691170cd3764cbf767636305e47987340f Mon Sep 17 00:00:00 2001
|
||||
From: "Lars T. Kyllingstad" <lars.kyllingstad@sintef.no>
|
||||
Date: Thu, 6 Jun 2024 18:37:08 +0200
|
||||
Subject: [PATCH] runtime(ccomplete): fix type mismatch error
|
||||
|
||||
fixes: #14927
|
||||
closes: #14928
|
||||
|
||||
Signed-off-by: Lars T. Kyllingstad <lars.kyllingstad@sintef.no>
|
||||
Signed-off-by: Christian Brabandt <cb@256bit.org>
|
||||
---
|
||||
runtime/autoload/ccomplete.vim | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/runtime/autoload/ccomplete.vim b/runtime/autoload/ccomplete.vim
|
||||
index 7096dcf4a..355f724d0 100644
|
||||
--- a/runtime/autoload/ccomplete.vim
|
||||
+++ b/runtime/autoload/ccomplete.vim
|
||||
@@ -210,7 +210,7 @@ export def Complete(findstart: bool, abase: string): any # {{{1
|
||||
# Find the variable in the tags file(s)
|
||||
var diclist: list<dict<any>> = taglist('^' .. items[0] .. '$')
|
||||
# Remove members, these can't appear without something in front.
|
||||
- ->filter((_, v: dict<string>): bool =>
|
||||
+ ->filter((_, v: dict<any>): bool =>
|
||||
v->has_key('kind') ? v.kind != 'm' : true)
|
||||
|
||||
res = []
|
||||
--
|
||||
2.55.0
|
||||
|
||||
14
vim.spec
14
vim.spec
@ -51,7 +51,7 @@ Summary: The VIM editor
|
||||
URL: http://www.vim.org/
|
||||
Name: vim
|
||||
Version: %{baseversion}.%{patchlevel}
|
||||
Release: 17%{?dist}
|
||||
Release: 18%{?dist}
|
||||
Epoch: 2
|
||||
# swift.vim contains Apache 2.0 with runtime library exception:
|
||||
# which is taken as Apache-2.0 WITH Swift-exception - reported to legal as https://gitlab.com/fedora/legal/fedora-license-data/-/issues/188
|
||||
@ -167,6 +167,12 @@ Patch3025: 0001-patch-9.2.0736-potential-command-execution-in-PHP-om.patch
|
||||
# https://redhat.atlassian.net/browse/RHEL-191361
|
||||
# https://github.com/vim/vim/commit/497f931f85339d175d7f69588dd249e8ccfed41b
|
||||
Patch3026: 0001-patch-9.2.0698-security-Out-of-bounds-write-with-sou.patch
|
||||
# RHEL-203886 CVE-2026-59858 vim: arbitrary Ex command execution during C omni-completion
|
||||
# https://redhat.atlassian.net/browse/RHEL-203886
|
||||
# https://github.com/vim/vim/commit/6b611b0d15603c52ebdad17172b0232b4f65704e
|
||||
# https://github.com/vim/vim/commit/d9ec67691170cd3764cbf767636305e47987340f
|
||||
Patch3027: 0001-patch-9.2.0735-security-arbitrary-Ex-command-executi.patch
|
||||
Patch3028: 0001-runtime-ccomplete-fix-type-mismatch-error.patch
|
||||
|
||||
|
||||
# uses autoconf in spec file
|
||||
@ -511,6 +517,8 @@ perl -pi -e "s,bin/nawk,bin/awk,g" runtime/tools/mve.awk
|
||||
%patch -P 3024 -p1 -b .cucumber-code-inject
|
||||
%patch -P 3025 -p1 -b .php-omni-cmd-exec
|
||||
%patch -P 3026 -p1 -b .soundfold-overflow
|
||||
%patch -P 3027 -p1 -b .ccomplete-ex-inject
|
||||
%patch -P 3028 -p1 -b .runtime-ccomplete
|
||||
|
||||
%build
|
||||
cd src
|
||||
@ -1141,6 +1149,10 @@ touch %{buildroot}/%{_datadir}/%{name}/vimfiles/doc/tags
|
||||
|
||||
|
||||
%changelog
|
||||
* Wed Jul 29 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2:9.1.083-18
|
||||
- RHEL-203886 CVE-2026-59858 vim: arbitrary Ex command execution in C
|
||||
omni-completion
|
||||
|
||||
* Wed Jul 29 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2:9.1.083-17
|
||||
- RHEL-191361 CVE-2026-57455 vim: Out-of-bounds write with soundfold()
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user