The posix_spawn implementation in gnulib comes from glibc as of 2008.
But in 2011 an important change was done in glibc: Remove the ability
to specify a script that does not start with a '#!' marker as executable.
Previously this file was /_assumed_/ to be a shell script.
Here are 3 patches:
- Change gnulib's posix_spawn to match what glibc does.
- Add unit tests.
- Fix the resulting test failures on many platforms (from GNU/Hurd [1] to
Solaris 11).
[1] https://lists.gnu.org/archive/html/bug-hurd/2020-12/msg00071.html
2020-12-23 Bruno Haible <[email protected]>
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 <[email protected]>
Add unit tests regarding execution of scripts.
* tests/executable-script: New file.
* tests/executable-shell-script: New file.
* tests/test-posix_spawn-script.c: New file.
* tests/test-posix_spawnp-script.c: New file.
* tests/test-execute-script.c: New file.
* tests/test-spawn-pipe-script.c: New file.
* modules/posix_spawn-tests (Files): Add
tests/test-posix_spawn-script.c, tests/executable-script,
tests/executable-shell-script.
(Makefile.am): Compile and run test-posix_spawn-script.
* modules/posix_spawnp-tests (Files): Add
tests/test-posix_spawnp-script.c, tests/executable-script,
tests/executable-shell-script.
(Makefile.am): Compile and run test-posix_spawnp-script.
* modules/execute-tests (Files): Add tests/test-execute-script.c,
tests/executable-script, tests/executable-shell-script.
(Makefile.am): Compile and run test-execute-script.
* modules/spawn-pipe-tests (Files): Add tests/test-spawn-pipe-script.c,
tests/executable-script, tests/executable-shell-script.
(Makefile.am): Compile and run test-spawn-pipe-script.
2020-12-23 Bruno Haible <[email protected]>
Don't execute scripts without '#!' marker through /bin/sh.
This reflects the change done in glibc through
<https://sourceware.org/bugzilla/show_bug.cgi?id=13134> and
<https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d96de9634a334af16c0ac711074c15ac1762b23c>.
* lib/spawni.c (internal_function): Remove macro.
(script_execute): Remove function.
(__spawni): Don't invoke script_execute.
* lib/execute.c (execute): Disable the ENOEXEC handling.
* lib/spawn-pipe.c (create_pipe): Likewise.
* NEWS: Mention the change.
>From 9042e0456ac72d9333e2d48970c7f2835acfbf79 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Wed, 23 Dec 2020 23:58:17 +0100
Subject: [PATCH 1/3] Don't execute scripts without '#!' marker through
/bin/sh.
This reflects the change done in glibc through
<https://sourceware.org/bugzilla/show_bug.cgi?id=13134> and
<https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d96de9634a334af16c0ac711074c15ac1762b23c>.
* lib/spawni.c (internal_function): Remove macro.
(script_execute): Remove function.
(__spawni): Don't invoke script_execute.
* lib/execute.c (execute): Disable the ENOEXEC handling.
* lib/spawn-pipe.c (create_pipe): Likewise.
* NEWS: Mention the change.
---
ChangeLog | 13 +++++++++++++
NEWS | 7 +++++++
lib/execute.c | 2 ++
lib/spawn-pipe.c | 4 ++++
lib/spawni.c | 39 ---------------------------------------
5 files changed, 26 insertions(+), 39 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 7d38a83..b0d67da 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,18 @@
2020-12-23 Bruno Haible <[email protected]>
+ Don't execute scripts without '#!' marker through /bin/sh.
+ This reflects the change done in glibc through
+ <https://sourceware.org/bugzilla/show_bug.cgi?id=13134> and
+ <https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d96de9634a334af16c0ac711074c15ac1762b23c>.
+ * lib/spawni.c (internal_function): Remove macro.
+ (script_execute): Remove function.
+ (__spawni): Don't invoke script_execute.
+ * lib/execute.c (execute): Disable the ENOEXEC handling.
+ * lib/spawn-pipe.c (create_pipe): Likewise.
+ * NEWS: Mention the change.
+
+2020-12-23 Bruno Haible <[email protected]>
+
posix_spawn[p]: Fix compilation error on Windows (regr. 2020-12-14).
Reported by Adrian Ebeling <[email protected]> in
<https://lists.gnu.org/archive/html/bug-gnulib/2020-12/msg00189.html>.
diff --git a/NEWS b/NEWS
index 7679be0..c347ecd 100644
--- a/NEWS
+++ b/NEWS
@@ -60,6 +60,13 @@ User visible incompatible changes
Date Modules Changes
+2020-12-23 execute These functions no longer execute scripts without
+ spawn-pipe '#!' marker through /bin/sh. To execute such a
+ posix_spawn script as a shell script, either add a '#!/bin/sh'
+ posix_spawnp marker in the first line, or specify "/bin/sh" as
+ the program to execute and the script as its first
+ argument.
+
2020-12-18 free This module, obsoleted in 2008, is gone.
2020-12-14 findprog-in The function 'find_in_given_path' now takes a 3rd
diff --git a/lib/execute.c b/lib/execute.c
index 41a2239..38dd07b 100644
--- a/lib/execute.c
+++ b/lib/execute.c
@@ -191,6 +191,7 @@ execute (const char *progname,
exitcode = spawnpvech (P_WAIT, prog_path, argv + 1,
(const char * const *) environ, directory,
stdin_handle, stdout_handle, stderr_handle);
+# if 0 /* Executing arbitrary files as shell scripts is unsecure. */
if (exitcode == -1 && errno == ENOEXEC)
{
/* prog is not a native executable. Try to execute it as a
@@ -201,6 +202,7 @@ execute (const char *progname,
(const char * const *) environ, directory,
stdin_handle, stdout_handle, stderr_handle);
}
+# endif
}
if (exitcode == -1)
saved_errno = errno;
diff --git a/lib/spawn-pipe.c b/lib/spawn-pipe.c
index d483520..aedbcb2 100644
--- a/lib/spawn-pipe.c
+++ b/lib/spawn-pipe.c
@@ -287,6 +287,7 @@ create_pipe (const char *progname,
child = spawnpvech (P_NOWAIT, prog_path, argv + 1,
(const char * const *) environ, directory,
stdin_handle, stdout_handle, stderr_handle);
+# if 0 /* Executing arbitrary files as shell scripts is unsecure. */
if (child == -1 && errno == ENOEXEC)
{
/* prog is not a native executable. Try to execute it as a
@@ -297,6 +298,7 @@ create_pipe (const char *progname,
(const char * const *) environ, directory,
stdin_handle, stdout_handle, stderr_handle);
}
+# endif
}
failed:
if (child == -1)
@@ -374,6 +376,7 @@ create_pipe (const char *progname,
{
child = _spawnvpe (P_NOWAIT, prog_path, argv + 1,
(const char **) environ);
+# if 0 /* Executing arbitrary files as shell scripts is unsecure. */
if (child == -1 && errno == ENOEXEC)
{
/* prog is not a native executable. Try to execute it as a
@@ -382,6 +385,7 @@ create_pipe (const char *progname,
child = _spawnvpe (P_NOWAIT, argv[0], argv,
(const char **) environ);
}
+# endif
}
if (child == -1)
saved_errno = errno;
diff --git a/lib/spawni.c b/lib/spawni.c
index 06d98b4..182d13f 100644
--- a/lib/spawni.c
+++ b/lib/spawni.c
@@ -75,9 +75,6 @@
# define sigprocmask __sigprocmask
# define strchrnul __strchrnul
# define vfork __vfork
-#else
-# undef internal_function
-# define internal_function /* empty */
#endif
@@ -105,36 +102,6 @@ __spawni (pid_t *pid, const char *file,
#else
-/* The file is accessible but it is not an executable file. Invoke
- the shell to interpret it as a script. */
-static void
-internal_function
-script_execute (const char *file, const char *const argv[],
- const char *const envp[])
-{
- /* Count the arguments. */
- int argc = 0;
- while (argv[argc++])
- ;
-
- /* Construct an argument list for the shell. */
- {
- const char **new_argv =
- (const char **) alloca ((argc + 1) * sizeof (const char *));
- new_argv[0] = _PATH_BSHELL;
- new_argv[1] = file;
- while (argc > 1)
- {
- new_argv[argc] = argv[argc - 1];
- --argc;
- }
-
- /* Execute the shell. */
- execve (new_argv[0], (char * const *) new_argv, (char * const *) envp);
- }
-}
-
-
/* Spawn a new process executing PATH with the attributes describes in *ATTRP.
Before running the process perform the actions described in FILE-ACTIONS. */
int
@@ -307,9 +274,6 @@ __spawni (pid_t *pid, const char *file,
/* The FILE parameter is actually a path. */
execve (file, (char * const *) argv, (char * const *) envp);
- if (errno == ENOEXEC)
- script_execute (file, argv, envp);
-
/* Oh, oh. 'execve' returns. This is bad. */
_exit (SPAWN_ERROR);
}
@@ -358,9 +322,6 @@ __spawni (pid_t *pid, const char *file,
/* Try to execute this name. If it works, execv will not return. */
execve (startp, (char * const *) argv, (char * const *) envp);
- if (errno == ENOEXEC)
- script_execute (startp, argv, envp);
-
switch (errno)
{
case EACCES:
--
2.7.4
>From 7e9ecfe3790f40ea5b9b18cbfdbb11d0277d27ed Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 24 Dec 2020 01:14:49 +0100
Subject: [PATCH 2/3] Add unit tests regarding execution of scripts.
* tests/executable-script: New file.
* tests/executable-shell-script: New file.
* tests/test-posix_spawn-script.c: New file.
* tests/test-posix_spawnp-script.c: New file.
* tests/test-execute-script.c: New file.
* tests/test-spawn-pipe-script.c: New file.
* modules/posix_spawn-tests (Files): Add
tests/test-posix_spawn-script.c, tests/executable-script,
tests/executable-shell-script.
(Makefile.am): Compile and run test-posix_spawn-script.
* modules/posix_spawnp-tests (Files): Add
tests/test-posix_spawnp-script.c, tests/executable-script,
tests/executable-shell-script.
(Makefile.am): Compile and run test-posix_spawnp-script.
* modules/execute-tests (Files): Add tests/test-execute-script.c,
tests/executable-script, tests/executable-shell-script.
(Makefile.am): Compile and run test-execute-script.
* modules/spawn-pipe-tests (Files): Add tests/test-spawn-pipe-script.c,
tests/executable-script, tests/executable-shell-script.
(Makefile.am): Compile and run test-spawn-pipe-script.
---
ChangeLog | 24 +++++++
modules/execute-tests | 8 +++
modules/posix_spawn-tests | 10 ++-
modules/posix_spawnp-tests | 15 +++-
modules/spawn-pipe-tests | 8 +++
tests/executable-script | 4 ++
tests/executable-shell-script | 4 ++
tests/test-execute-script.c | 88 ++++++++++++++++++++++++
tests/test-posix_spawn-script.c | 143 +++++++++++++++++++++++++++++++++++++++
tests/test-posix_spawnp-script.c | 143 +++++++++++++++++++++++++++++++++++++++
tests/test-spawn-pipe-script.c | 100 +++++++++++++++++++++++++++
11 files changed, 543 insertions(+), 4 deletions(-)
create mode 100755 tests/executable-script
create mode 100755 tests/executable-shell-script
create mode 100644 tests/test-execute-script.c
create mode 100644 tests/test-posix_spawn-script.c
create mode 100644 tests/test-posix_spawnp-script.c
create mode 100644 tests/test-spawn-pipe-script.c
diff --git a/ChangeLog b/ChangeLog
index b0d67da..bfb7f88 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,29 @@
2020-12-23 Bruno Haible <[email protected]>
+ Add unit tests regarding execution of scripts.
+ * tests/executable-script: New file.
+ * tests/executable-shell-script: New file.
+ * tests/test-posix_spawn-script.c: New file.
+ * tests/test-posix_spawnp-script.c: New file.
+ * tests/test-execute-script.c: New file.
+ * tests/test-spawn-pipe-script.c: New file.
+ * modules/posix_spawn-tests (Files): Add
+ tests/test-posix_spawn-script.c, tests/executable-script,
+ tests/executable-shell-script.
+ (Makefile.am): Compile and run test-posix_spawn-script.
+ * modules/posix_spawnp-tests (Files): Add
+ tests/test-posix_spawnp-script.c, tests/executable-script,
+ tests/executable-shell-script.
+ (Makefile.am): Compile and run test-posix_spawnp-script.
+ * modules/execute-tests (Files): Add tests/test-execute-script.c,
+ tests/executable-script, tests/executable-shell-script.
+ (Makefile.am): Compile and run test-execute-script.
+ * modules/spawn-pipe-tests (Files): Add tests/test-spawn-pipe-script.c,
+ tests/executable-script, tests/executable-shell-script.
+ (Makefile.am): Compile and run test-spawn-pipe-script.
+
+2020-12-23 Bruno Haible <[email protected]>
+
Don't execute scripts without '#!' marker through /bin/sh.
This reflects the change done in glibc through
<https://sourceware.org/bugzilla/show_bug.cgi?id=13134> and
diff --git a/modules/execute-tests b/modules/execute-tests
index 9562182..2213a32 100644
--- a/modules/execute-tests
+++ b/modules/execute-tests
@@ -2,6 +2,9 @@ Files:
tests/test-execute.sh
tests/test-execute-main.c
tests/test-execute-child.c
+tests/test-execute-script.c
+tests/executable-script
+tests/executable-shell-script
tests/macros.h
Depends-on:
@@ -23,3 +26,8 @@ test_execute_main_LDADD = $(LDADD) @LIBINTL@ $(LIBTHREAD)
# wrapper script, and should link against as few libraries as possible.
# Therefore don't link it against any libraries other than -lc.
test_execute_child_LDADD =
+
+TESTS += test-execute-script
+check_PROGRAMS += test-execute-script
+test_execute_script_LDADD = $(LDADD) @LIBINTL@ $(LIBTHREAD)
+test_execute_script_CPPFLAGS = $(AM_CPPFLAGS) -DSRCDIR=\"$(srcdir)/\"
diff --git a/modules/posix_spawn-tests b/modules/posix_spawn-tests
index 2c8b4a5..451dbd1 100644
--- a/modules/posix_spawn-tests
+++ b/modules/posix_spawn-tests
@@ -3,6 +3,9 @@ tests/test-posix_spawn-open1.c
tests/test-posix_spawn-open2.c
tests/test-posix_spawn-inherit0.c
tests/test-posix_spawn-inherit1.c
+tests/test-posix_spawn-script.c
+tests/executable-script
+tests/executable-shell-script
tests/signature.h
Depends-on:
@@ -31,10 +34,13 @@ TESTS += \
test-posix_spawn-open1 \
test-posix_spawn-open2 \
test-posix_spawn-inherit0 \
- test-posix_spawn-inherit1
+ test-posix_spawn-inherit1 \
+ test-posix_spawn-script
check_PROGRAMS += \
test-posix_spawn-open1 \
test-posix_spawn-open2 \
test-posix_spawn-inherit0 \
- test-posix_spawn-inherit1
+ test-posix_spawn-inherit1 \
+ test-posix_spawn-script
+test_posix_spawn_script_CPPFLAGS = $(AM_CPPFLAGS) -DSRCDIR=\"$(srcdir)/\"
endif
diff --git a/modules/posix_spawnp-tests b/modules/posix_spawnp-tests
index 1f6bbc7..04ccdbb 100644
--- a/modules/posix_spawnp-tests
+++ b/modules/posix_spawnp-tests
@@ -3,6 +3,9 @@ tests/test-posix_spawn-dup2-stdout.c
tests/test-posix_spawn-dup2-stdout.in.sh
tests/test-posix_spawn-dup2-stdin.c
tests/test-posix_spawn-dup2-stdin.in.sh
+tests/test-posix_spawnp-script.c
+tests/executable-script
+tests/executable-shell-script
tests/signature.h
Depends-on:
@@ -35,8 +38,14 @@ AM_CONDITIONAL([POSIX_SPAWN_PORTED], [test $posix_spawn_ported = yes])
Makefile.am:
if POSIX_SPAWN_PORTED
-TESTS += test-posix_spawn-dup2-stdout test-posix_spawn-dup2-stdin
-check_PROGRAMS += test-posix_spawn-dup2-stdout test-posix_spawn-dup2-stdin
+TESTS += \
+ test-posix_spawn-dup2-stdout \
+ test-posix_spawn-dup2-stdin \
+ test-posix_spawnp-script
+check_PROGRAMS += \
+ test-posix_spawn-dup2-stdout \
+ test-posix_spawn-dup2-stdin \
+ test-posix_spawnp-script
BUILT_SOURCES += test-posix_spawn-dup2-stdout.sh
test-posix_spawn-dup2-stdout.sh: test-posix_spawn-dup2-stdout.in.sh
@@ -51,4 +60,6 @@ test-posix_spawn-dup2-stdin.sh: test-posix_spawn-dup2-stdin.in.sh
cp $(srcdir)/test-posix_spawn-dup2-stdin.in.sh $@-t && \
mv $@-t $@
MOSTLYCLEANFILES += test-posix_spawn-dup2-stdin.sh test-posix_spawn-dup2-stdin.sh-t
+
+test_posix_spawnp_script_CPPFLAGS = $(AM_CPPFLAGS) -DSRCDIR=\"$(srcdir)/\"
endif
diff --git a/modules/spawn-pipe-tests b/modules/spawn-pipe-tests
index 5d1372c..80614a9 100644
--- a/modules/spawn-pipe-tests
+++ b/modules/spawn-pipe-tests
@@ -2,6 +2,9 @@ Files:
tests/test-spawn-pipe.sh
tests/test-spawn-pipe-main.c
tests/test-spawn-pipe-child.c
+tests/test-spawn-pipe-script.c
+tests/executable-script
+tests/executable-shell-script
tests/macros.h
Depends-on:
@@ -19,3 +22,8 @@ test_spawn_pipe_main_LDADD = $(LDADD) @LIBINTL@ $(LIBTHREAD)
# wrapper script, and should link against as few libraries as possible.
# Therefore don't link it against any libraries other than -lc.
test_spawn_pipe_child_LDADD =
+
+TESTS += test-spawn-pipe-script
+check_PROGRAMS += test-spawn-pipe-script
+test_spawn_pipe_script_LDADD = $(LDADD) @LIBINTL@ $(LIBTHREAD)
+test_spawn_pipe_script_CPPFLAGS = $(AM_CPPFLAGS) -DSRCDIR=\"$(srcdir)/\"
diff --git a/tests/executable-script b/tests/executable-script
new file mode 100755
index 0000000..7d8b9cc
--- /dev/null
+++ b/tests/executable-script
@@ -0,0 +1,4 @@
+printf 'Halle '
+printf "Potta"
+# This script is intentionally not immediately recognizable as a shell script.
+# Don't add a .sh suffix. Don't add a #! header in the first line.
diff --git a/tests/executable-shell-script b/tests/executable-shell-script
new file mode 100755
index 0000000..1e38117
--- /dev/null
+++ b/tests/executable-shell-script
@@ -0,0 +1,4 @@
+#!/bin/sh
+# This script is a proper shell script.
+printf 'Halle '
+printf "Potta"
diff --git a/tests/test-execute-script.c b/tests/test-execute-script.c
new file mode 100644
index 0000000..060f0c5
--- /dev/null
+++ b/tests/test-execute-script.c
@@ -0,0 +1,88 @@
+/* Test of execute.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "execute.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "read-file.h"
+#include "macros.h"
+
+#define DATA_FILENAME "test-execute-script.tmp"
+
+int
+main ()
+{
+ unlink (DATA_FILENAME);
+
+ /* Check an invocation of an executable script.
+ This should only be supported if the script has a '#!' marker; otherwise
+ it is unsecure: <https://sourceware.org/bugzilla/show_bug.cgi?id=13134>.
+ POSIX says that the execlp() and execvp() functions support executing
+ shell scripts
+ <https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html>,
+ but this is considered an antiquated feature. */
+
+ /* This test is an extension of
+ "Check stdout is inherited, part 1 (regular file)"
+ in test-execute-main.c. */
+ FILE *fp = freopen (DATA_FILENAME, "w", stdout);
+ ASSERT (fp != NULL);
+
+ {
+ const char *progname = "executable-script";
+ const char *prog_path = SRCDIR "executable-script";
+ const char *prog_argv[2] = { prog_path, NULL };
+
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ false, false, false, false, true, false, NULL);
+ ASSERT (ret == 127);
+ }
+
+#if defined _WIN32 && !defined __CYGWIN__
+ /* On native Windows, scripts - even with '#!' marker - are not executable.
+ Only .bat and .cmd files are. */
+ ASSERT (fclose (fp) == 0);
+ ASSERT (unlink (DATA_FILENAME) == 0);
+ fprintf (stderr, "Skipping test: scripts are not executable on this platform.\n");
+ return 77;
+#else
+ {
+ const char *progname = "executable-shell-script";
+ const char *prog_path = SRCDIR "executable-shell-script";
+ const char *prog_argv[2] = { prog_path, NULL };
+
+ int ret = execute (progname, prog_argv[0], prog_argv, NULL,
+ false, false, false, false, true, false, NULL);
+ ASSERT (ret == 0);
+
+ ASSERT (fclose (fp) == 0);
+
+ size_t length;
+ char *contents = read_file (DATA_FILENAME, 0, &length);
+ ASSERT (length == 11 && memcmp (contents, "Halle Potta", 11) == 0);
+ }
+
+ ASSERT (unlink (DATA_FILENAME) == 0);
+
+ return 0;
+#endif
+}
diff --git a/tests/test-posix_spawn-script.c b/tests/test-posix_spawn-script.c
new file mode 100644
index 0000000..a632841
--- /dev/null
+++ b/tests/test-posix_spawn-script.c
@@ -0,0 +1,143 @@
+/* Test of posix_spawn() function.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <spawn.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "macros.h"
+
+#define DATA_FILENAME "test-posix_spawn-script.tmp"
+
+int
+main ()
+{
+ unlink (DATA_FILENAME);
+
+ /* Check an invocation of an executable script.
+ This should only be supported if the script has a '#!' marker; otherwise
+ it is unsecure: <https://sourceware.org/bugzilla/show_bug.cgi?id=13134>.
+ POSIX says that the execlp() and execvp() functions support executing
+ shell scripts
+ <https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html>,
+ but this is considered an antiquated feature. */
+ pid_t child;
+
+ posix_spawn_file_actions_t actions;
+ ASSERT (posix_spawn_file_actions_init (&actions) == 0);
+ ASSERT (posix_spawn_file_actions_addopen (&actions, STDOUT_FILENO,
+ DATA_FILENAME,
+ O_RDWR | O_CREAT | O_TRUNC, 0600)
+ == 0);
+
+ {
+ const char *prog_path = SRCDIR "executable-script";
+ const char *prog_argv[2] = { prog_path, NULL };
+
+ int err = posix_spawn (&child, prog_path, &actions, NULL,
+ (char **) prog_argv, environ);
+ if (err != ENOEXEC)
+ {
+ if (err != 0)
+ {
+ errno = err;
+ perror ("posix_spawn");
+ return 1;
+ }
+
+ /* Wait for child. */
+ int status = 0;
+ while (waitpid (child, &status, 0) != child)
+ ;
+ if (!WIFEXITED (status))
+ {
+ fprintf (stderr, "subprocess terminated with unexpected wait status %d\n", status);
+ return 1;
+ }
+ int exitstatus = WEXITSTATUS (status);
+ if (exitstatus != 127)
+ {
+ fprintf (stderr, "subprocess terminated with unexpected exit status %d\n", exitstatus);
+ return 1;
+ }
+ }
+ }
+
+ {
+ const char *prog_path = SRCDIR "executable-shell-script";
+ const char *prog_argv[2] = { prog_path, NULL };
+
+ int err = posix_spawn (&child, prog_path, &actions, NULL,
+ (char **) prog_argv, environ);
+ if (err != 0)
+ {
+ errno = err;
+ perror ("posix_spawn");
+ return 1;
+ }
+
+ posix_spawn_file_actions_destroy (&actions);
+
+ /* Wait for child. */
+ int status = 0;
+ while (waitpid (child, &status, 0) != child)
+ ;
+ if (!WIFEXITED (status))
+ {
+ fprintf (stderr, "subprocess terminated with unexpected wait status %d\n", status);
+ return 1;
+ }
+ int exitstatus = WEXITSTATUS (status);
+ if (exitstatus != 0)
+ {
+ fprintf (stderr, "subprocess terminated with unexpected exit status %d\n", exitstatus);
+ return 1;
+ }
+
+ /* Check the contents of the data file. */
+ FILE *fp = fopen (DATA_FILENAME, "rb");
+ if (fp == NULL)
+ {
+ perror ("cannot open data file");
+ return 1;
+ }
+ char buf[1024];
+ int nread = fread (buf, 1, sizeof (buf), fp);
+ if (!(nread == 11 && memcmp (buf, "Halle Potta", 11) == 0))
+ {
+ fprintf (stderr, "data file wrong: has %d bytes, expected %d bytes\n", nread, 11);
+ return 1;
+ }
+ if (fclose (fp))
+ {
+ perror ("cannot close data file");
+ return 1;
+ }
+ }
+
+ /* Clean up data file. */
+ unlink (DATA_FILENAME);
+
+ return 0;
+}
diff --git a/tests/test-posix_spawnp-script.c b/tests/test-posix_spawnp-script.c
new file mode 100644
index 0000000..04bf496
--- /dev/null
+++ b/tests/test-posix_spawnp-script.c
@@ -0,0 +1,143 @@
+/* Test of posix_spawnp() function.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <spawn.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "macros.h"
+
+#define DATA_FILENAME "test-posix_spawn-script.tmp"
+
+int
+main ()
+{
+ unlink (DATA_FILENAME);
+
+ /* Check an invocation of an executable script.
+ This should only be supported if the script has a '#!' marker; otherwise
+ it is unsecure: <https://sourceware.org/bugzilla/show_bug.cgi?id=13134>.
+ POSIX says that the execlp() and execvp() functions support executing
+ shell scripts
+ <https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html>,
+ but this is considered an antiquated feature. */
+ pid_t child;
+
+ posix_spawn_file_actions_t actions;
+ ASSERT (posix_spawn_file_actions_init (&actions) == 0);
+ ASSERT (posix_spawn_file_actions_addopen (&actions, STDOUT_FILENO,
+ DATA_FILENAME,
+ O_RDWR | O_CREAT | O_TRUNC, 0600)
+ == 0);
+
+ {
+ const char *prog_path = SRCDIR "executable-script";
+ const char *prog_argv[2] = { prog_path, NULL };
+
+ int err = posix_spawnp (&child, prog_path, &actions, NULL,
+ (char **) prog_argv, environ);
+ if (err != ENOEXEC)
+ {
+ if (err != 0)
+ {
+ errno = err;
+ perror ("posix_spawn");
+ return 1;
+ }
+
+ /* Wait for child. */
+ int status = 0;
+ while (waitpid (child, &status, 0) != child)
+ ;
+ if (!WIFEXITED (status))
+ {
+ fprintf (stderr, "subprocess terminated with unexpected wait status %d\n", status);
+ return 1;
+ }
+ int exitstatus = WEXITSTATUS (status);
+ if (exitstatus != 127)
+ {
+ fprintf (stderr, "subprocess terminated with unexpected exit status %d\n", exitstatus);
+ return 1;
+ }
+ }
+ }
+
+ {
+ const char *prog_path = SRCDIR "executable-shell-script";
+ const char *prog_argv[2] = { prog_path, NULL };
+
+ int err = posix_spawnp (&child, prog_path, &actions, NULL,
+ (char **) prog_argv, environ);
+ if (err != 0)
+ {
+ errno = err;
+ perror ("posix_spawn");
+ return 1;
+ }
+
+ posix_spawn_file_actions_destroy (&actions);
+
+ /* Wait for child. */
+ int status = 0;
+ while (waitpid (child, &status, 0) != child)
+ ;
+ if (!WIFEXITED (status))
+ {
+ fprintf (stderr, "subprocess terminated with unexpected wait status %d\n", status);
+ return 1;
+ }
+ int exitstatus = WEXITSTATUS (status);
+ if (exitstatus != 0)
+ {
+ fprintf (stderr, "subprocess terminated with unexpected exit status %d\n", exitstatus);
+ return 1;
+ }
+
+ /* Check the contents of the data file. */
+ FILE *fp = fopen (DATA_FILENAME, "rb");
+ if (fp == NULL)
+ {
+ perror ("cannot open data file");
+ return 1;
+ }
+ char buf[1024];
+ int nread = fread (buf, 1, sizeof (buf), fp);
+ if (!(nread == 11 && memcmp (buf, "Halle Potta", 11) == 0))
+ {
+ fprintf (stderr, "data file wrong: has %d bytes, expected %d bytes\n", nread, 11);
+ return 1;
+ }
+ if (fclose (fp))
+ {
+ perror ("cannot close data file");
+ return 1;
+ }
+ }
+
+ /* Clean up data file. */
+ unlink (DATA_FILENAME);
+
+ return 0;
+}
diff --git a/tests/test-spawn-pipe-script.c b/tests/test-spawn-pipe-script.c
new file mode 100644
index 0000000..44f9d2c
--- /dev/null
+++ b/tests/test-spawn-pipe-script.c
@@ -0,0 +1,100 @@
+/* Test of create_pipe_in/wait_subprocess.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "spawn-pipe.h"
+#include "wait-process.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "macros.h"
+
+int
+main ()
+{
+ /* Check an invocation of an executable script.
+ This should only be supported if the script has a '#!' marker; otherwise
+ it is unsecure: <https://sourceware.org/bugzilla/show_bug.cgi?id=13134>.
+ POSIX says that the execlp() and execvp() functions support executing
+ shell scripts
+ <https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html>,
+ but this is considered an antiquated feature. */
+ int fd[1];
+ pid_t pid;
+
+ {
+ const char *progname = "executable-script";
+ const char *prog_path = SRCDIR "executable-script";
+ const char *prog_argv[2] = { prog_path, NULL };
+
+ pid = create_pipe_in (progname, prog_argv[0], prog_argv, NULL,
+ NULL, false, true, false, fd);
+ if (pid >= 0)
+ {
+ /* Wait for child. */
+ ASSERT (wait_subprocess (pid, progname, true, true, true, false, NULL)
+ == 127);
+ }
+ else
+ {
+ ASSERT (pid == -1);
+#if defined _WIN32 && !defined __CYGWIN__
+ ASSERT (errno == ENOENT);
+#else
+ ASSERT (errno == ENOEXEC);
+#endif
+ }
+ }
+
+#if defined _WIN32 && !defined __CYGWIN__
+ /* On native Windows, scripts - even with '#!' marker - are not executable.
+ Only .bat and .cmd files are. */
+ fprintf (stderr, "Skipping test: scripts are not executable on this platform.\n");
+ return 77;
+#else
+ {
+ const char *progname = "executable-shell-script";
+ const char *prog_path = SRCDIR "executable-shell-script";
+ const char *prog_argv[2] = { prog_path, NULL };
+
+ pid = create_pipe_in (progname, prog_argv[0], prog_argv, NULL,
+ NULL, false, true, false, fd);
+ ASSERT (pid >= 0);
+ ASSERT (fd[0] > STDERR_FILENO);
+
+ /* Get child's output. */
+ char buffer[1024];
+ FILE *fp = fdopen (fd[0], "r");
+ ASSERT (fp != NULL);
+ ASSERT (fread (buffer, 1, sizeof (buffer), fp) == 11);
+
+ /* Check the result. */
+ ASSERT (memcmp (buffer, "Halle Potta", 11) == 0);
+
+ /* Wait for child. */
+ ASSERT (wait_subprocess (pid, progname, true, false, true, true, NULL) == 0);
+
+ ASSERT (fclose (fp) == 0);
+ }
+
+ return 0;
+#endif
+}
--
2.7.4
>From 2845b3bed86ca649d3206d9b1e0fe30a4ca33110 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Thu, 24 Dec 2020 03:49:20 +0100
Subject: [PATCH 3/3] 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.
---
ChangeLog | 10 ++
doc/posix-functions/posix_spawn.texi | 5 +
doc/posix-functions/posix_spawnp.texi | 5 +
m4/posix_spawn.m4 | 169 +++++++++++++++++++++++++++++-----
4 files changed, 164 insertions(+), 25 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index bfb7f88..e7e5f11 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
2020-12-23 Bruno Haible <[email protected]>
+ 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 <[email protected]>
+
Add unit tests regarding execution of scripts.
* tests/executable-script: New file.
* tests/executable-shell-script: New file.
diff --git a/doc/posix-functions/posix_spawn.texi b/doc/posix-functions/posix_spawn.texi
index 7a3a2f6..e0b39b0 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 c420db9..2dd9f68 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 625b2ad..59e56fc 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],
[
--
2.7.4