[PATCH v3] Cygwin: ssp: Move command-line copy into run_program

Chandru Kumaresan <[email protected]> Thu, 25 Jun 2026 12:21:54 +0000
Newsgroups gmane.os.cygwin.patches
Message-ID <PN0P287MB0295A49DE62713D1E8BB8A1392EC2@PN0P287MB0295.INDP287.PROD.OUTLOOK.COM>
Hi Jon,

> Hmmm... from the explanation above, it seems like this is in the wrong
> place and should be inside run_program, around the call to CreateProcess?
> Otherwise, the same pointer which is passed to CreateProcess and
> potentially has its contents mutated by that is also assigned to
> dll_info[0].name, leading to a potentially corrupted string appearing in
> the DLL-profile table.
> If that supposition is correct, I'd appreciate it if you could come up
> with a follow-up patch to change that.

You are correct — moving the strdup into run_program, just before the
CreateProcess call, is the right fix. Please find the follow-up patch below.

Thanks and regards
K Chandru
Inline Patch

---
 winsup/utils/ssp.c | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/winsup/utils/ssp.c b/winsup/utils/ssp.c
index 84de523be..deae839d6 100644
--- a/winsup/utils/ssp.c
+++ b/winsup/utils/ssp.c
@@ -379,11 +379,27 @@ run_program (char *cmdline)
   int tix, i;
   HANDLE hThread;
   char *string;
+  char *cmdline_copy;

   memset (&startup, 0, sizeof (startup));
   startup.cb = sizeof (startup);

