]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0383: [security]: runtime(netrw): shell-injection via sftp: and file: URLs v9.2.0383
authorChristian Brabandt <cb@256bit.org>
Tue, 21 Apr 2026 19:03:02 +0000 (19:03 +0000)
committerChristian Brabandt <cb@256bit.org>
Tue, 21 Apr 2026 19:08:05 +0000 (19:08 +0000)
Problem:  runtime(netrw): shell-injection via sftp: and file: URLs
          (Joshua Rogers)
Solution: Escape temporary file names, harden filename suffix regex,
          drop unused g:netrw_tmpfile_escape variable

Supported by AI

Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/doc/pi_netrw.txt
runtime/doc/tags
runtime/pack/dist/opt/netrw/autoload/netrw.vim
runtime/pack/dist/opt/netrw/doc/netrw.txt
src/testdir/test_plugin_netrw.vim
src/version.c

index a86cac36baf3e9965e9518285bca13582a586a18..2d98a8407bfdc570da5c09ad1e0a326745b42355 100644 (file)
@@ -2854,10 +2854,6 @@ your browsing preferences.  (see also: |netrw-settings|)
                                such as listing, file removal, etc.
                                 default: ssh
 
-  *g:netrw_tmpfile_escape*     =' &;'
-                               escape() is applied to all temporary files
-                               to escape these characters.
-
   *g:netrw_timefmt*            specify format string to vim's strftime().
                                The default, "%c", is "the preferred date
                                and time representation for the current
