Add canon_path(), a realpath() wrapper that performs caching.
This also adds a new user_cmnd_dir variable that stores the canonicalized parent directory of the command to be run.
This commit is contained in:
1
MANIFEST
1
MANIFEST
@@ -740,6 +740,7 @@ plugins/sudoers/prompt.c
|
||||
plugins/sudoers/pwutil.c
|
||||
plugins/sudoers/pwutil.h
|
||||
plugins/sudoers/pwutil_impl.c
|
||||
plugins/sudoers/canon_path.c
|
||||
plugins/sudoers/redblack.c
|
||||
plugins/sudoers/redblack.h
|
||||
plugins/sudoers/regress/check_symbols/check_symbols.c
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: ISC
|
||||
#
|
||||
# Copyright (c) 1996, 1998-2005, 2007-2022
|
||||
# Copyright (c) 1996, 1998-2005, 2007-2023
|
||||
# Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software for any
|
||||
@@ -173,9 +173,9 @@ FUZZ_VERBOSE =
|
||||
|
||||
AUTH_OBJS = sudo_auth.lo @AUTH_OBJS@
|
||||
|
||||
LIBPARSESUDOERS_OBJS = alias.lo b64_decode.lo defaults.lo digestname.lo \
|
||||
exptilde.lo filedigest.lo gentime.lo gram.lo \
|
||||
match.lo match_addr.lo match_command.lo \
|
||||
LIBPARSESUDOERS_OBJS = alias.lo b64_decode.lo canon_path.lo defaults.lo \
|
||||
digestname.lo exptilde.lo filedigest.lo gentime.lo \
|
||||
gram.lo match.lo match_addr.lo match_command.lo \
|
||||
match_digest.lo pivot.lo pwutil.lo pwutil_impl.lo \
|
||||
redblack.lo strlist.lo sudoers_debug.lo timeout.lo \
|
||||
timestr.lo toke.lo toke_util.lo
|
||||
@@ -914,6 +914,30 @@ bsm_audit.i: $(srcdir)/bsm_audit.c $(devdir)/def_data.h \
|
||||
$(CC) -E -o $@ $(CPPFLAGS) $<
|
||||
bsm_audit.plog: bsm_audit.i
|
||||
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/bsm_audit.c --i-file $< --output-file $@
|
||||
canon_path.lo: $(srcdir)/canon_path.c $(devdir)/def_data.h \
|
||||
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
|
||||
$(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
|
||||
$(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
|
||||
$(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
|
||||
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
|
||||
$(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \
|
||||
$(srcdir)/redblack.h $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \
|
||||
$(srcdir)/sudoers_debug.h $(top_builddir)/config.h \
|
||||
$(top_builddir)/pathnames.h
|
||||
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/canon_path.c
|
||||
canon_path.i: $(srcdir)/canon_path.c $(devdir)/def_data.h \
|
||||
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
|
||||
$(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
|
||||
$(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
|
||||
$(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \
|
||||
$(incdir)/sudo_queue.h $(incdir)/sudo_util.h \
|
||||
$(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \
|
||||
$(srcdir)/redblack.h $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \
|
||||
$(srcdir)/sudoers_debug.h $(top_builddir)/config.h \
|
||||
$(top_builddir)/pathnames.h
|
||||
$(CC) -E -o $@ $(CPPFLAGS) $<
|
||||
canon_path.plog: canon_path.i
|
||||
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/canon_path.c --i-file $< --output-file $@
|
||||
check.lo: $(srcdir)/check.c $(devdir)/def_data.h $(incdir)/compat/stdbool.h \
|
||||
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \
|
||||
$(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \
|
||||
|
199
plugins/sudoers/canon_path.c
Normal file
199
plugins/sudoers/canon_path.c
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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 <sys/stat.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "sudoers.h"
|
||||
#include "redblack.h"
|
||||
|
||||
static struct rbtree *canon_cache;
|
||||
|
||||
/*
|
||||
* A cache_item includes storage for both the original path and the
|
||||
* resolved path. The resolved path is directly embedded into the
|
||||
* struct so that we can find the start of the struct cache_item
|
||||
* given the value of resolved. Storage for pathname is embedded
|
||||
* at the end, after resolved.
|
||||
*/
|
||||
struct cache_item {
|
||||
unsigned int refcnt;
|
||||
char *pathname;
|
||||
char resolved[1]; /* actually bigger */
|
||||
};
|
||||
|
||||
/*
|
||||
* Compare function for canon_cache.
|
||||
* v1 is the key to find or data to insert, v2 is in-tree data.
|
||||
*/
|
||||
static int
|
||||
compare(const void *v1, const void *v2)
|
||||
{
|
||||
const struct cache_item *ci1 = (const struct cache_item *)v1;
|
||||
const struct cache_item *ci2 = (const struct cache_item *)v2;
|
||||
return strcmp(ci1->pathname, ci2->pathname);
|
||||
}
|
||||
|
||||
/* Convert a pointer returned by canon_path() to a struct cache_item *. */
|
||||
#define resolved_to_item(_r) ((struct cache_item *)((_r) - offsetof(struct cache_item, resolved)))
|
||||
|
||||
/*
|
||||
* Delete a ref from item and free if the refcount reaches 0.
|
||||
*/
|
||||
static void
|
||||
canon_path_free_item(void *v)
|
||||
{
|
||||
struct cache_item *item = v;
|
||||
debug_decl(canon_path_free_item, SUDOERS_DEBUG_UTIL);
|
||||
|
||||
if (--item->refcnt == 0)
|
||||
free(item);
|
||||
|
||||
debug_return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete a ref from the item containing "resolved" and free if
|
||||
* the refcount reaches 0.
|
||||
*/
|
||||
void
|
||||
canon_path_free(char *resolved)
|
||||
{
|
||||
debug_decl(canon_path_free, SUDOERS_DEBUG_UTIL);
|
||||
if (resolved != NULL)
|
||||
canon_path_free_item(resolved_to_item(resolved));
|
||||
debug_return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free canon_cache.
|
||||
* This only removes the reference for that the cache owns.
|
||||
* Other references remain valid until canon_path_free() is called.
|
||||
*/
|
||||
void
|
||||
canon_path_free_cache(void)
|
||||
{
|
||||
debug_decl(canon_path_free_cache, SUDOERS_DEBUG_UTIL);
|
||||
|
||||
if (canon_cache != NULL) {
|
||||
rbdestroy(canon_cache, canon_path_free_item);
|
||||
canon_cache = NULL;
|
||||
}
|
||||
|
||||
debug_return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Like realpath(3) but caches the result. Returns an entry from the
|
||||
* cache on success (with an added reference) or NULL on failure.
|
||||
*/
|
||||
char *
|
||||
canon_path(const char *inpath)
|
||||
{
|
||||
size_t item_size, inlen, reslen = 0;
|
||||
char *resolved, resbuf[PATH_MAX];
|
||||
struct cache_item key, *item;
|
||||
struct rbnode *node = NULL;
|
||||
debug_decl(canon_path, SUDOERS_DEBUG_UTIL);
|
||||
|
||||
if (canon_cache == NULL) {
|
||||
canon_cache = rbcreate(compare);
|
||||
if (canon_cache == NULL) {
|
||||
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
||||
debug_return_str(NULL);
|
||||
}
|
||||
} else {
|
||||
/* Check cache. */
|
||||
key.pathname = (char *)inpath;
|
||||
if ((node = rbfind(canon_cache, &key)) != NULL) {
|
||||
item = node->data;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Not cached, call realpath(3).
|
||||
* Older realpath() doesn't support passing a NULL buffer.
|
||||
* We special-case the empty string to resolve to "/".
|
||||
* XXX - warn on errors other than ENOENT?
|
||||
*/
|
||||
if (*inpath == '\0')
|
||||
resolved = (char *)"/";
|
||||
else
|
||||
resolved = realpath(inpath, resbuf);
|
||||
|
||||
inlen = strlen(inpath);
|
||||
item_size = sizeof(*item) + inlen + 1;
|
||||
if (resolved != NULL) {
|
||||
reslen = strlen(resolved);
|
||||
item_size += reslen;
|
||||
}
|
||||
item = malloc(item_size);
|
||||
if (item == NULL) {
|
||||
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
||||
debug_return_str(NULL);
|
||||
}
|
||||
if (resolved != NULL)
|
||||
memcpy(item->resolved, resolved, reslen);
|
||||
item->resolved[reslen] = '\0';
|
||||
item->pathname = item->resolved + reslen + 1;
|
||||
memcpy(item->pathname, inpath, inlen);
|
||||
item->pathname[inlen] = '\0';
|
||||
item->refcnt = 1;
|
||||
switch (rbinsert(canon_cache, item, NULL)) {
|
||||
case 1:
|
||||
/* should not happen */
|
||||
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
|
||||
"path \"%s\" already exists in the cache", inpath);
|
||||
item->refcnt = 0;
|
||||
break;
|
||||
case -1:
|
||||
/* can't cache item, just return it */
|
||||
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
|
||||
"can't cache path \"%s\"", inpath);
|
||||
item->refcnt = 0;
|
||||
break;
|
||||
}
|
||||
done:
|
||||
if (item->refcnt != 0) {
|
||||
sudo_debug_printf(SUDO_DEBUG_DEBUG,
|
||||
"%s: path %s -> %s (%s)", __func__, inpath,
|
||||
item->resolved[0] ? item->resolved : "NULL",
|
||||
node ? "cache hit" : "cached");
|
||||
}
|
||||
if (item->resolved[0] == '\0') {
|
||||
/* negative result, free item if not cached */
|
||||
if (item->refcnt == 0)
|
||||
free(item);
|
||||
debug_return_str(NULL);
|
||||
}
|
||||
item->refcnt++;
|
||||
debug_return_str(item->resolved);
|
||||
}
|
@@ -1007,6 +1007,8 @@ set_cmnd_path(const char *runchroot)
|
||||
list_cmnd = NULL;
|
||||
free(user_cmnd);
|
||||
user_cmnd = NULL;
|
||||
canon_path_free(user_cmnd_dir);
|
||||
user_cmnd_dir = NULL;
|
||||
if (def_secure_path && !user_is_exempt())
|
||||
path = def_secure_path;
|
||||
|
||||
@@ -1031,6 +1033,17 @@ set_cmnd_path(const char *runchroot)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (cmnd_out != NULL) {
|
||||
char *slash = strrchr(cmnd_out, '/');
|
||||
if (slash != NULL) {
|
||||
*slash = '\0';
|
||||
user_cmnd_dir = canon_path(cmnd_out);
|
||||
if (user_cmnd_dir == NULL && errno == ENOMEM)
|
||||
goto error;
|
||||
*slash = '/';
|
||||
}
|
||||
}
|
||||
|
||||
if (ISSET(sudo_mode, MODE_CHECK))
|
||||
list_cmnd = cmnd_out;
|
||||
else
|
||||
@@ -1849,6 +1862,7 @@ sudoers_cleanup(void)
|
||||
sudo_user_free();
|
||||
sudo_freepwcache();
|
||||
sudo_freegrcache();
|
||||
canon_path_free_cache();
|
||||
|
||||
/* Clear globals */
|
||||
list_pw = NULL;
|
||||
@@ -1908,6 +1922,7 @@ sudo_user_free(void)
|
||||
free(user_srunhost);
|
||||
free(user_runhost);
|
||||
free(user_cmnd);
|
||||
canon_path_free(user_cmnd_dir);
|
||||
free(user_args);
|
||||
free(list_cmnd);
|
||||
free(safe_cmnd);
|
||||
|
@@ -102,6 +102,7 @@ struct sudo_user {
|
||||
char *cmnd;
|
||||
char *cmnd_args;
|
||||
char *cmnd_base;
|
||||
char *cmnd_dir;
|
||||
char *cmnd_list;
|
||||
char *cmnd_safe;
|
||||
char *cmnd_saved;
|
||||
@@ -239,6 +240,7 @@ struct sudo_user {
|
||||
#define user_ttypath (sudo_user.ttypath)
|
||||
#define user_cwd (sudo_user.cwd)
|
||||
#define user_cmnd (sudo_user.cmnd)
|
||||
#define user_cmnd_dir (sudo_user.cmnd_dir)
|
||||
#define user_args (sudo_user.cmnd_args)
|
||||
#define user_base (sudo_user.cmnd_base)
|
||||
#define user_stat (sudo_user.cmnd_stat)
|
||||
@@ -476,6 +478,11 @@ bool sudoers_gc_remove(enum sudoers_gc_types type, void *ptr);
|
||||
void sudoers_gc_init(void);
|
||||
void sudoers_gc_run(void);
|
||||
|
||||
/* canon_path.c */
|
||||
char *canon_path(const char *inpath);
|
||||
void canon_path_free(char *resolved);
|
||||
void canon_path_free_cache(void);
|
||||
|
||||
/* strlcpy_unesc.c */
|
||||
size_t strlcpy_unescape(char *dst, const char *src, size_t size);
|
||||
|
||||
|
Reference in New Issue
Block a user