Commit: patch 9.2.0840: [security]: code injection in netrw via bookmarks

2 views
Skip to first unread message

Christian Brabandt

unread,
Jul 23, 2026, 4:15:16 PM (18 hours ago) Jul 23
to vim...@googlegroups.com
patch 9.2.0840: [security]: code injection in netrw via bookmarks

Commit: https://github.com/vim/vim/commit/29c6fd090d4520592f8be7d9ec81190edf25ef69
Author: Yasuhiro Matsumoto <matt...@gmail.com>
Date: Mon Jul 6 13:54:03 2026 +0900

patch 9.2.0840: [security]: code injection in netrw via bookmarks

Problem: [security]: code injection in netrw via bookmarks and history
(David Carliez)
Solution: Escape the '|' explicitly (Yasuhiro Matsumoto)

The bookmark and history menu builders interpolate paths into :execute'd
:menu commands using g:netrw_menu_escape, which did not escape the Ex
command separator '|'. A crafted path could break out of the :menu command
and run arbitrary Ex/shell commands when the menu was built or triggered.

Add '|' to g:netrw_menu_escape for the menu names, escape the :e right-hand
side with fnameescape(), and quote the netrw#MakeTgt() argument with
string() instead of raw single-quote interpolation.

Github Security Advisory:
https://github.com/vim/vim/security/advisories/GHSA-rcr7-f3wr-22r2

Signed-off-by: Yasuhiro Matsumoto <matt...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw.vim b/runtime/pack/dist/opt/netrw/autoload/netrw.vim
index 64b5ce3aa..d745fd36f 100644
--- a/runtime/pack/dist/opt/netrw/autoload/netrw.vim
+++ b/runtime/pack/dist/opt/netrw/autoload/netrw.vim
@@ -1,7 +1,7 @@
" Creator: Charles E Campbell
" Previous Maintainer: Luca Saccarola <github...@aleeas.com>
" Maintainer: This runtime file is looking for a new maintainer.
-" Last Change: 2026 Jul 22
+" Last Change: 2026 Jul 23
" Copyright: Copyright (C) 2016 Charles E. Campbell {{{1
" Permission is hereby granted to use and distribute this code,
" with or without modifications, provided that this copyright
@@ -375,7 +375,7 @@ if has("win32")
else
call s:NetrwInit("g:netrw_glob_escape",'*[]?`{~$\')
endif
-call s:NetrwInit("g:netrw_menu_escape",'.&? \')
+call s:NetrwInit("g:netrw_menu_escape",'.&? \|')
call s:NetrwInit("s:netrw_map_escape","<|

\\<C-V>\"")
if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4')
let s:treedepthstring= "│ "
@@ -3777,13 +3777,14 @@ function s:NetrwBookmarkMenu()
if exists("g:netrw_bookmarklist") && g:netrw_bookmarklist != [] && g:netrw_dirhistmax > 0
let cnt= 1
for bmd in g:netrw_bookmarklist
- let bmd= escape(bmd,g:netrw_menu_escape)
+ let ebmd= escape(bmd,g:netrw_menu_escape)
+ let fbmd= escape(fnameescape(bmd),'|')

" show bookmarks for goto menu
- exe 'sil! menu '.g:NetrwMenuPriority.".2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks.'.bmd.' :e '.bmd."\<cr>"
+ exe 'sil! menu '.g:NetrwMenuPriority.".2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks.'.ebmd.' :e '.fbmd."\<cr>"

" show bookmarks for deletion menu
- exe 'sil! menu '.g:NetrwMenuPriority.".8.2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Delete.'.bmd.' '.cnt."mB"
+ exe 'sil! menu '.g:NetrwMenuPriority.".8.2.".cnt." ".g:NetrwTopLvlMenu.'Bookmarks\ and\ History.Bookmark\ Delete.'.ebmd.' '.cnt."mB"
let cnt= cnt + 1
endfor

@@ -3799,7 +3800,8 @@ function s:NetrwBookmarkMenu()
let priority = g:netrw_dirhistcnt + histcnt
if exists("g:netrw_dirhist_{cnt}")
let histdir= escape(g:netrw_dirhist_{cnt},g:netrw_menu_escape)
- exe 'sil! menu '.g:NetrwMenuPriority.".3.".priority." ".g:NetrwTopLvlMenu.'History.'.histdir.' :e '.histdir."\<cr>"
+ let ehistdir= escape(fnameescape(g:netrw_dirhist_{cnt}),'|')
+ exe 'sil! menu '.g:NetrwMenuPriority.".3.".priority." ".g:NetrwTopLvlMenu.'History.'.histdir.' :e '.ehistdir."\<cr>"
endif
let first = 0
let cnt = ( cnt - 1 ) % g:netrw_dirhistmax
@@ -7152,7 +7154,7 @@ function s:NetrwTgtMenu()
let tgtdict[bmd]= cnt
let ebmd= escape(bmd,g:netrw_menu_escape)
" show bookmarks for goto menu
- exe 'sil! menu <silent> '.g:NetrwMenuPriority.".19.1.".cnt." ".g:NetrwTopLvlMenu.'Targets.'.ebmd." :call netrw#MakeTgt('".bmd."')\<cr>"
+ exe 'sil! menu <silent> '.g:NetrwMenuPriority.".19.1.".cnt." ".g:NetrwTopLvlMenu.'Targets.'.ebmd." :call netrw#MakeTgt(".escape(string(bmd),'|').")\<cr>"
let cnt= cnt + 1
endfor
endif
@@ -7170,7 +7172,7 @@ function s:NetrwTgtMenu()
endif
let tgtdict[histentry] = histcnt
let ehistentry = escape(histentry,g:netrw_menu_escape)
- exe 'sil! menu <silent> '.g:NetrwMenuPriority.".19.2.".priority." ".g:NetrwTopLvlMenu.'Targets.'.ehistentry." :call netrw#MakeTgt('".histentry."')\<cr>"
+ exe 'sil! menu <silent> '.g:NetrwMenuPriority.".19.2.".priority." ".g:NetrwTopLvlMenu.'Targets.'.ehistentry." :call netrw#MakeTgt(".escape(string(histentry),'|').")\<cr>"
endif
let histcnt = histcnt + 1
endwhile
diff --git a/src/version.c b/src/version.c
index 9d3c95b00..75110ae0a 100644
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 840,
/**/
839,
/**/
Reply all
Reply to author
Forward
0 new messages