diff --git a/lib/libc/gen/exec.c b/lib/libc/gen/exec.c index 926c50d64852..12020a79f6b4 100644 --- a/lib/libc/gen/exec.c +++ b/lib/libc/gen/exec.c @@ -1,302 +1,369 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "namespace.h" #include #include +#include #include #include +#include #include #include #include #include #include #include "un-namespace.h" #include "libc_private.h" static const char execvPe_err_preamble[] = "execvP: "; static const char execvPe_err_trailer[] = ": path too long\n"; int execl(const char *name, const char *arg, ...) { va_list ap; const char **argv; int n; va_start(ap, arg); n = 1; while (va_arg(ap, char *) != NULL) n++; va_end(ap); argv = alloca((n + 1) * sizeof(*argv)); if (argv == NULL) { errno = ENOMEM; return (-1); } va_start(ap, arg); n = 1; argv[0] = arg; while ((argv[n] = va_arg(ap, char *)) != NULL) n++; va_end(ap); return (_execve(name, __DECONST(char **, argv), environ)); } int execle(const char *name, const char *arg, ...) { va_list ap; const char **argv; char **envp; int n; va_start(ap, arg); n = 1; while (va_arg(ap, char *) != NULL) n++; va_end(ap); argv = alloca((n + 1) * sizeof(*argv)); if (argv == NULL) { errno = ENOMEM; return (-1); } va_start(ap, arg); n = 1; argv[0] = arg; while ((argv[n] = va_arg(ap, char *)) != NULL) n++; envp = va_arg(ap, char **); va_end(ap); return (_execve(name, __DECONST(char **, argv), envp)); } int execlp(const char *name, const char *arg, ...) { va_list ap; const char **argv; int n; va_start(ap, arg); n = 1; while (va_arg(ap, char *) != NULL) n++; va_end(ap); argv = alloca((n + 1) * sizeof(*argv)); if (argv == NULL) { errno = ENOMEM; return (-1); } va_start(ap, arg); n = 1; argv[0] = arg; while ((argv[n] = va_arg(ap, char *)) != NULL) n++; va_end(ap); return (execvp(name, __DECONST(char **, argv))); } int execv(const char *name, char * const *argv) { (void)_execve(name, argv, environ); return (-1); } int execvp(const char *name, char * const *argv) { return (__libc_execvpe(name, argv, environ)); } +/* + * Returns 0 if we don't consider this a terminal condition, -1 if we do. + */ +static int +execvPe_prog(const char *path, char * const *argv, char * const *envp) +{ + struct stat sb; + const char **memp; + size_t cnt; + int save_errno; + + (void)_execve(path, argv, envp); + /* Grouped roughly by never terminal vs. usually terminal conditions */ + switch (errno) { + case ELOOP: + case ENAMETOOLONG: + case ENOENT: + case ENOTDIR: + /* Non-terminal: property of the path we're trying */ + break; + case ENOEXEC: + /* + * Failures here are considered terminal because we must handle + * this via the ENOEXEC fallback path; doing any further + * searching would be categorically incorrect. + */ + + for (cnt = 0; argv[cnt] != NULL; ++cnt) + ; + + /* + * cnt may be 0 above; always allocate at least + * 3 entries so that we can at least fit "sh", path, and + * the NULL terminator. We can rely on cnt to take into + * account the NULL terminator in all other scenarios, + * as we drop argv[0]. + */ + memp = alloca(MAX(3, cnt + 2) * sizeof(char *)); + assert(memp != NULL); + if (cnt > 0) { + memp[0] = argv[0]; + memp[1] = path; + memcpy(&memp[2], &argv[1], cnt * sizeof(char *)); + } else { + memp[0] = "sh"; + memp[1] = path; + memp[2] = NULL; + } + + (void)_execve(_PATH_BSHELL, __DECONST(char **, memp), envp); + return (-1); + case ENOMEM: + case E2BIG: + /* Terminal: persistent condition */ + return (-1); + case ETXTBSY: + /* + * Terminal: we used to retry here, but sh(1) doesn't. + */ + return (-1); + default: + /* + * EACCES may be for an inaccessible directory or + * a non-executable file. Call stat() to decide + * which. This also handles ambiguities for EFAULT + * and EIO, and undocumented errors like ESTALE. + * We hope that the race for a stat() is unimportant. + */ + save_errno = errno; + if (stat(path, &sb) == -1) { + /* + * We force errno to ENOENT here to disambiguate the + * EACCESS case; the results of execve(2) are somewhat + * inconclusive because either the file did not exist or + * we just don't have search permissions, but the caller + * only really wants to see EACCES if the file did exist + * but was not accessible. + */ + if (save_errno == EACCES) + errno = ENOENT; + break; + } + + errno = save_errno; + + /* + * Non-terminal: the file did exist and we just didn't have + * access to it, so we surface the EACCES and let the search + * continue for a candidate that we do have access to. + */ + if (errno == EACCES) + break; + + /* + * All other errors here are terminal, as prescribed by exec(3). + */ + return (-1); + } + + return (0); +} + static int execvPe(const char *name, const char *path, char * const *argv, char * const *envp) { - const char **memp; - size_t cnt, lp, ln; - int eacces, save_errno; char buf[MAXPATHLEN]; - const char *bp, *np, *op, *p; - struct stat sb; + size_t ln, lp; + const char *np, *op, *p; + bool eacces; - eacces = 0; + eacces = false; /* If it's an absolute or relative path name, it's easy. */ - if (strchr(name, '/')) { - bp = name; - op = NULL; - goto retry; + if (strchr(name, '/') != NULL) { + /* + * We ignore non-terminal conditions because we don't have any + * further paths to try -- we can just bubble up the errno from + * execve(2) here. + */ + (void)execvPe_prog(name, argv, envp); + return (-1); } - bp = buf; /* If it's an empty path name, fail in the usual POSIX way. */ if (*name == '\0') { errno = ENOENT; return (-1); } op = path; ln = strlen(name); while (op != NULL) { np = strchrnul(op, ':'); /* * It's a SHELL path -- double, leading and trailing colons * mean the current directory. */ if (np == op) { /* Empty component. */ p = "."; lp = 1; } else { /* Non-empty component. */ p = op; lp = np - op; } /* Advance to the next component or terminate after this. */ if (*np == '\0') op = NULL; else op = np + 1; /* - * If the path is too long complain. This is a possible - * security issue; given a way to make the path too long - * the user may execute the wrong program. + * If the path is too long, then complain. This is a possible + * security issue: given a way to make the path too long, the + * user may execute the wrong program. + * + * Remember to exercise caution here with assembling our final + * buf and any output, as we may be running in a vfork() context + * via posix_spawnp(). */ if (lp + ln + 2 > sizeof(buf)) { (void)_write(STDERR_FILENO, execvPe_err_preamble, sizeof(execvPe_err_preamble) - 1); (void)_write(STDERR_FILENO, p, lp); (void)_write(STDERR_FILENO, execvPe_err_trailer, sizeof(execvPe_err_trailer) - 1); + continue; } - bcopy(p, buf, lp); + + memcpy(&buf[0], p, lp); buf[lp] = '/'; - bcopy(name, buf + lp + 1, ln); + memcpy(&buf[lp + 1], name, ln); buf[lp + ln + 1] = '\0'; -retry: (void)_execve(bp, argv, envp); - switch (errno) { - case E2BIG: - goto done; - case ELOOP: - case ENAMETOOLONG: - case ENOENT: - break; - case ENOEXEC: - for (cnt = 0; argv[cnt]; ++cnt) - ; - - /* - * cnt may be 0 above; always allocate at least - * 3 entries so that we can at least fit "sh", bp, and - * the NULL terminator. We can rely on cnt to take into - * account the NULL terminator in all other scenarios, - * as we drop argv[0]. - */ - memp = alloca(MAX(3, cnt + 2) * sizeof(char *)); - if (memp == NULL) { - /* errno = ENOMEM; XXX override ENOEXEC? */ - goto done; - } - if (cnt > 0) { - memp[0] = argv[0]; - memp[1] = bp; - bcopy(argv + 1, memp + 2, cnt * sizeof(char *)); - } else { - memp[0] = "sh"; - memp[1] = bp; - memp[2] = NULL; - } - (void)_execve(_PATH_BSHELL, - __DECONST(char **, memp), envp); - goto done; - case ENOMEM: - goto done; - case ENOTDIR: - break; - case ETXTBSY: - /* - * We used to retry here, but sh(1) doesn't. - */ - goto done; - default: - /* - * EACCES may be for an inaccessible directory or - * a non-executable file. Call stat() to decide - * which. This also handles ambiguities for EFAULT - * and EIO, and undocumented errors like ESTALE. - * We hope that the race for a stat() is unimportant. - */ - save_errno = errno; - if (stat(bp, &sb) != 0) - break; - if (save_errno == EACCES) { - eacces = 1; - continue; - } - errno = save_errno; - goto done; - } + /* + * For terminal conditions we can just return immediately. If + * it was non-terminal, we just need to note if we had an + * EACCES -- execvPe_prog would do a stat(2) and leave us with + * an errno of EACCES only if the file did exist; otherwise it + * would coerce it to an ENOENT because we may not know if a + * file actually existed there or not. + */ + if (execvPe_prog(buf, argv, envp) == -1) + return (-1); + if (errno == EACCES) + eacces = true; } + + /* + * We don't often preserve errors encountering during the PATH search, + * so we override it here. ENOENT would be misleading if we found a + * candidate but couldn't access it, but most of the other conditions + * are either terminal or indicate that nothing was there. + */ if (eacces) errno = EACCES; else errno = ENOENT; -done: + return (-1); } int execvP(const char *name, const char *path, char * const argv[]) { return execvPe(name, path, argv, environ); } int __libc_execvpe(const char *name, char * const argv[], char * const envp[]) { const char *path; /* Get the path we're searching. */ if ((path = getenv("PATH")) == NULL) path = _PATH_DEFPATH; return (execvPe(name, path, argv, envp)); } __weak_reference(__libc_execvpe, execvpe); diff --git a/lib/libc/tests/gen/posix_spawn_test.c b/lib/libc/tests/gen/posix_spawn_test.c index 77c3b5a569d9..22133cf1d59a 100644 --- a/lib/libc/tests/gen/posix_spawn_test.c +++ b/lib/libc/tests/gen/posix_spawn_test.c @@ -1,136 +1,186 @@ /*- * Copyright (c) 2011 Jilles Tjoelker * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Test program for posix_spawn() and posix_spawnp() as specified by * IEEE Std. 1003.1-2008. */ +#include +#include #include #include +#include #include #include #include #include #include +static const char true_script[] = + "#!/usr/bin/env\n" + "/usr/bin/true\n"; + char *myenv[2] = { "answer=42", NULL }; ATF_TC_WITHOUT_HEAD(posix_spawn_simple_test); ATF_TC_BODY(posix_spawn_simple_test, tc) { char *myargs[4]; int error, status; pid_t pid, waitres; /* Make sure we have no child processes. */ while (waitpid(-1, NULL, 0) != -1) ; ATF_REQUIRE_MSG(errno == ECHILD, "errno was not ECHILD: %d", errno); /* Simple test. */ myargs[0] = "sh"; myargs[1] = "-c"; myargs[2] = "exit $answer"; myargs[3] = NULL; error = posix_spawnp(&pid, myargs[0], NULL, NULL, myargs, myenv); ATF_REQUIRE(error == 0); waitres = waitpid(pid, &status, 0); ATF_REQUIRE(waitres == pid); ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 42); } ATF_TC_WITHOUT_HEAD(posix_spawn_no_such_command_negative_test); ATF_TC_BODY(posix_spawn_no_such_command_negative_test, tc) { char *myargs[4]; int error, status; pid_t pid, waitres; /* * If the executable does not exist, the function shall either fail * and not create a child process or succeed and create a child * process that exits with status 127. */ myargs[0] = "/var/empty/nonexistent"; myargs[1] = NULL; error = posix_spawn(&pid, myargs[0], NULL, NULL, myargs, myenv); if (error == 0) { waitres = waitpid(pid, &status, 0); ATF_REQUIRE(waitres == pid); ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 127); } else { ATF_REQUIRE(error == ENOENT); waitres = waitpid(-1, NULL, 0); ATF_REQUIRE(waitres == -1 && errno == ECHILD); } } ATF_TC_WITHOUT_HEAD(posix_spawnp_enoexec_fallback); ATF_TC_BODY(posix_spawnp_enoexec_fallback, tc) { char buf[FILENAME_MAX]; char *myargs[2]; int error, status; pid_t pid, waitres; snprintf(buf, sizeof(buf), "%s/spawnp_enoexec.sh", atf_tc_get_config_var(tc, "srcdir")); myargs[0] = buf; myargs[1] = NULL; error = posix_spawnp(&pid, myargs[0], NULL, NULL, myargs, myenv); ATF_REQUIRE(error == 0); waitres = waitpid(pid, &status, 0); ATF_REQUIRE(waitres == pid); ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 42); } ATF_TC_WITHOUT_HEAD(posix_spawnp_enoexec_fallback_null_argv0); ATF_TC_BODY(posix_spawnp_enoexec_fallback_null_argv0, tc) { char buf[FILENAME_MAX]; char *myargs[1]; int error; pid_t pid; snprintf(buf, sizeof(buf), "%s/spawnp_enoexec.sh", atf_tc_get_config_var(tc, "srcdir")); myargs[0] = NULL; error = posix_spawnp(&pid, buf, NULL, NULL, myargs, myenv); ATF_REQUIRE(error == EINVAL); } +ATF_TC(posix_spawnp_eacces); +ATF_TC_HEAD(posix_spawnp_eacces, tc) +{ + atf_tc_set_md_var(tc, "descr", "Verify EACCES behavior in posix_spawnp"); + atf_tc_set_md_var(tc, "require.user", "unprivileged"); +} +ATF_TC_BODY(posix_spawnp_eacces, tc) +{ + const struct spawnp_eacces_tc { + const char *pathvar; + int error_expected; + } spawnp_eacces_tests[] = { + { ".", EACCES }, /* File exists, but not +x */ + { "unsearchable", ENOENT }, /* File exists, dir not +x */ + }; + char *myargs[2] = { "eacces", NULL }; + int error; + + error = mkdir("unsearchable", 0755); + ATF_REQUIRE(error == 0); + error = symlink("/usr/bin/true", "unsearchable/eacces"); + ATF_REQUIRE(error == 0); + + (void)chmod("unsearchable", 0444); + + /* this will create a non-executable file */ + atf_utils_create_file("eacces", true_script); + + for (size_t i = 0; i < nitems(spawnp_eacces_tests); i++) { + const struct spawnp_eacces_tc *tc = &spawnp_eacces_tests[i]; + pid_t pid; + + error = setenv("PATH", tc->pathvar, 1); + ATF_REQUIRE_EQ(0, error); + + error = posix_spawnp(&pid, myargs[0], NULL, NULL, myargs, + myenv); + ATF_CHECK_INTEQ_MSG(tc->error_expected, error, + "path '%s'", tc->pathvar); + } +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, posix_spawn_simple_test); ATF_TP_ADD_TC(tp, posix_spawn_no_such_command_negative_test); ATF_TP_ADD_TC(tp, posix_spawnp_enoexec_fallback); ATF_TP_ADD_TC(tp, posix_spawnp_enoexec_fallback_null_argv0); + ATF_TP_ADD_TC(tp, posix_spawnp_eacces); return (atf_no_error()); }