Opened 3 weeks ago
Closed 2 weeks ago
#5982 closed enhancement (fixed)
vim-9.2.0847 (Security Update)
| Reported by: | Joe Locash | Owned by: | SecurityAdvisory |
|---|---|---|---|
| Priority: | normal | Milestone: | 13.1 |
| Component: | Book | Version: | git |
| Severity: | normal | Keywords: | |
| Cc: |
Description
5 CVE's have been fixed.
Arbitrary Code Execution via Shell Keyword Lookup in Vim < 9.2.0839
===================================================================
Date: 23.07.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Improper Neutralization of Special Elements used in an OS Command
(CWE-78)
## Summary
The shell filetype plugins `runtime/ftplugin/sh.vim` and
`runtime/ftplugin/zsh.vim` install a buffer-local `keywordprg` that
interpolates its argument into a `bash -c` or `zsh -c` command without
shell escaping. Because a `keywordprg` beginning with `:` is escaped with
`fnameescape()`, which does not neutralize shell metacharacters, and because
`K` in Visual mode passes the whole selection verbatim, a crafted line in a
shell script can execute arbitrary commands when the user selects it and
presses `K`. `runtime/ftplugin/ps1.vim` is affected in the same way through
PowerShell.
## Description
When a buffer's filetype resolves to bash, zsh or PowerShell, the bundled
filetype plugin defines a keyword lookup command and points `keywordprg` at
it, for example:
command! -buffer -nargs=1 ShKeywordPrg silent exe
\ ':hor term bash -c "help "<args>" 2>/dev/null || man "<args>""'
setlocal keywordprg=:ShKeywordPrg
For a `keywordprg` that starts with `:`, Vim escapes the argument of `K`
with `vim_strsave_fnameescape()` (`src/normal.c`), which uses
`PATH_ESC_CHARS` (`src/vim.h`). That set omits the shell metacharacters
`;`, `&`, `(`, `)` and `>`; those appear only in `SHELL_ESC_CHARS`, which is
used exclusively for a `keywordprg` that is not an Ex command. The filetype
plugin adds no escaping of its own, so these characters reach the inner
`bash -c` unchanged and terminate the intended command.
In Visual mode, `K` passes the entire selection rather than the keyword
under the cursor, so shell metacharacters are preserved. In Normal mode the
argument is restricted to 'iskeyword' characters, which excludes these
metacharacters, and the issue does not arise.
The same pattern applies to `runtime/ftplugin/zsh.vim`, which builds a
`zsh -c` command, and to `runtime/ftplugin/ps1.vim`, which passes the
argument to PowerShell via `-Command`.
## Impact
Arbitrary operating-system command execution in the context of the user
running Vim. Exploitation requires:
- Vim with filetype plugins enabled
- the buffer's filetype resolving to sh, bash, zsh or PowerShell
- the victim opening a crafted file, selecting the crafted line in Visual
mode and invoking the keyword lookup with `K`.
The severity is rated Medium because Normal-mode `K` is not affected and
exploitation requires the victim to deliberately select the crafted text in
Visual mode and invoke the keyword lookup; the bug does not fire on
file-open alone.
## Acknowledgements
The Vim project would like to thank Github user @manus-use for reporting the issue.
## References
The issue has been fixed as of Vim patch [v9.2.0839](https://github.com/vim/vim/releases/tag/v9.2.0839).
- [Commit](https://github.com/vim/vim/commit/c5a82fe013e73c98004ad7cd4f906b1ad1ed610e)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-r5v6-q6j8-8qw2)
Arbitrary Code Execution via Netrw Menu Construction in Vim < 9.2.0840
=======================================================================
Date: 23.07.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Improper Control of Generation of Code (CWE-94),
Incomplete List of Disallowed Inputs (CWE-184)
## Summary
The netrw file browser builds its Bookmarks, History and Targets menus by
interpolating directory paths into `:execute`d `:menu` commands. The paths
are escaped with the character set in `g:netrw_menu_escape`, which did not
include the Ex command separator `|`. Because `:menu` treats an unescaped
bar as the end of the command, a directory path containing a bar terminates
the `:menu` command and the remainder of the path is executed as Ex
commands, and via `:!` as operating-system commands. Two of the affected
sites additionally interpolate the path into a single-quoted Vim string
without neutralizing the quote.
## Description
`runtime/plugin/netrwPlugin.vim` loads the netrw package on startup, so no
opt-in is required. While browsing, netrw records visited directories in
its history and rebuilds its menus, calling `s:NetrwBookmarkMenu()` and
`s:NetrwTgtMenu()` in `runtime/pack/dist/opt/netrw/autoload/netrw.vim`.
Those functions construct the menu entries by concatenation, for example:
let bmd = escape(bmd, g:netrw_menu_escape)
exe 'sil! menu ' .. ... .. 'Bookmarks.' .. bmd .. ' :e ' .. bmd .. "\<cr>"
`g:netrw_menu_escape` defaulted to `'.&? \'`, which neutralizes characters
significant to menu rendering but not the bar. The `:menu` command carries
the `EX_TRLBAR` attribute, so an unescaped bar in the interpolated path ends
the `:menu` command and everything after it is parsed as a further Ex
command.
The Targets menu entries additionally embed the path in a single-quoted Vim
string passed to `netrw#MakeTgt()` without escaping, so a path containing a
single quote can terminate that string early and inject Vim script.
Five construction sites were affected: the bookmark goto and bookmark delete
entries, the history entry, and the bookmark and history entries of the
Targets menu.
Menu construction is guarded by `has("gui")`, `has("menu")`,
`has("gui_running")`, the `'m'` flag in 'guioptions' and `g:netrw_menu`.
Vim running in a terminal is therefore not affected; the issue applies to
the GUI version with the menu bar enabled.
## Impact
Arbitrary Ex command execution, and via the `:!` command arbitrary
operating-system command execution, in the context of the user running Vim.
Exploitation requires:
- the GUI version of Vim with menus enabled (`has("gui_running")`, the `'m'`
flag in 'guioptions' and `g:netrw_menu` set, which are the defaults),
- a directory path under the attacker's control, for example on a shared
filesystem or a remote host browsed over FTP or SFTP,
- the victim browsing that path with netrw, or bookmarking it, so that it
enters netrw's history or bookmark list and the menus are rebuilt.
The severity is rated Medium because the console version of Vim is not
affected and the crafted path must first be recorded in the bookmark list or
the browsing history.
## Acknowledgements
The Vim project would like to thank David Carliez for reporting the issue.
## References
The issue has been fixed as of Vim patch [v9.2.0840](https://github.com/vim/vim/releases/tag/v9.2.0840).
- [Commit](https://github.com/vim/vim/commit/29c6fd090d4520592f8be7d9ec81190edf25ef69)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-rcr7-f3wr-22r2)
Heap Buffer Overflow in Text Property Handling in Vim < 9.2.0841
================================================================
Date: 23.07.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Integer Overflow or Wraparound (CWE-190),
Heap-based Buffer Overflow (CWE-122)
## Summary
The number of text properties attached to a line is stored in the memline
as a 16-bit value. When adding a property, `prop_add_one()` in
`src/textprop.c` computes the new count as `(uint16_t)(proplen + 1)`
without checking the existing count against the 16-bit ceiling. On a line
that already holds 65535 text properties the increment wraps to zero, so
the buffer allocated for the rewritten line reserves no space for any
property records, while the existing records are then copied into it. This
writes far past the end of the allocation.
## Description
Text properties are stored inline in the memline entry for a line, in the
layout `[text][NUL][prop_count][textprop_T...][vtext...]`, where
`prop_count` is a `uint16_t`. When `prop_add()` adds a property,
`prop_add_one()` obtains the current count from `get_text_props()` and
computes the new one:
uint16_t new_propcount = (uint16_t)(proplen + 1);
The size of the replacement line is then derived from that value:
new_line_len = (int)textlen + (int)PROP_COUNT_SIZE
+ new_propcount * (int)sizeof(textprop_T)
+ vtext_total;
newtext = alloc(new_line_len);
When `proplen` is already 65535, `proplen + 1` is 65536, which does not fit
in a `uint16_t` and truncates to zero. The allocation therefore contains
room for the line text and the count field but for no property records at
all. The subsequent copies, which move the existing records into the new
buffer, are driven by `proplen` rather than by the truncated count, so all
65535 existing records are written into a buffer sized for none of them.
The existing validation of the property block, added in patch 9.2.0670,
checks the structural consistency of the stored data but does not impose an
upper bound on the property count, so nothing prevents a line from reaching
65535 properties.
The data written past the allocation consists of the existing `textprop_T`
records, whose field values derive from the arguments of earlier
`prop_add()` calls.
## Impact
An out-of-bounds heap write of attacker-influenced data, which typically
results in a crash and may be usable for further exploitation.
Exploitation requires:
- Vim built with the `textprop` feature, which is included in the "huge"
feature set used by most distributions,
- 65536 text properties being added to a single line of a buffer, either by
a Vim script the user runs, or by a plugin that derives text properties
from data the attacker controls, such as the contents of an opened file.
The severity is rated Medium because reaching the 16-bit ceiling on a
single line does not occur in normal use and requires either a script
written for the purpose or a plugin driven with crafted input.
## Acknowledgements
The Vim project would like to thank Github user @Wang1rrr for reporting the issue.
## References
The issue has been fixed as of Vim patch [v9.2.0841](https://github.com/vim/vim/releases/tag/v9.2.0841).
- [Commit](https://github.com/vim/vim/commit/a9336b476fd1a182e3f79b5f83c0ffb04f8a922b)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-hm4g-pjfx-m27j)
Stack Buffer Overflow in the Vim Socket Server in Vim < 9.2.0842
=================================================================
Date: 23.07.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Stack-based Buffer Overflow (CWE-121),
Out-of-bounds Write (CWE-787)
## Summary
The socket server backend of Vim's client-server feature accepted an
unbounded number of client connections. Every accepted connection becomes a
channel with its own file descriptor, and each wait cycle of the main loop
adds all channel descriptors to fixed-size structures: an `fd_set` when Vim
uses `select()`, or a stack array of `struct pollfd` sized for a small fixed
number of channels when Vim uses `poll()`. Neither the number of accepted
clients nor the value of a descriptor was checked against those limits, so a
process able to connect to the server socket can make Vim write past the end
of a stack object.
## Description
A Vim server started with `--servername` and the socket backend listens on a
unix domain socket, or on a loopback address when a `channel:` address is
used. Each accepted connection is turned into a channel by
`socketserver_accept()` in `src/socketserver.c` and linked into both the
client list and the global channel list, without any limit on the number of
clients.
On builds that use `select()`, `channel_select_setup()` in `src/channel.c`
adds every channel descriptor to an `fd_set` with `FD_SET()`. An `fd_set`
holds a fixed number of descriptors, given by `FD_SETSIZE`, which is 1024 on
common systems. A descriptor greater than or equal to that value writes
past the end of the set, and the following `select()` call causes the kernel
to read and write back beyond it as well. The same unchecked pattern was
present in `channel_fill_wfds()`, `channel_select_check()` and in the client
loop of `socketserver_wait()`.
On builds that use `poll()` instead, `socketserver_wait()` and
`RealWaitForChar()` in `src/os_unix.c` collect the descriptors in a stack
array of `struct pollfd` dimensioned by `MAX_OPEN_CHANNELS`, which is ten.
Here no unusual descriptor numbers are required: the array is exceeded once
more than about ten clients are connected.
Reaching a descriptor number of 1024 or more on the `select()` path requires
the process limit on open files to be higher than that value, which is the
case for services and desktop sessions on current systems, but not where the
limit is left at 1024. On the `poll()` path no such condition applies.
The issue has been addressed by limiting the number of simultaneously
accepted client connections, by checking descriptors against `FD_SETSIZE`
before adding them to an `fd_set`, and by sizing the `struct pollfd` arrays
for the new limit.
## Impact
An out-of-bounds write to a stack object. On the builds shipped by most
distributions, which enable `_FORTIFY_SOURCE`, the affected library routine
detects the condition and terminates the process, so the practical result is
that the Vim server is killed and unsaved changes are lost. On builds
without that hardening the write is a single bit at an offset determined by
the descriptor number, together with the kernel writing back past the set.
Exploitation requires:
- Vim built with the socket server backend of the client-server feature and
running as a server, that is started with `--servername`,
- a process able to connect to that server: for the default unix domain
socket, one running under the same user account, since the socket
directory is only accessible to its owner; for a `channel:` address, any
process on the local machine, since such addresses are bound to the
loopback interface,
- on builds using `select()`, a limit on open files above 1024 and enough
connections to exceed it; on builds using `poll()`, about ten
connections.
No action by the user of the Vim server is required, since an idle server
reaches the affected code continuously.
The severity is rated Medium because the impact is limited to availability,
the attacker must already be able to reach the server socket, and on the
`select()` path a raised limit on open files is required as well.
## Acknowledgements
The Vim project would like to thank @tdjackey for reporting the issue.
## References
The issue has been fixed as of Vim patch [v9.2.0842](https://github.com/vim/vim/releases/tag/v9.2.0842).
- [Commit](https://github.com/vim/vim/commit/2e967091c3c78cab4524609aa67fbca49e10a32d)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-49m8-wwxj-mr69)
Out-of-bounds Access in Popup Opacity Handling in Vim >= 9.2.0469 && Vim < 9.2.0843
===================================================================================
Date: 23.07.2026
Severity: Medium
CVE: *requested, not yet assigned*
CWE: Buffer Underwrite (CWE-124),
Out-of-bounds Read (CWE-125)
## Summary
A popup window created with the "clipwindow" option is clipped to its host
window rather than to the screen. When such a popup is anchored to a text
property and the host window is scrolled so that the anchor moves above the
visible area, the popup's window row is negative by design, and the rows
clipped off the top are recorded separately. The code that marks the screen
cells covered by a popup using the "opacity" option did not account for
this: it iterated from the popup's window row without a lower bound, so it
indexed the screen-sized array before its start. This causes a read outside
the allocated array, and a write outside it when the stored value is smaller
than the popup's z-index.
## Description
`popup_mark_opacity_zindex()` in `src/popupwin.c` fills an array holding, for
each screen cell, the highest z-index of an opacity popup covering that
cell. The array is allocated for the current screen size, and the cell
offset is computed as the row multiplied by the number of screen columns
plus the column.
The loop over the popup's rows was bounded above, by the popup height and by
the number of screen rows, but not below. For a "clipwindow" popup whose
anchor has scrolled above the top of the host window, the window row is
negative, so the computed offset is negative as well and refers to memory
before the array. The value found there is compared with the popup's
z-index, which is set from Vim script, and overwritten when it is smaller.
The column dimension is not affected, because the popup's window column is
always clamped to a non-negative value. A related function that reads the
same array checks its arguments before indexing, so only the marking code
was affected.
The issue has been addressed by making the popup's window row always refer
to the first visible row, and recording the rows clipped off the top only in
the separate top offset, so that no consumer of the window row has to clamp
it.
## Impact
A read outside the bounds of a heap array, and a conditional write of a
16-bit value outside it. The written value comes from the popup's z-index
and the affected offsets follow from the scroll position and the popup
geometry, so both are influenced by the Vim script that creates the popup.
In a default layout the accesses fall into a neighbouring array used for the
popup mask, and the editor continues to run, so the effect is silent.
Exploitation requires Vim script that creates a popup using the
"clipwindow", "opacity" and text property anchor options together, and a
host window that is scrolled so the anchor leaves the visible area. The
issue is therefore reachable from a plugin or from a script the user runs,
but not from opening a file, since a modeline cannot call functions.
The severity is rated Medium because the affected code can only be reached
by running Vim script, and because the write is constrained: it stores a
16-bit value and only where the value already present is smaller, so it can
raise the affected memory but not set it to an arbitrary value.
## Acknowledgements
The Vim project would like to thank @tdjackey for reporting the issue.
## References
The issue has been fixed as of Vim patch [v9.2.0843](https://github.com/vim/vim/releases/tag/v9.2.0843).
- [Commit](https://github.com/vim/vim/commit/975e191dc817d8d00abca7197c4529a417c2f805)
- [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-pmvp-6rcj-98p4)
Change History (3)
comment:1 by , 3 weeks ago
| Summary: | vim-9.2.0843 (Security Update) → vim-9.2.0847 (Security Update) |
|---|
comment:2 by , 2 weeks ago
| Owner: | changed from to |
|---|
Updated at commit f1476cdc33c. Leaving open for security advisory.
Update to vim-9.2.0858 (Security Update). Update to glibc-2.44 (Security Update).
comment:3 by , 2 weeks ago
| Resolution: | → fixed |
|---|---|
| Status: | new → closed |
Advisory sa-13.0-185 has been issued.
Note:
See TracTickets
for help on using tickets.

4 more:
Use-after-free in JSON Decoding in Vim >= 9.2.0511 && Vim < 9.2.0844 ==================================================================== Date: 23.07.2026 Severity: Medium CVE: *requested, not yet assigned* CWE: Use After Free (CWE-416), Out-of-bounds Read (CWE-125) ## Summary When Vim decodes a JSON message received on a channel, the decoder may need more bytes than the current read buffer holds. It then obtains the next buffer, joins the two into a newly allocated one and frees the previous buffer. The function decoding the surrounding item keeps its own pointer into the old buffer and does not refresh it after such a refill. When the decoding of a string fails after a refill has taken place, for example because of an invalid escape sequence, the shared error path passes that stale pointer to the routine that formats the error message, which reads the freed memory. ## Description Channel JSON decoding uses a reader that can request more input while an item is being parsed. `channel_fill()` in `src/channel.c` obtains the next queued read buffer, allocates a combined buffer holding the unparsed remainder followed by the new data, frees the previous buffer and stores the new one in the reader. `json_decode_string()` in `src/json.c` handles this correctly: before asking for more input it converts its position into an index, and it recomputes its pointer from the reader afterwards. `json_decode_item()`, which parses the enclosing value, keeps a separate pointer into the buffer. That pointer is refreshed at several points in its loop, but not after the call that decodes a string. If that call first triggers a refill and then fails, the buffer the pointer refers to has been freed, and the error path at the end of the function reports the position using it. The error text is composed from the memory at that address, so the freed buffer is read. Any channel using one of the JSON based modes is affected, since the reader is always given the ability to refill. A peer that writes a message in parts controls where the input is split, and therefore whether the refill happens in the middle of a string. The socket server backend of the client-server feature is reachable without any authentication: it decodes whatever bytes a client has sent, before any field of the message is examined, so a client can trigger the condition with two writes and without a valid message. ## Impact A read of freed heap memory. The bytes read are used to compose an error message, which is suppressed while a channel message is decoded and is only recorded when channel logging has been enabled, so the contents are normally not shown anywhere. Depending on the state of the heap the read can also fault and end the Vim process, which for a server means the loss of unsaved changes. Exploitation requires: - Vim decoding JSON from a channel, either a channel opened by the user in one of the JSON based modes, or the socket server backend of the client-server feature, which is used when Vim is started with `--servername` and that backend, - a peer that can send a message split into parts, which for the socket server means any process able to connect to it: one running under the same user account for the default unix domain socket, or any process on the local machine for a `channel:` address, since those are bound to the loopback interface. The severity is rated Medium because the impact is limited to reading freed memory and, where that memory is no longer mapped, to ending the process; the contents that are read are not returned to the peer and are normally not displayed. ## Acknowledgements The Vim project would like to thank @tdjackey for reporting the issue. ## References The issue has been fixed as of Vim patch [v9.2.0844](https://github.com/vim/vim/releases/tag/v9.2.0844). - [Commit](https://github.com/vim/vim/commit/f8126294a526aa80c5123eb3079e325daee9ec75) - [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-69ch-22ch-r887)Arbitrary Ex Command Execution in C Omni-Completion in Vim < 9.2.0845 ====================================================================== Date: 23.07.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) ## Summary The C omni-completion script in `runtime/autoload/ccomplete.vim` looks up struct members by building a `:vimgrep` command that contains the type name taken from the `typeref:` field of a tags file entry, and running it with `:execute`. The value was escaped only for the pattern delimiter and the backslash. That is not sufficient: a value containing an unterminated collection makes Vim's own pattern skipping fail, after which the command parser looks for a command separator across the whole argument. A crafted tags file can therefore run arbitrary Ex commands, and through them shell commands, when the user invokes omni-completion on a member access. This is a bypass of the fix released as patch v9.2.0735 for GHSA-mf92-v4xw-j45x, which prevented the same injection only for values containing the pattern delimiter. ## 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 files listed in the tags file: execute 'silent! keepjumps noautocmd ' .. n .. 'vimgrep ' .. '/\t' .. escape(typename, '/\') .. '\(\t\|$\)/j ' .. fnames `typename` comes verbatim from the `typeref:` or `typename:` extension field of a tags entry, which Vim returns unchanged. The `:vimgrep` command may be followed by another command after a bar, so before looking for that bar Vim first skips over the search pattern. When skipping the pattern does not end at the closing delimiter, the routine returns a failure and the caller silently falls back to the beginning of the argument, after which the first unescaped bar is treated as a command separator. Skipping the pattern fails on an unterminated collection: an opening bracket makes the pattern skipping look for the matching closing bracket and run to the end of the text instead. A `typeref:` value that opens a bracket and then contains a bar therefore ends the `:vimgrep` command early, and what follows the bar is executed as an Ex command. No delimiter character is needed, so the escaping applied by the earlier fix does not prevent it. The leading `:silent!` suppresses the resulting error, so the injected command runs without a visible failure. The issue has been addressed by matching the field literally, using the "very nomagic" mode for the part of the pattern that holds the value, so that no character in it can affect how the pattern is parsed. ## 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, which is the default in `runtime/defaults.vim` and in most distribution configurations, so that the C omni-completion function is installed on C buffers, - a tags file under the attacker's control, which is ordinary data to receive together with source code, for example in a cloned repository or an unpacked archive, - 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 because the crafted tags file has no effect until the user invokes omni-completion, which is a deliberate action, and because the type must not be declared in the edited buffer, since the completion would otherwise not consult the tags file at all. ## Acknowledgements The Vim project would like to thank Threonine for reporting the issue. ## References The issue has been fixed as of Vim patch [v9.2.0845](https://github.com/vim/vim/releases/tag/v9.2.0845). - [Commit](https://github.com/vim/vim/commit/2f628d8104958fa7421664f792ca6d4f7a39a10f) - [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-cx73-phcg-3j5g)Heap Buffer Overflow when Loading a Spell File in Vim < 9.2.0846 ================================================================ Date: 24.07.2026 Severity: Medium CVE: *requested, not yet assigned* CWE: Heap-based Buffer Overflow (CWE-122), Out-of-bounds Write (CWE-787) ## Summary A spell file may contain a section holding sound-folding rules and a section holding a simple character mapping used for the same purpose. Reading the first of these fills an index table with the value minus one for every entry. Reading the second uses that same table to count how many mappings share a byte, but did not reset it beforehand, so every count came out one too low. The buffer allocated from that count is then too small for the mappings that are stored into it, and the surplus is written past its end. A crafted spell file therefore causes an out-of-bounds write when it is loaded, with part of the written data taken from the file. ## Description Vim loads a spell file when a language is selected with the 'spelllang' option. The file is a sequence of sections, and the loader in `src/spellfile.c` dispatches on the section identifier without imposing an order on them or requiring them to be unique. Reading the sound-folding section ends with a call to `set_sal_first()`, which sets all 256 entries of the language's first-index table to minus one. This happens even when that section contains no rules at all. Reading the character mapping section calls `set_sofo()`, which reuses the same table as a temporary counter. For every mapped character above 255 it increments the entry belonging to the low byte of that character, allocates one list per entry from the resulting counts, and only afterwards clears the table again. Since the counting started from minus one rather than zero, each count is one too low. For a byte value used by two mapped characters the list is allocated for one pair and two are written into it, so the storing loop writes past the end of the allocation; part of what is written is the mapped-to character taken from the file. For a byte value used by exactly one mapped character the count becomes zero, no list is allocated at all, and the storing loop follows a null pointer. The two sections are mutually exclusive by construction and the tool that generates spell files never emits both, but nothing in the loader rejected a file that contains them in that order. The issue has been addressed by clearing the table before it is used as a counter. ## Impact An out-of-bounds write to the heap, or a null pointer dereference, while a spell file is being loaded. Part of the written data is taken from the spell file, and the sizes of the other sections in the same file influence the surrounding heap layout, so the effect is not limited to a crash, although no further consequence has been demonstrated. Exploitation requires the victim to load a crafted spell file, which happens when 'spelllang' is set to a value naming that file, or to a language for which the crafted file is found in the directories searched for spell files. Vim must be built with the spell checking and multi-byte features, which is the case for the "huge" feature set used by most distributions. The severity is rated Medium because a spell file has to be placed where Vim will load it and the option has to be set to select it. ## Acknowledgements The Vim project would like to thank Yazan Balawneh for reporting the issue. ## References The issue has been fixed as of Vim patch [v9.2.0846](https://github.com/vim/vim/releases/tag/v9.2.0846). - [Commit](https://github.com/vim/vim/commit/05c41c922309c7a11b6ec2f124be66551c90d66a) - [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-9jqx-hgpr-6v64)Arbitrary Command Execution via the Vimball Record File in Vim < 9.2.0847 ========================================================================= Date: 24.07.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 vimball plugin records the files it extracts in a plain text file named `.VimballRecord`, so that they can be removed again later. Each line of that file holds the commands to undo one installation, and they were executed without being checked. The plugin refused to extract members whose name could inject into that file, but it did not refuse a member that *is* the record file. A crafted vimball can therefore write chosen commands into the record, which are then executed the next time any vimball operation consults it. ## Description `runtime/autoload/vimball.vim` writes one line per installed vimball into `.VimballRecord` in the directory used for vimball installations, listing the commands that delete the files that were extracted. When a vimball is installed or removed, `vimball#RmVimball()` searches that file for the line belonging to the archive, strips the archive name from the front of it and runs the remainder: sil! keepalt keepjumps exe exestring The name of each member of an archive was already checked, and names that contain a bar, a quote or a closing parenthesis were rejected, precisely so that a file name could not inject commands into the record. That check does not apply to a member whose name is `.VimballRecord` itself: extracting it overwrites the record with content taken straight from the archive, and the content of a member is not examined at all. The commands placed there are run later, when a vimball with the matching name is installed or removed, and not while the crafted archive is being extracted. The issue has been addressed by refusing to extract a member named `.VimballRecord`, and by executing only entries of the expected form, namely a single call that deletes one file or directory. Anything else in the record is reported and skipped. ## Impact Execution of arbitrary Ex commands, and through commands such as `:!` arbitrary operating-system commands, in the context of the user running Vim. Exploitation requires the victim to install a vimball from a source controlled by the attacker, either by sourcing it or through the getscript plugin, which downloads and sources vimballs automatically. Installing a vimball already runs the commands contained in it, so the attacker does not gain the ability to run commands as such. What the issue adds is that the archive itself can appear harmless while leaving commands behind that run at a later time, during an unrelated vimball installation or removal, and that continue to do so after the archive that placed them has been removed. The severity is rated Medium because installing a vimball from an untrusted source already permits commands from that vimball to run, so the issue extends the reach of such an installation rather than creating it. ## Acknowledgements The Vim project would like to thank tdjackey for reporting the issue. ## References The issue has been fixed as of Vim patch [v9.2.0847](https://github.com/vim/vim/releases/tag/v9.2.0847). - [Commit](https://github.com/vim/vim/commit/581a2f3ac9c6f96a26324f6b2c8c11415fd0d452) - [Github Security Advisory](https://github.com/vim/vim/security/advisories/GHSA-r22p-fhw4-84p2)