patch 9.2.0434: cscope: filename interpreted by /bin/sh

Problem:  cs_create_connection() builds the cscope command by
          interpolating csinfo[i].fname (and ppath, flags) into a
          string and lets the shell parse it.  Shell metacharacters
          in a database filename are therefore evaluated by /bin/sh
          before cscope is exec'd, rather than being passed through as a
          literal path (q1uf3ng)
Solution: Build argv directly and execvp() the cscope binary
          without an intervening shell.

closes: #20119

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Christian Brabandt
2026-05-03 17:47:50 +00:00
parent 5c700152ae
commit fde5a56ecb
3 changed files with 100 additions and 34 deletions
+91 -34
View File
@@ -829,11 +829,11 @@ cs_create_connection(int i)
int to_cs[2], from_cs[2];
# endif
int cmdlen;
int len;
char *prog, *cmd, *ppath = NULL;
size_t proglen;
# ifdef MSWIN
int fd;
int len;
SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pi;
STARTUPINFO si;
@@ -872,7 +872,6 @@ err_closing:
else if (csinfo[i].pid == 0) // child: run cscope.
{
char **argv = NULL;
int argc = 0;
if (dup2(to_cs[0], STDIN_FILENO) == -1)
PERROR("cs_create_connection 1");
@@ -956,48 +955,93 @@ err_closing:
// run the cscope command
# ifdef UNIX
vim_snprintf(cmd, cmdlen, "/bin/sh -c \"exec %s -dl -f %s",
prog, csinfo[i].fname);
# else
vim_snprintf(cmd, cmdlen, "%s -dl -f %s", prog, csinfo[i].fname);
# endif
if (csinfo[i].ppath != NULL)
{
len = (int)STRLEN(cmd);
vim_snprintf(cmd + len, cmdlen - len, " -P%s", csinfo[i].ppath);
}
if (csinfo[i].flags != NULL)
{
len = (int)STRLEN(cmd);
vim_snprintf(cmd + len, cmdlen - len, " %s", csinfo[i].flags);
}
# ifdef UNIX
// terminate the -c command argument
STRCAT(cmd, "\"");
garray_T ga_argv;
char **tok = NULL;
int tokc = 0;
// on Win32 we still need prog
vim_free(prog);
# endif
vim_free(ppath);
ga_init2(&ga_argv, sizeof(char *), 8);
// 'cscopeprg' may be multi-word (e.g. "cscope --foo");
// split it into separate argv entries.
if (build_argv_from_string((char_u *)prog, &tok, &tokc) == FAIL)
exit(EXIT_FAILURE);
for (int k = 0; k < tokc; ++k)
{
if (ga_grow(&ga_argv, 1) == FAIL)
exit(EXIT_FAILURE);
((char **)ga_argv.ga_data)[ga_argv.ga_len++] = tok[k];
}
VIM_CLEAR(tok);
// Literal arguments: "-dl", "-f", fname.
if (ga_grow(&ga_argv, 3) == FAIL)
exit(EXIT_FAILURE);
((char **)ga_argv.ga_data)[ga_argv.ga_len++] =
(char *)vim_strsave((char_u *)"-dl");
((char **)ga_argv.ga_data)[ga_argv.ga_len++] =
(char *)vim_strsave((char_u *)"-f");
((char **)ga_argv.ga_data)[ga_argv.ga_len++] =
(char *)vim_strsave((char_u *)csinfo[i].fname);
// "-P<ppath>" as a single argv entry, if set.
if (ppath != NULL)
{
size_t plen = STRLEN(ppath) + 3;
char *parg = alloc(plen);
if (parg == NULL || ga_grow(&ga_argv, 1) == FAIL)
exit(EXIT_FAILURE);
vim_snprintf(parg, plen, "-P%s", ppath);
((char **)ga_argv.ga_data)[ga_argv.ga_len++] = parg;
}
// 'flags' is intended to be a sequence of cscope tokens
// like "-q -i somefile"; split it the same way as prog.
if (csinfo[i].flags != NULL)
{
if (build_argv_from_string((char_u *)csinfo[i].flags,
&tok, &tokc) == FAIL)
exit(EXIT_FAILURE);
for (int k = 0; k < tokc; ++k)
{
if (ga_grow(&ga_argv, 1) == FAIL)
exit(EXIT_FAILURE);
((char **)ga_argv.ga_data)[ga_argv.ga_len++] = tok[k];
}
vim_free(tok);
}
// NULL-terminate argv.
if (ga_grow(&ga_argv, 1) == FAIL)
exit(EXIT_FAILURE);
((char **)ga_argv.ga_data)[ga_argv.ga_len] = NULL;
vim_free(prog);
vim_free(ppath);
vim_free(cmd);
# if defined(UNIX)
# if defined(HAVE_SETSID) || defined(HAVE_SETPGID)
// Change our process group to avoid cscope receiving SIGWINCH.
// Change our process group to avoid cscope receiving SIGWINCH.
# if defined(HAVE_SETSID)
(void)setsid();
(void)setsid();
# else
if (setpgid(0, 0) == -1)
PERROR(_("cs_create_connection setpgid failed"));
if (setpgid(0, 0) == -1)
PERROR(_("cs_create_connection setpgid failed"));
# endif
# endif
if (build_argv_from_string((char_u *)cmd, &argv, &argc) == FAIL)
exit(EXIT_FAILURE);
if (execvp(argv[0], argv) == -1)
PERROR(_("cs_create_connection exec failed"));
argv = (char **)ga_argv.ga_data;
if (argv[0] == NULL || execvp(argv[0], argv) == -1)
{
fprintf(stderr, "%s\n",
_("cs_create_connection exec failed"));
fflush(stderr);
}
exit(127);
// NOTREACHED
exit(127);
// NOTREACHED
}
}
else // parent.
{
@@ -1016,6 +1060,19 @@ err_closing:
}
# else
// MSWIN
vim_snprintf(cmd, cmdlen, "%s -dl -f %s", prog, csinfo[i].fname);
if (csinfo[i].ppath != NULL)
{
len = (int)STRLEN(cmd);
vim_snprintf(cmd + len, cmdlen - len, " -P%s", csinfo[i].ppath);
}
if (csinfo[i].flags != NULL)
{
len = (int)STRLEN(cmd);
vim_snprintf(cmd + len, cmdlen - len, " %s", csinfo[i].flags);
}
vim_free(ppath);
// Create a new process to run cscope and use pipes to talk with it
GetStartupInfo(&si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
+7
View File
@@ -288,6 +288,13 @@ func Test_cscopeWithCscopeConnections()
call assert_equal(cscope_connection(5, 'out'), 0)
call assert_equal(cscope_connection(-1, 'out'), 0)
cscope kill -1
" Test: cscope file with shell meta chars
call writefile([], 'Xcscope2.out`id>Xid`', 'D')
call assert_fails('cscope add Xcscope2.out`id>Xid`', 'E609:')
call assert_false(filereadable('Xid'))
cscope kill -1
call CscopeSetupOrClean(0)
endfunc
+2
View File
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
434,
/**/
433,
/**/