See how vim compares to other vendors in security performance
Vim is an open source, command line text editor. Prior to 9.2.0840, runtime/plugin/netrwPlugin.vim loads netrw and runtime/pack/dist/opt/netrw/autoload/netrw.vim constructs Bookmarks, History, and Targets menu entries by interpolating attacker-controlled directory paths into executed :menu commands. s:NetrwBookmarkMenu(), s:NetrwTgtMenu(), g:netrwmenuescape, EXTRLBAR, and netrw#MakeTgt() fail to neutralize the | command separator or single quotes at five construction sites, allowing a crafted path browsed or bookmarked in GUI Vim to execute arbitrary Ex and operating-system commands. This issue is fixed in version 9.2.0840.
Vim is an open source, command line text editor. Prior to 9.2.0839, the runtime/ftplugin/sh.vim, runtime/ftplugin/zsh.vim, and runtime/ftplugin/ps1.vim filetype plugins pass attacker-controlled Visual-mode selections from K through keywordprg commands without safely separating shell arguments. fnameescape() and PATHESCCHARS do not neutralize shell metacharacters before ShKeywordPrg, ZshKeywordPrg, or GetHelp invokes bash, zsh, or PowerShell, allowing arbitrary operating-system commands to execute with the privileges of the user running Vim. This issue is fixed in version 9.2.0839.
Vim is an open source, command line text editor. Prior to 9.2.0847, runtime/autoload/vimball.vim allows a crafted vimball member named .VimballRecord to overwrite the installation record with attacker-chosen commands. When vimball#RmVimball() later processes the matching record entry, the stored Ex commands, including operating-system commands invoked through :!, execute with the privileges of the user running Vim. This issue is fixed in version 9.2.0847.
Vim is an open source, command line text editor. From 9.2.0469 until 9.2.0843, popupmarkopacityzindex() in src/popupwin.c can use a negative wwinrow for a text-property-anchored popup with clipwindow and opacity, indexing before the screen array instead of accounting for wpopuptopoff and causing an out-of-bounds read and conditional write. This issue is fixed in version 9.2.0843.
Vim is an open source, command line text editor. Prior to 9.2.0841, propaddone() in src/textprop.c uses the proplen value from gettextprops() to increment a uint16t property count beyond 0xffff, wrapping the count to zero and copying existing text-property records into a heap allocation sized for none of them. This issue is fixed in version 9.2.0841.
Vim is an open source, command line text editor. Prior to 9.2.0846, setsofo() in src/spellfile.c reuses slsalfirst[] without resetting values left by setsalfirst(), so a crafted spell file containing an SNSAL section before an SNSOFO section causes under-counted mapping lists and attacker-influenced writes beyond a heap allocation. This issue is fixed in version 9.2.0846.
Vim is an open source, command line text editor. From 9.2.0511 until 9.2.0844, jsondecodeitem() in src/json.c can retain a stale pointer after jsondecodestring() invokes channelfill() to refill and free the current buffer, causing the error path to read freed memory instead of reader->jsbuf + reader->jsused when an invalid JSON string spans buffers. This issue is fixed in version 9.2.0844.
Vim is an open source, command line text editor. Prior to 9.2.0842, the socket server backend in src/socketserver.c accepts unbounded client connections in socketserveraccept(), causing descriptors to overflow fdset structures in src/channel.c and fixed-size struct pollfd arrays in src/osunix.c, which allows a local process that can connect to the server socket to corrupt stack memory or terminate the Vim server. This issue is fixed in version 9.2.0842.
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.
- Commit - Github Security Advisory
Best, Chris -- Man sagt nicht 'Nichts!', man sagt dafür 'Jenseits' oder 'Gott'. -- Friedrich Wilhelm Nietzsche
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 setsalfirst(), 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 setsofo(), 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.
- Commit - Github Security Advisory
Best, Chris -- "Friede seiner Asche" wird geschüttelreimt zu "Ade seiner Frische", was auf das gleiche hinauskommt.
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.
- Commit - Github Security Advisory
Best, Chris -- Verurteile niemanden, bevor du nicht in seiner Lage warst. -- Talmud (Literaturwerk des Judentums)
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. channelfill() 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.
jsondecodestring() 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. jsondecodeitem(), 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.
- Commit - Github Security Advisory
Best, Chris -- Die nützlichsten Bücher sind die, die den Leser anregen, sie zu ergänzen. -- François Marie Voltaire
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
popupmarkopacityzindex() 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.
- Commit - Github Security Advisory
Thanks, Chris -- Wer Glück im Spiel hat, hat auch Geld für die Liebe.
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 fdset 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 socketserveraccept() 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(), channelselectsetup() in src/channel.c adds every channel descriptor to an fdset with FDSET(). An fdset holds a fixed number of descriptors, given by FDSETSIZE, 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 channelfillwfds(), channelselectcheck() and in the client loop of socketserverwait().
On builds that use poll() instead, socketserverwait() and RealWaitForChar() in src/osunix.c collect the descriptors in a stack array of struct pollfd dimensioned by MAXOPENCHANNELS, 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 FDSETSIZE before adding them to an fdset, 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 FORTIFYSOURCE, 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.
- Commit - Github Security Advisory
Thanks, Chris -- Leider sind meines Erachtens nicht zu vernachlässigende Teile der Bevölkerung potentiell bis akut ausreichend 'vertrottelt'. -- Helmut Springer
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, propaddone() in src/textprop.c computes the new count as (uint16t)(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][propcount][textpropT...][vtext...], where propcount is a uint16t. When propadd() adds a property, propaddone() obtains the current count from gettextprops() and computes the new one:
uint16t newpropcount = (uint16t)(proplen + 1);
The size of the replacement line is then derived from that value:
newlinelen = (int)textlen + (int)PROPCOUNTSIZE + newpropcount (int)sizeof(textpropT) + vtexttotal; newtext = alloc(newlinelen);
When proplen is already 65535, proplen + 1 is 65536, which does not fit in a uint16t 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 textpropT records, whose field values derive from the arguments of earlier propadd() 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.
- Commit - Github Security Advisory
Thanks, Chris -- Theorie ist, wenn man alles weiß und nix funktioniert; Praxis ist, wenn alles funktioniert und keiner weiß, warum.
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 :executed :menu commands. The paths are escaped with the character set in g:netrwmenuescape, 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:netrwmenuescape) exe 'sil! menu ' .. ... .. 'Bookmarks.' .. bmd .. ' :e ' .. bmd .. "\<cr>"
g:netrwmenuescape defaulted to '.&? \', which neutralizes characters significant to menu rendering but not the bar. The :menu command carries the EXTRLBAR 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("guirunning"), the 'm' flag in 'guioptions' and g:netrwmenu. 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("guirunning"), the 'm' flag in 'guioptions' and g:netrwmenu 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.
- Commit - Github Security Advisory
Thanks, Chris -- Wir leben im Zeitalter der medialen Massenverblödung. -- Peter Scholl-Latour
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 vimstrsavefnameescape() (src/normal.c), which uses PATHESCCHARS (src/vim.h). That set omits the shell metacharacters ;, &, (, ) and >; those appear only in SHELLESCCHARS, 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.
- Commit - Github Security Advisory
Thanks, Chris -- Von wem stammt der Ausspruch: lernen, lernen und nochmals lernen? Von Lenin. Ja, aber wann hat er das gesagt? Na, als er das Zeugnis von Ulbricht gesehen hat!
Vim is an open source, command line text editor. Prior to 9.2.0735, the C omni-completion script in runtime/autoload/ccomplete.vim interpolates the typeref: or typename: extension field of a tags entry, without escaping, into a :vimgrep pattern that is run through :execute. Because :vimgrep honors the bar as a command separator, a crafted tag field can close the search pattern and append an arbitrary Ex command; opening a hostile .c file whose project tags file contains such an entry and invoking C omni-completion runs that command as the editing user. This issue is fixed in version 9.2.0735.
Last updated 13 July 2026
Last updated 13 July 2026
Last updated 13 July 2026
Arbitrary Code Execution via PHP Omni-Completion in Vim < 9.2.0736 ================================================================== Date: 28.06.2026 Severity: Medium CVE: requested, not yet assigned CWE: Improper Neutralization of Special Elements (CWE-94), Inclusion of Functionality from Untrusted Control Sphere (CWE-829)
Summary The PHP omni-completion script in runtime/autoload/phpcomplete.vim interpolates a class or trait name, taken from the contents of the edited buffer, into a search() pattern that is run via winexecute() without escaping. A name containing a single quote can terminate the search() string argument early; because the bar (|) is honored as an Ex command separator, the remainder of the name is then run as Ex commands. Via the :! command this allows arbitrary command execution.
Description runtime/ftplugin/php.vim installs omnifunc=phpcomplete#Complete on every PHP buffer when Vim has filetype plugins enabled. When the user invokes omni-completion with CTRL-X CTRL-O, phpcomplete#GetClassContentsStructure() locates a class declaration by building a search() command and running it through winexecute():
call winexecute(popupid, \ 'call search(''\c\(class\|interface\|trait\)\s\+' \ .. a:classname .. '\(\>\|$\)'')')
The class name is concatenated into the single-quoted search() argument without neutralizing the single quote. A ' in the name ends that string early, and the :call command honors the EXTRLBAR attribute, so a bar following the quote begins a new Ex command that winexecute() then runs.
The name passed to this function can originate from the buffer's own contents (for example a class or trait name parsed from the file), so a crafted PHP file can place an injecting value where a class name is expected.
Impact Arbitrary Ex command execution, and via the :! command arbitrary operating- system command execution, in the context of the user running Vim.
Exploitation requires:
- Vim with filetype plugins enabled (filetype plugin on, the default in runtime/defaults.vim and most distribution vimrc files), - the PHP omni-completion function in use (omnifunc=phpcomplete#Complete, set by the bundled PHP ftplugin), - the victim opening a crafted PHP file and invoking omni-completion.
The severity is rated Medium because the user must open the crafted file and manually invoke omni-completion; the bug does not fire on file-open alone.
Acknowledgements The Vim project would like to thank Hirohito Higashi for reporting, analyzing and fixing the issue.
References The issue has been fixed as of Vim patch v9.2.0736. - Commit - Github Security Advisory
Best, Christian -- Nur auf dem Boden harter Arbeit bereitet sich normalerweise der Einfall vor. -- Max Weber
Arbitrary Code Execution via C Omni-Completion in Vim < 9.2.0735 ================================================================ Date: 26.06.2026 Severity: Medium CVE: requested, not yet assigned CWE: Improper Neutralization of Special Elements (CWE-94), Inclusion of Functionality from Untrusted Control Sphere (CWE-829)
Summary The C omni-completion script in runtime/autoload/ccomplete.vim interpolates the typeref: / typename: extension field of a tags entry, without escaping, into a :vimgrep pattern that is run through :execute. Because :vimgrep honors the bar (|) as a command separator, a crafted tag field can close the search pattern and append an arbitrary Ex command. Opening a hostile .c file whose project tags file contains such an entry and invoking C omni-completion runs that command as the editing user.
Description runtime/ftplugin/c.vim installs omnifunc=ccomplete#Complete on every C buffer when Vim has filetype plugins enabled. When the user invokes omni- completion with CTRL-X CTRL-O on a structure-member access (for example myvar.field), the completer looks up the variable in the tags files, reads the type from the entry's typeref: (or typename:) field, and searches the tags files for members of that type.
The member search builds a :vimgrep command and runs it with :execute. The type name taken from the tag field is concatenated directly into the search pattern with no escaping, while the file-name argument is passed through escape(). The typeref: field is parsed verbatim by taglist(); its only constraint is that it contains no internal whitespace, so the characters / | ' " ( ) all survive.
:vimgrep carries the EXTRLBAR attribute, which means an unescaped bar in the constructed command line terminates the :vimgrep and begins a new Ex command. A tag field of the form x/|<command>|" therefore closes the regular expression with /, starts a fresh Ex command after |, and comments out the remainder of the generated line.
Impact Arbitrary local code execution as the user running Vim, with that user's full credential set, file-system access, and network egress. Realistic delivery vectors include:
- cloning or checking out a third-party repository that ships a tags file alongside its C sources, - extracting a source tarball or archive whose layout places a hostile tags file next to the .c file being inspected, - auditing a malware sample or untrusted source tree in its own directory.
Exploitation requires:
- Vim with filetype plugins enabled (filetype plugin on, the default in runtime/defaults.vim and most distribution vimrc files), - a tags file reachable through the 'tags' option (the default ./tags resolves to the attacker's file in the conventional ctags workflow, where Vim's working directory is the project root), - the victim opening the hostile .c file and invoking C omni-completion on a member access.
The severity is rated Medium because the user must manually invoke omni- completion on a member access after opening the file; the bug does not fire on file-open alone and the ccomplete.vim produces an error message, which makes the whole attack quite noticeable.
Acknowledgements The Vim project would like to thank Cipher / Causal Security (https://causalsecurity.com/) for reporting and analyzing the issue and providing a proof of concept.
References The issue has been fixed as of Vim patch v9.2.0735. - Commit - Github Security Advisory
Thanks, Christian -- Ich schoß ihn über seinen eigenen Haufen. -- Heinz Erhardt
Vim is an open source, command line text editor. Prior to 9.2.0653, the treecountwords() function in src/spellfile.c fills in the word-count fields of a spell-file word trie by walking it iteratively with a depth counter. The counter is bounded only by the trie structure itself; it is never checked against the size of the fixed MAXWLEN-element stack arrays it indexes (arridx[], curi[], wordcount[]). A crafted .spl/.sug file pair, loaded when the user invokes spell suggestion, can drive the descent arbitrarily deep, so the function writes past the end of those arrays. This is a stack out-of-bounds write that corrupts the call frame and crashes the editor. This vulnerability is fixed in 9.2.0653.
Vim is an open source, command line text editor. Prior to 9.2.0699, Vim's Python omni-completion (runtime/autoload/python3complete.vim and the legacy pythoncomplete.vim) executes reconstructed function and class definitions from the current buffer with exec() as part of populating the completion dictionary. When reconstructing that source, each scope's docstring is inserted verbatim between triple quotes with no escaping, so a hostile buffer can break out of the triple-quoted literal and execute attacker-controlled Python during omni-completion. This vulnerability is fixed in 9.2.0699.
Vim is an open source, command line text editor. Prior to 9.2.0698, the single-byte branch of spellsoundfoldsofo() in src/spell.c translates a word through a spell file's SOFO (sound-folding) byte map into a caller-owned result buffer. Its copy loop advances the output index ri with no upper bound and terminates only on the input NUL, writing one byte per input byte into the MAXWLEN-element stack buffer the caller provides. A word longer than MAXWLEN, passed to soundfold() (or reached via sound-based spell suggestion) while a SOFO-based spell language is active, therefore writes past the end of that buffer. This is a stack out-of-bounds write that corrupts the call frame and crashes the editor. This vulnerability is fixed in 9.2.0698.
Last updated 2 July 2026
Last updated 2 July 2026
Last updated 2 July 2026
Vim is an open source, command line text editor. Prior to 9.2.0670, gettextprops() in src/textprop.c reads a uint16 property count stored inline after a line's text and returns it as the number of 32-byte textpropT entries that follow. The only check is a floor that guarantees room for a single entry; the count is never checked against the amount of data actually present. A line that declares a large count while carrying little data causes consumers to read far past the end of the line buffer. Such a line can be delivered through a crafted undo file, leading to a crash. This vulnerability is fixed in 9.2.0670.