diff --git a/ChangeLog b/ChangeLog index 45fb9cad15..3ef2c69006 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,18 @@ 2016-03-07 Adhemerval Zanella + * posix/execvpe.c (__execvpe): Remove dynamic allocation. + * posix/Makefile (tests): Add tst-execvpe{1,2,3,4,5,6}. + * posix/tst-execvp1.c (do_test): Use a macro to call execvp. + * posix/tst-execvp2.c (do_test): Likewise. + * posix/tst-execvp3.c (do_test): Likewise. + * posix/tst-execvp4.c (do_test): Likewise. + * posix/tst-execvpe1.c: New file. + * posix/tst-execvpe2.c: Likewise. + * posix/tst-execvpe3.c: Likewise. + * posix/tst-execvpe4.c: Likewise. + * posix/tst-execvpe5.c: Likewise. + * posix/tst-execvpe6.c: Likewise. + [BZ #19534] * posix/execl.c (execl): Remove dynamic memory allocation. * posix/execle.c (execle): Likewise. diff --git a/posix/Makefile b/posix/Makefile index 4e90a9540a..7ec110e80c 100644 --- a/posix/Makefile +++ b/posix/Makefile @@ -82,6 +82,8 @@ tests := tstgetopt testfnm runtests runptests \ tst-execv1 tst-execv2 tst-execl1 tst-execl2 \ tst-execve1 tst-execve2 tst-execle1 tst-execle2 \ tst-execvp3 tst-execvp4 tst-rfc3484 tst-rfc3484-2 \ + tst-execvpe1 tst-execvpe2 tst-execvpe3 tst-execvpe4 \ + tst-execvpe5 tst-execvpe6 \ tst-rfc3484-3 \ tst-getaddrinfo3 tst-fnmatch2 tst-cpucount tst-cpuset \ bug-getopt1 bug-getopt2 bug-getopt3 bug-getopt4 \ @@ -229,6 +231,7 @@ tstgetopt-ARGS = -a -b -cfoobar --required foobar --optional=bazbug \ tst-exec-ARGS = -- $(host-test-program-cmd) tst-exec-static-ARGS = $(tst-exec-ARGS) +tst-execvpe5-ARGS = -- $(host-test-program-cmd) tst-spawn-ARGS = -- $(host-test-program-cmd) tst-spawn-static-ARGS = $(tst-spawn-ARGS) tst-dir-ARGS = `pwd` `cd $(common-objdir)/$(subdir); pwd` `cd $(common-objdir); pwd` $(objpfx)tst-dir diff --git a/posix/execvpe.c b/posix/execvpe.c index 61697a74f0..d933f9c92a 100644 --- a/posix/execvpe.c +++ b/posix/execvpe.c @@ -15,7 +15,6 @@ License along with the GNU C Library; if not, see . */ -#include #include #include #include @@ -23,22 +22,43 @@ #include #include #include +#include +#include +#ifndef PATH_MAX +# ifdef MAXPATHLEN +# define PATH_MAX MAXPATHLEN +# else +# define PATH_MAX 1024 +# endif +#endif /* The file is accessible but it is not an executable file. Invoke the shell to interpret it as a script. */ static void -internal_function -scripts_argv (const char *file, char *const argv[], int argc, char **new_argv) +maybe_script_execute (const char *file, char *const argv[], char *const envp[]) { + ptrdiff_t argc = 0; + while (argv[argc++] != NULL) + { + if (argc == INT_MAX - 1) + { + errno = E2BIG; + return; + } + } + /* Construct an argument list for the shell. */ + char *new_argv[argc + 1]; new_argv[0] = (char *) _PATH_BSHELL; new_argv[1] = (char *) file; - while (argc > 1) - { - new_argv[argc] = argv[argc - 1]; - --argc; - } + if (argc > 1) + memcpy (new_argv + 2, argv + 1, argc * sizeof(char *)); + else + new_argv[2] = NULL; + + /* Execute the shell. */ + __execve (new_argv[0], new_argv, envp); } @@ -47,170 +67,111 @@ scripts_argv (const char *file, char *const argv[], int argc, char **new_argv) int __execvpe (const char *file, char *const argv[], char *const envp[]) { + /* We check the simple case first. */ if (*file == '\0') { - /* We check the simple case first. */ __set_errno (ENOENT); return -1; } + /* Don't search when it contains a slash. */ if (strchr (file, '/') != NULL) { - /* Don't search when it contains a slash. */ __execve (file, argv, envp); if (errno == ENOEXEC) - { - /* Count the arguments. */ - int argc = 0; - while (argv[argc++]) - ; - size_t len = (argc + 1) * sizeof (char *); - char **script_argv; - void *ptr = NULL; - if (__libc_use_alloca (len)) - script_argv = alloca (len); - else - script_argv = ptr = malloc (len); - - if (script_argv != NULL) - { - scripts_argv (file, argv, argc, script_argv); - __execve (script_argv[0], script_argv, envp); - - free (ptr); - } - } + maybe_script_execute (file, argv, envp); + + return -1; } - else + + const char *path = getenv ("PATH"); + if (!path) + path = CS_PATH; + /* Although GLIBC does not enforce NAME_MAX, we set it as the maximum + size to avoid unbounded stack allocation. Same applies for + PATH_MAX. */ + size_t file_len = __strnlen (file, NAME_MAX + 1); + size_t path_len = __strnlen (path, PATH_MAX - 1) + 1; + + if ((file_len > NAME_MAX) + || !__libc_alloca_cutoff (path_len + file_len + 1)) { - size_t pathlen; - size_t alloclen = 0; - char *path = getenv ("PATH"); - if (path == NULL) - { - pathlen = confstr (_CS_PATH, (char *) NULL, 0); - alloclen = pathlen + 1; - } - else - pathlen = strlen (path); + errno = ENAMETOOLONG; + return -1; + } - size_t len = strlen (file) + 1; - alloclen += pathlen + len + 1; + const char *subp; + bool got_eacces = false; + char buffer[path_len + file_len + 1]; + for (const char *p = path; ; p = subp) + { + subp = __strchrnul (p, ':'); - char *name; - char *path_malloc = NULL; - if (__libc_use_alloca (alloclen)) - name = alloca (alloclen); - else + /* PATH is larger than PATH_MAX and thus potentially larger than + the stack allocation. */ + if (subp - p >= path_len) { - path_malloc = name = malloc (alloclen); - if (name == NULL) - return -1; + /* If there is only one path, bail out. */ + if (*subp == '\0') + break; + /* Otherwise skip to next one. */ + continue; } - if (path == NULL) - { - /* There is no `PATH' in the environment. - The default search path is the current directory - followed by the path `confstr' returns for `_CS_PATH'. */ - path = name + pathlen + len + 1; - path[0] = ':'; - (void) confstr (_CS_PATH, path + 1, pathlen); - } + /* Use the current path entry, plus a '/' if nonempty, plus the file to + execute. */ + char *pend = mempcpy (buffer, p, subp - p); + *pend = '/'; + memcpy (pend + (p < subp), file, file_len + 1); + + __execve (buffer, argv, envp); - /* Copy the file name at the top. */ - name = (char *) memcpy (name + pathlen + 1, file, len); - /* And add the slash. */ - *--name = '/'; + if (errno == ENOEXEC) + /* This has O(P*C) behavior, where P is the length of the path and C + is the argument count. A better strategy would be allocate the + substitute argv and reuse it each time through the loop (so it + behaves as O(P+C) instead. */ + maybe_script_execute (buffer, argv, envp); - char **script_argv = NULL; - void *script_argv_malloc = NULL; - bool got_eacces = false; - char *p = path; - do + switch (errno) { - char *startp; - - path = p; - p = __strchrnul (path, ':'); - - if (p == path) - /* Two adjacent colons, or a colon at the beginning or the end - of `PATH' means to search the current directory. */ - startp = name + 1; - else - startp = (char *) memcpy (name - (p - path), path, p - path); - - /* Try to execute this name. If it works, execve will not return. */ - __execve (startp, argv, envp); - - if (errno == ENOEXEC) - { - if (script_argv == NULL) - { - /* Count the arguments. */ - int argc = 0; - while (argv[argc++]) - ; - size_t arglen = (argc + 1) * sizeof (char *); - if (__libc_use_alloca (alloclen + arglen)) - script_argv = alloca (arglen); - else - script_argv = script_argv_malloc = malloc (arglen); - if (script_argv == NULL) - { - /* A possible EACCES error is not as important as - the ENOMEM. */ - got_eacces = false; - break; - } - scripts_argv (startp, argv, argc, script_argv); - } - - __execve (script_argv[0], script_argv, envp); - } - - switch (errno) - { - case EACCES: - /* Record the we got a `Permission denied' error. If we end - up finding no executable we can use, we want to diagnose - that we did find one but were denied access. */ - got_eacces = true; - case ENOENT: - case ESTALE: - case ENOTDIR: - /* Those errors indicate the file is missing or not executable - by us, in which case we want to just try the next path - directory. */ - case ENODEV: - case ETIMEDOUT: - /* Some strange filesystems like AFS return even - stranger error numbers. They cannot reasonably mean - anything else so ignore those, too. */ - break; - - default: - /* Some other error means we found an executable file, but - something went wrong executing it; return the error to our - caller. */ - return -1; - } + case EACCES: + /* Record that we got a 'Permission denied' error. If we end + up finding no executable we can use, we want to diagnose + that we did find one but were denied access. */ + got_eacces = true; + case ENOENT: + case ESTALE: + case ENOTDIR: + /* Those errors indicate the file is missing or not executable + by us, in which case we want to just try the next path + directory. */ + case ENODEV: + case ETIMEDOUT: + /* Some strange filesystems like AFS return even + stranger error numbers. They cannot reasonably mean + anything else so ignore those, too. */ + break; + + default: + /* Some other error means we found an executable file, but + something went wrong executing it; return the error to our + caller. */ + return -1; } - while (*p++ != '\0'); - /* We tried every element and none of them worked. */ - if (got_eacces) - /* At least one failure was due to permissions, so report that - error. */ - __set_errno (EACCES); - - free (script_argv_malloc); - free (path_malloc); + if (*subp++ == '\0') + break; } - /* Return the error from the last attempt (probably ENOENT). */ + /* We tried every element and none of them worked. */ + if (got_eacces) + /* At least one failure was due to permissions, so report that + error. */ + __set_errno (EACCES); + return -1; } + weak_alias (__execvpe, execvpe) diff --git a/posix/tst-execvp1.c b/posix/tst-execvp1.c index ecc673d124..8b718485d0 100644 --- a/posix/tst-execvp1.c +++ b/posix/tst-execvp1.c @@ -3,6 +3,10 @@ #include #include +#ifndef EXECVP +# define EXECVP(file, argv) execvp (file, argv) +#endif + static int do_test (void) { @@ -19,7 +23,7 @@ do_test (void) char *argv[] = { (char *) "does-not-exist", NULL }; errno = 0; - execvp (argv[0], argv); + EXECVP (argv[0], argv); if (errno != ENOENT) { diff --git a/posix/tst-execvp2.c b/posix/tst-execvp2.c index 7e0f5d882c..440dfab438 100644 --- a/posix/tst-execvp2.c +++ b/posix/tst-execvp2.c @@ -14,6 +14,9 @@ static int do_test (void); #define TEST_FUNCTION do_test () #include "../test-skeleton.c" +#ifndef EXECVP +# define EXECVP(file, argv) execvp (file, argv) +#endif static char *copy; @@ -70,7 +73,7 @@ do_test (void) char *argv[] = { basename (copy), NULL }; errno = 0; - execvp (argv[0], argv); + EXECVP (argv[0], argv); if (errno != EACCES) { diff --git a/posix/tst-execvp3.c b/posix/tst-execvp3.c index 5ebc87952d..02a937c2da 100644 --- a/posix/tst-execvp3.c +++ b/posix/tst-execvp3.c @@ -12,6 +12,9 @@ static int do_test (void); #include "../test-skeleton.c" +#ifndef EXECVP +# define EXECVP(file, argv) execvp (file, argv) +#endif static char *fname; @@ -35,7 +38,7 @@ do_test (void) } char *argv[] = { fname, NULL }; - execvp (basename (fname), argv); + EXECVP (basename (fname), argv); /* If we come here, the execvp call failed. */ return 1; diff --git a/posix/tst-execvp4.c b/posix/tst-execvp4.c index 531fab227b..589a56018f 100644 --- a/posix/tst-execvp4.c +++ b/posix/tst-execvp4.c @@ -5,6 +5,10 @@ #include #include +#ifndef EXECVP +# define EXECVP(file, argv) execvp (file, argv) +#endif + static int do_test (void) { @@ -27,7 +31,7 @@ do_test (void) unsetenv ("PATH"); char *argv[] = { buf + 9, NULL }; - execvp (argv[0], argv); + EXECVP (argv[0], argv); return 0; } diff --git a/posix/tst-execvpe1.c b/posix/tst-execvpe1.c new file mode 100644 index 0000000000..622eb79bc8 --- /dev/null +++ b/posix/tst-execvpe1.c @@ -0,0 +1,20 @@ +/* Check ENOENT failure for execvpe. + Copyright (C) 2016 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#define EXECVP(file, argv) execvpe (file, argv, NULL) +#include diff --git a/posix/tst-execvpe2.c b/posix/tst-execvpe2.c new file mode 100644 index 0000000000..cdec09ae51 --- /dev/null +++ b/posix/tst-execvpe2.c @@ -0,0 +1,20 @@ +/* Check EACCES for execvpe. + Copyright (C) 2016 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#define EXECVP(file, argv) execvpe (file, argv, NULL) +#include diff --git a/posix/tst-execvpe3.c b/posix/tst-execvpe3.c new file mode 100644 index 0000000000..47cdc14e87 --- /dev/null +++ b/posix/tst-execvpe3.c @@ -0,0 +1,20 @@ +/* Check script execution without shebang for execvpe. + Copyright (C) 2016 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#define EXECVP(file, argv) execvpe (file, argv, NULL) +#include diff --git a/posix/tst-execvpe4.c b/posix/tst-execvpe4.c new file mode 100644 index 0000000000..e8883aa060 --- /dev/null +++ b/posix/tst-execvpe4.c @@ -0,0 +1,20 @@ +/* Check unexistent binary for execvpe. + Copyright (C) 2016 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#define EXECVP(file, argv) execvpe (file, argv, NULL) +#include diff --git a/posix/tst-execvpe5.c b/posix/tst-execvpe5.c new file mode 100644 index 0000000000..ffd764a639 --- /dev/null +++ b/posix/tst-execvpe5.c @@ -0,0 +1,157 @@ +/* General tests for execpve. + Copyright (C) 2016 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include + + +/* Nonzero if the program gets called via `exec'. */ +static int restart; + + +#define CMDLINE_OPTIONS \ + { "restart", no_argument, &restart, 1 }, + +/* Prototype for our test function. */ +extern void do_prepare (int argc, char *argv[]); +extern int do_test (int argc, char *argv[]); + +#include "../test-skeleton.c" + +#define EXECVPE_KEY "EXECVPE_ENV" +#define EXECVPE_VALUE "execvpe_test" + + +static int +handle_restart (void) +{ + /* First check if only one variable is passed on execvpe. */ + int env_count = 0; + for (char **e = environ; *e != NULL; ++e) + if (++env_count == INT_MAX) + { + printf ("Environment variable number overflow"); + exit (EXIT_FAILURE); + } + if (env_count != 1) + { + printf ("Wrong number of environment variables"); + exit (EXIT_FAILURE); + } + + /* Check if the combinarion os "EXECVPE_ENV=execvpe_test" */ + const char *env = getenv (EXECVPE_KEY); + if (env == NULL) + { + printf ("Test environment variable not found"); + exit (EXIT_FAILURE); + } + + if (strncmp (env, EXECVPE_VALUE, sizeof (EXECVPE_VALUE))) + { + printf ("Test environment variable with wrong value"); + exit (EXIT_FAILURE); + } + + return 0; +} + + +int +do_test (int argc, char *argv[]) +{ + pid_t pid; + int status; + + /* We must have + - one or four parameters left if called initially + + path for ld.so optional + + "--library-path" optional + + the library path optional + + the application name + */ + + if (restart) + { + if (argc != 1) + { + printf ("Wrong number of arguments (%d)\n", argc); + exit (EXIT_FAILURE); + } + + return handle_restart (); + } + + if (argc != 2 && argc != 5) + { + printf ("Wrong number of arguments (%d)\n", argc); + exit (EXIT_FAILURE); + } + + /* We want to test the `execvpe' function. To do this we restart the + program with an additional parameter. */ + pid = fork (); + if (pid == 0) + { + /* This is the child. Construct the command line. */ + + /* We cast here to char* because the test itself does not modify neither + the argument nor the environment list. */ + char *envs[] = { (char*)(EXECVPE_KEY "=" EXECVPE_VALUE), NULL }; + if (argc == 5) + { + char *args[] = { argv[1], argv[2], argv[3], argv[4], + (char *) "--direct", (char *) "--restart", NULL }; + execvpe (args[0], args, envs); + } + else + { + char *args[] = { argv[1], argv[1], + (char *) "--direct", (char *) "--restart", NULL }; + execvpe (args[0], args, envs); + } + + puts ("Cannot exec"); + exit (EXIT_FAILURE); + } + else if (pid == (pid_t) -1) + { + puts ("Cannot fork"); + return 1; + } + + /* Wait for the child. */ + if (waitpid (pid, &status, 0) != pid) + { + puts ("Wrong child"); + return 1; + } + + if (WTERMSIG (status) != 0) + { + puts ("Child terminated incorrectly"); + return 1; + } + status = WEXITSTATUS (status); + + return status; +} diff --git a/posix/tst-execvpe6.c b/posix/tst-execvpe6.c new file mode 100644 index 0000000000..bfb6ee11ac --- /dev/null +++ b/posix/tst-execvpe6.c @@ -0,0 +1,150 @@ +/* Check execvpe script argument handling. + Copyright (C) 2016 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include + +static char *fname1; +static char *fname2; +static char *logname; + +static void do_prepare (void); +#define PREPARE(argc, argv) do_prepare () +static int do_test (void); +#define TEST_FUNCTION do_test () + +#include "../test-skeleton.c" + +static void +do_prepare (void) +{ + int logfd = create_temp_file ("logfile", &logname); + close (logfd); + + int fd1 = create_temp_file ("testscript", &fname1); + dprintf (fd1, "echo foo $1 $2 $3 > %s\n", logname); + fchmod (fd1, 0700); + close (fd1); + + int fd2 = create_temp_file ("testscript", &fname2); + dprintf (fd2, "echo foo > %s\n", logname); + fchmod (fd2, 0700); + close (fd2); +} + +static int +run_script (const char *fname, char *args[]) +{ + /* We want to test the `execvpe' function. To do this we restart the + program with an additional parameter. */ + int status; + pid_t pid = fork (); + if (pid == 0) + { + execvpe (fname, args, NULL); + + puts ("Cannot exec"); + exit (EXIT_FAILURE); + } + else if (pid == (pid_t) -1) + { + puts ("Cannot fork"); + return 1; + } + + /* Wait for the child. */ + if (waitpid (pid, &status, 0) != pid) + { + puts ("Wrong child"); + return 1; + } + + if (WTERMSIG (status) != 0) + { + puts ("Child terminated incorrectly"); + return 1; + } + + return 0; +} + +static int +check_output (const char *expected) +{ + /* Check log output. */ + FILE *arq = fopen (logname, "r"); + if (arq == NULL) + { + puts ("Error opening output file"); + return 1; + } + + char line[128]; + if (fgets (line, sizeof (line), arq) == NULL) + { + puts ("Error reading output file"); + return 1; + } + fclose (arq); + + if (strcmp (line, expected) != 0) + { + puts ("Output file different than expected"); + return 1; + } + + return 0; +} + +static int +do_test (void) +{ + if (setenv ("PATH", test_dir, 1) != 0) + { + puts ("setenv failed"); + return 1; + } + + /* First check resulting script run with some arguments results in correct + output file. */ + char *args1[] = { fname1, (char*) "1", (char *) "2", (char *) "3", NULL }; + if (run_script (fname1,args1)) + return 1; + if (check_output ("foo 1 2 3\n")) + return 1; + + /* Same as before but with an expected empty argument list. */ + char *args2[] = { fname2, NULL }; + if (run_script (fname2, args2)) + return 1; + if (check_output ("foo\n")) + return 1; + + /* Same as before but with an empty argument list. */ + char *args3[] = { NULL }; + if (run_script (fname2, args3)) + return 1; + if (check_output ("foo\n")) + return 1; + + return 0; +}