From: Bruno Haible <bruno@clisp.org>
Date: Thu, 24 Dec 2020 02:49:20 +0000 (+0100)
Subject: posix_spawn, posix_spawnp: Fix execution of scripts.
X-Git-Tag: v1.0~3331
X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=2845b3bed86ca649d3206d9b1e0fe30a4ca33110;p=gnulib.git

posix_spawn, posix_spawnp: Fix execution of scripts.

* m4/posix_spawn.m4 (gl_POSIX_SPAWN_SECURE): New macro.
(gl_POSIX_SPAWN_BODY): Invoke it. Set REPLACE_POSIX_SPAWN if posix_spawn
or posix_spawnp allows unsecure execution of scripts.
* doc/posix-functions/posix_spawn.texi: Document the script execution
problem.
* doc/posix-functions/posix_spawnp.texi: Likewise.
---

diff --git a/ChangeLog b/ChangeLog
index bfb7f88251..e7e5f11ed9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2020-12-23  Bruno Haible  <bruno@clisp.org>
+
+	posix_spawn, posix_spawnp: Fix execution of scripts.
+	* m4/posix_spawn.m4 (gl_POSIX_SPAWN_SECURE): New macro.
+	(gl_POSIX_SPAWN_BODY): Invoke it. Set REPLACE_POSIX_SPAWN if posix_spawn
+	or posix_spawnp allows unsecure execution of scripts.
+	* doc/posix-functions/posix_spawn.texi: Document the script execution
+	problem.
+	* doc/posix-functions/posix_spawnp.texi: Likewise.
+
 2020-12-23  Bruno Haible  <bruno@clisp.org>
 
 	Add unit tests regarding execution of scripts.
diff --git a/doc/posix-functions/posix_spawn.texi b/doc/posix-functions/posix_spawn.texi
index 7a3a2f603e..e0b39b0dd7 100644
--- a/doc/posix-functions/posix_spawn.texi
+++ b/doc/posix-functions/posix_spawn.texi
@@ -15,6 +15,11 @@ FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, IRIX 6.5,
 When this function fails, it causes the stdio buffer contents to be output
 twice on some platforms:
 AIX 6.1.
