mirror of
https://github.com/sudo-project/sudo.git
synced 2025-08-22 01:49:11 +00:00
Revert pivot_root and go back to prepending the new root directory.
We cannot perform passwd/group lookups _after_ changing the root directory. This does mean that symbolic links in a path are not currently handled properly when matching chroot()ed commands. Fixes a local privilege escalation vulnerability where a user could craft their own nsswitch.conf file to load a shared library of their choosing and run arbitrary code. CVE-2025-32463 Reported by Rich Mirch @ Stratascale Cyber Research Unit (CRU).
This commit is contained in:
parent
d530367828
commit
fdafc2ceb3
2
MANIFEST
2
MANIFEST
@ -687,8 +687,6 @@ plugins/sudoers/mkdefaults
|
|||||||
plugins/sudoers/parse.h
|
plugins/sudoers/parse.h
|
||||||
plugins/sudoers/parse_ldif.c
|
plugins/sudoers/parse_ldif.c
|
||||||
plugins/sudoers/parser_warnx.c
|
plugins/sudoers/parser_warnx.c
|
||||||
plugins/sudoers/pivot.c
|
|
||||||
plugins/sudoers/pivot.h
|
|
||||||
plugins/sudoers/po/README
|
plugins/sudoers/po/README
|
||||||
plugins/sudoers/po/ast.mo
|
plugins/sudoers/po/ast.mo
|
||||||
plugins/sudoers/po/ast.po
|
plugins/sudoers/po/ast.po
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -147,7 +147,7 @@ resolve_editor(const char *ed, size_t edlen, int nfiles, char * const *files,
|
|||||||
goto oom;
|
goto oom;
|
||||||
|
|
||||||
/* If we can't find the editor in the user's PATH, give up. */
|
/* If we can't find the editor in the user's PATH, give up. */
|
||||||
if (find_path(editor, &editor_path, &user_editor_sb, getenv("PATH"),
|
if (find_path(editor, &editor_path, &user_editor_sb, getenv("PATH"), NULL,
|
||||||
false, allowlist) != FOUND) {
|
false, allowlist) != FOUND) {
|
||||||
errno = ENOENT;
|
errno = ENOENT;
|
||||||
goto bad;
|
goto bad;
|
||||||
|
@ -43,14 +43,14 @@
|
|||||||
* On failure, returns false.
|
* On failure, returns false.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
cmnd_allowed(char *cmnd, size_t cmnd_size, struct stat *cmnd_sbp,
|
cmnd_allowed(char *cmnd, size_t cmnd_size, const char *runchroot,
|
||||||
char * const *allowlist)
|
struct stat *cmnd_sbp, char * const *allowlist)
|
||||||
{
|
{
|
||||||
const char *cmnd_base;
|
const char *cmnd_base;
|
||||||
char * const *al;
|
char * const *al;
|
||||||
debug_decl(cmnd_allowed, SUDOERS_DEBUG_UTIL);
|
debug_decl(cmnd_allowed, SUDOERS_DEBUG_UTIL);
|
||||||
|
|
||||||
if (!sudo_goodpath(cmnd, cmnd_sbp))
|
if (!sudo_goodpath(cmnd, runchroot, cmnd_sbp))
|
||||||
debug_return_bool(false);
|
debug_return_bool(false);
|
||||||
|
|
||||||
if (allowlist == NULL)
|
if (allowlist == NULL)
|
||||||
@ -67,7 +67,7 @@ cmnd_allowed(char *cmnd, size_t cmnd_size, struct stat *cmnd_sbp,
|
|||||||
if (strcmp(cmnd_base, base) != 0)
|
if (strcmp(cmnd_base, base) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (sudo_goodpath(path, &sb) &&
|
if (sudo_goodpath(path, runchroot, &sb) &&
|
||||||
sb.st_dev == cmnd_sbp->st_dev && sb.st_ino == cmnd_sbp->st_ino) {
|
sb.st_dev == cmnd_sbp->st_dev && sb.st_ino == cmnd_sbp->st_ino) {
|
||||||
/* Overwrite cmnd with safe version from allowlist. */
|
/* Overwrite cmnd with safe version from allowlist. */
|
||||||
if (strlcpy(cmnd, path, cmnd_size) < cmnd_size)
|
if (strlcpy(cmnd, path, cmnd_size) < cmnd_size)
|
||||||
@ -87,7 +87,8 @@ cmnd_allowed(char *cmnd, size_t cmnd_size, struct stat *cmnd_sbp,
|
|||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
find_path(const char *infile, char **outfile, struct stat *sbp,
|
find_path(const char *infile, char **outfile, struct stat *sbp,
|
||||||
const char *path, bool ignore_dot, char * const *allowlist)
|
const char *path, const char *runchroot, bool ignore_dot,
|
||||||
|
char * const *allowlist)
|
||||||
{
|
{
|
||||||
char command[PATH_MAX];
|
char command[PATH_MAX];
|
||||||
const char *cp, *ep, *pathend;
|
const char *cp, *ep, *pathend;
|
||||||
@ -108,7 +109,8 @@ find_path(const char *infile, char **outfile, struct stat *sbp,
|
|||||||
errno = ENAMETOOLONG;
|
errno = ENAMETOOLONG;
|
||||||
debug_return_int(NOT_FOUND_ERROR);
|
debug_return_int(NOT_FOUND_ERROR);
|
||||||
}
|
}
|
||||||
found = cmnd_allowed(command, sizeof(command), sbp, allowlist);
|
found = cmnd_allowed(command, sizeof(command), runchroot, sbp,
|
||||||
|
allowlist);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +139,8 @@ find_path(const char *infile, char **outfile, struct stat *sbp,
|
|||||||
errno = ENAMETOOLONG;
|
errno = ENAMETOOLONG;
|
||||||
debug_return_int(NOT_FOUND_ERROR);
|
debug_return_int(NOT_FOUND_ERROR);
|
||||||
}
|
}
|
||||||
found = cmnd_allowed(command, sizeof(command), sbp, allowlist);
|
found = cmnd_allowed(command, sizeof(command), runchroot,
|
||||||
|
sbp, allowlist);
|
||||||
if (found)
|
if (found)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -151,7 +154,8 @@ find_path(const char *infile, char **outfile, struct stat *sbp,
|
|||||||
errno = ENAMETOOLONG;
|
errno = ENAMETOOLONG;
|
||||||
debug_return_int(NOT_FOUND_ERROR);
|
debug_return_int(NOT_FOUND_ERROR);
|
||||||
}
|
}
|
||||||
found = cmnd_allowed(command, sizeof(command), sbp, allowlist);
|
found = cmnd_allowed(command, sizeof(command), runchroot,
|
||||||
|
sbp, allowlist);
|
||||||
if (found && ignore_dot)
|
if (found && ignore_dot)
|
||||||
debug_return_int(NOT_FOUND_DOT);
|
debug_return_int(NOT_FOUND_DOT);
|
||||||
}
|
}
|
||||||
|
@ -39,13 +39,25 @@
|
|||||||
* Verify that path is a normal file and executable by root.
|
* Verify that path is a normal file and executable by root.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
sudo_goodpath(const char *path, struct stat *sbp)
|
sudo_goodpath(const char *path, const char *runchroot, struct stat *sbp)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
struct stat sb;
|
|
||||||
debug_decl(sudo_goodpath, SUDOERS_DEBUG_UTIL);
|
debug_decl(sudo_goodpath, SUDOERS_DEBUG_UTIL);
|
||||||
|
|
||||||
if (path != NULL) {
|
if (path != NULL) {
|
||||||
|
char pathbuf[PATH_MAX];
|
||||||
|
struct stat sb;
|
||||||
|
|
||||||
|
if (runchroot != NULL) {
|
||||||
|
/* XXX - handle symlinks and '..' in path outside chroot */
|
||||||
|
const int len =
|
||||||
|
snprintf(pathbuf, sizeof(pathbuf), "%s%s", runchroot, path);
|
||||||
|
if (len >= ssizeof(pathbuf)) {
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
path = pathbuf; // -V507
|
||||||
|
}
|
||||||
if (sbp == NULL)
|
if (sbp == NULL)
|
||||||
sbp = &sb;
|
sbp = &sb;
|
||||||
|
|
||||||
@ -57,5 +69,6 @@ sudo_goodpath(const char *path, struct stat *sbp)
|
|||||||
errno = EACCES;
|
errno = EACCES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
done:
|
||||||
debug_return_bool(ret);
|
debug_return_bool(ret);
|
||||||
}
|
}
|
||||||
|
@ -122,14 +122,26 @@ command_args_match(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
* Returns true on success, else false.
|
* Returns true on success, else false.
|
||||||
*/
|
*/
|
||||||
static bool
|
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];
|
||||||
bool ret;
|
bool ret;
|
||||||
debug_decl(do_stat, SUDOERS_DEBUG_MATCH);
|
debug_decl(do_stat, SUDOERS_DEBUG_MATCH);
|
||||||
|
|
||||||
if (fd != -1) {
|
if (fd != -1) {
|
||||||
ret = fstat(fd, sb) == 0;
|
ret = fstat(fd, sb) == 0;
|
||||||
} else {
|
} else {
|
||||||
|
/* Make path relative to the new root, if any. */
|
||||||
|
if (runchroot != NULL) {
|
||||||
|
/* XXX - handle symlinks and '..' in path outside chroot */
|
||||||
|
const int len =
|
||||||
|
snprintf(pathbuf, sizeof(pathbuf), "%s%s", runchroot, path);
|
||||||
|
if (len >= ssizeof(pathbuf)) {
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
debug_return_bool(false);
|
||||||
|
}
|
||||||
|
path = pathbuf;
|
||||||
|
}
|
||||||
ret = stat(path, sb) == 0;
|
ret = stat(path, sb) == 0;
|
||||||
}
|
}
|
||||||
debug_return_bool(ret);
|
debug_return_bool(ret);
|
||||||
@ -158,15 +170,29 @@ is_script(int fd)
|
|||||||
* Returns false on error, else true.
|
* Returns false on error, else true.
|
||||||
*/
|
*/
|
||||||
static bool
|
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;
|
int fd;
|
||||||
|
char pathbuf[PATH_MAX];
|
||||||
debug_decl(open_cmnd, SUDOERS_DEBUG_MATCH);
|
debug_decl(open_cmnd, SUDOERS_DEBUG_MATCH);
|
||||||
|
|
||||||
/* Only open the file for fdexec or for digest matching. */
|
/* Only open the file for fdexec or for digest matching. */
|
||||||
if (def_fdexec != always && TAILQ_EMPTY(digests))
|
if (def_fdexec != always && TAILQ_EMPTY(digests))
|
||||||
debug_return_bool(true);
|
debug_return_bool(true);
|
||||||
|
|
||||||
|
/* Make path relative to the new root, if any. */
|
||||||
|
if (runchroot != NULL) {
|
||||||
|
/* XXX - handle symlinks and '..' in path outside chroot */
|
||||||
|
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);
|
fd = open(path, O_RDONLY|O_NONBLOCK);
|
||||||
# ifdef O_EXEC
|
# ifdef O_EXEC
|
||||||
if (fd == -1 && errno == EACCES && TAILQ_EMPTY(digests)) {
|
if (fd == -1 && errno == EACCES && TAILQ_EMPTY(digests)) {
|
||||||
@ -185,7 +211,7 @@ open_cmnd(const char *path, const struct command_digest_list *digests, int *fdp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
set_cmnd_fd(struct sudoers_context *ctx, int fd, int real_root)
|
set_cmnd_fd(struct sudoers_context *ctx, int fd)
|
||||||
{
|
{
|
||||||
debug_decl(set_cmnd_fd, SUDOERS_DEBUG_MATCH);
|
debug_decl(set_cmnd_fd, SUDOERS_DEBUG_MATCH);
|
||||||
|
|
||||||
@ -200,19 +226,11 @@ set_cmnd_fd(struct sudoers_context *ctx, int fd, int real_root)
|
|||||||
} else if (is_script(fd)) {
|
} else if (is_script(fd)) {
|
||||||
char fdpath[PATH_MAX];
|
char fdpath[PATH_MAX];
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
int error, flags;
|
int flags;
|
||||||
|
|
||||||
/* We can only use fexecve() on a script if /dev/fd/N exists. */
|
/* We can only use fexecve() on a script if /dev/fd/N exists. */
|
||||||
if (real_root != -1) {
|
|
||||||
/* Path relative to old root directory. */
|
|
||||||
(void)snprintf(fdpath, sizeof(fdpath), "dev/fd/%d", fd);
|
|
||||||
error = fstatat(real_root, fdpath, &sb, 0);
|
|
||||||
} else {
|
|
||||||
/* Absolute path. */
|
|
||||||
(void)snprintf(fdpath, sizeof(fdpath), "/dev/fd/%d", fd);
|
(void)snprintf(fdpath, sizeof(fdpath), "/dev/fd/%d", fd);
|
||||||
error = stat(fdpath, &sb);
|
if (stat(fdpath, &sb) != 0) {
|
||||||
}
|
|
||||||
if (error != 0) {
|
|
||||||
/* Missing /dev/fd file, can't use fexecve(). */
|
/* Missing /dev/fd file, can't use fexecve(). */
|
||||||
close(fd);
|
close(fd);
|
||||||
fd = -1;
|
fd = -1;
|
||||||
@ -238,14 +256,28 @@ set_cmnd_fd(struct sudoers_context *ctx, int fd, int real_root)
|
|||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
command_matches_dir(struct sudoers_context *ctx, const char *sudoers_dir,
|
command_matches_dir(struct sudoers_context *ctx, const char *sudoers_dir,
|
||||||
size_t dlen, int real_root, const struct command_digest_list *digests)
|
size_t dlen, const char *runchroot,
|
||||||
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
struct stat sudoers_stat;
|
struct stat sudoers_stat;
|
||||||
char path[PATH_MAX];
|
char path[PATH_MAX], sdbuf[PATH_MAX];
|
||||||
|
size_t chrootlen = 0;
|
||||||
int len, fd = -1;
|
int len, fd = -1;
|
||||||
int ret = DENY;
|
int ret = DENY;
|
||||||
debug_decl(command_matches_dir, SUDOERS_DEBUG_MATCH);
|
debug_decl(command_matches_dir, SUDOERS_DEBUG_MATCH);
|
||||||
|
|
||||||
|
/* Make sudoers_dir relative to the new root, if any. */
|
||||||
|
if (runchroot != NULL) {
|
||||||
|
/* XXX - handle symlinks and '..' in path outside chroot */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/* Compare the canonicalized directories, if possible. */
|
/* Compare the canonicalized directories, if possible. */
|
||||||
if (ctx->user.cmnd_dir != NULL) {
|
if (ctx->user.cmnd_dir != NULL) {
|
||||||
char *resolved = canon_path(sudoers_dir);
|
char *resolved = canon_path(sudoers_dir);
|
||||||
@ -264,18 +296,19 @@ command_matches_dir(struct sudoers_context *ctx, const char *sudoers_dir,
|
|||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* Open the file for fdexec or for digest matching. */
|
/* Open the file for fdexec or for digest matching. */
|
||||||
if (!open_cmnd(path, digests, &fd))
|
if (!open_cmnd(path, NULL, digests, &fd))
|
||||||
goto done;
|
goto done;
|
||||||
if (!do_stat(fd, path, &sudoers_stat))
|
if (!do_stat(fd, path, NULL, &sudoers_stat))
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
if (ctx->user.cmnd_stat == NULL ||
|
if (ctx->user.cmnd_stat == NULL ||
|
||||||
(ctx->user.cmnd_stat->st_dev == sudoers_stat.st_dev &&
|
(ctx->user.cmnd_stat->st_dev == sudoers_stat.st_dev &&
|
||||||
ctx->user.cmnd_stat->st_ino == sudoers_stat.st_ino)) {
|
ctx->user.cmnd_stat->st_ino == sudoers_stat.st_ino)) {
|
||||||
if (digest_matches(fd, path, digests) != ALLOW)
|
/* path is already relative to runchroot */
|
||||||
|
if (digest_matches(fd, path, NULL, digests) != ALLOW)
|
||||||
goto done;
|
goto done;
|
||||||
free(ctx->runas.cmnd);
|
free(ctx->runas.cmnd);
|
||||||
if ((ctx->runas.cmnd = strdup(path)) == NULL) {
|
if ((ctx->runas.cmnd = strdup(path + chrootlen)) == NULL) {
|
||||||
sudo_warnx(U_("%s: %s"), __func__,
|
sudo_warnx(U_("%s: %s"), __func__,
|
||||||
U_("unable to allocate memory"));
|
U_("unable to allocate memory"));
|
||||||
}
|
}
|
||||||
@ -295,7 +328,8 @@ done:
|
|||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
command_matches_dir(struct sudoers_context *ctx, const char *sudoers_dir,
|
command_matches_dir(struct sudoers_context *ctx, const char *sudoers_dir,
|
||||||
size_t dlen, int real_root, const struct command_digest_list *digests)
|
size_t dlen, const char *runchroot,
|
||||||
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
debug_decl(command_matches_dir, SUDOERS_DEBUG_MATCH);
|
debug_decl(command_matches_dir, SUDOERS_DEBUG_MATCH);
|
||||||
@ -309,11 +343,11 @@ command_matches_dir(struct sudoers_context *ctx, const char *sudoers_dir,
|
|||||||
goto bad;
|
goto bad;
|
||||||
|
|
||||||
/* Open the file for fdexec or for digest matching. */
|
/* Open the file for fdexec or for digest matching. */
|
||||||
if (!open_cmnd(ctx->user.cmnd, digests, &fd))
|
if (!open_cmnd(ctx->user.cmnd, runchroot, digests, &fd))
|
||||||
goto bad;
|
goto bad;
|
||||||
if (digest_matches(fd, ctx->user.cmnd, digests) != ALLOW)
|
if (digest_matches(fd, ctx->user.cmnd, runchroot, digests) != ALLOW)
|
||||||
goto bad;
|
goto bad;
|
||||||
set_cmnd_fd(ctx, fd, real_root);
|
set_cmnd_fd(ctx, fd);
|
||||||
|
|
||||||
debug_return_int(ALLOW);
|
debug_return_int(ALLOW);
|
||||||
bad:
|
bad:
|
||||||
@ -324,7 +358,7 @@ bad:
|
|||||||
#endif /* SUDOERS_NAME_MATCH */
|
#endif /* SUDOERS_NAME_MATCH */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
command_matches_all(struct sudoers_context *ctx, int real_root,
|
command_matches_all(struct sudoers_context *ctx, const char *runchroot,
|
||||||
const struct command_digest_list *digests)
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
#ifndef SUDOERS_NAME_MATCH
|
#ifndef SUDOERS_NAME_MATCH
|
||||||
@ -336,10 +370,10 @@ command_matches_all(struct sudoers_context *ctx, int real_root,
|
|||||||
if (strchr(ctx->user.cmnd, '/') != NULL) {
|
if (strchr(ctx->user.cmnd, '/') != NULL) {
|
||||||
#ifndef SUDOERS_NAME_MATCH
|
#ifndef SUDOERS_NAME_MATCH
|
||||||
/* Open the file for fdexec or for digest matching. */
|
/* Open the file for fdexec or for digest matching. */
|
||||||
bool open_error = !open_cmnd(ctx->user.cmnd, digests, &fd);
|
bool open_error = !open_cmnd(ctx->user.cmnd, runchroot, digests, &fd);
|
||||||
|
|
||||||
/* A non-existent file is not an error for "sudo ALL". */
|
/* A non-existent file is not an error for "sudo ALL". */
|
||||||
if (do_stat(fd, ctx->user.cmnd, &sb)) {
|
if (do_stat(fd, ctx->user.cmnd, runchroot, &sb)) {
|
||||||
if (open_error) {
|
if (open_error) {
|
||||||
/* File exists but we couldn't open it above? */
|
/* File exists but we couldn't open it above? */
|
||||||
goto bad;
|
goto bad;
|
||||||
@ -347,14 +381,14 @@ command_matches_all(struct sudoers_context *ctx, int real_root,
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
/* Open the file for fdexec or for digest matching. */
|
/* Open the file for fdexec or for digest matching. */
|
||||||
(void)open_cmnd(ctx->user.cmnd, digests, &fd);
|
(void)open_cmnd(ctx->user.cmnd, runchroot, digests, &fd);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check digest of ctx->user.cmnd since we have no sudoers_cmnd for ALL. */
|
/* Check digest of ctx->user.cmnd since we have no sudoers_cmnd for ALL. */
|
||||||
if (digest_matches(fd, ctx->user.cmnd, digests) != ALLOW)
|
if (digest_matches(fd, ctx->user.cmnd, runchroot, digests) != ALLOW)
|
||||||
goto bad;
|
goto bad;
|
||||||
set_cmnd_fd(ctx, fd, real_root);
|
set_cmnd_fd(ctx, fd);
|
||||||
|
|
||||||
/* No need to set ctx->runas.cmnd for ALL. */
|
/* No need to set ctx->runas.cmnd for ALL. */
|
||||||
debug_return_int(ALLOW);
|
debug_return_int(ALLOW);
|
||||||
@ -366,7 +400,7 @@ bad:
|
|||||||
|
|
||||||
static int
|
static int
|
||||||
command_matches_fnmatch(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
command_matches_fnmatch(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
||||||
const char *sudoers_args, int real_root,
|
const char *sudoers_args, const char *runchroot,
|
||||||
const struct command_digest_list *digests)
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
const char *cmnd = ctx->user.cmnd;
|
const char *cmnd = ctx->user.cmnd;
|
||||||
@ -384,6 +418,7 @@ command_matches_fnmatch(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
* c) there are args in sudoers and on command line and they match
|
* c) there are args in sudoers and on command line and they match
|
||||||
* else return DENY.
|
* else return DENY.
|
||||||
*
|
*
|
||||||
|
* Neither sudoers_cmnd nor user_cmnd are relative to runchroot.
|
||||||
* We do not attempt to match a relative path unless there is a
|
* We do not attempt to match a relative path unless there is a
|
||||||
* canonicalized version.
|
* canonicalized version.
|
||||||
*/
|
*/
|
||||||
@ -402,16 +437,16 @@ command_matches_fnmatch(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
|
|
||||||
if (command_args_match(ctx, sudoers_cmnd, sudoers_args) == ALLOW) {
|
if (command_args_match(ctx, sudoers_cmnd, sudoers_args) == ALLOW) {
|
||||||
/* Open the file for fdexec or for digest matching. */
|
/* Open the file for fdexec or for digest matching. */
|
||||||
if (!open_cmnd(cmnd, digests, &fd))
|
if (!open_cmnd(cmnd, runchroot, digests, &fd))
|
||||||
goto bad;
|
goto bad;
|
||||||
#ifndef SUDOERS_NAME_MATCH
|
#ifndef SUDOERS_NAME_MATCH
|
||||||
if (!do_stat(fd, cmnd, &sb))
|
if (!do_stat(fd, cmnd, runchroot, &sb))
|
||||||
goto bad;
|
goto bad;
|
||||||
#endif
|
#endif
|
||||||
/* Check digest of cmnd since sudoers_cmnd is a pattern. */
|
/* Check digest of cmnd since sudoers_cmnd is a pattern. */
|
||||||
if (digest_matches(fd, cmnd, digests) != ALLOW)
|
if (digest_matches(fd, cmnd, runchroot, digests) != ALLOW)
|
||||||
goto bad;
|
goto bad;
|
||||||
set_cmnd_fd(ctx, fd, real_root);
|
set_cmnd_fd(ctx, fd);
|
||||||
|
|
||||||
/* No need to set ctx->runas.cmnd since cmnd matches sudoers_cmnd */
|
/* No need to set ctx->runas.cmnd since cmnd matches sudoers_cmnd */
|
||||||
debug_return_int(ALLOW);
|
debug_return_int(ALLOW);
|
||||||
@ -424,7 +459,7 @@ bad:
|
|||||||
|
|
||||||
static int
|
static int
|
||||||
command_matches_regex(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
command_matches_regex(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
||||||
const char *sudoers_args, int real_root,
|
const char *sudoers_args, const char *runchroot,
|
||||||
const struct command_digest_list *digests)
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
const char *cmnd = ctx->user.cmnd;
|
const char *cmnd = ctx->user.cmnd;
|
||||||
@ -441,6 +476,8 @@ command_matches_regex(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
* b) there are no args on command line and none required by 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
|
* c) there are args in sudoers and on command line and they match
|
||||||
* else return DENY.
|
* else return DENY.
|
||||||
|
*
|
||||||
|
* Neither sudoers_cmnd nor user_cmnd are relative to runchroot.
|
||||||
*/
|
*/
|
||||||
if (cmnd[0] != '/' || regex_matches(sudoers_cmnd, cmnd) != ALLOW) {
|
if (cmnd[0] != '/' || regex_matches(sudoers_cmnd, cmnd) != ALLOW) {
|
||||||
/* No match, retry using the canonicalized path (if possible). */
|
/* No match, retry using the canonicalized path (if possible). */
|
||||||
@ -457,16 +494,16 @@ command_matches_regex(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
|
|
||||||
if (command_args_match(ctx, sudoers_cmnd, sudoers_args) == ALLOW) {
|
if (command_args_match(ctx, sudoers_cmnd, sudoers_args) == ALLOW) {
|
||||||
/* Open the file for fdexec or for digest matching. */
|
/* Open the file for fdexec or for digest matching. */
|
||||||
if (!open_cmnd(cmnd, digests, &fd))
|
if (!open_cmnd(cmnd, runchroot, digests, &fd))
|
||||||
goto bad;
|
goto bad;
|
||||||
#ifndef SUDOERS_NAME_MATCH
|
#ifndef SUDOERS_NAME_MATCH
|
||||||
if (!do_stat(fd, cmnd, &sb))
|
if (!do_stat(fd, cmnd, runchroot, &sb))
|
||||||
goto bad;
|
goto bad;
|
||||||
#endif
|
#endif
|
||||||
/* Check digest of cmnd since sudoers_cmnd is a pattern. */
|
/* Check digest of cmnd since sudoers_cmnd is a pattern. */
|
||||||
if (digest_matches(fd, cmnd, digests) != ALLOW)
|
if (digest_matches(fd, cmnd, runchroot, digests) != ALLOW)
|
||||||
goto bad;
|
goto bad;
|
||||||
set_cmnd_fd(ctx, fd, real_root);
|
set_cmnd_fd(ctx, fd);
|
||||||
|
|
||||||
/* No need to set ctx->runas.cmnd since cmnd matches sudoers_cmnd */
|
/* No need to set ctx->runas.cmnd since cmnd matches sudoers_cmnd */
|
||||||
debug_return_int(ALLOW);
|
debug_return_int(ALLOW);
|
||||||
@ -480,17 +517,31 @@ bad:
|
|||||||
#ifndef SUDOERS_NAME_MATCH
|
#ifndef SUDOERS_NAME_MATCH
|
||||||
static int
|
static int
|
||||||
command_matches_glob(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
command_matches_glob(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
||||||
const char *sudoers_args, int real_root,
|
const char *sudoers_args, const char *runchroot,
|
||||||
const struct command_digest_list *digests)
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
struct stat sudoers_stat;
|
struct stat sudoers_stat;
|
||||||
bool bad_digest = false;
|
bool bad_digest = false;
|
||||||
char **ap, *base, *cp;
|
char **ap, *base, *cp;
|
||||||
|
char pathbuf[PATH_MAX];
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
size_t dlen;
|
size_t dlen, chrootlen = 0;
|
||||||
glob_t gl;
|
glob_t gl;
|
||||||
debug_decl(command_matches_glob, SUDOERS_DEBUG_MATCH);
|
debug_decl(command_matches_glob, SUDOERS_DEBUG_MATCH);
|
||||||
|
|
||||||
|
/* Make sudoers_cmnd relative to the new root, if any. */
|
||||||
|
if (runchroot != NULL) {
|
||||||
|
/* XXX - handle symlinks and '..' in path outside chroot */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* First check to see if we can avoid the call to glob(3).
|
* First check to see if we can avoid the call to glob(3).
|
||||||
* Short circuit if there are no meta chars in the command itself
|
* Short circuit if there are no meta chars in the command itself
|
||||||
@ -522,19 +573,21 @@ command_matches_glob(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
close(fd);
|
close(fd);
|
||||||
fd = -1;
|
fd = -1;
|
||||||
}
|
}
|
||||||
|
/* Remove the runchroot, if any. */
|
||||||
|
cp += chrootlen;
|
||||||
|
|
||||||
if (strcmp(cp, ctx->user.cmnd) != 0)
|
if (strcmp(cp, ctx->user.cmnd) != 0)
|
||||||
continue;
|
continue;
|
||||||
/* Open the file for fdexec or for digest matching. */
|
/* Open the file for fdexec or for digest matching. */
|
||||||
if (!open_cmnd(cp, digests, &fd))
|
if (!open_cmnd(cp, runchroot, digests, &fd))
|
||||||
continue;
|
continue;
|
||||||
if (!do_stat(fd, cp, &sudoers_stat))
|
if (!do_stat(fd, cp, runchroot, &sudoers_stat))
|
||||||
continue;
|
continue;
|
||||||
if (ctx->user.cmnd_stat == NULL ||
|
if (ctx->user.cmnd_stat == NULL ||
|
||||||
(ctx->user.cmnd_stat->st_dev == sudoers_stat.st_dev &&
|
(ctx->user.cmnd_stat->st_dev == sudoers_stat.st_dev &&
|
||||||
ctx->user.cmnd_stat->st_ino == sudoers_stat.st_ino)) {
|
ctx->user.cmnd_stat->st_ino == sudoers_stat.st_ino)) {
|
||||||
/* There could be multiple matches, check digest early. */
|
/* There could be multiple matches, check digest early. */
|
||||||
if (digest_matches(fd, cp, digests) != ALLOW) {
|
if (digest_matches(fd, cp, runchroot, digests) != ALLOW) {
|
||||||
bad_digest = true;
|
bad_digest = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -558,11 +611,13 @@ command_matches_glob(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
close(fd);
|
close(fd);
|
||||||
fd = -1;
|
fd = -1;
|
||||||
}
|
}
|
||||||
|
/* Remove the runchroot, if any. */
|
||||||
|
cp += chrootlen;
|
||||||
|
|
||||||
/* If it ends in '/' it is a directory spec. */
|
/* If it ends in '/' it is a directory spec. */
|
||||||
dlen = strlen(cp);
|
dlen = strlen(cp);
|
||||||
if (cp[dlen - 1] == '/') {
|
if (cp[dlen - 1] == '/') {
|
||||||
if (command_matches_dir(ctx, cp, dlen, real_root, digests) == ALLOW) {
|
if (command_matches_dir(ctx, cp, dlen, runchroot, digests) == ALLOW) {
|
||||||
globfree(&gl);
|
globfree(&gl);
|
||||||
debug_return_int(ALLOW);
|
debug_return_int(ALLOW);
|
||||||
}
|
}
|
||||||
@ -593,14 +648,14 @@ command_matches_glob(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Open the file for fdexec or for digest matching. */
|
/* Open the file for fdexec or for digest matching. */
|
||||||
if (!open_cmnd(cp, digests, &fd))
|
if (!open_cmnd(cp, runchroot, digests, &fd))
|
||||||
continue;
|
continue;
|
||||||
if (!do_stat(fd, cp, &sudoers_stat))
|
if (!do_stat(fd, cp, runchroot, &sudoers_stat))
|
||||||
continue;
|
continue;
|
||||||
if (ctx->user.cmnd_stat == NULL ||
|
if (ctx->user.cmnd_stat == NULL ||
|
||||||
(ctx->user.cmnd_stat->st_dev == sudoers_stat.st_dev &&
|
(ctx->user.cmnd_stat->st_dev == sudoers_stat.st_dev &&
|
||||||
ctx->user.cmnd_stat->st_ino == sudoers_stat.st_ino)) {
|
ctx->user.cmnd_stat->st_ino == sudoers_stat.st_ino)) {
|
||||||
if (digest_matches(fd, cp, digests) != ALLOW)
|
if (digest_matches(fd, cp, runchroot, digests) != ALLOW)
|
||||||
continue;
|
continue;
|
||||||
free(ctx->runas.cmnd);
|
free(ctx->runas.cmnd);
|
||||||
if ((ctx->runas.cmnd = strdup(cp)) == NULL) {
|
if ((ctx->runas.cmnd = strdup(cp)) == NULL) {
|
||||||
@ -617,7 +672,7 @@ done:
|
|||||||
if (cp != NULL) {
|
if (cp != NULL) {
|
||||||
if (command_args_match(ctx, sudoers_cmnd, sudoers_args) == ALLOW) {
|
if (command_args_match(ctx, sudoers_cmnd, sudoers_args) == ALLOW) {
|
||||||
/* ctx->runas.cmnd was set above. */
|
/* ctx->runas.cmnd was set above. */
|
||||||
set_cmnd_fd(ctx, fd, real_root);
|
set_cmnd_fd(ctx, fd);
|
||||||
debug_return_int(ALLOW);
|
debug_return_int(ALLOW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -628,7 +683,7 @@ done:
|
|||||||
|
|
||||||
static int
|
static int
|
||||||
command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
||||||
const char *sudoers_args, int real_root,
|
const char *sudoers_args, const char *runchroot,
|
||||||
const struct command_digest_list *digests)
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
struct stat sudoers_stat;
|
struct stat sudoers_stat;
|
||||||
@ -641,7 +696,7 @@ command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
dlen = strlen(sudoers_cmnd);
|
dlen = strlen(sudoers_cmnd);
|
||||||
if (sudoers_cmnd[dlen - 1] == '/') {
|
if (sudoers_cmnd[dlen - 1] == '/') {
|
||||||
debug_return_int(command_matches_dir(ctx, sudoers_cmnd, dlen,
|
debug_return_int(command_matches_dir(ctx, sudoers_cmnd, dlen,
|
||||||
real_root, digests));
|
runchroot, digests));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Only proceed if ctx->user.cmnd_base and basename(sudoers_cmnd) match */
|
/* Only proceed if ctx->user.cmnd_base and basename(sudoers_cmnd) match */
|
||||||
@ -672,7 +727,7 @@ command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Open the file for fdexec or for digest matching. */
|
/* 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;
|
goto bad;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -682,7 +737,7 @@ command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
* c) there are args in sudoers and on command line and they match
|
* c) there are args in sudoers and on command line and they match
|
||||||
* d) there is a digest and it matches
|
* d) there is a digest and it matches
|
||||||
*/
|
*/
|
||||||
if (ctx->user.cmnd_stat != NULL && do_stat(fd, sudoers_cmnd, &sudoers_stat)) {
|
if (ctx->user.cmnd_stat != NULL && do_stat(fd, sudoers_cmnd, runchroot, &sudoers_stat)) {
|
||||||
if (ctx->user.cmnd_stat->st_dev != sudoers_stat.st_dev ||
|
if (ctx->user.cmnd_stat->st_dev != sudoers_stat.st_dev ||
|
||||||
ctx->user.cmnd_stat->st_ino != sudoers_stat.st_ino)
|
ctx->user.cmnd_stat->st_ino != sudoers_stat.st_ino)
|
||||||
goto bad;
|
goto bad;
|
||||||
@ -693,7 +748,7 @@ command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
}
|
}
|
||||||
if (command_args_match(ctx, sudoers_cmnd, sudoers_args) != ALLOW)
|
if (command_args_match(ctx, sudoers_cmnd, sudoers_args) != ALLOW)
|
||||||
goto bad;
|
goto bad;
|
||||||
if (digest_matches(fd, sudoers_cmnd, digests) != ALLOW) {
|
if (digest_matches(fd, sudoers_cmnd, runchroot, digests) != ALLOW) {
|
||||||
/* XXX - log functions not available but we should log very loudly */
|
/* XXX - log functions not available but we should log very loudly */
|
||||||
goto bad;
|
goto bad;
|
||||||
}
|
}
|
||||||
@ -702,7 +757,7 @@ command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
||||||
goto bad;
|
goto bad;
|
||||||
}
|
}
|
||||||
set_cmnd_fd(ctx, fd, real_root);
|
set_cmnd_fd(ctx, fd);
|
||||||
debug_return_int(ALLOW);
|
debug_return_int(ALLOW);
|
||||||
bad:
|
bad:
|
||||||
if (fd != -1)
|
if (fd != -1)
|
||||||
@ -712,16 +767,16 @@ bad:
|
|||||||
#else /* SUDOERS_NAME_MATCH */
|
#else /* SUDOERS_NAME_MATCH */
|
||||||
static int
|
static int
|
||||||
command_matches_glob(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
command_matches_glob(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
||||||
const char *sudoers_args, int real_root,
|
const char *sudoers_args, const char *runchroot,
|
||||||
const struct command_digest_list *digests)
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
return command_matches_fnmatch(ctx, sudoers_cmnd, sudoers_args, real_root,
|
return command_matches_fnmatch(ctx, sudoers_cmnd, sudoers_args, runchroot,
|
||||||
digests);
|
digests);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
||||||
const char *sudoers_args, int real_root,
|
const char *sudoers_args, const char *runchroot,
|
||||||
const struct command_digest_list *digests)
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
size_t dlen;
|
size_t dlen;
|
||||||
@ -731,16 +786,16 @@ command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
/* If it ends in '/' it is a directory spec. */
|
/* If it ends in '/' it is a directory spec. */
|
||||||
dlen = strlen(sudoers_cmnd);
|
dlen = strlen(sudoers_cmnd);
|
||||||
if (sudoers_cmnd[dlen - 1] == '/') {
|
if (sudoers_cmnd[dlen - 1] == '/') {
|
||||||
debug_return_int(command_matches_dir(ctx, sudoers_cmnd, dlen, real_root,
|
debug_return_int(command_matches_dir(ctx, sudoers_cmnd, dlen, runchroot,
|
||||||
digests));
|
digests));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(ctx->user.cmnd, sudoers_cmnd) == 0) {
|
if (strcmp(ctx->user.cmnd, sudoers_cmnd) == 0) {
|
||||||
if (command_args_match(ctx, sudoers_cmnd, sudoers_args) == ALLOW) {
|
if (command_args_match(ctx, sudoers_cmnd, sudoers_args) == ALLOW) {
|
||||||
/* Open the file for fdexec or for digest matching. */
|
/* Open the file for fdexec or for digest matching. */
|
||||||
if (!open_cmnd(ctx->user.cmnd, digests, &fd))
|
if (!open_cmnd(ctx->user.cmnd, runchroot, digests, &fd))
|
||||||
goto bad;
|
goto bad;
|
||||||
if (digest_matches(fd, ctx->user.cmnd, digests) != ALLOW)
|
if (digest_matches(fd, ctx->user.cmnd, runchroot, digests) != ALLOW)
|
||||||
goto bad;
|
goto bad;
|
||||||
|
|
||||||
/* Successful match. */
|
/* Successful match. */
|
||||||
@ -750,7 +805,7 @@ command_matches_normal(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
U_("unable to allocate memory"));
|
U_("unable to allocate memory"));
|
||||||
goto bad;
|
goto bad;
|
||||||
}
|
}
|
||||||
set_cmnd_fd(ctx, fd, real_root);
|
set_cmnd_fd(ctx, fd);
|
||||||
debug_return_int(ALLOW);
|
debug_return_int(ALLOW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -771,11 +826,8 @@ command_matches(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
const char *sudoers_args, const char *runchroot, struct cmnd_info *info,
|
const char *sudoers_args, const char *runchroot, struct cmnd_info *info,
|
||||||
const struct command_digest_list *digests)
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
struct sudoers_pivot pivot_state = SUDOERS_PIVOT_INITIALIZER;
|
|
||||||
char *saved_user_cmnd = NULL;
|
char *saved_user_cmnd = NULL;
|
||||||
struct stat saved_user_stat;
|
struct stat saved_user_stat;
|
||||||
bool reset_cmnd = false;
|
|
||||||
int real_root = -1;
|
|
||||||
int ret = DENY;
|
int ret = DENY;
|
||||||
debug_decl(command_matches, SUDOERS_DEBUG_MATCH);
|
debug_decl(command_matches, SUDOERS_DEBUG_MATCH);
|
||||||
|
|
||||||
@ -793,18 +845,6 @@ command_matches(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
runchroot = def_runchroot;
|
runchroot = def_runchroot;
|
||||||
} else {
|
} else {
|
||||||
/* Rule-specific runchroot, must reset cmnd and cmnd_stat. */
|
/* Rule-specific runchroot, must reset cmnd and cmnd_stat. */
|
||||||
reset_cmnd = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pivot root. */
|
|
||||||
if (runchroot != NULL) {
|
|
||||||
if (!pivot_root(runchroot, &pivot_state))
|
|
||||||
goto done;
|
|
||||||
real_root = pivot_state.saved_root;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reset_cmnd) {
|
|
||||||
/* Rule-specific runchroot, set cmnd and cmnd_stat after pivot. */
|
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
/* Save old ctx->user.cmnd first, set_cmnd_path() will free it. */
|
/* Save old ctx->user.cmnd first, set_cmnd_path() will free it. */
|
||||||
@ -812,7 +852,7 @@ command_matches(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
ctx->user.cmnd = NULL;
|
ctx->user.cmnd = NULL;
|
||||||
if (ctx->user.cmnd_stat != NULL)
|
if (ctx->user.cmnd_stat != NULL)
|
||||||
saved_user_stat = *ctx->user.cmnd_stat;
|
saved_user_stat = *ctx->user.cmnd_stat;
|
||||||
status = set_cmnd_path(ctx, NULL);
|
status = set_cmnd_path(ctx, runchroot);
|
||||||
if (status != FOUND) {
|
if (status != FOUND) {
|
||||||
ctx->user.cmnd = saved_user_cmnd;
|
ctx->user.cmnd = saved_user_cmnd;
|
||||||
saved_user_cmnd = NULL;
|
saved_user_cmnd = NULL;
|
||||||
@ -823,13 +863,13 @@ command_matches(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
|
|
||||||
if (sudoers_cmnd == NULL) {
|
if (sudoers_cmnd == NULL) {
|
||||||
sudoers_cmnd = "ALL";
|
sudoers_cmnd = "ALL";
|
||||||
ret = command_matches_all(ctx, real_root, digests);
|
ret = command_matches_all(ctx, runchroot, digests);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check for regular expressions first. */
|
/* Check for regular expressions first. */
|
||||||
if (sudoers_cmnd[0] == '^') {
|
if (sudoers_cmnd[0] == '^') {
|
||||||
ret = command_matches_regex(ctx, sudoers_cmnd, sudoers_args, real_root,
|
ret = command_matches_regex(ctx, sudoers_cmnd, sudoers_args, runchroot,
|
||||||
digests);
|
digests);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
@ -860,20 +900,16 @@ command_matches(struct sudoers_context *ctx, const char *sudoers_cmnd,
|
|||||||
*/
|
*/
|
||||||
if (def_fast_glob) {
|
if (def_fast_glob) {
|
||||||
ret = command_matches_fnmatch(ctx, sudoers_cmnd, sudoers_args,
|
ret = command_matches_fnmatch(ctx, sudoers_cmnd, sudoers_args,
|
||||||
real_root, digests);
|
runchroot, digests);
|
||||||
} else {
|
} else {
|
||||||
ret = command_matches_glob(ctx, sudoers_cmnd, sudoers_args,
|
ret = command_matches_glob(ctx, sudoers_cmnd, sudoers_args,
|
||||||
real_root, digests);
|
runchroot, digests);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ret = command_matches_normal(ctx, sudoers_cmnd, sudoers_args,
|
ret = command_matches_normal(ctx, sudoers_cmnd, sudoers_args,
|
||||||
real_root, digests);
|
runchroot, digests);
|
||||||
}
|
}
|
||||||
done:
|
done:
|
||||||
/* Restore root. */
|
|
||||||
if (runchroot != NULL)
|
|
||||||
(void)unpivot_root(&pivot_state);
|
|
||||||
|
|
||||||
/* Restore ctx->user.cmnd and ctx->user.cmnd_stat. */
|
/* Restore ctx->user.cmnd and ctx->user.cmnd_stat. */
|
||||||
if (saved_user_cmnd != NULL) {
|
if (saved_user_cmnd != NULL) {
|
||||||
if (info != NULL) {
|
if (info != NULL) {
|
||||||
|
@ -40,13 +40,14 @@
|
|||||||
#include <gram.h>
|
#include <gram.h>
|
||||||
|
|
||||||
int
|
int
|
||||||
digest_matches(int fd, const char *path,
|
digest_matches(int fd, const char *path, const char *runchroot,
|
||||||
const struct command_digest_list *digests)
|
const struct command_digest_list *digests)
|
||||||
{
|
{
|
||||||
unsigned int digest_type = SUDO_DIGEST_INVALID;
|
unsigned int digest_type = SUDO_DIGEST_INVALID;
|
||||||
unsigned char *file_digest = NULL;
|
unsigned char *file_digest = NULL;
|
||||||
unsigned char *sudoers_digest = NULL;
|
unsigned char *sudoers_digest = NULL;
|
||||||
struct command_digest *digest;
|
struct command_digest *digest;
|
||||||
|
char pathbuf[PATH_MAX];
|
||||||
size_t digest_len;
|
size_t digest_len;
|
||||||
int matched = DENY;
|
int matched = DENY;
|
||||||
int fd2 = -1;
|
int fd2 = -1;
|
||||||
@ -66,6 +67,17 @@ digest_matches(int fd, const char *path,
|
|||||||
fd = fd2;
|
fd = fd2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (runchroot != NULL) {
|
||||||
|
/* XXX - handle symlinks and '..' in path outside chroot */
|
||||||
|
const int len =
|
||||||
|
snprintf(pathbuf, sizeof(pathbuf), "%s%s", runchroot, path);
|
||||||
|
if (len >= ssizeof(pathbuf)) {
|
||||||
|
errno = ENAMETOOLONG;
|
||||||
|
debug_return_bool(false);
|
||||||
|
}
|
||||||
|
path = pathbuf;
|
||||||
|
}
|
||||||
|
|
||||||
TAILQ_FOREACH(digest, digests, entries) {
|
TAILQ_FOREACH(digest, digests, entries) {
|
||||||
/* Compute file digest if needed. */
|
/* Compute file digest if needed. */
|
||||||
if (digest->digest_type != digest_type) {
|
if (digest->digest_type != digest_type) {
|
||||||
|
@ -418,7 +418,7 @@ int addr_matches(char *n);
|
|||||||
int command_matches(struct sudoers_context *ctx, const char *sudoers_cmnd, const char *sudoers_args, const char *runchroot, struct cmnd_info *info, const struct command_digest_list *digests);
|
int command_matches(struct sudoers_context *ctx, const char *sudoers_cmnd, const char *sudoers_args, const char *runchroot, struct cmnd_info *info, const struct command_digest_list *digests);
|
||||||
|
|
||||||
/* match_digest.c */
|
/* match_digest.c */
|
||||||
int digest_matches(int fd, const char *path, const struct command_digest_list *digests);
|
int digest_matches(int fd, const char *path, const char *runchroot, const struct command_digest_list *digests);
|
||||||
|
|
||||||
/* match.c */
|
/* match.c */
|
||||||
struct group;
|
struct group;
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-License-Identifier: ISC
|
|
||||||
*
|
|
||||||
* Copyright (c) 2023 Todd C. Miller <Todd.Miller@sudo.ws>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
|
||||||
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <config.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <sudoers.h>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Pivot to a new root directory, storing the old root and old cwd
|
|
||||||
* in state. Changes current working directory to the new root.
|
|
||||||
* Returns true on success, else false.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
pivot_root(const char *new_root, struct sudoers_pivot *state)
|
|
||||||
{
|
|
||||||
debug_decl(pivot_root, SUDOERS_DEBUG_UTIL);
|
|
||||||
|
|
||||||
state->saved_root = open("/", O_RDONLY);
|
|
||||||
state->saved_cwd = open(".", O_RDONLY);
|
|
||||||
if (state->saved_root == -1 || state->saved_cwd == -1 || chroot(new_root) == -1) {
|
|
||||||
if (state->saved_root != -1) {
|
|
||||||
close(state->saved_root);
|
|
||||||
state->saved_root = -1;
|
|
||||||
}
|
|
||||||
if (state->saved_cwd != -1) {
|
|
||||||
close(state->saved_cwd);
|
|
||||||
state->saved_cwd = -1;
|
|
||||||
}
|
|
||||||
debug_return_bool(false);
|
|
||||||
}
|
|
||||||
debug_return_bool(chdir("/") == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Pivot back to the stored root directory and restore the old cwd.
|
|
||||||
* Returns true on success, else false.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
unpivot_root(struct sudoers_pivot *state)
|
|
||||||
{
|
|
||||||
bool ret = true;
|
|
||||||
debug_decl(unpivot_root, SUDOERS_DEBUG_UTIL);
|
|
||||||
|
|
||||||
/* Order is important: restore old root, *then* change cwd. */
|
|
||||||
if (state->saved_root != -1) {
|
|
||||||
if (fchdir(state->saved_root) == -1 || chroot(".") == -1) {
|
|
||||||
sudo_warn("%s", U_("unable to restore root directory"));
|
|
||||||
ret = false;
|
|
||||||
}
|
|
||||||
close(state->saved_root);
|
|
||||||
state->saved_root = -1;
|
|
||||||
}
|
|
||||||
if (state->saved_cwd != -1) {
|
|
||||||
if (fchdir(state->saved_cwd) == -1) {
|
|
||||||
sudo_warn("%s", U_("unable to restore current working directory"));
|
|
||||||
ret = false;
|
|
||||||
}
|
|
||||||
close(state->saved_cwd);
|
|
||||||
state->saved_cwd = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_return_bool(ret);
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-License-Identifier: ISC
|
|
||||||
*
|
|
||||||
* Copyright (c) 2023 Todd C. Miller <Todd.Miller@sudo.ws>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SUDOERS_PIVOT_H
|
|
||||||
#define SUDOERS_PIVOT_H
|
|
||||||
|
|
||||||
#define SUDOERS_PIVOT_INITIALIZER { -1, -1 }
|
|
||||||
|
|
||||||
struct sudoers_pivot {
|
|
||||||
int saved_root;
|
|
||||||
int saved_cwd;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool pivot_root(const char *new_root, struct sudoers_pivot *state);
|
|
||||||
bool unpivot_root(struct sudoers_pivot *state);
|
|
||||||
|
|
||||||
#endif /* SUDOERS_PIVOT_H */
|
|
@ -80,7 +80,8 @@ sudo_dso_public int main(int argc, char *argv[]);
|
|||||||
/* STUB */
|
/* STUB */
|
||||||
int
|
int
|
||||||
find_path(const char *infile, char **outfile, struct stat *sbp,
|
find_path(const char *infile, char **outfile, struct stat *sbp,
|
||||||
const char *path, bool ignore_dot, char * const *allowlist)
|
const char *path, const char *runchroot, bool ignore_dot,
|
||||||
|
char * const *allowlist)
|
||||||
{
|
{
|
||||||
if (infile[0] == '/') {
|
if (infile[0] == '/') {
|
||||||
*outfile = strdup(infile);
|
*outfile = strdup(infile);
|
||||||
|
@ -832,7 +832,8 @@ display_privs(struct sudoers_context *ctx, const struct sudo_nss_list *snl,
|
|||||||
/* STUB */
|
/* STUB */
|
||||||
int
|
int
|
||||||
find_path(const char *infile, char **outfile, struct stat *sbp,
|
find_path(const char *infile, char **outfile, struct stat *sbp,
|
||||||
const char *path, bool ignore_dot, char * const *allowlist)
|
const char *path, const char *runchroot, bool ignore_dot,
|
||||||
|
char * const *allowlist)
|
||||||
{
|
{
|
||||||
switch (pass) {
|
switch (pass) {
|
||||||
case PASS_CHECK_NOT_FOUND:
|
case PASS_CHECK_NOT_FOUND:
|
||||||
@ -855,9 +856,9 @@ find_path(const char *infile, char **outfile, struct stat *sbp,
|
|||||||
/* STUB */
|
/* STUB */
|
||||||
int
|
int
|
||||||
resolve_cmnd(struct sudoers_context *ctx, const char *infile, char **outfile,
|
resolve_cmnd(struct sudoers_context *ctx, const char *infile, char **outfile,
|
||||||
const char *path)
|
const char *path, const char *runchroot)
|
||||||
{
|
{
|
||||||
return find_path(infile, outfile, NULL, path, false, NULL);
|
return find_path(infile, outfile, NULL, path, NULL, false, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* STUB */
|
/* STUB */
|
||||||
|
@ -57,18 +57,6 @@ init_eventlog_config(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
pivot_root(const char *new_root, struct sudoers_pivot *state)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
unpivot_root(struct sudoers_pivot *state)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
group_plugin_query(const char *user, const char *group, const struct passwd *pw)
|
group_plugin_query(const char *user, const char *group, const struct passwd *pw)
|
||||||
{
|
{
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
resolve_cmnd(struct sudoers_context *ctx, const char *infile,
|
resolve_cmnd(struct sudoers_context *ctx, const char *infile,
|
||||||
char **outfile, const char *path)
|
char **outfile, const char *path, const char *runchroot)
|
||||||
{
|
{
|
||||||
int ret = NOT_FOUND_ERROR;
|
int ret = NOT_FOUND_ERROR;
|
||||||
debug_decl(resolve_cmnd, SUDOERS_DEBUG_UTIL);
|
debug_decl(resolve_cmnd, SUDOERS_DEBUG_UTIL);
|
||||||
@ -42,7 +42,7 @@ resolve_cmnd(struct sudoers_context *ctx, const char *infile,
|
|||||||
if (!set_perms(ctx, PERM_RUNAS))
|
if (!set_perms(ctx, PERM_RUNAS))
|
||||||
goto done;
|
goto done;
|
||||||
ret = find_path(infile, outfile, ctx->user.cmnd_stat, path,
|
ret = find_path(infile, outfile, ctx->user.cmnd_stat, path,
|
||||||
def_ignore_dot, NULL);
|
runchroot, def_ignore_dot, NULL);
|
||||||
if (!restore_perms())
|
if (!restore_perms())
|
||||||
goto done;
|
goto done;
|
||||||
if (ret == NOT_FOUND) {
|
if (ret == NOT_FOUND) {
|
||||||
@ -50,7 +50,7 @@ resolve_cmnd(struct sudoers_context *ctx, const char *infile,
|
|||||||
if (!set_perms(ctx, PERM_USER))
|
if (!set_perms(ctx, PERM_USER))
|
||||||
goto done;
|
goto done;
|
||||||
ret = find_path(infile, outfile, ctx->user.cmnd_stat, path,
|
ret = find_path(infile, outfile, ctx->user.cmnd_stat, path,
|
||||||
def_ignore_dot, NULL);
|
runchroot, def_ignore_dot, NULL);
|
||||||
if (!restore_perms())
|
if (!restore_perms())
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
@ -94,17 +94,3 @@ init_eventlog_config(void)
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* STUB */
|
|
||||||
bool
|
|
||||||
pivot_root(const char *new_root, struct sudoers_pivot *state)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* STUB */
|
|
||||||
bool
|
|
||||||
unpivot_root(struct sudoers_pivot *state)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
@ -1092,7 +1092,6 @@ init_vars(struct sudoers_context *ctx, char * const envp[])
|
|||||||
int
|
int
|
||||||
set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)
|
set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)
|
||||||
{
|
{
|
||||||
struct sudoers_pivot pivot_state = SUDOERS_PIVOT_INITIALIZER;
|
|
||||||
const char *cmnd_in;
|
const char *cmnd_in;
|
||||||
char *cmnd_out = NULL;
|
char *cmnd_out = NULL;
|
||||||
char *path = ctx->user.path;
|
char *path = ctx->user.path;
|
||||||
@ -1111,13 +1110,7 @@ set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)
|
|||||||
if (def_secure_path && !user_is_exempt(ctx))
|
if (def_secure_path && !user_is_exempt(ctx))
|
||||||
path = def_secure_path;
|
path = def_secure_path;
|
||||||
|
|
||||||
/* Pivot root. */
|
ret = resolve_cmnd(ctx, cmnd_in, &cmnd_out, path, runchroot);
|
||||||
if (runchroot != NULL) {
|
|
||||||
if (!pivot_root(runchroot, &pivot_state))
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = resolve_cmnd(ctx, cmnd_in, &cmnd_out, path);
|
|
||||||
if (ret == FOUND) {
|
if (ret == FOUND) {
|
||||||
char *slash = strrchr(cmnd_out, '/');
|
char *slash = strrchr(cmnd_out, '/');
|
||||||
if (slash != NULL) {
|
if (slash != NULL) {
|
||||||
@ -1134,14 +1127,8 @@ set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)
|
|||||||
else
|
else
|
||||||
ctx->user.cmnd = cmnd_out;
|
ctx->user.cmnd = cmnd_out;
|
||||||
|
|
||||||
/* Restore root. */
|
|
||||||
if (runchroot != NULL)
|
|
||||||
(void)unpivot_root(&pivot_state);
|
|
||||||
|
|
||||||
debug_return_int(ret);
|
debug_return_int(ret);
|
||||||
error:
|
error:
|
||||||
if (runchroot != NULL)
|
|
||||||
(void)unpivot_root(&pivot_state);
|
|
||||||
free(cmnd_out);
|
free(cmnd_out);
|
||||||
debug_return_int(NOT_FOUND_ERROR);
|
debug_return_int(NOT_FOUND_ERROR);
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,6 @@
|
|||||||
#include <defaults.h>
|
#include <defaults.h>
|
||||||
#include <logging.h>
|
#include <logging.h>
|
||||||
#include <parse.h>
|
#include <parse.h>
|
||||||
#include <pivot.h>
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Info passed in from the sudo front-end.
|
* Info passed in from the sudo front-end.
|
||||||
@ -314,15 +313,16 @@ struct stat;
|
|||||||
* Function prototypes
|
* Function prototypes
|
||||||
*/
|
*/
|
||||||
/* goodpath.c */
|
/* goodpath.c */
|
||||||
bool sudo_goodpath(const char *path, struct stat *sbp);
|
bool sudo_goodpath(const char *path, const char *runchroot, struct stat *sbp);
|
||||||
|
|
||||||
/* findpath.c */
|
/* findpath.c */
|
||||||
int find_path(const char *infile, char **outfile, struct stat *sbp,
|
int find_path(const char *infile, char **outfile, struct stat *sbp,
|
||||||
const char *path, bool ignore_dot, char * const *allowlist);
|
const char *path, const char *runchroot, bool ignore_dot,
|
||||||
|
char * const *allowlist);
|
||||||
|
|
||||||
/* resolve_cmnd.c */
|
/* resolve_cmnd.c */
|
||||||
int resolve_cmnd(struct sudoers_context *ctx, const char *infile,
|
int resolve_cmnd(struct sudoers_context *ctx, const char *infile,
|
||||||
char **outfile, const char *path);
|
char **outfile, const char *path, const char *runchroot);
|
||||||
|
|
||||||
/* check.c */
|
/* check.c */
|
||||||
int check_user(struct sudoers_context *ctx, unsigned int validated, unsigned int mode);
|
int check_user(struct sudoers_context *ctx, unsigned int validated, unsigned int mode);
|
||||||
|
@ -604,18 +604,6 @@ init_eventlog_config(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
pivot_root(const char *new_root, struct sudoers_pivot *state)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
unpivot_root(struct sudoers_pivot *state)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)
|
set_cmnd_path(struct sudoers_context *ctx, const char *runchroot)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user