I wrote:
> >   $ ./configure gl_cv_func_posix_spawnp_secure_exec=no --quiet
> 
> I reproduce it. Smaller reproducer:
>   $ cd src
>   $ mkdir subdir
>   $ ./ginstall -s -c -m 555 dd subdir
> 
> It crashes if and only if $PATH is long enough:

I can force the crash using the attached testcase.sh, that sets
a PATH of size ca. 4 KB, on
  - Linux/SPARC
  - Linux/ppc64, Linux/ppc64le
  - Solaris 10 and 11 / SPARC

I'm committing this fix, which fixes the problem on all these platforms.


2025-11-07  Bruno Haible  <[email protected]>

        posix_spawn-internal: Fix use of vfork on SPARC and PowerPC platforms.
        Reported by Pádraig Brady <[email protected]> in
        <https://lists.gnu.org/archive/html/coreutils/2025-11/msg00052.html>.
        * lib/spawni.c (__spawni): Revert last change. Do the stack allocations
        with alloca() in the parent process, not in the child process.

diff --git a/lib/spawni.c b/lib/spawni.c
index b99f8eeabb..7500be0f1f 100644
--- a/lib/spawni.c
+++ b/lib/spawni.c
@@ -869,11 +869,6 @@ __spawni (pid_t *pid, const char *file,
           const posix_spawnattr_t *attrp, const char *const argv[],
           const char *const envp[], int use_path)
 {
-  pid_t new_pid;
-  char *path, *p, *name;
-  size_t len;
-  size_t pathlen;
-
   /* Do this once.  */
   short int flags = attrp == NULL ? 0 : attrp->_flags;
 
@@ -881,9 +876,46 @@ __spawni (pid_t *pid, const char *file,
        "variable 'flags' might be clobbered by 'longjmp' or 'vfork'"  */
   (void) &flags;
 
+  use_path = use_path && strchr (file, '/') == NULL;
+
+  /* Prepare a stack-allocated copy of $PATH and FILE, for iterating through
+     $PATH.  We do this already in the parent, because on Linux/SPARC,
+     Linux/ppc64, Linux/ppc64le, and Solaris/SPARC, it causes a SIGBUS or
+     SIGSEGV when done in the child process after vfork() and when $PATH is 
long
+     (ca. 4 KB or so).  */
+  char *path, *name;
+  if (use_path)
+    {
+      /* We have to search for FILE on the path.  */
+      path = getenv ("PATH");
+      if (path == NULL)
+        {
+#if HAVE_CONFSTR
+          /* There is no 'PATH' in the environment.
+             The default search path is the current directory
+             followed by the path 'confstr' returns for '_CS_PATH'.  */
+          size_t len = confstr (_CS_PATH, (char *) NULL, 0);
+          path = (char *) alloca (1 + len);
+          path[0] = ':';
+          (void) confstr (_CS_PATH, path + 1, len);
+#else
+          /* Pretend that the PATH contains only the current directory.  */
+          path = "";
+#endif
+        }
+
+      size_t len = strlen (file) + 1;
+      size_t pathlen = strlen (path);
+      name = alloca (pathlen + len + 1);
+      /* Copy the file name at the top.  */
+      name = (char *) memcpy (name + pathlen + 1, file, len);
+      /* And add the slash.  */
+      *--name = '/';
+    }
+
   /* Generate the new process.  */
-  /* Use of vfork() on Solaris/SPARC crashes; avoid it there.  */
-#if HAVE_VFORK && !(defined __sun && defined __sparc)
+  pid_t new_pid;
+#if HAVE_VFORK
   if ((flags & POSIX_SPAWN_USEVFORK) != 0
       /* If no major work is done, allow using vfork.  Note that we
          might perform the path searching.  But this would be done by
@@ -1029,42 +1061,16 @@ __spawni (pid_t *pid, const char *file,
         }
     }
 
-  if (! use_path || strchr (file, '/') != NULL)
+  if (! use_path)
     {
-      /* The FILE parameter is actually a path.  */
+      /* No need to iterate through $PATH.  Use FILE directly.  */
       execve (file, (char * const *) argv, (char * const *) envp);
 
       /* Oh, oh.  'execve' returns.  This is bad.  */
       _exit (SPAWN_ERROR);
     }
 
-  /* We have to search for FILE on the path.  */
-  path = getenv ("PATH");
-  if (path == NULL)
-    {
-#if HAVE_CONFSTR
-      /* There is no 'PATH' in the environment.
-         The default search path is the current directory
-         followed by the path 'confstr' returns for '_CS_PATH'.  */
-      len = confstr (_CS_PATH, (char *) NULL, 0);
-      path = (char *) alloca (1 + len);
-      path[0] = ':';
-      (void) confstr (_CS_PATH, path + 1, len);
-#else
-      /* Pretend that the PATH contains only the current directory.  */
-      path = "";
-#endif
-    }
-
-  len = strlen (file) + 1;
-  pathlen = strlen (path);
-  name = alloca (pathlen + len + 1);
-  /* Copy the file name at the top.  */
-  name = (char *) memcpy (name + pathlen + 1, file, len);
-  /* And add the slash.  */
-  *--name = '/';
-
-  p = path;
+  char *p = path;
   do
     {
       char *startp;

Attachment: testcase.sh
Description: application/shellscript

Reply via email to