/*1* SPDX-License-Identifier: ISC2*3* Copyright (c) 1996, 1998-2005, 2010-2015, 2017-20194* Todd C. Miller <[email protected]>5*6* Permission to use, copy, modify, and distribute this software for any7* purpose with or without fee is hereby granted, provided that the above8* copyright notice and this permission notice appear in all copies.9*10* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES11* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF12* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR13* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES14* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN15* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF16* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.17*18* Sponsored in part by the Defense Advanced Research Projects19* Agency (DARPA) and Air Force Research Laboratory, Air Force20* Materiel Command, USAF, under agreement number F39502-99-1-0512.21*/2223#include <config.h>2425#include <sys/stat.h>26#include <stdio.h>27#include <stdlib.h>28#include <string.h>29#include <unistd.h>30#include <errno.h>3132#include <sudoers.h>3334/*35* Check the given command against the specified allowlist (NULL-terminated).36* On success, rewrites cmnd based on the allowlist and returns true.37* On failure, returns false.38*/39static bool40cmnd_allowed(char *cmnd, size_t cmnd_size, const char *runchroot,41struct stat *cmnd_sbp, char * const *allowlist)42{43const char *cmnd_base;44char * const *al;45debug_decl(cmnd_allowed, SUDOERS_DEBUG_UTIL);4647if (!sudo_goodpath(cmnd, runchroot, cmnd_sbp))48debug_return_bool(false);4950if (allowlist == NULL)51debug_return_bool(true); /* nothing to check */5253/* We compare the base names to avoid excessive stat()ing. */54cmnd_base = sudo_basename(cmnd);5556for (al = allowlist; *al != NULL; al++) {57const char *base, *path = *al;58struct stat sb;5960base = sudo_basename(path);61if (strcmp(cmnd_base, base) != 0)62continue;6364if (sudo_goodpath(path, runchroot, &sb) &&65sb.st_dev == cmnd_sbp->st_dev && sb.st_ino == cmnd_sbp->st_ino) {66/* Overwrite cmnd with safe version from allowlist. */67if (strlcpy(cmnd, path, cmnd_size) < cmnd_size)68debug_return_bool(true);69}70}71debug_return_bool(false);72}7374/*75* This function finds the full pathname for a command and stores it76* in a statically allocated array, filling in a pointer to the array.77* Returns FOUND if the command was found, NOT_FOUND if it was not found,78* NOT_FOUND_DOT if it would have been found but it is in '.' and79* ignore_dot is set or NOT_FOUND_ERROR if an error occurred.80* The caller is responsible for freeing the output file.81*/82int83find_path(const char *infile, char **outfile, struct stat *sbp,84const char *path, const char *runchroot, bool ignore_dot,85char * const *allowlist)86{87char command[PATH_MAX];88const char *cp, *ep, *pathend;89bool found = false;90bool checkdot = false;91int len;92debug_decl(find_path, SUDOERS_DEBUG_UTIL);9394sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,95"resolving %s", infile);9697/*98* If we were given a fully qualified or relative path99* there is no need to look at $PATH.100*/101if (strchr(infile, '/') != NULL) {102if (strlcpy(command, infile, sizeof(command)) >= sizeof(command)) {103errno = ENAMETOOLONG;104debug_return_int(NOT_FOUND_ERROR);105}106found = cmnd_allowed(command, sizeof(command), runchroot, sbp,107allowlist);108goto done;109}110111if (path == NULL)112debug_return_int(NOT_FOUND);113114pathend = path + strlen(path);115for (cp = sudo_strsplit(path, pathend, ":", &ep); cp != NULL;116cp = sudo_strsplit(NULL, pathend, ":", &ep)) {117118/*119* Search current dir last if it is in PATH.120* This will miss sneaky things like using './' or './/' (XXX)121*/122if (cp == ep || (*cp == '.' && cp + 1 == ep)) {123checkdot = 1;124continue;125}126127/*128* Resolve the path and exit the loop if found.129*/130len = snprintf(command, sizeof(command), "%.*s/%s",131(int)(ep - cp), cp, infile);132if (len < 0 || len >= ssizeof(command)) {133errno = ENAMETOOLONG;134debug_return_int(NOT_FOUND_ERROR);135}136found = cmnd_allowed(command, sizeof(command), runchroot,137sbp, allowlist);138if (found)139break;140}141142/*143* Check current dir if dot was in the PATH144*/145if (!found && checkdot) {146len = snprintf(command, sizeof(command), "./%s", infile);147if (len < 0 || len >= ssizeof(command)) {148errno = ENAMETOOLONG;149debug_return_int(NOT_FOUND_ERROR);150}151found = cmnd_allowed(command, sizeof(command), runchroot,152sbp, allowlist);153if (found && ignore_dot)154debug_return_int(NOT_FOUND_DOT);155}156157done:158if (found) {159sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,160"found %s", command);161if ((*outfile = strdup(command)) == NULL)162debug_return_int(NOT_FOUND_ERROR);163debug_return_int(FOUND);164}165debug_return_int(NOT_FOUND);166}167168169