From f8163e5ea3ec204727c77cfd06fec788ad9679f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20L=C3=B3pez?= Date: Tue, 7 Jul 2026 17:00:14 +0200 Subject: [PATCH] RHEL-189002: dot_dot Minimal backport of commits 620ba7a3880d9934b349c28c9a670a8346a37c91 and 8443ab434e20f8003ca4bc11783462401722b987 --- include/sudo_util.h | 4 +++ lib/util/Makefile.in | 20 +++++++++---- lib/util/dotdot.c | 51 +++++++++++++++++++++++++++++++++ lib/util/util.exp.in | 1 + plugins/sudoers/match_command.c | 6 ++-- 5 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 lib/util/dotdot.c diff --git a/include/sudo_util.h b/include/sudo_util.h index 2da62d6..fdd81db 100644 --- a/include/sudo_util.h +++ b/include/sudo_util.h @@ -182,6 +182,10 @@ sudo_dso_public int aix_setauthdb_v2(char *user, char *registry); sudo_dso_public char *sudo_basename_v1(const char *filename); #define sudo_basename(_a) sudo_basename_v1(_a) +/* dotdot.c */ +sudo_dso_public bool sudo_contains_dot_dot_v1(const char *str); +#define sudo_contains_dot_dot(_a) sudo_contains_dot_dot_v1(_a) + /* gethostname.c */ sudo_dso_public char *sudo_gethostname_v1(void); #define sudo_gethostname() sudo_gethostname_v1() diff --git a/lib/util/Makefile.in b/lib/util/Makefile.in index 428a127..695ab8a 100644 --- a/lib/util/Makefile.in +++ b/lib/util/Makefile.in @@ -145,7 +145,7 @@ DEVEL = @DEVEL@ SHELL = @SHELL@ -LTOBJS = basename.lo @DIGEST@ event.lo fatal.lo key_val.lo gethostname.lo \ +LTOBJS = basename.lo @DIGEST@ dotdot.lo event.lo fatal.lo key_val.lo gethostname.lo \ gettime.lo getgrouplist.lo gidlist.lo hexchar.lo json.lo lbuf.lo \ locking.lo logfac.lo login_max.lo logpri.lo mkdir_parents.lo \ mmap_alloc.lo multiarch.lo parseln.lo progname.lo rcstr.lo regex.lo \ @@ -678,6 +678,16 @@ digest_test.i: $(srcdir)/regress/digest/digest_test.c \ $(CPP) $(CPPFLAGS) $(srcdir)/regress/digest/digest_test.c > $@ digest_test.plog: digest_test.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/digest/digest_test.c --i-file digest_test.i --output-file $@ +dotdot.lo: $(srcdir)/dotdot.c $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/dotdot.c +dotdot.i: $(srcdir)/dotdot.c $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h $(top_builddir)/config.h + $(CPP) $(CPPFLAGS) $(srcdir)/dotdot.c > $@ +dotdot.plog: dotdot.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --source-file $(srcdir)/dotdot.c --i-file dotdot.i --output-file $@ dup3.lo: $(srcdir)/dup3.c $(incdir)/sudo_compat.h $(top_builddir)/config.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/dup3.c dup3.i: $(srcdir)/dup3.c $(incdir)/sudo_compat.h $(top_builddir)/config.h diff --git a/lib/util/dotdot.c b/lib/util/dotdot.c new file mode 100644 index 0000000..66fd2bb --- /dev/null +++ b/lib/util/dotdot.c @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2025 Todd C. Miller + * + * 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. + */ + +#include + +#ifdef HAVE_STDBOOL_H +# include +#else +# include +#endif /* HAVE_STDBOOL_H */ +#include +#include +#include + +#include +#include +#include + +bool +sudo_contains_dot_dot_v1(const char *str) +{ + const char *cp; + debug_decl(sudo_contains_dot_dot, SUDO_DEBUG_UTIL); + + for (cp = str; *cp != '\0'; cp++) { + /* Match ".." */ + if (cp[0] != '.' || cp[1] != '.') + continue; + + /* Match "^.." or "/.." then "../" or "..$" */ + if ((cp == str || cp[-1] == '/') && (cp[2] == '/' || cp[2] == '\0')) + debug_return_bool(true); + } + + debug_return_bool(false); +} diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in index 0363147..98af276 100644 --- a/lib/util/util.exp.in +++ b/lib/util/util.exp.in @@ -17,6 +17,7 @@ sudo_conf_plugins_v1 sudo_conf_probe_interfaces_v1 sudo_conf_read_v1 sudo_conf_sesh_path_v1 +sudo_contains_dot_dot_v1 sudo_debug_deregister_v1 sudo_debug_enter_v1 sudo_debug_execve2_v1 diff --git a/plugins/sudoers/match_command.c b/plugins/sudoers/match_command.c index dc4d244..b2d231e 100644 --- a/plugins/sudoers/match_command.c +++ b/plugins/sudoers/match_command.c @@ -423,7 +423,8 @@ command_matches_fnmatch(struct sudoers_context *ctx, const char *sudoers_cmnd, * We do not attempt to match a relative path unless there is a * canonicalized version. */ - if (cmnd[0] != '/' || fnmatch(sudoers_cmnd, cmnd, FNM_PATHNAME) != 0) { + if (cmnd[0] != '/' || sudo_contains_dot_dot(cmnd) || + fnmatch(sudoers_cmnd, cmnd, FNM_PATHNAME) != 0) { /* No match, retry using the canonicalized path (if possible). */ if (ctx->user.cmnd_dir == NULL) debug_return_int(DENY); @@ -480,7 +481,8 @@ command_matches_regex(struct sudoers_context *ctx, const char *sudoers_cmnd, * * Neither sudoers_cmnd nor user_cmnd are relative to runchroot. */ - if (cmnd[0] != '/' || regex_matches(sudoers_cmnd, cmnd) != ALLOW) { + if (cmnd[0] != '/' || sudo_contains_dot_dot(cmnd) || + regex_matches(sudoers_cmnd, cmnd) != ALLOW) { /* No match, retry using the canonicalized path (if possible). */ if (ctx->user.cmnd_dir == NULL) debug_return_int(DENY); -- 2.54.0