patch 9.1.1947: [security]: Windows: Vim may execute commands from current directory

Problem:  [security]: Windows: Vim may execute commands from current
          directory (Simon Zuckerbraun)
Solution: Set the $NoDefaultCurrentDirectoryInExePath before running
          external commands.

Github Advisory:
https://github.com/vim/vim/security/advisories/GHSA-g77q-xrww-p834

Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Christian Brabandt
2025-11-25 22:45:58 +01:00
parent c0f2d2f140
commit 083ec6d9a3
4 changed files with 76 additions and 1 deletions

View File

@ -2711,13 +2711,15 @@ executable({expr}) *executable()*
then the name is also tried without adding an extension. then the name is also tried without adding an extension.
On MS-Windows it only checks if the file exists and is not a On MS-Windows it only checks if the file exists and is not a
directory, not if it's really executable. directory, not if it's really executable.
On MS-Windows an executable in the same directory as the Vim On MS-Windows an executable in the same directory as the Vim
executable is always found. Since this directory is added to executable is always found. Since this directory is added to
$PATH it should also work to execute it |win32-PATH|. $PATH it should also work to execute it |win32-PATH|.
*NoDefaultCurrentDirectoryInExePath* *NoDefaultCurrentDirectoryInExePath*
On MS-Windows an executable in Vim's current working directory On MS-Windows an executable in Vim's current working directory
is also normally found, but this can be disabled by setting is also normally found, but this can be disabled by setting
the $NoDefaultCurrentDirectoryInExePath environment variable. the `$NoDefaultCurrentDirectoryInExePath` environment variable.
This is always done for |:!| commands, for security reasons.
The result is a Number: The result is a Number:
1 exists 1 exists

View File

@ -5483,6 +5483,21 @@ mch_call_shell_terminal(
return retval; return retval;
} }
#endif #endif
/* Restore a previous environment variable value, or unset it if NULL.
* 'must_free' indicates whether 'old_value' was allocated.
*/
static void
restore_env_var(char_u *name, char_u *old_value, int must_free)
{
if (old_value != NULL)
{
vim_setenv(name, old_value);
if (must_free)
vim_free(old_value);
return;
}
vim_unsetenv(name);
}
/* /*
* Either execute a command by calling the shell or start a new shell * Either execute a command by calling the shell or start a new shell
@ -5495,6 +5510,8 @@ mch_call_shell(
int x = 0; int x = 0;
int tmode = cur_tmode; int tmode = cur_tmode;
WCHAR szShellTitle[512]; WCHAR szShellTitle[512];
int must_free;
char_u *oldval;
#ifdef FEAT_EVAL #ifdef FEAT_EVAL
ch_log(NULL, "executing shell command: %s", cmd); ch_log(NULL, "executing shell command: %s", cmd);
@ -5519,6 +5536,11 @@ mch_call_shell(
} }
} }
} }
// do not execute anything from the current directory by setting the
// environemnt variable $NoDefaultCurrentDirectoryInExePath
oldval = vim_getenv((char_u *)"NoDefaultCurrentDirectoryInExePath",
&must_free);
vim_setenv((char_u *)"NoDefaultCurrentDirectoryInExePath", (char_u *)"1");
out_flush(); out_flush();
@ -5552,6 +5574,8 @@ mch_call_shell(
// Use a terminal window to run the command in. // Use a terminal window to run the command in.
x = mch_call_shell_terminal(cmd, options); x = mch_call_shell_terminal(cmd, options);
resettitle(); resettitle();
restore_env_var((char_u *)"NoDefaultCurrentDirectoryInExePath",
oldval, must_free);
return x; return x;
} }
} }
@ -5776,6 +5800,10 @@ mch_call_shell(
} }
} }
// Restore original value of NoDefaultCurrentDirectoryInExePath
restore_env_var((char_u *)"NoDefaultCurrentDirectoryInExePath",
oldval, must_free);
if (tmode == TMODE_RAW) if (tmode == TMODE_RAW)
{ {
// The shell may have messed with the mode, always set it. // The shell may have messed with the mode, always set it.

View File

@ -440,6 +440,14 @@ func Test_zz2_terminal_guioptions_bang()
call writefile(contents, filename, 'D') call writefile(contents, filename, 'D')
call setfperm(filename, 'rwxrwx---') call setfperm(filename, 'rwxrwx---')
if has("win32")
" should not execute anything below the current directory
let exitval = 1
execute printf(':!%s%s %d', prefix, filename, exitval)
call assert_equal(exitval, v:shell_error)
let prefix = '.\'
endif
" Check if v:shell_error is equal to the exit status. " Check if v:shell_error is equal to the exit status.
let exitval = 0 let exitval = 0
execute printf(':!%s%s %d', prefix, filename, exitval) execute printf(':!%s%s %d', prefix, filename, exitval)
@ -732,5 +740,40 @@ func Test_term_gettty()
exe buf . 'bwipe' exe buf . 'bwipe'
endfunc endfunc
func Test_windows_external_cmd_in_cwd()
" Check that Vim does not execute anything from current directory
CheckMSWindows
" just in case
call system('rd /S /Q Xfolder')
call mkdir('Xfolder', 'R')
cd Xfolder
let contents = ['@echo off', 'echo filename1.txt:1:AAAA']
call writefile(contents, 'findstr.cmd')
let file1 = ['AAAA', 'THIS FILE SHOULD NOT BE FOUND']
let file2 = ['BBBB', 'THIS FILE SHOULD BE FOUND']
call writefile(file1, 'filename1.txt')
call writefile(file2, 'filename2.txt')
" use silent to avoid hit-enter-prompt
sil grep BBBB filename*.txt
call assert_equal('filename2.txt', @%)
let output = system('findstr BBBB filename*')
" Match trailing newline byte
call assert_match('filename2.txt:BBBB.', output)
set guioptions+=!
let output = system('findstr BBBB filename*')
call assert_match('filename2.txt:BBBB.', output)
cd -
set guioptions&
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1947,
/**/ /**/
1946, 1946,
/**/ /**/