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

083ec6d9a3

Co-authored-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
zeertzjq
2025-12-04 10:35:09 +08:00
parent 6383123326
commit ac3e2ca675
5 changed files with 41 additions and 7 deletions

View File

@@ -1877,7 +1877,8 @@ executable({expr}) *executable()*
*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

@@ -1652,7 +1652,8 @@ function vim.fn.eventhandler() end
--- *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

@@ -2163,7 +2163,8 @@ M.funcs = {
*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

@@ -1291,3 +1291,19 @@ void vim_setenv_ext(const char *name, const char *val)
didset_vimruntime = false; 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

View File

@@ -33,6 +33,7 @@
#include "nvim/message.h" #include "nvim/message.h"
#include "nvim/option_vars.h" #include "nvim/option_vars.h"
#include "nvim/os/fs.h" #include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h" #include "nvim/os/os_defs.h"
#include "nvim/os/shell.h" #include "nvim/os/shell.h"
#include "nvim/os/signal.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, static int do_os_system(char **argv, const char *input, size_t len, char **output, size_t *nread,
bool silent, bool forward_output) 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_decide_throttle(0); // Initialize throttle decider.
out_data_ring(NULL, 0); // Initialize output ring-buffer. out_data_ring(NULL, 0); // Initialize output ring-buffer.
bool has_input = (input != NULL && len > 0); 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_outtrans(prog, 0, false);
msg_putchar('\n'); msg_putchar('\n');
} }
multiqueue_free(events); goto end;
return -1;
} }
// Note: unlike process events, stream events are not queued, as we want to // 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)) { if (!wstream_write(&proc->in, input_buffer)) {
// couldn't write, stop the process and tell the user about it // couldn't write, stop the process and tell the user about it
proc_stop(proc); proc_stop(proc);
return -1; goto end;
} }
// close the input stream after everything is written // close the input stream after everything is written
wstream_set_write_cb(&proc->in, shell_write_cb, NULL); 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; msg_no_more = true;
lines_left = -1; lines_left = -1;
} }
int exitcode = proc_wait(proc, -1, NULL); exitcode = proc_wait(proc, -1, NULL);
if (!got_int && out_data_decide_throttle(0)) { if (!got_int && out_data_decide_throttle(0)) {
// Last chunk of output was skipped; display it now. // Last chunk of output was skipped; display it now.
out_data_ring(NULL, SIZE_MAX); 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)); assert(multiqueue_empty(events));
end:
multiqueue_free(events); multiqueue_free(events);
#ifdef MSWIN
// Restore original value of NoDefaultCurrentDirectoryInExePath
restore_env_var("NoDefaultCurrentDirectoryInExePath", oldval, true);
#endif
return exitcode; return exitcode;
} }