]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0073: [security]: possible command injection using netrw v9.2.0073
authorChristian Brabandt <cb@256bit.org>
Sun, 22 Feb 2026 21:24:48 +0000 (21:24 +0000)
committerChristian Brabandt <cb@256bit.org>
Fri, 27 Feb 2026 20:29:14 +0000 (20:29 +0000)
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 <cb@256bit.org>
runtime/pack/dist/opt/netrw/autoload/netrw.vim
src/testdir/test_plugin_netrw.vim
src/version.c

index 51e05f2b1f6e81ba168fdd73826eb3cfa1ccd445..25ca4e8a1a711abf6a81962d22ee65c0107fc83d 100644 (file)
@@ -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
index 23a060227d4fe861b363b7f90981455b4505f0b7..99dba4b96d9eaab7ebc33459cea8b93af4b36ccf 100644 (file)
@@ -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, "\n")[-1]
+  call assert_match('Rejecting invalid hostname', msg)
+endfunction
+
 " vim:ts=8 sts=2 sw=2 et
index 11467caeaaf5d6a07da83ed22d250eff1623a169..3d969453bc38935c1077568edd7a16b819374b07 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    73,
 /**/
     72,
 /**/