index 720eb330f78bedf8608fee88baf86dc5d2095d3a..2b0e6cab39d9b0a51dc47550a33abf1fdced50ad 100644 (file)
@@ -7970,7 +7970,6 @@ g:netrw_ssh_browse_reject pi_netrw.txt    /*g:netrw_ssh_browse_reject*
 g:netrw_ssh_cmd        pi_netrw.txt    /*g:netrw_ssh_cmd*
 g:netrw_sshport        pi_netrw.txt    /*g:netrw_sshport*
 g:netrw_timefmt        pi_netrw.txt    /*g:netrw_timefmt*
-g:netrw_tmpfile_escape pi_netrw.txt    /*g:netrw_tmpfile_escape*
 g:netrw_uid    pi_netrw.txt    /*g:netrw_uid*
 g:netrw_use_noswf      pi_netrw.txt    /*g:netrw_use_noswf*
 g:netrw_use_nt_rcp     pi_netrw.txt    /*g:netrw_use_nt_rcp*
index e3028cb28e2ebaab67f5f5b5fdf8d40034d01223..538ab0377b09317c63eb49a71128491d6e906db3 100644 (file)
@@ -26,6 +26,8 @@
 " 2026 Apr 05 by Vim Project Fix netrw#RFC2396() #19913
 " 2026 Apr 15 by Vim Project Add missing escape()
 " 2026 Apr 19 by Vim Project expand ~ on Windows #20003
+" 2026 Apr 21 by Vim Project fix shell-injection via tempfile suffix (sftp://, file://)
+" 2026 Apr 21 by Vim Project drop unused g:netrw_tmpfile_escape
 " 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
@@ -400,7 +402,6 @@ else
   call s:NetrwInit("g:netrw_glob_escape",'*[]?`{~$\')
 endif
 call s:NetrwInit("g:netrw_menu_escape",'.&? \')
-call s:NetrwInit("g:netrw_tmpfile_escape",' &;')
 call s:NetrwInit("s:netrw_map_escape","<|\n\r\\\<C-V>\"")
 if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4')
   let s:treedepthstring= "│ "
@@ -1821,14 +1822,14 @@ function netrw#NetRead(mode,...)
             ".........................................
         " NetRead: (sftp) NetRead Method #9 {{{3
         elseif     b:netrw_method  == 9
-            call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".netrw#os#Escape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile)
+            call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".netrw#os#Escape(g:netrw_machine.":".b:netrw_fname,1)." ".netrw#os#Escape(tmpfile,1))
             let result          = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
             let b:netrw_lastfile = choice
 
             ".........................................
         " NetRead: (file) NetRead Method #10 {{{3
         elseif      b:netrw_method == 10 && exists("g:netrw_file_cmd")
-            call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_file_cmd." ".netrw#os#Escape(b:netrw_fname,1)." ".tmpfile)
+            call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_file_cmd." ".netrw#os#Escape(b:netrw_fname,1)." ".netrw#os#Escape(tmpfile,1))
             let result           = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
             let b:netrw_lastfile = choice
 
@@ -8965,14 +8966,17 @@ function s:GetTempfile(fname)
     endif
 
     " use fname's suffix for the temporary file
+    " Restrict the suffix to word characters so shell metacharacters in a
+    " remote filename (e.g. sftp://host/foo.txt;id) cannot ride along into
+    " the tempfile name and out into a downstream shell command.
     if a:fname != ""
-        if a:fname =~ '\.[^./]\+$'
+        if a:fname =~ '\.\w\+$'
             if a:fname =~ '\.tar\.gz$' || a:fname =~ '\.tar\.bz2$' || a:fname =~ '\.tar\.xz$'
-                let suffix = ".tar".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
+                let suffix = ".tar".substitute(a:fname,'^.*\(\.\w\+\)$','\1','e')
             elseif a:fname =~ '.txz$'
-                let suffix = ".txz".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
+                let suffix = ".txz".substitute(a:fname,'^.*\(\.\w\+\)$','\1','e')
             else
-                let suffix = substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
+                let suffix = substitute(a:fname,'^.*\(\.\w\+\)$','\1','e')
             endif
             let tmpfile= substitute(tmpfile,'\.tmp$','','e')
             let tmpfile .= suffix
index 01a5bda5978ba533e69ed0b3b06fd60cc24abf8b..144bab5fb37876882be295295c482530afaff5de 100644 (file)
@@ -2854,10 +2854,6 @@ your browsing preferences.  (see also: |netrw-settings|)
                                such as listing, file removal, etc.
                                 default: ssh
 
-  *g:netrw_tmpfile_escape*     =' &;'
-                               escape() is applied to all temporary files
-                               to escape these characters.
-
   *g:netrw_timefmt*            specify format string to vim's strftime().
                                The default, "%c", is "the preferred date
                                and time representation for the current
index 6ee731d88ffc8ab5aa45caede74d3353205b3053..893e4243621f7d3a5a74b88b55f63171dc743dc6 100644 (file)
@@ -604,6 +604,36 @@ func Test_netrw_FileUrlEdit_pipe_injection()
   call assert_false(filereadable(fname), 'Command injection via pipe in file URL')
 endfunc
 
+" The remote filename after '.' was allowed to contain shell metacharacters
+" and rode unescaped into the tempfile name passed to sftp/file_cmd, giving a
+" shell injection on :e sftp://host/foo.txt;<cmd>.
+func Test_netrw_tempfile_suffix_injection()
+  CheckUnix
+  CheckExecutable id
+  let save_sftp = g:netrw_sftp_cmd
+  let save_file = exists('g:netrw_file_cmd') ? g:netrw_file_cmd : v:null
+  let g:netrw_sftp_cmd = 'true'
+  let g:netrw_file_cmd = 'true'
+  let fname = 'Xrce_marker'
+  try
+    call delete(fname)
+    sil! call netrw#NetRead(2, 'sftp://localhost/foo.txt;id>'..fname)
+    call assert_false(filereadable(fname), 'Command injection via sftp:// tempfile suffix')
+
+    call delete(fname)
+    sil! call netrw#NetRead(2, 'file://localhost/foo.txt;id>'..fname)
+    call assert_false(filereadable(fname), 'Command injection via file:// tempfile suffix')
+  finally
+    call delete(fname)
+    let g:netrw_sftp_cmd = save_sftp
+    if save_file is v:null
+      unlet! g:netrw_file_cmd
+    else
+      let g:netrw_file_cmd = save_file
+    endif
+  endtry
+endfunc
+
 func Test_netrw_RFC2396()
   let fname = 'a%20b'
   call assert_equal('a b', netrw#RFC2396(fname))
index 3f85f2f5794c5f37a573041eaba042f6e8cc119f..895c22e9c0a4a5b8b9c9d04fd8e01e457d264dd3 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    383,
 /**/
     382,
 /**/