+@item
+When the program to be invoked is an executable script without a @samp{#!}
+marker in the first line, this function executes the script as if it were
+a shell script, on some platforms:
+GNU/Hurd.
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/doc/posix-functions/posix_spawnp.texi b/doc/posix-functions/posix_spawnp.texi
index c420db9a8e..2dd9f689bc 100644
--- a/doc/posix-functions/posix_spawnp.texi
+++ b/doc/posix-functions/posix_spawnp.texi
@@ -15,6 +15,11 @@ FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, IRIX 6.5,
 When this function fails, it causes the stdio buffer contents to be output
 twice on some platforms:
 AIX 6.1.
+@item
+When the program to be invoked is an executable script without a @samp{#!}
+marker in the first line, this function executes the script as if it were
+a shell script, on some platforms:
+glibc 2.14, GNU/Hurd, Mac OS X 10.13, FreeBSD 12.0, OpenBSD 6.7, AIX 7.2, Solaris 11.4, Cygwin 2.9.
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/m4/posix_spawn.m4 b/m4/posix_spawn.m4
index 625b2ad0e8..59e56fcb5f 100644
--- a/m4/posix_spawn.m4
+++ b/m4/posix_spawn.m4
@@ -1,4 +1,4 @@
-# posix_spawn.m4 serial 18
+# posix_spawn.m4 serial 19
 dnl Copyright (C) 2008-2020 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -54,40 +54,52 @@ AC_DEFUN([gl_POSIX_SPAWN_BODY],
     if test $REPLACE_POSIX_SPAWN = 0; then
       gl_POSIX_SPAWN_WORKS
       case "$gl_cv_func_posix_spawn_works" in
-        *yes)
-          dnl Assume that these functions are available if POSIX_SPAWN_SETSCHEDULER
-          dnl evaluates to nonzero.
-          dnl AC_CHECK_FUNCS_ONCE([posix_spawnattr_getschedpolicy])
-          dnl AC_CHECK_FUNCS_ONCE([posix_spawnattr_setschedpolicy])
-          AC_CACHE_CHECK([whether posix_spawnattr_setschedpolicy is supported],
-            [gl_cv_func_spawnattr_setschedpolicy],
-            [AC_EGREP_CPP([POSIX scheduling supported], [
+        *yes) ;;
+        *) REPLACE_POSIX_SPAWN=1 ;;
+      esac
+    fi
+    if test $REPLACE_POSIX_SPAWN = 0; then
+      gl_POSIX_SPAWN_SECURE
+      case "$gl_cv_func_posix_spawn_secure_exec" in
+        *yes) ;;
+        *) REPLACE_POSIX_SPAWN=1 ;;
+      esac
+      case "$gl_cv_func_posix_spawnp_secure_exec" in
+        *yes) ;;
+        *) REPLACE_POSIX_SPAWN=1 ;;
+      esac
+    fi
+    if test $REPLACE_POSIX_SPAWN = 0; then
+      dnl Assume that these functions are available if POSIX_SPAWN_SETSCHEDULER
+      dnl evaluates to nonzero.
+      dnl AC_CHECK_FUNCS_ONCE([posix_spawnattr_getschedpolicy])
+      dnl AC_CHECK_FUNCS_ONCE([posix_spawnattr_setschedpolicy])
+      AC_CACHE_CHECK([whether posix_spawnattr_setschedpolicy is supported],
+        [gl_cv_func_spawnattr_setschedpolicy],
+        [AC_EGREP_CPP([POSIX scheduling supported], [
 #include <spawn.h>
 #if POSIX_SPAWN_SETSCHEDULER
  POSIX scheduling supported
 #endif
 ],
-               [gl_cv_func_spawnattr_setschedpolicy=yes],
-               [gl_cv_func_spawnattr_setschedpolicy=no])
-            ])
-          dnl Assume that these functions are available if POSIX_SPAWN_SETSCHEDPARAM
-          dnl evaluates to nonzero.
-          dnl AC_CHECK_FUNCS_ONCE([posix_spawnattr_getschedparam])
-          dnl AC_CHECK_FUNCS_ONCE([posix_spawnattr_setschedparam])
-          AC_CACHE_CHECK([whether posix_spawnattr_setschedparam is supported],
-            [gl_cv_func_spawnattr_setschedparam],
-            [AC_EGREP_CPP([POSIX scheduling supported], [
+           [gl_cv_func_spawnattr_setschedpolicy=yes],
+           [gl_cv_func_spawnattr_setschedpolicy=no])
+        ])
+      dnl Assume that these functions are available if POSIX_SPAWN_SETSCHEDPARAM
+      dnl evaluates to nonzero.
+      dnl AC_CHECK_FUNCS_ONCE([posix_spawnattr_getschedparam])
+      dnl AC_CHECK_FUNCS_ONCE([posix_spawnattr_setschedparam])
+      AC_CACHE_CHECK([whether posix_spawnattr_setschedparam is supported],
+        [gl_cv_func_spawnattr_setschedparam],
+        [AC_EGREP_CPP([POSIX scheduling supported], [
 #include <spawn.h>
 #if POSIX_SPAWN_SETSCHEDPARAM
  POSIX scheduling supported
 #endif
 ],
-               [gl_cv_func_spawnattr_setschedparam=yes],
-               [gl_cv_func_spawnattr_setschedparam=no])
-            ])
-          ;;
-        *) REPLACE_POSIX_SPAWN=1 ;;
-      esac
+           [gl_cv_func_spawnattr_setschedparam=yes],
+           [gl_cv_func_spawnattr_setschedparam=no])
+        ])
     fi
   fi
   if test $ac_cv_func_posix_spawn != yes || test $REPLACE_POSIX_SPAWN = 1; then
@@ -417,6 +429,113 @@ main (int argc, char *argv[])
     ])
 ])
 
