patch 9.2.0434: cscope: filename interpreted by /bin/sh
Commit:
https://github.com/vim/vim/commit/fde5a56ecbf9101314ddcc572533e147a9fb11ff
Author: Christian Brabandt <
c...@256bit.org>
Date: Sun May 3 17:47:50 2026 +0000
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) <
nor...@anthropic.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/src/if_cscope.c b/src/if_cscope.c
index 796200077..54cae51f7 100644
--- a/src/if_cscope.c
+++ b/src/if_cscope.c
@@ -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
",
+ _("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;
diff --git a/src/testdir/test_cscope.vim b/src/testdir/test_cscope.vim
index ddd4de0b8..f44a480ec 100644
--- a/src/testdir/test_cscope.vim
+++ b/src/testdir/test_cscope.vim
@@ -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
diff --git a/src/version.c b/src/version.c
index 3a27d0c52..6d8157e4a 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 434,
/**/
433,
/**/