diff options
author | Samuel Thibault <samuel.thibault@ens-lyon.org> | 2018-12-27 17:58:54 +0000 |
---|---|---|
committer | Samuel Thibault <samuel.thibault@ens-lyon.org> | 2018-12-27 17:58:54 +0000 |
commit | 1ba06384fb715fb7732b20911ffc56a61d16cb0d (patch) | |
tree | 0fcfaf14f40169b289044495b1984a5fa3e0e965 /posix/execvpe.c | |
parent | 1af57d5793424a5a6ec542d0a5ef0b7c4f312623 (diff) | |
parent | 963c37d5c0eb62b38f8764b23931c0dcdd497a13 (diff) |
Merge commit 'refs/top-bases/t/sysvshm' into t/sysvshm
Diffstat (limited to 'posix/execvpe.c')
-rw-r--r-- | posix/execvpe.c | 293 |
1 files changed, 138 insertions, 155 deletions
diff --git a/posix/execvpe.c b/posix/execvpe.c index 61697a74f0..ea67d19fcd 100644 --- a/posix/execvpe.c +++ b/posix/execvpe.c @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2016 Free Software Foundation, Inc. +/* Copyright (C) 1991-2018 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 @@ -15,7 +15,6 @@ License along with the GNU C Library; if not, see <http://www.gnu.org/licenses/>. */ -#include <alloca.h> #include <unistd.h> #include <stdarg.h> #include <stdbool.h> @@ -23,194 +22,178 @@ #include <string.h> #include <errno.h> #include <paths.h> +#include <confstr.h> +#include <sys/param.h> +#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[]) { - /* Construct an argument list for the shell. */ - new_argv[0] = (char *) _PATH_BSHELL; - new_argv[1] = (char *) file; - while (argc > 1) + ptrdiff_t argc; + for (argc = 0; argv[argc] != NULL; argc++) { - new_argv[argc] = argv[argc - 1]; - --argc; + if (argc == INT_MAX - 1) + { + errno = E2BIG; + return; + } } -} + /* Construct an argument list for the shell based on original arguments: + 1. Empty list (argv = { NULL }, argc = 1 }: new argv will contain 3 + arguments - default shell, script to execute, and ending NULL. + 2. Non empty argument list (argc = { ..., NULL }, argc > 1}: new argv + will contain also the default shell and the script to execute. It + will also skip the script name in arguments and only copy script + arguments. */ + char *new_argv[argc > 1 ? 2 + argc : 3]; + new_argv[0] = (char *) _PATH_BSHELL; + new_argv[1] = (char *) file; + if (argc > 1) + memcpy (new_argv + 2, argv + 1, argc * sizeof(char *)); + else + new_argv[2] = NULL; -/* Execute FILE, searching in the `PATH' environment variable if it contains - no slashes, with arguments ARGV and environment from ENVP. */ -int -__execvpe (const char *file, char *const argv[], char *const envp[]) + /* Execute the shell. */ + __execve (new_argv[0], new_argv, envp); +} + +static int +__execvpe_common (const char *file, char *const argv[], char *const envp[], + bool exec_script) { + /* 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); - } - } + if (errno == ENOEXEC && exec_script) + 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; + + /* NAME_MAX does not include the terminating null character. */ + if ((file_len - 1 > 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; + /* The resulting string maximum size would be potentially a entry + in PATH plus '/' (path_len + 1) and then the the resulting file name + plus '\0' (file_len since it already accounts for the '\0'). */ + 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); - /* Copy the file name at the top. */ - name = (char *) memcpy (name + pathlen + 1, file, len); - /* And add the slash. */ - *--name = '/'; + __execve (buffer, argv, envp); - char **script_argv = NULL; - void *script_argv_malloc = NULL; - bool got_eacces = false; - char *p = path; - do + if (errno == ENOEXEC && exec_script) + /* 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); + + 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; } + +/* Execute FILE, searching in the `PATH' environment variable if it contains + no slashes, with arguments ARGV and environment from ENVP. */ +int +__execvpe (const char *file, char *const argv[], char *const envp[]) +{ + return __execvpe_common (file, argv, envp, true); +} weak_alias (__execvpe, execvpe) + +/* Same as __EXECVPE, but does not try to execute NOEXEC files. */ +int +__execvpex (const char *file, char *const argv[], char *const envp[]) +{ + return __execvpe_common (file, argv, envp, false); +} |