>From 7e9ecfe3790f40ea5b9b18cbfdbb11d0277d27ed Mon Sep 17 00:00:00 2001 From: Bruno Haible 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 + 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 + Don't execute scripts without '#!' marker through /bin/sh. This reflects the change done in glibc through 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 . */ + +#include + +#include "execute.h" + +#include +#include +#include +#include + +#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: . + POSIX says that the execlp() and execvp() functions support executing + shell scripts + , + 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 . */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#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: . + POSIX says that the execlp() and execvp() functions support executing + shell scripts + , + 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 . */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#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: . + POSIX says that the execlp() and execvp() functions support executing + shell scripts + , + 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 . */ + +#include + +#include "spawn-pipe.h" +#include "wait-process.h" + +#include +#include +#include +#include +#include + +#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: . + POSIX says that the execlp() and execvp() functions support executing + shell scripts + , + 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