commit 22c3e5802a7c8823a98f01649c0345a0e98cc39a
parent 6383123326483db401c09a5e1f4dc99f11c900d6
Author: zeertzjq <zeertzjq@outlook.com>
Date: Fri, 5 Dec 2025 07:32:43 +0800
Merge pull request #36827 from zeertzjq/vim-9.1.1947
vim-patch:9.1.{1947,1948,1951}
Diffstat:
8 files changed, 95 insertions(+), 16 deletions(-)
diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt
@@ -1875,9 +1875,12 @@ executable({expr}) *executable()*
On MS-Windows an executable in the same directory as the Vim
executable is always found (it's added to $PATH at |startup|).
*NoDefaultCurrentDirectoryInExePath*
- On MS-Windows an executable in Vim's current working directory
- is also normally found, but this can be disabled by setting
- the $NoDefaultCurrentDirectoryInExePath environment variable.
+ On MS-Windows when using cmd.exe as 'shell' an executable in
+ Vim's current working directory is also normally found, which
+ can be disabled by setting the
+ `$NoDefaultCurrentDirectoryInExePath` environment variable.
+ This is always done when executing external commands using
+ e.g. |:!|, |:make|, |system()| for security reasons.
The result is a Number:
1 exists
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
@@ -1650,9 +1650,12 @@ function vim.fn.eventhandler() end
--- On MS-Windows an executable in the same directory as the Vim
--- executable is always found (it's added to $PATH at |startup|).
--- *NoDefaultCurrentDirectoryInExePath*
---- On MS-Windows an executable in Vim's current working directory
---- is also normally found, but this can be disabled by setting
---- the $NoDefaultCurrentDirectoryInExePath environment variable.
+--- On MS-Windows when using cmd.exe as 'shell' an executable in
+--- Vim's current working directory is also normally found, which
+--- can be disabled by setting the
+--- `$NoDefaultCurrentDirectoryInExePath` environment variable.
+--- This is always done when executing external commands using
+--- e.g. |:!|, |:make|, |system()| for security reasons.
---
--- The result is a Number:
--- 1 exists
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
@@ -2161,9 +2161,12 @@ M.funcs = {
On MS-Windows an executable in the same directory as the Vim
executable is always found (it's added to $PATH at |startup|).
*NoDefaultCurrentDirectoryInExePath*
- On MS-Windows an executable in Vim's current working directory
- is also normally found, but this can be disabled by setting
- the $NoDefaultCurrentDirectoryInExePath environment variable.
+ On MS-Windows when using cmd.exe as 'shell' an executable in
+ Vim's current working directory is also normally found, which
+ can be disabled by setting the
+ `$NoDefaultCurrentDirectoryInExePath` environment variable.
+ This is always done when executing external commands using
+ e.g. |:!|, |:make|, |system()| for security reasons.
The result is a Number:
1 exists
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
@@ -1291,3 +1291,19 @@ void vim_setenv_ext(const char *name, const char *val)
didset_vimruntime = false;
}
}
+
+#ifdef MSWIN
+/// Restore a previous environment variable value, or unset it if NULL.
+/// "must_free" indicates whether "old_value" was allocated.
+void restore_env_var(const char *name, char *old_value, bool must_free)
+{
+ if (old_value != NULL) {
+ os_setenv(name, old_value, true);
+ if (must_free) {
+ xfree(old_value);
+ }
+ return;
+ }
+ os_unsetenv(name);
+}
+#endif
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
@@ -355,7 +355,8 @@ static bool is_executable_in_path(const char *name, char **abspath)
#ifdef MSWIN
char *path = NULL;
- if (!os_env_exists("NoDefaultCurrentDirectoryInExePath", false)) {
+ if (!os_env_exists("NoDefaultCurrentDirectoryInExePath", false)
+ && strstr(path_tail(p_sh), "cmd.exe") != NULL) {
// Prepend ".;" to $PATH.
size_t pathlen = strlen(path_env);
path = xmallocz(pathlen + 2);
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
@@ -33,6 +33,7 @@
#include "nvim/message.h"
#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
+#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/shell.h"
#include "nvim/os/signal.h"
@@ -857,6 +858,15 @@ int os_system(char **argv, const char *input, size_t len, char **output,
static int do_os_system(char **argv, const char *input, size_t len, char **output, size_t *nread,
bool silent, bool forward_output)
{
+ int exitcode = -1;
+
+#ifdef MSWIN
+ // do not execute anything from the current directory by setting the
+ // environemnt variable $NoDefaultCurrentDirectoryInExePath
+ char *oldval = os_getenv("NoDefaultCurrentDirectoryInExePath");
+ os_setenv("NoDefaultCurrentDirectoryInExePath", "1", true);
+#endif
+
out_data_decide_throttle(0); // Initialize throttle decider.
out_data_ring(NULL, 0); // Initialize output ring-buffer.
bool has_input = (input != NULL && len > 0);
@@ -894,8 +904,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
msg_outtrans(prog, 0, false);
msg_putchar('\n');
}
- multiqueue_free(events);
- return -1;
+ goto end;
}
// Note: unlike process events, stream events are not queued, as we want to
@@ -917,7 +926,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
if (!wstream_write(&proc->in, input_buffer)) {
// couldn't write, stop the process and tell the user about it
proc_stop(proc);
- return -1;
+ goto end;
}
// close the input stream after everything is written
wstream_set_write_cb(&proc->in, shell_write_cb, NULL);
@@ -933,7 +942,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
msg_no_more = true;
lines_left = -1;
}
- int exitcode = proc_wait(proc, -1, NULL);
+ exitcode = proc_wait(proc, -1, NULL);
if (!got_int && out_data_decide_throttle(0)) {
// Last chunk of output was skipped; display it now.
out_data_ring(NULL, SIZE_MAX);
@@ -965,8 +974,14 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
}
assert(multiqueue_empty(events));
+end:
multiqueue_free(events);
+#ifdef MSWIN
+ // Restore original value of NoDefaultCurrentDirectoryInExePath
+ restore_env_var("NoDefaultCurrentDirectoryInExePath", oldval, true);
+#endif
+
return exitcode;
}
diff --git a/test/functional/vimscript/executable_spec.lua b/test/functional/vimscript/executable_spec.lua
@@ -202,9 +202,9 @@ describe('executable() (Windows)', function()
clear({ env = { PATHEXT = '' } })
command('set shell=sh')
for _, ext in ipairs(exts) do
- eq(1, call('executable', 'test_executable_' .. ext .. '.' .. ext))
+ eq(0, call('executable', 'test_executable_' .. ext .. '.' .. ext))
end
- eq(1, call('executable', 'test_executable_zzz.zzz'))
+ eq(0, call('executable', 'test_executable_zzz.zzz'))
end)
it("relative path, Unix-style 'shell' (backslashes)", function()
diff --git a/test/old/testdir/test_system.vim b/test/old/testdir/test_system.vim
@@ -141,4 +141,42 @@ func Test_system_with_shell_quote()
endtry
endfunc
+" Check that Vim does not execute anything from current directory
+func Test_windows_external_cmd_in_cwd()
+ 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')
+
+ if has('quickfix')
+ " use silent to avoid hit-enter-prompt
+ sil grep BBBB filename*.txt
+ call assert_equal('filename2.txt', @%)
+ endif
+
+ let output = system('findstr BBBB filename*')
+ " Match trailing newline byte
+ call assert_match('filename2.txt:BBBB.', output)
+
+ if has('gui')
+ set guioptions+=!
+ let output = system('findstr BBBB filename*')
+ call assert_match('filename2.txt:BBBB.', output)
+ endif
+
+ cd -
+ set guioptions&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab