]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0434: cscope: filename interpreted by /bin/sh v9.2.0434
authorChristian Brabandt <cb@256bit.org>
Sun, 3 May 2026 17:47:50 +0000 (17:47 +0000)
committerChristian Brabandt <cb@256bit.org>
Sun, 3 May 2026 17:47:50 +0000 (17:47 +0000)
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>
src/if_cscope.c
src/testdir/test_cscope.vim
src/version.c

index 796200077748bca71ca0dca4832505468a9ffda5..54cae51f75d3b7641f0b40c9c3bfb7e3dcc78d19 100644 (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;
index ddd4de0b89c8c129834046a878a2ca8984b6b85d..f44a480ec08de0c55212f9b563fe44ae5193cfc2 100644 (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
index 3a27d0c52b510f311248490f48aa741bd89d3d62..6d8157e4aafd3ca56f6904fdfaabac8c8b9f4af4 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    434,
 /**/
     433,
 /**/