Fix CVE-2026-59858: arbitrary Ex command execution in C omni-completion

Backport upstream patch fixing CVE-2026-59858, where a
crafted tags file could execute arbitrary Ex commands during
C omni-completion (struct/union member completion via
ccomplete.vim).

Patch 1 (upstream 6b611b0d) escapes the typeref field with
escape(typename, '/\') before interpolating it into the
:vimgrep pattern.

Patch is adapted to the legacy Vimscript style used
in the RHEL 8.2 codebase. A new test file
test_plugin_ccomplete.vim is added covering both injection
vectors and normal typeref completion.

CVE: CVE-2026-59858
Upstream patches:
 - 6b611b0d15.patch
Resolves: RHEL-203984

This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent.

Assisted-by: Ymir
This commit is contained in:
RHEL Packaging Agent 2026-07-29 12:36:33 +00:00 committed by Zdenek Dohnal
parent e7e02f6acf
commit ba1ed15145
2 changed files with 138 additions and 1 deletions

View File

@ -0,0 +1,127 @@
From a3304c9e3252a9e74bb114d5f72bd4df35017229 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 95a20e16b..ae1fcf7d6 100644
--- a/runtime/autoload/ccomplete.vim
+++ b/runtime/autoload/ccomplete.vim
@@ -514,7 +514,7 @@ func s:StructMembers(typename, items, all)
endif
if !cached
while 1
- exe 'silent! keepj noautocmd ' . n . 'vimgrep /\t' . typename . '\(\t\|$\)/j ' . fnames
+ exe 'silent! keepj noautocmd ' . n . 'vimgrep /\t' . escape(typename, '/\') . '\(\t\|$\)/j ' . fnames
let qflist = getqflist()
if len(qflist) > 0 || match(typename, "::") < 0
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index 7fd4e89a2..2430bc78c 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -209,6 +209,7 @@ NEW_TESTS = \
test_partial \
test_paste \
test_perl \
+ test_plugin_ccomplete \
test_plugin_tar \
test_plus_arg_edit \
test_popup \
@@ -448,6 +449,7 @@ NEW_TESTS_RES = \
test_partial.res \
test_paste.res \
test_perl.res \
+ test_plugin_ccomplete.res \
test_plugin_tar.res \
test_plus_arg_edit.res \
test_popup.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

View File

@ -27,7 +27,7 @@ Summary: The VIM editor
URL: http://www.vim.org/
Name: vim
Version: %{baseversion}.%{patchlevel}
Release: 33%{?dist}
Release: 34%{?dist}
License: Vim and MIT
Source0: ftp://ftp.vim.org/pub/vim/unix/vim-%{baseversion}-%{patchlevel}.tar.bz2
Source1: virc
@ -206,6 +206,11 @@ Patch3072: 0001-patch-9.2.0479-security-runtime-tar-command-injectio.patch
# https://redhat.atlassian.net/browse/RHEL-185873
# https://github.com/vim/vim/commit/a65a52d684bc58535ad28a4ae824d22e76399934
Patch3073: 0001-patch-9.2.0496-security-Code-Injection-in-cucumber-f.patch
# RHEL-203984 CVE-2026-59858 arbitrary Ex command execution during C omni-completion
# https://redhat.atlassian.net/browse/RHEL-203984
# https://github.com/vim/vim/commit/6b611b0d15603c52ebdad17172b0232b4f65704e
# adjusted: kept legacy Vimscript style (exe/keepj/.); added source check.vim for CheckUnix
Patch3074: 0001-patch-9.2.0735-security-arbitrary-Ex-command-executi.patch
# gcc is no longer in buildroot by default
@ -467,6 +472,7 @@ perl -pi -e "s,bin/nawk,bin/awk,g" runtime/tools/mve.awk
%patch -P 3071 -p1 -b .modeline-tests
%patch -P 3072 -p1 -b .tar-cmd-inject
%patch -P 3073 -p1 -b .cucumber-code-inject
%patch -P 3074 -p1 -b .ccomplete-typeref-escape
%build
cd src
@ -1019,6 +1025,10 @@ touch %{buildroot}/%{_datadir}/%{name}/vimfiles/doc/tags
%endif
%changelog
* Wed Jul 29 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2:8.2.2637-34
- RHEL-203984 CVE-2026-59858 vim: arbitrary Ex command execution during C
omni-completion
* Thu Jul 16 2026 Zdenek Dohnal <zdohnal@redhat.com> - 2:8.2.2637-33
- RHEL-185873 CVE-2026-47167 vim: Code Injection in cucumber filetype plugin