Commit: patch 9.2.0073: [security]: possible command injection using netrw

1 view
Skip to first unread message

Christian Brabandt

unread,
Feb 27, 2026, 4:16:50 PM (5 days ago) Feb 27
to vim...@googlegroups.com
patch 9.2.0073: [security]: possible command injection using netrw

Commit: https://github.com/vim/vim/commit/79348dbbc09332130f4c86045e1541d68514fcc1
Author: Christian Brabandt <c...@256bit.org>
Date: Sun Feb 22 21:24:48 2026 +0000

patch 9.2.0073: [security]: possible command injection using netrw

Problem: [security]: Insufficient validation of hostname and port in
netrw URIs allows command injection via shell metacharacters
(ehdgks0627, un3xploitable).
Solution: Implement stricter RFC1123 hostname and IP validation.
Use shellescape() for the provided hostname and port.

Github Advisory:
https://github.com/vim/vim/security/advisories/GHSA-m3xh-9434-g336

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 51e05f2b1..25ca4e8a1 100644
--- a/runtime/pack/dist/opt/netrw/autoload/netrw.vim
+++ b/runtime/pack/dist/opt/netrw/autoload/netrw.vim
@@ -20,6 +20,7 @@
" 2026 Jan 19 by Vim Project do not create swapfiles #18854
" 2026 Feb 15 by Vim Project fix global variable initialization for MS-Windows #19287
" 2026 Feb 21 by Vim Project better absolute path detection on MS-Windows #19477
+" 2026 Feb 27 by Vim Project Make the hostname validation more strict
" 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
@@ -2591,13 +2592,26 @@ endfunction

" s:NetrwValidateHostname: Validate that the hostname is valid {{{2
" Input:
-" hostname
+" hostname, may include an optional username, e.g. user@hostname
+" allow a alphanumeric hostname or an IPv(4/6) address
" Output:
" true if g:netrw_machine is valid according to RFC1123 #Section 2
function s:NetrwValidateHostname(hostname)
- " RFC1123#section-2 mandates, a valid hostname starts with letters or digits
- " so reject everyhing else
- return a:hostname =~? '^[a-z0-9]'
+ " Username:
+ let user_pat = '\%([a-zA-Z0-9._-]\+@\)\?'
+ " Hostname: 1-64 chars, alphanumeric/dots/hyphens.
+ " No underscores. No leading/trailing dots/hyphens.
+ let host_pat = '[a-zA-Z0-9]\%([-a-zA-Z0-9.]{,62}[a-zA-Z0-9]\)\?$'
+
+ " IPv4: 1-3 digits separated by dots
+ let ipv4_pat = '\%(\d\{1,3}\.\)\{3\}\d\{1,3\}$'
+
+ " IPv6: Hex, colons, and optional brackets
+ let ipv6_pat = '\[\?\%([a-fA-F0-9:]\{2,}\)\+\]\?$'
+
+ return a:hostname =~? '^'.user_pat.host_pat ||
+ \ a:hostname =~? '^'.user_pat.ipv4_pat ||
+ \ a:hostname =~? '^'.user_pat.ipv6_pat
endfunction

" NetUserPass: set username and password for subsequent ftp transfer {{{2
@@ -8961,15 +8975,15 @@ endfunction
" s:MakeSshCmd: transforms input command using USEPORT HOSTNAME into {{{2
" a correct command for use with a system() call
function s:MakeSshCmd(sshcmd)
- if s:user == ""
- let sshcmd = substitute(a:sshcmd,'\<HOSTNAME\>',s:machine,'')
- else
- let sshcmd = substitute(a:sshcmd,'\<HOSTNAME\>',s:user."@".s:machine,'')
+ let machine = shellescape(s:machine, 1)
+ if s:user != ''
+ let machine = shellescape(s:user, 1).'@'.machine
endif
+ let sshcmd = substitute(a:sshcmd,'\<HOSTNAME\>',machine,'')
if exists("g:netrw_port") && g:netrw_port != ""
- let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.g:netrw_port,'')
+ let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.shellescape(g:netrw_port,1),'')
elseif exists("s:port") && s:port != ""
- let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.s:port,'')
+ let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.shellescape(s:port,1),'')
else
let sshcmd= substitute(sshcmd,"USEPORT ",'','')
endif
diff --git a/src/testdir/test_plugin_netrw.vim b/src/testdir/test_plugin_netrw.vim
index 23a060227..99dba4b96 100644
--- a/src/testdir/test_plugin_netrw.vim
+++ b/src/testdir/test_plugin_netrw.vim
@@ -291,7 +291,7 @@ func Test_netrw_parse_special_char_user()
call assert_equal(result.path, 'test.txt')
endfunction

-func Test_netrw_wipe_empty_buffer_fastpath()
+func Test_netrw_empty_buffer_fastpath_wipe()
" SetUp() may have opened some buffers
let previous = bufnr('$')
let g:netrw_fastbrowse=0
@@ -560,4 +560,10 @@ func Test_netrw_filemove_pwsh()
call s:test_netrw_filemove()
endfunc

+func Test_netrw_reject_evil_hostname()
+ let msg = execute(':e scp://x;touch RCE;x/dir/')
+ let msg = split(msg, "
")[-1]
+ call assert_match('Rejecting invalid hostname', msg)
+endfunction
+
" vim:ts=8 sts=2 sw=2 et
diff --git a/src/version.c b/src/version.c
index 11467caea..3d969453b 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =

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