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

Backport fix for CVE-2026-59858 from upstream commit 6b611b0d
to vim 8.0. A crafted typeref field in a tags file could break
out of the :vimgrep pattern in ccomplete.vim's s:StructMembers()
and execute arbitrary Ex commands during C omni-completion. The
fix escapes the typename variable with escape(typename, '/\')
before interpolation into the pattern. The patch was adapted from
vim9script syntax to legacy Vim script for the 8.0 codebase, and
includes test coverage for both the security fix and regression
testing of legitimate typeref completion.

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

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-14 09:06:02 +00:00 committed by Zdenek Dohnal
parent df0a6513e7
commit dab3bfc2b3
2 changed files with 127 additions and 1 deletions

View File

@ -0,0 +1,116 @@
From 3670c2d9794931e2c3f01dcc2b26165f31306b90 Mon Sep 17 00:00:00 2001
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
Date: Tue, 14 Jul 2026 08:48:36 +0000
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).
Adapted for vim 8.0: the ccomplete.vim in 8.0 uses legacy Vim script syntax
(exe with dot concatenation) instead of vim9script (execute with .. operator).
The same escape() call is applied to the typename variable.
Stripped src/version.c hunk as per downstream policy.
---
runtime/autoload/ccomplete.vim | 2 +-
src/testdir/Make_all.mak | 1 +
src/testdir/test_plugin_ccomplete.vim | 62 +++++++++++++++++++++++++++
3 files changed, 64 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 d5bfa07..cdbb1ca 100644
--- a/runtime/autoload/ccomplete.vim
+++ b/runtime/autoload/ccomplete.vim
@@ -500,7 +500,7 @@ function! 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 ed348a4..xxxxxxx 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -146,5 +146,6 @@ NEW_TESTS = test_arabic.res \
test_paste.res \
test_perl.res \
+ test_plugin_ccomplete.res \
test_plugin_netrw.res \
test_plugin_python3complete.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 0000000..a635bd5
--- /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

@ -24,7 +24,7 @@ Summary: The VIM editor
URL: http://www.vim.org/
Name: vim
Version: %{baseversion}.%{patchlevel}
Release: 30%{?dist}
Release: 31%{?dist}
License: Vim and MIT
Source0: ftp://ftp.vim.org/pub/vim/unix/vim-%{baseversion}-%{patchlevel}.tar.bz2
Source1: vim.sh
@ -210,6 +210,11 @@ Patch3065: 0001-patch-9.2.0698-security-Out-of-bounds-write-with-sou.patch
# literals with system printf, used mkdir 'p' with manual cleanup instead
# of 'pR', added test to Make_all.mak)
Patch3066: 0001-patch-9.2.0653-security-out-of-bounds-write-in-tree_.patch
# RHEL-203873 CVE-2026-59858 arbitrary Ex command execution during C omni-completion
# https://redhat.atlassian.net/browse/RHEL-203873
# https://github.com/vim/vim/commit/6b611b0d15603c52ebdad17172b0232b4f65704e
# stripped src/version.c hunk, adapted ccomplete.vim fix for vim 8.0 legacy script syntax
Patch3067: 0001-patch-9.2.0735-security-arbitrary-Ex-command-executi.patch
# gcc is no longer in buildroot by default
@ -458,6 +463,7 @@ perl -pi -e "s,bin/nawk,bin/awk,g" runtime/tools/mve.awk
%patch -P 3064 -p1 -b .CVE-2026-57456
%patch -P 3065 -p1 -b .CVE-2026-57455
%patch -P 3066 -p1 -b .CVE-2026-55693
%patch -P 3067 -p1 -b .CVE-2026-59858
%build
@ -977,6 +983,10 @@ touch %{buildroot}/%{_datadir}/%{name}/vimfiles/doc/tags
%{_datadir}/icons/locolor/*/apps/*
%changelog
* Tue Jul 14 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2:8.0.1763-31
- RHEL-203873 CVE-2026-59858 vim: arbitrary Ex command execution in
C omni-completion
* Mon Jul 13 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2:8.0.1763-30
- RHEL-194055 CVE-2026-55693 vim: out-of-bounds write in
tree_count_words()