-  if (!CreateProcess (0, cmdline, 0, 0, 0,
+  /* CreateProcess (called with lpApplicationName == NULL) is documented to
+     modify the lpCommandLine buffer in place.  dll_info[0].name below points
+     at the caller's original string, which is read later when printing the
+     DLL-profile table, so hand CreateProcess a private writable copy to
+     scribble on instead; otherwise the program name comes back mangled
+     (observed on aarch64-cygwin as 'test_hello.exe' -> 'st_hello.exxee').
+     The copy is only needed for the CreateProcess call and is intentionally
+     leaked rather than freed, as ssp is short-lived.  */
+  cmdline_copy = strdup (cmdline);
+  if (!cmdline_copy)
+    {
+      fprintf (stderr, "Out of memory duplicating cmdline\n");
+      exit (1);
+    }
+
+  if (!CreateProcess (0, cmdline_copy, 0, 0, 0,
           CREATE_NEW_PROCESS_GROUP
           | CREATE_SUSPENDED
           | DEBUG_PROCESS
@@ -1029,23 +1045,7 @@ main (int argc, char **argv)

   fprintf (stderr, "prun: [" CONTEXT_REG_FMT "," CONTEXT_REG_FMT "] Running '%s'\n",
     low_pc, high_pc, argv[optind]);
-  {
-    /* CreateProcess (called below with lpApplicationName == NULL) is
-       documented to modify the lpCommandLine buffer in place.  argv[optind]
-       points into our own argv, so passing it directly lets CreateProcess
-       scribble on it; this was observed on aarch64-cygwin as the command
-       line coming back mangled (e.g. 'test_hello.exe' -> 'st_hello.exxee')
-       on later use.  Pass a private writable copy instead.  It is not freed
-       because run_program() stores it in dll_info[0].name, which is read
-       later when printing the DLL-profile table.  */
-    char *cmdline_copy = strdup (argv[optind]);
-    if (!cmdline_copy)
-      {
-  fprintf (stderr, "Out of memory duplicating cmdline\n");
-  exit (1);
-      }
-    run_program (cmdline_copy);
-  }
+  run_program (argv[optind]);

   hdr.lpc = low_pc;
   hdr.hpc = high_pc;
--
2.49.0.windows.1
Cygwin-ssp-Move-command-line-copy-into-run_program.patch (application/octet-stream, 3.8 KB)
From 798d62951882b7c18b427e38a804485bd303e301 Mon Sep 17 00:00:00 2001
From: chandru-mcw <[email protected]>
Date: Thu, 25 Jun 2026 17:26:44 +0530
Subject: [PATCH v3] Cygwin: ssp: Move command-line copy into run_program
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The previous patch duplicated argv[optind] in main() before passing it to
run_program(), but run_program() immediately assigns the received pointer
to dll_info[0].name and then passes the same pointer to CreateProcess.
Since CreateProcess (with lpApplicationName == NULL) may modify its
lpCommandLine buffer in place, dll_info[0].name could end up pointing at
corrupted data, producing a mangled name in the DLL-profile table.

Fix this by performing the strdup inside run_program(), immediately before
the CreateProcess call.  The original cmdline pointer is stored in
dll_info[0].name as before, preserving the intact name for later
reporting, while CreateProcess receives the private copy to mutate freely.
The copy is intentionally leaked; ssp is short-lived and freeing it would
require threading the pointer across the entire debug loop.

Revert the main() change from the previous patch so the caller passes
argv[optind] directly again.

Signed-off-by: Radek Bartoň <[email protected]>
Signed-off-by: Thirumalai Nagalingam <[email protected]>
Signed-off-by: Chandru Kumaresan <[email protected]>
---
 winsup/utils/ssp.c | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/winsup/utils/ssp.c b/winsup/utils/ssp.c
index 84de523be..deae839d6 100644
--- a/winsup/utils/ssp.c
+++ b/winsup/utils/ssp.c
@@ -379,11 +379,27 @@ run_program (char *cmdline)
   int tix, i;
   HANDLE hThread;
   char *string;
+  char *cmdline_copy;
 
   memset (&startup, 0, sizeof (startup));
   startup.cb = sizeof (startup);
 
-  if (!CreateProcess (0, cmdline, 0, 0, 0,
+  /* CreateProcess (called with lpApplicationName == NULL) is documented to
+     modify the lpCommandLine buffer in place.  dll_info[0].name below points
+     at the caller's original string, which is read later when printing the
+     DLL-profile table, so hand CreateProcess a private writable copy to
+     scribble on instead; otherwise the program name comes back mangled
+     (observed on aarch64-cygwin as 'test_hello.exe' -> 'st_hello.exxee').
+     The copy is only needed for the CreateProcess call and is intentionally
+     leaked rather than freed, as ssp is short-lived.  */
+  cmdline_copy = strdup (cmdline);
+  if (!cmdline_copy)
+    {
+      fprintf (stderr, "Out of memory duplicating cmdline\n");
+      exit (1);
+    }
+
+  if (!CreateProcess (0, cmdline_copy, 0, 0, 0,
 		     CREATE_NEW_PROCESS_GROUP
 		     | CREATE_SUSPENDED
 		     | DEBUG_PROCESS
@@ -1029,23 +1045,7 @@ main (int argc, char **argv)
 
   fprintf (stderr, "prun: [" CONTEXT_REG_FMT "," CONTEXT_REG_FMT "] Running '%s'\n",
 	  low_pc, high_pc, argv[optind]);
-  {
-    /* CreateProcess (called below with lpApplicationName == NULL) is
-       documented to modify the lpCommandLine buffer in place.  argv[optind]
-       points into our own argv, so passing it directly lets CreateProcess
-       scribble on it; this was observed on aarch64-cygwin as the command
-       line coming back mangled (e.g. 'test_hello.exe' -> 'st_hello.exxee')
-       on later use.  Pass a private writable copy instead.  It is not freed
-       because run_program() stores it in dll_info[0].name, which is read
-       later when printing the DLL-profile table.  */
-    char *cmdline_copy = strdup (argv[optind]);
-    if (!cmdline_copy)
-      {
-	fprintf (stderr, "Out of memory duplicating cmdline\n");
-	exit (1);
-      }
-    run_program (cmdline_copy);
-  }
+  run_program (argv[optind]);
 
   hdr.lpc = low_pc;
   hdr.hpc = high_pc;
-- 
2.49.0.windows.1