commit ac3e2ca6756598a0c9b342dfb3a8b25d9775ca9f
parent 6383123326483db401c09a5e1f4dc99f11c900d6
Author: zeertzjq <zeertzjq@outlook.com>
Date: Thu, 4 Dec 2025 10:35:09 +0800
vim-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
https://github.com/vim/vim/commit/083ec6d9a3b7b09006e0ce69ac802597d25856d6
Co-authored-by: Christian Brabandt <cb@256bit.org>
Diffstat:
5 files changed, 41 insertions(+), 7 deletions(-)
diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt
@@ -1877,7 +1877,8 @@ executable({expr}) *executable()*
*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.
+ the `$NoDefaultCurrentDirectoryInExePath` environment variable.
+ This is always done for |:!| commands, 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
@@ -1652,7 +1652,8 @@ function vim.fn.eventhandler() end
--- *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.
+--- the `$NoDefaultCurrentDirectoryInExePath` environment variable.
+--- This is always done for |:!| commands, for security reasons.
---
--- The result is a Number:
--- 1 exists
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
@@ -2163,7 +2163,8 @@ M.funcs = {
*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.
+ the `$NoDefaultCurrentDirectoryInExePath` environment variable.
+ This is always done for |:!| commands, 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/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;
}