Opened 56 minutes ago

Last modified 51 minutes ago

#6006 assigned enhancement

vim-9.2.1014 (Security Update)

Reported by: Joe Locash Owned by: Bruce Dubbs
Priority: normal Milestone: 13.1
Component: Book Version: git
Severity: normal Keywords:
Cc:

Description

The following have been fixed recently:

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](https://github.com/vim/vim/releases/tag/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](https://github.com/vim/vim/releases/tag/v9.2.1011).

- [Commit](https://github.com/vim/vim/commit/331d5d67028505c5b6043603a57d1e8497b922b5)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-r77m-8m55-rpr6)
Out-of-bounds Access in libvterm Resize Handling in Vim < 9.2.1013
==================================================================

Date: 26.08.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Out-of-bounds Write (CWE-787),
     Improper Validation of Specified Quantity in Input (CWE-1284)

## Summary

The bundled libvterm keeps the terminal size in two places: `VTermState`,
which holds the cursor position and the bounds used to clamp it, and
`VTermScreen`, which owns the storage for the cells and the line
information.  The `CSI 8 ; rows ; cols t` sequence, with which a program
running in a terminal window asks for a new size, was written into
`VTermState` unchanged, while the screen clamped it to `VTERM_MAX_ROWS` and
`VTERM_MAX_COLS` when allocating the storage.  The clamped value was not
reported back, so the state kept the requested size.  All later bound checks
then used a size that is much larger than the allocated line information,
and ordinary text output could move the cursor beyond it, reading and
writing past the end of the allocation.

## Description

Programs in a terminal window can send `CSI 8 ; rows ; cols t` to
request a new size.  The only limit on the values is the one the argument
parser applies to any numeric argument, about 1.07 billion.  `on_resize()`
in `src/libvterm/src/state.c` stored them directly:

    state->rows = rows;
    state->cols = cols;

and raised `scrollregion_bottom` accordingly, before calling the resize
callback.  That callback, `resize()` in `src/libvterm/src/screen.c`, clamps
the size to `VTERM_MAX_ROWS` and `VTERM_MAX_COLS`, both 1000, and allocates
the `VTermLineInfo` array for the clamped number of rows.  It updates the
screen's own size but has no way to correct the state, and `on_resize()`
does not read the size back.

After a request such as `CSI 8 ; 50000 ; 200 t` the state therefore reports
50000 rows while the line information holds 1000 entries.  Because the
scroll region was widened as well, `linefeed()` no longer scrolls at the
bottom of the screen, it only advances the cursor row.  Printing more than
1000 lines, or printing enough text to wrap that often, moves the cursor
past the end of the array.

The issue has been addressed by clamping the requested size to the same
limits the screen uses, at the start of `on_resize()`, before the state is
updated and before any storage is allocated.

## Impact

Heap out-of-bounds read and write in the process running Vim.  The values
written are small fixed constants rather than attacker-controlled data and
the offset follows the amount of output produced, so the demonstrated
consequence is memory corruption leading to a crash.  There is no channel
through which the program in the terminal can observe what is read.

Exploitation requires:

- Vim compiled with the `terminal` feature and a terminal window open,
- output rendered in that window from a program under the attacker's
  control, for example a connection to a malicious SSH server, a file shown
  with a pager, or the log of a compromised build tool,
- the escape sequence and the following output to arrive together, without
  Vim redrawing in between, which happens for any program producing a
  continuous stream of output.

No privileges on the host running Vim are needed and the sequence is
ordinary output on the program's own standard output stream.  The severity
is rated Medium because the impact demonstrated is a denial of service
rather than control over the process, and because the attacker must already
have output displayed in a terminal window.

This is the second issue in the same sequence in the bundled libvterm.
Patch v9.2.0569 rejected missing, zero and negative dimensions, however the values
above the supported maximum remained unchecked.

## 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.1013](https://github.com/vim/vim/releases/tag/v9.2.1013).

- [Commit](https://github.com/vim/vim/commit/e4f51e505ae0d64c4797c89314ecff1be88135be)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-vfc7-mhvm-gjp8)
- [Related patch v9.2.0569](https://github.com/vim/vim/releases/tag/v9.2.0569)
Integer Overflow in Undo File Entry Size Check in Vim < v9.2.1014 && Vim >= v8.1.0688
=====================================================================================

Date: 26.08.2026
Severity: Low
CVE: *requested, not yet assigned*
CWE: Integer Overflow or Wraparound (CWE-190),
     Out-of-bounds Write (CWE-787)

## Summary

When Vim reads a persistent undo file it allocates an array holding the
lines of an undo entry, with the number of lines taken from the file.  The
check that guards this allocation against an overflow of the multiplication
compares against the size of a pointer, while the array is allocated using
the size of its actual element type, which is three times larger on a
32-bit system.  The check therefore accepts a line count for which the
multiplication does not fit, resulting in an allocation that is much smaller
than requested.

Only 32-bit builds of Vim are affected.

## Description

`unserialize_uep()` in `src/undo.c` reads the number of lines of an undo
entry from the file and allocates the array for them:

    if (uep->ue_size < LONG_MAX / (int)sizeof(char_u *))
        array = U_ALLOC_LINE(sizeof(undoline_T) * uep->ue_size);

The multiplication is computed with type `size_t`, which wraps around
instead of failing when the result does not fit.  The check exists to
prevent that, but it uses the size of a pointer where the allocation uses
the size of `undoline_T`.  On a 32-bit system a pointer is 4 bytes and
`undoline_T` is 12, so the check permits a line count three times larger
than the one at which the multiplication starts to wrap.

For a line count in that range the allocation succeeds with a size much
smaller than needed, and the loop that follows fills the array using the
line count from the file, writing past the end of the allocation.

Until patch v8.1.0688 the array held plain pointers and the check was
correct.  That patch changed the element type to `undoline_T` so that text
properties are restored by undo, and updated the allocation but not the
check.

On 64-bit builds the line count is read as a 32-bit value and the
multiplication cannot overflow a 64-bit `size_t`, so those builds are not
affected.

The issue has been addressed by using the size of `undoline_T` for the
check as well.

## Impact

Heap out-of-bounds write in the process running Vim, when a crafted undo
file is read.  This happens either explicitly with `:rundo`, or
automatically when `'undofile'` is set and Vim finds a matching undo file
for the edited file.

The values written are a heap pointer and two lengths derived from the undo
file rather than freely chosen data, and the write proceeds sequentially
from the end of the allocation, so the expected consequence is heap
corruption leading to a crash.

The severity is rated Low.  Besides being limited to 32-bit builds, a
crafted file must contain a valid entry for every line up to the point
where the allocation ends, which for the smallest overflowing line count
means an undo file of well over a hundred megabytes.  Whether the wrapped
allocation succeeds at all depends on the memory available to a 32-bit
process.

## 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.1014](https://github.com/vim/vim/releases/tag/v9.2.1014).

- [Commit](https://github.com/vim/vim/commit/e41756455701b9419f2493ab2e2e0f01557fe939)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-cvc5-p4x9-3f9f)

Change History (1)

comment:1 by Bruce Dubbs, 51 minutes ago

Milestone: 13.213.1
Owner: changed from lfs-book to Bruce Dubbs
Status: newassigned

Nothing depends on this so it can be updated at any time for both LFS and BLFS. Since changes are made daily, I'll wait until August 30th to update this in both books.

Note: See TracTickets for help on using tickets.