Opened 3 weeks ago

Closed 10 days ago

#5940 closed enhancement (fixed)

vim-9.2.0567 (Security Update)

Reported by: Douglas R. Reno Owned by: SecurityAdvisory
Priority: high Milestone: 13.1
Component: Book Version: git
Severity: normal Keywords:
Cc:

Description

This fixes three security vulnerabilities so far:

CVE-2026-47167

Vimscript Code Injection in netrw NetrwBookHistSave() via crafted directory name affects Vim < 9.2.495
======================================================================================================
Date: 17.05.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Improper Control of Generation of Code (CWE-94) /
     Improper Neutralization of Special Elements in Output Used by a Downstream Component (CWE-74)

## Summary

A Vimscript code injection vulnerability exists in `s:NetrwBookHistSave()`
in the netrw plugin (`runtime/pack/dist/opt/netrw/autoload/netrw.vim`)
when serializing browsed directory paths to the history file
`~/.vim/.netrwhist`.  A directory name derived from the filesystem is
interpolated into a single-quoted Vimscript string literal without
escaping embedded single quotes, allowing a crafted directory name to
break out of the string context and execute arbitrary Vimscript,
including shell commands via `system()` and `:!`, the next time the
history file is sourced.

## Description

netrw records every directory the user browses into a per-user history
file at `~/.vim/.netrwhist`.  `s:NetrwBookHistSave()` writes each entry
as a Vimscript assignment statement:

    call setline(lastline,'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'")

The directory path `g:netrw_dirhist_{cnt}` is inserted between two
literal `'` characters without escaping any single quotes contained in
the path, an embedded single quote therefore terminates the string
early, after which the remainder of the path is parsed as additional
Vimscript statement.  Vim statements may be chained with `|`, so a directory name
of the form

    x'|<injected Vimscript>|let y='z

is serialized as three valid statements on a single line.

The sibling bookmark serializer thirty lines below in the same function
uses `string()` correctly, which is the canonical primitive for this
purpose; only the history serializer was missing the escape.

The history file is read back on every netrw initialization with:

    exe "keepalt NetrwKeepj so ".savefile

The `:source` invocation evaluates every statement in the file, so any
injected Vimscript executes with the privileges of the user running
Vim.  Calls such as `system()`, `:!`, or `writefile()` in the injected
fragment yield arbitrary command execution.

The directory name reaches `s:NetrwBookHistSave()` through the normal
netrw browse flow: when the user navigates into a directory via netrw,
the path is appended to the in-memory history list, which is flushed to
disk by an autocmd on `VimLeave`.  The injection therefore arms on any
netrw browse of a maliciously named directory and fires on any
subsequent Vim invocation that opens a directory through netrw.

The default configuration is affected: `g:netrw_dirhistmax` defaults to
10, which enables the history mechanism.

POSIX forbids `/` inside a single filename component, so the dirname
payload cannot directly construct an absolute path; this constraint is
bypassed via environment-variable expansion (e.g. `$HOME`) or relative
paths evaluated at Vim's current working directory.

## Impact

The vulnerability allows arbitrary Vimscript execution, and by
extension arbitrary shell command execution, with the privileges of the
user running Vim.  Exploitation requires:

- a Unix-like system on which a filename may contain a single quote,
- a crafted directory present in a location the victim browses with
  netrw (e.g. delivered via a cloned repository, extracted archive, or
  shared filesystem), and
- the victim to (1) browse the crafted directory once with netrw, then
  (2) launch Vim on any directory at a later point so that
  `.netrwhist` is sourced.

The severity is rated Medium because exploitation requires a planted
directory name with an unusual name and a deliberate "edit directory" command
by the victim, although the resulting primitive is full
command execution as the victim user.

The injection persists in `.netrwhist` until the entry is rotated out of
the history.

## Acknowledgements

The Vim project would like to thank Srinivas Piskala Ganesh Babu for
reporting and analyzing the issue and suggesting a fix.

## References

The issue has been fixed as of Vim patch [v9.2.0495](https://github.com/vim/vim/releases/tag/v9.2.0495).

- [Commit](https://github.com/vim/vim/commit/f08ab2f4d7d2947c8dd6c179ae08ee6146a2694b)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-crm5-rh6j-2c7c)

CVE-2026-47162


Vimscript Code Injection in cucumber filetype plugin via crafted step-definition regex affects Vim < 9.2.0496
=============================================================================================================
Date: 17.05.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Improper Control of Generation of Code (CWE-94) /
     Improper Neutralization of Directives in Dynamically Evaluated Code (CWE-95)

## Summary

A code injection vulnerability exists in `s:stepmatch()` in the
cucumber filetype plugin (`runtime/ftplugin/cucumber.vim`) on Vim builds
with `+ruby` support.  Step-definition patterns read from `.rb` files
under the repository's `features/*/` or `stories/*/` directories are
embedded into a Ruby `Kernel.eval` argument without sufficient escaping,
allowing a crafted pattern in an attacker-controlled repository to
execute arbitrary Ruby (and through it arbitrary shell commands) when
the user invokes a step-jump mapping (`[d`, `]d`).

## Description

The cucumber ftplugin's step-jump mappings call `s:steps()` which in
turn calls `s:stepmatch()` for every step definition discovered by
`s:allsteps()`.  For regex-style step patterns delimited by `/.../`,
`s:stepmatch()` falls back to Ruby evaluation when Vim's own regex
engine cannot match:

    if has("ruby") && pattern !~ '\\\@<!#{'
      ruby VIM.command("return #{if (begin;
        Kernel.eval('/'+VIM.evaluate('pattern')+'/');
      rescue SyntaxError; end) === VIM.evaluate('a:target')
      then 1 else 0 end}")

The pattern value is concatenated into the Ruby source passed to
`Kernel.eval`.  The `#{` guard rejects Ruby string-interpolation
sequences but does not prevent the pattern from terminating the regex
literal with `/` and appending arbitrary Ruby statements.  A pattern of
the form

    x/; system("touch marker"); #

is evaluated by Ruby as a regex literal, a `system()` call, and a
comment — three valid expressions chained on one line.  `system()`
runs with the privileges of the user running Vim.

The pattern reaches `s:stepmatch()` through `s:allsteps()`, which
scans `.rb` files matching `b:cucumber_steps_glob` (by default
`features/*/*.rb` and `stories/*/*.rb`) for any line resembling a step
definition.  The injection therefore arms whenever a cucumber-style
repository under the working directory contains an attacker-controlled
`.rb` file, and fires the first time the victim invokes a step-jump
mapping on a step whose target text the planted regex matches.

The omni-completion path (`CucumberComplete()`) uses `s:allsteps()`
directly without going through `s:stepmatch()`, so completion alone
does not trigger the vulnerability.

## Impact

The vulnerability allows arbitrary Ruby execution, and by extension
arbitrary shell command execution, with the privileges of the user
running Vim.  Exploitation requires:

- a Vim build compiled with `+ruby` support,
- a cucumber-style repository (with `features/` or `stories/`
  subdirectories containing `.rb` step definitions) opened by the
  victim, and
- the victim to invoke a step-jump mapping (`[d` or `]d`) on a
  feature line whose target text is matched by the crafted regex.

The severity is rated Medium because exploitation requires a `+ruby`
build (not the default in many distributions), an attacker-planted
step-definition file with an unusual pattern syntax, and a deliberate
step-jump action by the victim on a feature line that the planted
regex matches, although the resulting primitive is full command
execution as the victim user.

## Acknowledgements

The Vim project would like to thank Aisle Research for reporting and
analyzing the issue.

## References

The issue has been fixed as of Vim patch
[v9.2.0496](https://github.com/vim/vim/releases/tag/v9.2.0496).

- [Commit](https://github.com/vim/vim/commit/a65a52d684bc58535ad28a4ae824d22e76399934)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-4473-94jm-w5x9)

CVE Not Yet Assigned

Multiple Memory Safety Issues in Vim Spell File Parser affects Vim < 9.2.0513
=============================================================================
Date: 22.05.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Out-of-bounds Read (CWE-125),
     Use of Uninitialized Resource (CWE-908),
     Uncontrolled Recursion (CWE-674)

## Summary
Three related memory-safety issues exist in the Vim spell file (`.spl`)
parser in `src/spellfile.c`.  A crafted spell file can cause:

1. a heap out-of-bounds read in `read_tree_node()` via a `BY_INDEX`
   shared tree node that references an uninitialized array position,
2. a one-byte heap out-of-bounds read in `tree_count_words()` past the
   end of the word-tree byte array, and
3. a stack overflow in `read_tree_node()` through uncontrolled recursion
   on a deep linear node chain.

Because the `'spelllang'` option can be set from a modeline, a text
file modeline can trigger spell file loading if a malicious `.spl` file
has been planted on the runtimepath, which can happen when cloning a vim
package.

## Description

### 1. Uninitialized shared-node target in read_tree_node()
In `spell_read_tree()` the byte array `bp` for a word tree was allocated
with `alloc(len)` and not zero-initialized, while the companion index
array used `lalloc_clear()`.  The tree parser validated `BY_INDEX`
shared-node references only against `maxidx` (the allocated array
size), not against positions that were actually written by
`read_tree_node()`.  A crafted file can declare a `<nodecount>` larger
than the tree it serializes and include a `BY_INDEX` reference into the
unwritten tail, leaving `byts[N]` containing uninitialized heap data.

On `z=` (spell suggest) or `spellsuggest()`, the suggestion walk reads
`byts[arridx]` as a sibling count and iterates that many slots,
producing a further out-of-bounds heap read with an attacker-influenced
length.

### 2. Missing length guard in tree_count_words()
`tree_count_words()` skipped runs of trailing NUL siblings with
`while (byts[n + 1] == 0)` and had no length guard.  The structurally
identical loop in `sug_filltree()` already carried
`n + 1 < slang->sl_fbyts_len && ...` with the explicit comment
"But don't go over the end."; that guard had not been propagated to
`tree_count_words()`.  When called during `.sug` file loading on a
tree whose final sibling is `BY_NOFLAGS`, the walk reads one byte past
the end of the byts array.

### 3. Uncontrolled recursion in read_tree_node()
`read_tree_node()` recursed once per non-shared, non-end-of-word
sibling without a depth limit.  A crafted `.spl` file containing a
linear chain of nodes (siblingcount=1, non-NUL byte at each level)
drives the recursion to a depth bounded only by the declared
`<nodecount>` (a 4-byte field).  On default 8 MB stacks, approximately
88,000 nested frames exhaust the stack and crash Vim with SIGSEGV.

## Impact
Issues 1 and 2 are out-of-bounds heap reads with attacker-influenced
range; the practical outcome is a crash of the Vim process and a small
window of uninitialized or adjacent heap data being consumed by the
suggestion algorithm.  Issue 3 reliably crashes Vim through stack
exhaustion.

Exploitation requires a malicious `.spl` file to be present on the
runtimepath and the victim to either:

- explicitly enable spell checking with the matching language
- open any text file containing a modeline that sets `'spelllang'` and
  enables `'spell'`, while `'modeline'` is enabled.

The severity is rated Medium because exploitation requires both a
planted spell file and a separate triggering action by the victim, and
the practical outcome is a crash rather than code execution.

## Acknowledgements
The Vim project would like to thank github user tacdm for reporting and
analyzing the issues and suggesting fixes.

## References
The issues have been fixed as of Vim patch [v9.2.0513](https://github.com/vim/vim/releases/tag/v9.2.0513).
- [Commit](https://github.com/vim/vim/commit/25e4e46c584840806b45da20ed)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-3h95-3962-mmvf)

Change History (4)

comment:1 by Douglas R. Reno, 2 weeks ago

Summary: vim-9.2.0513 (Security Update)vim-9.2.0561 (Security Update)

Now 9.2.0561

Arbitrary Code Execution via Python Omni-Completion in Vim < 9.2.561
====================================================================
Date: 29.05.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Improper Control of Generation of Code (CWE-94),
     Inclusion of Functionality from Untrusted Control Sphere (CWE-829)

## Summary
The Python omni-completion script in `python3complete.vim` for Vim with the
`+python3` interpreter enabled (and the legacy `pythoncomplete.vim` for builds
with the `+python` interpreter) executes the `import` and `from` statements
found in the current buffer through Python's import machinery.  Because the
buffer's working directory is on `sys.path`, opening a hostile `.py` file
with a sibling Python package and invoking omni-completion runs that
package's top-level code as the editing user.

## Description
`runtime/ftplugin/python.vim` installs `omnifunc=python3complete#Complete`
on every Python buffer when Vim has `+python3` (or `+python`).
When the user invokes omni-completion with `CTRL-X CTRL-O` in insert mode, the
completer parses the buffer with an embedded Python tokenizer, regenerates a
Python source string from the parsed scope, and passes it to `exec(src,
self.compldict)` to populate the completion dictionary.

The regenerated source re-emits every top-level `import X` and
`from X import Y` statement that the parser harvested from the buffer.
Additionally, the completer extends `sys.path` with `['.', '..']` so
that sibling modules in the buffer's working directory are importable.
The combined effect: invoking omni-completion on a `.py` file runs
Python's import machinery on attacker-supplied module names with the
attacker's working directory on the search path.

A crafted `.py` file containing `import evil_pkg` and a sibling
`evil_pkg/__init__.py` in the same directory will execute the
`__init__.py` code when the victim opens the file and presses
`CTRL-X CTRL-O`.

## Impact
Arbitrary local code execution as the user running Vim, with the user's
full credential set (SSH keys, cloud credentials, etc.), file-system
access, and network egress.  Realistic delivery vectors include:

- reviewing a third-party Python contribution by checking out a fork
  branch and opening any `.py` file in it,
- auditing an extracted source tarball, malware sample, or repository
  whose layout the attacker controls,
- opening a `.py` file from any downloaded archive where the extracted
  layout places a hostile package next to the file being inspected.

Exploitation requires:

- Vim built with `+python3` (or `+python3/dyn` with a working Python 3
  runtime)
- Filetype plugins enabled (`filetype plugin on`, the default in
  `runtime/defaults.vim` and most distribution `vimrc`s).
- The victim opens the hostile `.py` file from the attacker-controlled
  working directory and invokes omni-completion.

The severity is rated Medium because the user must manually invoke omni-
completion after opening the file; the bug does not fire on file-open alone.

## Mitigation
As of Vim patch v9.2.0561 the omni-completer no longer executes
`import` or `from` statements harvested from the buffer by default.
Users who require completion of imported module members (for example
`os.<C-X><C-O>` offering `getcwd`, `path`, etc.) can opt back in with: >

    let g:pythoncomplete_allow_import = 1

Setting this variable re-enables the import-execution behavior and
should only be used when editing code from trusted sources.  When the
variable is unset or `0`, in-buffer symbols (classes, functions,
variables defined in the file) still complete normally; only completion
of names that would require executing imports is unavailable.

## Acknowledgements
The Vim project would like to thank github user tonghuaroot for
reporting, analyzing the issue, providing a proof of concept
and suggesting a fix.

## References
The issue has been fixed as of Vim patch [v9.2.0561](https://github.com/vim/vim/releases/tag/v9.2.0561).
- [Commit](https://github.com/vim/vim/commit/4b850457e12e1a678dd209f2868154f7553cbf8d)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-52mc-rq6p-rc7c)

comment:2 by Joe Locash, 12 days ago

Summary: vim-9.2.0561 (Security Update)vim-9.2.0565 (Security Update)
Out-of-bounds Read in Terminal Screen Snapshot in Vim < 9.2.565
================================================================
Date: 30.05.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Out-of-bounds Read (CWE-125)

## Summary
The `update_snapshot()` function in `src/terminal.c` copies the visible
terminal screen into the scrollback buffer when a snapshot is taken.  For
each screen cell it walks the cell's `chars[]` array with no upper bound,
stopping only when it encounters a NUL terminator.  When a cell legitimately
fills all `VTERM_MAX_CHARS_PER_CELL` (6) slots — a base character plus five
combining marks — the bundled libvterm returns the array without a
terminating NUL, so the loop reads past the fixed six-element array and
appends the out-of-bounds values to a buffer reserved for only six
characters.  A program whose output is rendered inside a `:terminal` window
can trigger this with a short byte sequence and no Vim scripting, leading to
a crash.

## Description
`update_snapshot()` is invoked whenever the terminal's visible screen is
snapshotted into the scrollback buffer, for example when the user enters
Terminal-Normal mode with `CTRL-W N`, or when the terminal job exits.  For
each cell it retrieves the cell with `vterm_screen_get_cell()` and emits its
characters with:

    for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
        ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
             (char_u *)ga.ga_data + ga.ga_len);

The loop has no `i < VTERM_MAX_CHARS_PER_CELL` guard and relies on the array
being NUL-terminated.  The bundled libvterm fills `cell.chars[]` with up to
`VTERM_MAX_CHARS_PER_CELL` entries and only writes a terminator when fewer
than that many characters are present.  A cell holding a base glyph plus five
combining marks therefore fills all six slots and is returned unterminated,
so the loop reads `cell.chars[6]` and beyond — past the end of the array —
and appends each out-of-bounds value to the snapshot buffer, which was grown
for only `VTERM_MAX_CHARS_PER_CELL` characters.

## Impact
A program running inside a `:terminal` window normally controls only its own
output and cannot affect the parent Vim process's memory.  By emitting a
single cell that fills all six character slots, such a program causes Vim to
read past a fixed-size array and append attacker-influenced, out-of-bounds
values to a buffer sized for only six characters.  The reliably reproduced
outcome is an out-of-bounds read leading to a crash (denial of service) of
the editor.

## Mitigation
The issue is fixed as of Vim patch v9.2.0565, which bounds the loop in
`update_snapshot()` with `i < VTERM_MAX_CHARS_PER_CELL`, mirroring the
existing bound in `handle_pushline()`.

## Acknowledgements
The Vim project would like to thank github user andrejtomci for reporting and
analyzing the issue and suggesting a fix.

## References
The issue has been fixed as of Vim patch [v9.2.565](https://github.com/vim/vim/releases/tag/v9.2.0565).
- [Commit](https://github.com/vim/vim/commit/63680c6d3d52477817b49cd1a66e7aabe8a7aa19)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-47gw-8gc3-mgcm)

comment:3 by Bruce Dubbs, 12 days ago

Owner: changed from lfs-book to SecurityAdvisory
Summary: vim-9.2.0565 (Security Update)vim-9.2.0567 (Security Update)

Updated to vim-9.2.0567 at commit 6890c71897. Leaving open for security advisory.

comment:4 by Douglas R. Reno, 10 days ago

Resolution: fixed
Status: newclosed

SA-13.0-111 issued

Note: See TracTickets for help on using tickets.