+dnl Test whether posix_spawn and posix_spawnp are secure.
+AC_DEFUN([gl_POSIX_SPAWN_SECURE],
+[
+  AC_REQUIRE([AC_PROG_CC])
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+  dnl On many platforms, posix_spawn or posix_spawnp allow executing a
+  dnl script without a '#!' marker as a shell script. This is unsecure.
+  AC_CACHE_CHECK([whether posix_spawn rejects scripts without shebang],
+    [gl_cv_func_posix_spawn_secure_exec],
+    [echo ':' > conftest.scr
+     chmod a+x conftest.scr
+     AC_RUN_IFELSE([AC_LANG_SOURCE([[
+       #include <errno.h>
+       #include <spawn.h>
+       #include <stddef.h>
+       #include <sys/types.h>
+       #include <sys/wait.h>
+       int
+       main ()
+       {
+         const char *prog_path = "./conftest.scr";
+         const char *prog_argv[2] = { prog_path, NULL };
+         const char *environment[2] = { "PATH=.", NULL };
+         pid_t child;
+         int status;
+         int err = posix_spawn (&child, prog_path, NULL, NULL,
+                                (char **) prog_argv, (char **) environment);
+         if (err == ENOEXEC)
+           return 0;
+         if (err != 0)
+           return 1;
+         status = 0;
+         while (waitpid (child, &status, 0) != child)
+           ;
+         if (!WIFEXITED (status))
+           return 2;
+         if (WEXITSTATUS (status) != 127)
+           return 3;
+         return 0;
+       }
+       ]])],
+       [gl_cv_func_posix_spawn_secure_exec=yes],
+       [gl_cv_func_posix_spawn_secure_exec=no],
+       [case "$host_os" in
+          # Guess no on GNU/Hurd.
+          gnu*)
+            gl_cv_func_posix_spawn_secure_exec="guessing no" ;;
+          # Guess yes on all other platforms.
+          *)
+            gl_cv_func_posix_spawn_secure_exec="guessing yes" ;;
+        esac
+       ])
+     rm -f conftest.scr
+    ])
+  AC_CACHE_CHECK([whether posix_spawnp rejects scripts without shebang],
+    [gl_cv_func_posix_spawnp_secure_exec],
+    [echo ':' > conftest.scr
+     chmod a+x conftest.scr
+     AC_RUN_IFELSE([AC_LANG_SOURCE([[
+       #include <errno.h>
+       #include <spawn.h>
+       #include <stddef.h>
+       #include <sys/types.h>
+       #include <sys/wait.h>
+       int
+       main ()
+       {
+         const char *prog_path = "./conftest.scr";
+         const char *prog_argv[2] = { prog_path, NULL };
+         const char *environment[2] = { "PATH=.", NULL };
+         pid_t child;
+         int status;
+         int err = posix_spawnp (&child, prog_path, NULL, NULL,
+                                 (char **) prog_argv, (char **) environment);
+         if (err == ENOEXEC)
+           return 0;
+         if (err != 0)
+           return 1;
+         status = 0;
+         while (waitpid (child, &status, 0) != child)
+           ;
+         if (!WIFEXITED (status))
+           return 2;
+         if (WEXITSTATUS (status) != 127)
+           return 3;
+         return 0;
+       }
+       ]])],
+       [gl_cv_func_posix_spawnp_secure_exec=yes],
+       [gl_cv_func_posix_spawnp_secure_exec=no],
+       [case "$host_os" in
+          # Guess yes on glibc systems (glibc >= 2.15 actually) except GNU/Hurd,
+          # musl libc, NetBSD.
+          *-gnu* | *-musl* | netbsd*)
+            gl_cv_func_posix_spawnp_secure_exec="guessing yes" ;;
+          # Guess no on GNU/Hurd, macOS, FreeBSD, OpenBSD, AIX, Solaris, Cygwin.
+          gnu* | darwin* | freebsd* | dragonfly* | openbsd* | aix* | solaris* | cygwin*)
+            gl_cv_func_posix_spawnp_secure_exec="guessing no" ;;
+          # If we don't know, obey --enable-cross-guesses.
+          *)
+            gl_cv_func_posix_spawnp_secure_exec="$gl_cross_guess_normal" ;;
+        esac
+       ])
+     rm -f conftest.scr
+    ])
+])
+
 # Prerequisites of lib/spawni.c.
 AC_DEFUN([gl_PREREQ_POSIX_SPAWN_INTERNAL],
 [