Arbitrary Ex Command Execution via File Names in C Omni-Completion in Vim < 9.2.1011
Date: 25.08.2026
Severity: Medium
CVE: requested, not yet assigned
CWE: Improper Control of Generation of Code ('Code Injection') (CWE-94),
Inclusion of Functionality from Untrusted Control Sphere (CWE-829),
Improper Neutralization of Argument Delimiters in a Command ('Argument Injection') (CWE-88)
Summary
The C omni-completion script in runtime/autoload/ccomplete.vim looks up
struct members by building a :vimgrep command and running it with
:execute. Besides the type name, that command line also holds the list of
tags file names returned by tagfiles(). Those names were escaped for the
space, the backslash, # and %, but not for the bar. Since :vimgrep
accepts another command after a bar, a file name whose name contains one
ends the :vimgrep command early and what follows is executed as an Ex
command when the user invokes omni-completion on a member access.
This is the same defect in the same command line as GHSA-cx73-phcg-3j5g,
fixed in patch v9.2.0845, which addressed only the type name.
Unlike that issue, no crafted tags file content is needed here: the file name
itself carries the payload.
Description
runtime/ftplugin/c.vim sets omnifunc=ccomplete#Complete on C buffers
when filetype plugins are enabled. When completing a member access, and the
declaration is not found in the buffer itself, StructMembers() searches the
tags files:
var fnames: string = tagfiles()
->map((_, v: string) => escape(v, ' \#%'))
->join()
...
execute 'silent! keepjumps noautocmd '
.. n .. 'vimgrep ' .. '/\t\V' .. escape(typename, '/\') .. '\m\(\t\|$\)/j '
.. fnames
tagfiles() returns the names of the tags files in effect for the buffer,
derived from the 'tags' option. The names are appended unquoted to the
:vimgrep argument list.
The :vimgrep command is defined with the EX_TRLBAR flag, so a bar ends
the command and starts a new one. A bar in a file name is therefore not
part of the name but a command separator, and the remainder of the line is
parsed and executed as an independent Ex command by the same :execute.
Escaping the space limits what such a command can contain, but does not
prevent it, and ccomplete.vim is a vim9script file, so the injected text
is parsed with Vim9 syntax. The leading :silent! suppresses the resulting
error, so the injected command runs without a visible failure.
The issue has been addressed by escaping the bar in the tags file names as
well.
Impact
Arbitrary Ex command execution, and through commands such as :! arbitrary
operating-system command execution, in the context of the user running Vim.
Exploitation requires:
- Vim with filetype plugins enabled
- a tags file whose path contains a bar, for example because it is stored in
a directory whose name contains one, and a 'tags' value under which that
file is found,
- the victim opening a C file from that tree and invoking omni-completion
with CTRL-X CTRL-O on a member access whose type is only known from the
tags file.
The severity is rated Medium. The payload travels in a path rather than in
file content, so it is not visible to review that inspects files only. On
the other hand a bar in a directory name is unusual and conspicuous, it is
not a valid character in file names on MS-Windows, and the crafted name has
no effect until the user deliberately invokes omni-completion on a type that
is not declared in the edited buffer, since the completion would otherwise
not consult the tags files at all.
Acknowledgements
The Vim project would like to thank Yazan Balawneh, Cystack.ps for reporting
the issue.
References
The issue has been fixed as of Vim patch v9.2.1011.
Arbitrary Ex Command Execution via File Names in C Omni-Completion in Vim < 9.2.1011
Date: 25.08.2026
Severity: Medium
CVE: requested, not yet assigned
CWE: Improper Control of Generation of Code ('Code Injection') (CWE-94),
Inclusion of Functionality from Untrusted Control Sphere (CWE-829),
Improper Neutralization of Argument Delimiters in a Command ('Argument Injection') (CWE-88)
Summary
The C omni-completion script in
runtime/autoload/ccomplete.vimlooks upstruct members by building a
:vimgrepcommand and running it with:execute. Besides the type name, that command line also holds the list oftags file names returned by
tagfiles(). Those names were escaped for thespace, the backslash,
#and%, but not for the bar. Since:vimgrepaccepts another command after a bar, a file name whose name contains one
ends the
:vimgrepcommand early and what follows is executed as an Excommand when the user invokes omni-completion on a member access.
This is the same defect in the same command line as GHSA-cx73-phcg-3j5g,
fixed in patch v9.2.0845, which addressed only the type name.
Unlike that issue, no crafted tags file content is needed here: the file name
itself carries the payload.
Description
runtime/ftplugin/c.vimsetsomnifunc=ccomplete#Completeon C bufferswhen filetype plugins are enabled. When completing a member access, and the
declaration is not found in the buffer itself,
StructMembers()searches thetags files:
tagfiles()returns the names of the tags files in effect for the buffer,derived from the
'tags'option. The names are appended unquoted to the:vimgrepargument list.The
:vimgrepcommand is defined with theEX_TRLBARflag, so a bar endsthe command and starts a new one. A bar in a file name is therefore not
part of the name but a command separator, and the remainder of the line is
parsed and executed as an independent Ex command by the same
:execute.Escaping the space limits what such a command can contain, but does not
prevent it, and
ccomplete.vimis avim9scriptfile, so the injected textis parsed with Vim9 syntax. The leading
:silent!suppresses the resultingerror, so the injected command runs without a visible failure.
The issue has been addressed by escaping the bar in the tags file names as
well.
Impact
Arbitrary Ex command execution, and through commands such as
:!arbitraryoperating-system command execution, in the context of the user running Vim.
Exploitation requires:
a directory whose name contains one, and a
'tags'value under which thatfile is found,
with
CTRL-X CTRL-Oon a member access whose type is only known from thetags file.
The severity is rated Medium. The payload travels in a path rather than in
file content, so it is not visible to review that inspects files only. On
the other hand a bar in a directory name is unusual and conspicuous, it is
not a valid character in file names on MS-Windows, and the crafted name has
no effect until the user deliberately invokes omni-completion on a type that
is not declared in the edited buffer, since the completion would otherwise
not consult the tags files at all.
Acknowledgements
The Vim project would like to thank Yazan Balawneh, Cystack.ps for reporting
the issue.
References
The issue has been fixed as of Vim patch v9.2.1011.