Take the chroot into account when search for the command.
This could a a user-specific chroot via the -R option, a runchroot Defaults value, or a per-command CHROOT spec in the sudoers rule.
This commit is contained in:
@@ -84,12 +84,24 @@ command_args_match(const char *sudoers_cmnd, const char *sudoers_args)
|
||||
* Returns true on success, else false.
|
||||
*/
|
||||
static bool
|
||||
do_stat(int fd, const char *path, struct stat *sb)
|
||||
do_stat(int fd, const char *path, const char *runchroot, struct stat *sb)
|
||||
{
|
||||
char pathbuf[PATH_MAX];
|
||||
debug_decl(do_stat, SUDOERS_DEBUG_MATCH);
|
||||
|
||||
if (fd != -1)
|
||||
debug_return_bool(fstat(fd, sb) == 0);
|
||||
|
||||
/* Make path relative to the new root, if any. */
|
||||
if (runchroot != NULL) {
|
||||
const int len =
|
||||
snprintf(pathbuf, sizeof(pathbuf), "%s%s", runchroot, path);
|
||||
if (len >= ssizeof(pathbuf)) {
|
||||
errno = ENAMETOOLONG;
|
||||
debug_return_bool(false);
|
||||
}
|
||||
path = pathbuf;
|
||||
}
|
||||
debug_return_bool(stat(path, sb) == 0);
|
||||
}
|
||||
|
||||
@@ -119,15 +131,28 @@ is_script(int fd)
|
||||
* Returns false on error, else true.
|
||||
*/
|
||||
static bool
|
||||
open_cmnd(const char *path, const struct command_digest_list *digests, int *fdp)
|
||||
open_cmnd(const char *path, const char *runchroot,
|
||||
const struct command_digest_list *digests, int *fdp)
|
||||
{
|
||||
int fd = -1;
|
||||
char pathbuf[PATH_MAX];
|
||||
debug_decl(open_cmnd, SUDOERS_DEBUG_MATCH);
|
||||
|
||||
/* Only open the file for fdexec or for digest matching. */
|
||||
if (def_fdexec != always && TAILQ_EMPTY(digests))
|
||||
debug_return_bool(true);
|
||||
|
||||
/* Make path relative to the new root, if any. */
|
||||
if (runchroot != NULL) {
|
||||
const int len =
|
||||
snprintf(pathbuf, sizeof(pathbuf), "%s%s", runchroot, path);
|
||||
if (len >= ssizeof(pathbuf)) {
|
||||
errno = ENAMETOOLONG;
|
||||
debug_return_bool(false);
|
||||
}
|
||||
path = pathbuf;
|
||||
}
|
||||
|
||||
fd = open(path, O_RDONLY|O_NONBLOCK);
|
||||
# ifdef O_EXEC
|
||||
if (fd == -1 && errno == EACCES && TAILQ_EMPTY(digests)) {
|
||||
@@ -189,16 +214,29 @@ set_cmnd_fd(int fd)
|
||||
* Return true if user_cmnd names one of the inodes in dir, else false.
|
||||
*/
|
||||
static bool
|
||||
command_matches_dir(const char *sudoers_dir, size_t dlen,
|
||||
command_matches_dir(const char *sudoers_dir, size_t dlen, const char *runchroot,
|
||||
const struct command_digest_list *digests)
|
||||
{
|
||||
char buf[PATH_MAX], sdbuf[PATH_MAX];
|
||||
struct stat sudoers_stat;
|
||||
struct dirent *dent;
|
||||
char buf[PATH_MAX];
|
||||
size_t chrootlen = 0;
|
||||
int fd = -1;
|
||||
DIR *dirp;
|
||||
debug_decl(command_matches_dir, SUDOERS_DEBUG_MATCH);
|
||||
|
||||
/* Make sudoers_dir relative to the new root, if any. */
|
||||
if (runchroot != NULL) {
|
||||
const int len =
|
||||
snprintf(sdbuf, sizeof(sdbuf), "%s%s", runchroot, sudoers_dir);
|
||||
if (len >= ssizeof(sdbuf)) {
|
||||
errno = ENAMETOOLONG;
|
||||
debug_return_bool(false);
|
||||
}
|
||||
sudoers_dir = sdbuf;
|
||||
chrootlen = strlen(runchroot);
|
||||
}
|
||||
|
||||
/*
|
||||
* Grot through directory entries, looking for user_base.
|
||||
*/
|
||||
@@ -226,9 +264,9 @@ command_matches_dir(const char *sudoers_dir, size_t dlen,
|
||||
continue;
|
||||
|
||||
/* Open the file for fdexec or for digest matching. */
|
||||
if (!open_cmnd(buf, digests, &fd))
|
||||
if (!open_cmnd(buf, NULL, digests, &fd))
|
||||
continue;
|
||||
if (!do_stat(fd, buf, &sudoers_stat))
|
||||
if (!do_stat(fd, buf, NULL, &sudoers_stat))
|
||||
continue;
|
||||
|
||||
if (user_stat == NULL ||
|
||||
@@ -237,7 +275,7 @@ command_matches_dir(const char *sudoers_dir, size_t dlen,
|
||||
if (!digest_matches(fd, buf, digests))
|
||||
continue;
|
||||
free(safe_cmnd);
|
||||
if ((safe_cmnd = strdup(buf)) == NULL) {
|
||||
if ((safe_cmnd = strdup(buf + chrootlen)) == NULL) {
|
||||
sudo_warnx(U_("%s: %s"), __func__,
|
||||
U_("unable to allocate memory"));
|
||||
dent = NULL;
|
||||
@@ -257,7 +295,8 @@ command_matches_dir(const char *sudoers_dir, size_t dlen,
|
||||
}
|
||||
|
||||
static bool
|
||||
command_matches_all(const struct command_digest_list *digests)
|
||||
command_matches_all(const char *runchroot,
|
||||
const struct command_digest_list *digests)
|
||||
{
|
||||
struct stat sb; /* XXX - unused */
|
||||
int fd = -1;
|
||||
@@ -265,9 +304,9 @@ command_matches_all(const struct command_digest_list *digests)
|
||||
|
||||
if (user_cmnd[0] == '/') {
|
||||
/* Open the file for fdexec or for digest matching. */
|
||||
if (!open_cmnd(user_cmnd, digests, &fd))
|
||||
if (!open_cmnd(user_cmnd, runchroot, digests, &fd))
|
||||
goto bad;
|
||||
if (!do_stat(fd, user_cmnd, &sb))
|
||||
if (!do_stat(fd, user_cmnd, runchroot, &sb))
|
||||
goto bad;
|
||||
}
|
||||
|
||||
@@ -288,7 +327,7 @@ bad:
|
||||
|
||||
static bool
|
||||
command_matches_fnmatch(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
const struct command_digest_list *digests)
|
||||
const char *runchroot, const struct command_digest_list *digests)
|
||||
{
|
||||
struct stat sb; /* XXX - unused */
|
||||
int fd = -1;
|
||||
@@ -299,15 +338,17 @@ command_matches_fnmatch(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
* a) there are no args in sudoers OR
|
||||
* b) there are no args on command line and none required by sudoers OR
|
||||
* c) there are args in sudoers and on command line and they match
|
||||
* else return false.
|
||||
* else return false.
|
||||
*
|
||||
* Neither sudoers_cmnd nor user_cmnd are relative to runchroot.
|
||||
*/
|
||||
if (fnmatch(sudoers_cmnd, user_cmnd, FNM_PATHNAME) != 0)
|
||||
debug_return_bool(false);
|
||||
if (command_args_match(sudoers_cmnd, sudoers_args)) {
|
||||
/* Open the file for fdexec or for digest matching. */
|
||||
if (!open_cmnd(user_cmnd, digests, &fd))
|
||||
if (!open_cmnd(user_cmnd, runchroot, digests, &fd))
|
||||
goto bad;
|
||||
if (!do_stat(fd, user_cmnd, &sb))
|
||||
if (!do_stat(fd, user_cmnd, runchroot, &sb))
|
||||
goto bad;
|
||||
/* Check digest of user_cmnd since sudoers_cmnd is a pattern. */
|
||||
if (!digest_matches(fd, user_cmnd, digests))
|
||||
@@ -328,13 +369,14 @@ bad:
|
||||
|
||||
static bool
|
||||
command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
const struct command_digest_list *digests)
|
||||
const char *runchroot, const struct command_digest_list *digests)
|
||||
{
|
||||
struct stat sudoers_stat;
|
||||
bool bad_digest = false;
|
||||
char **ap, *base, *cp;
|
||||
char pathbuf[PATH_MAX];
|
||||
int fd = -1;
|
||||
size_t dlen;
|
||||
size_t dlen, chrootlen = 0;
|
||||
glob_t gl;
|
||||
debug_decl(command_matches_glob, SUDOERS_DEBUG_MATCH);
|
||||
|
||||
@@ -351,6 +393,19 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
debug_return_bool(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sudoers_cmnd relative to the new root, if any. */
|
||||
if (runchroot != NULL) {
|
||||
const int len =
|
||||
snprintf(pathbuf, sizeof(pathbuf), "%s%s", runchroot, sudoers_cmnd);
|
||||
if (len >= ssizeof(pathbuf)) {
|
||||
errno = ENAMETOOLONG;
|
||||
debug_return_bool(false);
|
||||
}
|
||||
sudoers_cmnd = pathbuf;
|
||||
chrootlen = strlen(runchroot);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if we find a match in the glob(3) results AND
|
||||
* a) there are no args in sudoers OR
|
||||
@@ -369,12 +424,15 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
/* Remove the runchroot, if any. */
|
||||
cp += chrootlen;
|
||||
|
||||
if (strcmp(cp, user_cmnd) != 0)
|
||||
continue;
|
||||
/* Open the file for fdexec or for digest matching. */
|
||||
if (!open_cmnd(cp, digests, &fd))
|
||||
if (!open_cmnd(cp, runchroot, digests, &fd))
|
||||
continue;
|
||||
if (!do_stat(fd, cp, &sudoers_stat))
|
||||
if (!do_stat(fd, cp, runchroot, &sudoers_stat))
|
||||
continue;
|
||||
if (user_stat == NULL ||
|
||||
(user_stat->st_dev == sudoers_stat.st_dev &&
|
||||
@@ -405,10 +463,13 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
/* Remove the runchroot, if any. */
|
||||
cp += chrootlen;
|
||||
|
||||
/* If it ends in '/' it is a directory spec. */
|
||||
dlen = strlen(cp);
|
||||
if (cp[dlen - 1] == '/') {
|
||||
if (command_matches_dir(cp, dlen, digests))
|
||||
if (command_matches_dir(cp, dlen, runchroot, digests))
|
||||
debug_return_bool(true);
|
||||
continue;
|
||||
}
|
||||
@@ -422,9 +483,9 @@ command_matches_glob(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
continue;
|
||||
|
||||
/* Open the file for fdexec or for digest matching. */
|
||||
if (!open_cmnd(cp, digests, &fd))
|
||||
if (!open_cmnd(cp, runchroot, digests, &fd))
|
||||
continue;
|
||||
if (!do_stat(fd, cp, &sudoers_stat))
|
||||
if (!do_stat(fd, cp, runchroot, &sudoers_stat))
|
||||
continue;
|
||||
if (user_stat == NULL ||
|
||||
(user_stat->st_dev == sudoers_stat.st_dev &&
|
||||
@@ -456,7 +517,8 @@ done:
|
||||
}
|
||||
|
||||
static bool
|
||||
command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const struct command_digest_list *digests)
|
||||
command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
const char *runchroot, const struct command_digest_list *digests)
|
||||
{
|
||||
struct stat sudoers_stat;
|
||||
const char *base;
|
||||
@@ -467,7 +529,7 @@ command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const
|
||||
/* If it ends in '/' it is a directory spec. */
|
||||
dlen = strlen(sudoers_cmnd);
|
||||
if (sudoers_cmnd[dlen - 1] == '/')
|
||||
debug_return_bool(command_matches_dir(sudoers_cmnd, dlen, digests));
|
||||
debug_return_bool(command_matches_dir(sudoers_cmnd, dlen, runchroot, digests));
|
||||
|
||||
/* Only proceed if user_base and basename(sudoers_cmnd) match */
|
||||
if ((base = strrchr(sudoers_cmnd, '/')) == NULL)
|
||||
@@ -478,7 +540,7 @@ command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const
|
||||
debug_return_bool(false);
|
||||
|
||||
/* Open the file for fdexec or for digest matching. */
|
||||
if (!open_cmnd(sudoers_cmnd, digests, &fd))
|
||||
if (!open_cmnd(sudoers_cmnd, runchroot, digests, &fd))
|
||||
goto bad;
|
||||
|
||||
/*
|
||||
@@ -488,7 +550,7 @@ command_matches_normal(const char *sudoers_cmnd, const char *sudoers_args, const
|
||||
* c) there are args in sudoers and on command line and they match
|
||||
* d) there is a digest and it matches
|
||||
*/
|
||||
if (user_stat != NULL && do_stat(fd, sudoers_cmnd, &sudoers_stat)) {
|
||||
if (user_stat != NULL && do_stat(fd, sudoers_cmnd, runchroot, &sudoers_stat)) {
|
||||
if (user_stat->st_dev != sudoers_stat.st_dev ||
|
||||
user_stat->st_ino != sudoers_stat.st_ino)
|
||||
goto bad;
|
||||
@@ -521,13 +583,37 @@ bad:
|
||||
* otherwise, return true if user_cmnd names one of the inodes in path.
|
||||
*/
|
||||
bool
|
||||
command_matches(const char *sudoers_cmnd, const char *sudoers_args, const struct command_digest_list *digests)
|
||||
command_matches(const char *sudoers_cmnd, const char *sudoers_args,
|
||||
const char *runchroot, const struct command_digest_list *digests)
|
||||
{
|
||||
char *saved_user_cmnd = NULL;
|
||||
struct stat saved_user_stat;
|
||||
bool rc = false;
|
||||
debug_decl(command_matches, SUDOERS_DEBUG_MATCH);
|
||||
|
||||
if (user_runchroot != NULL) {
|
||||
if (runchroot != NULL && strcmp(runchroot, "*") != 0 &&
|
||||
strcmp(runchroot, user_runchroot) != 0) {
|
||||
/* CHROOT mismatch */
|
||||
goto done;
|
||||
}
|
||||
/* User-specified runchroot (user_stat already set appropriately). */
|
||||
runchroot = user_runchroot;
|
||||
} else if (runchroot == NULL) {
|
||||
/* No rule-specific runchroot, use global (user_stat already set). */
|
||||
if (def_runchroot != NULL && strcmp(def_runchroot, "*") != '\0')
|
||||
runchroot = def_runchroot;
|
||||
} else {
|
||||
/* Rule-specific runchroot, reset user_cmnd and user_stat. */
|
||||
saved_user_cmnd = user_cmnd;
|
||||
if (user_stat != NULL)
|
||||
saved_user_stat = *user_stat;
|
||||
if (set_cmnd_path(runchroot) != FOUND)
|
||||
saved_user_cmnd = NULL;
|
||||
}
|
||||
|
||||
if (sudoers_cmnd == NULL) {
|
||||
rc = command_matches_all(digests);
|
||||
rc = command_matches_all(runchroot, digests);
|
||||
goto done;
|
||||
}
|
||||
|
||||
@@ -554,17 +640,24 @@ command_matches(const char *sudoers_cmnd, const char *sudoers_args, const struct
|
||||
* use glob(3) and/or fnmatch(3) to do the matching.
|
||||
*/
|
||||
if (def_fast_glob)
|
||||
rc = command_matches_fnmatch(sudoers_cmnd, sudoers_args, digests);
|
||||
rc = command_matches_fnmatch(sudoers_cmnd, sudoers_args, runchroot, digests);
|
||||
else
|
||||
rc = command_matches_glob(sudoers_cmnd, sudoers_args, digests);
|
||||
rc = command_matches_glob(sudoers_cmnd, sudoers_args, runchroot, digests);
|
||||
} else {
|
||||
rc = command_matches_normal(sudoers_cmnd, sudoers_args, digests);
|
||||
rc = command_matches_normal(sudoers_cmnd, sudoers_args, runchroot, digests);
|
||||
}
|
||||
done:
|
||||
if (saved_user_cmnd != NULL) {
|
||||
free(user_cmnd);
|
||||
user_cmnd = saved_user_cmnd;
|
||||
if (user_stat != NULL)
|
||||
*user_stat = saved_user_stat;
|
||||
}
|
||||
sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
|
||||
"user command \"%s%s%s\" matches sudoers command \"%s%s%s\": %s",
|
||||
"user command \"%s%s%s\" matches sudoers command \"%s%s%s\"%s%s: %s",
|
||||
user_cmnd, user_args ? " " : "", user_args ? user_args : "",
|
||||
sudoers_cmnd, sudoers_args ? " " : "", sudoers_args ? sudoers_args : "",
|
||||
runchroot ? ", chroot " : "", runchroot ? runchroot : "",
|
||||
rc ? "true" : "false");
|
||||
debug_return_bool(rc);
|
||||
}
|
||||
|
Reference in New Issue
Block a user