diff --git a/MANIFEST b/MANIFEST index 565b506f9..2b6ac648c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -230,6 +230,7 @@ lib/util/logfac.c lib/util/logpri.c lib/util/memrchr.c lib/util/mkdir_parents.c +lib/util/mkdirat.c lib/util/mksiglist.c lib/util/mksigname.c lib/util/mktemp.c diff --git a/config.h.in b/config.h.in index 5066ca563..098edd32f 100644 --- a/config.h.in +++ b/config.h.in @@ -587,6 +587,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_MINIX_CONFIG_H +/* Define to 1 if you have the `mkdirat' function. */ +#undef HAVE_MKDIRAT + /* Define to 1 if you have the `mkdtemp' function. */ #undef HAVE_MKDTEMP diff --git a/configure b/configure index 0387c9cc8..865913235 100755 --- a/configure +++ b/configure @@ -22530,6 +22530,32 @@ esac fi +fi + +done + + for ac_func in mkdirat +do : + ac_fn_c_check_func "$LINENO" "mkdirat" "ac_cv_func_mkdirat" +if test "x$ac_cv_func_mkdirat" = xyes +then : + printf "%s\n" "#define HAVE_MKDIRAT 1" >>confdefs.h + +else $as_nop + + case " $LIBOBJS " in + *" mkdirat.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS mkdirat.$ac_objext" + ;; +esac + + + for _sym in sudo_mkdirat; do + COMPAT_EXP="${COMPAT_EXP}${_sym} +" + done + + fi done diff --git a/configure.ac b/configure.ac index eee97159a..9efcce526 100644 --- a/configure.ac +++ b/configure.ac @@ -2820,6 +2820,10 @@ AC_CHECK_FUNCS(nanosleep, [], [ SUDO_APPEND_COMPAT_EXP(sudo_nanosleep) ]) ]) +AC_CHECK_FUNCS([mkdirat], [], [ + AC_LIBOBJ(mkdirat) + SUDO_APPEND_COMPAT_EXP(sudo_mkdirat) +]) AC_CHECK_FUNCS([openat], [], [ AC_LIBOBJ(openat) SUDO_APPEND_COMPAT_EXP(sudo_openat) diff --git a/include/sudo_compat.h b/include/sudo_compat.h index 8f3ad622b..bf619925e 100644 --- a/include/sudo_compat.h +++ b/include/sudo_compat.h @@ -533,6 +533,11 @@ sudo_dso_public void *sudo_memrchr(const void *s, int c, size_t n); # undef memrchr # define memrchr(_a, _b, _c) sudo_memrchr((_a), (_b), (_c)) #endif /* HAVE_MEMRCHR */ +#ifndef HAVE_MKDIRAT +sudo_dso_public int sudo_mkdirat(int dfd, const char *path, mode_t mode); +# undef mkdirat +# define mkdirat(_a, _b, _c) sudo_mkdirat((_a), (_b), (_c)) +#endif /* HAVE_MKDIRAT */ #if !defined(HAVE_MKDTEMP) || !defined(HAVE_MKSTEMPS) sudo_dso_public char *sudo_mkdtemp(char *path); # undef mkdtemp diff --git a/include/sudo_util.h b/include/sudo_util.h index 1e5305b37..9f115746b 100644 --- a/include/sudo_util.h +++ b/include/sudo_util.h @@ -238,7 +238,7 @@ sudo_dso_public const char *sudo_logpri2str_v1(int num); #define sudo_logpri2str(_a) sudo_logpri2str_v1((_a)) /* mkdir_parents.c */ -sudo_dso_public bool sudo_mkdir_parents_v1(char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet); +sudo_dso_public bool sudo_mkdir_parents_v1(const char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet); #define sudo_mkdir_parents(_a, _b, _c, _d, _e) sudo_mkdir_parents_v1((_a), (_b), (_c), (_d), (_e)) /* parseln.c */ diff --git a/lib/util/Makefile.in b/lib/util/Makefile.in index 5155e00e6..048191cda 100644 --- a/lib/util/Makefile.in +++ b/lib/util/Makefile.in @@ -1058,6 +1058,12 @@ mkdir_parents.i: $(srcdir)/mkdir_parents.c $(incdir)/compat/stdbool.h \ $(CC) -E -o $@ $(CPPFLAGS) $< mkdir_parents.plog: mkdir_parents.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/mkdir_parents.c --i-file $< --output-file $@ +mkdirat.lo: $(srcdir)/mkdirat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/mkdirat.c +mkdirat.i: $(srcdir)/mkdirat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +mkdirat.plog: mkdirat.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/mkdirat.c --i-file $< --output-file $@ mksiglist.lo: $(srcdir)/mksiglist.c $(incdir)/sudo_compat.h \ $(srcdir)/mksiglist.h $(top_builddir)/config.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/mksiglist.c diff --git a/lib/util/mkdir_parents.c b/lib/util/mkdir_parents.c index ef0d8bae3..31d2c8700 100644 --- a/lib/util/mkdir_parents.c +++ b/lib/util/mkdir_parents.c @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2009-2017 Todd C. Miller + * Copyright (c) 2009-2021 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 @@ -33,6 +33,7 @@ #endif /* HAVE_STDBOOL_H */ #include #include +#include #include #include @@ -44,68 +45,111 @@ /* * Create any parent directories needed by path (but not path itself). - * Note that path is modified but is restored before it returns. */ bool -sudo_mkdir_parents_v1(char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet) +sudo_mkdir_parents_v1(const char *path, uid_t uid, gid_t gid, mode_t mode, bool quiet) { - char *slash = path; + const char *cp, *ep, *pathend; + bool ret = false; + int parentfd; debug_decl(sudo_mkdir_parents, SUDO_DEBUG_UTIL); - while ((slash = strchr(slash + 1, '/')) != NULL) { - struct stat sb; - int dfd; + /* Starting parent dir is either root or cwd. */ + cp = path; + if (*cp == '/') { + do { + cp++; + } while (*cp == '/'); + parentfd = open("/", O_RDONLY|O_NONBLOCK); + } else { + parentfd = open(".", O_RDONLY|O_NONBLOCK); + } + if (parentfd == -1) { + if (!quiet) + sudo_warn(U_("unable to open %s"), *path == '/' ? "/" : "."); + debug_return_bool(false); + } + + /* Iterate over path components, skipping the last one. */ + pathend = cp + strlen(cp); + for (cp = sudo_strsplit(cp, pathend, "/", &ep); cp != NULL && ep != NULL; + cp = sudo_strsplit(NULL, pathend, "/", &ep)) { + + struct stat sb; + char name[MAXNAMLEN + 1]; + int dfd, len; - *slash = '\0'; sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO, - "mkdir %s, mode 0%o, uid %d, gid %d", path, (unsigned int)mode, - (int)uid, (int)gid); + "mkdir %.*s, mode 0%o, uid %d, gid %d", (int)(ep - path), + path, (unsigned int)mode, (int)uid, (int)gid); + len = snprintf(name, sizeof(name), "%.*s", (int)(ep - cp), cp); + if (len >= ssizeof(name)) { + errno = ENAMETOOLONG; + if (!quiet) + sudo_warn(U_("unable to open %.*s"), (int)(ep - path), path); + goto done; + } reopen: - dfd = open(path, O_RDONLY|O_NONBLOCK); + dfd = openat(parentfd, name, O_RDONLY|O_NONBLOCK, 0); if (dfd == -1) { if (errno != ENOENT) { - if (!quiet) - sudo_warn(U_("unable to open %s"), path); - goto bad; + if (!quiet) { + sudo_warn(U_("unable to open %.*s"), + (int)(ep - path), path); + } + goto done; } - if (mkdir(path, mode) == 0) { + if (mkdirat(parentfd, name, mode) == 0) { + dfd = openat(parentfd, name, O_RDONLY|O_NONBLOCK, 0); + if (dfd == -1) { + if (!quiet) { + sudo_warn(U_("unable to open %.*s"), + (int)(ep - path), path); + } + goto done; + } if (uid != (uid_t)-1 && gid != (gid_t)-1) { - if (chown(path, uid, gid) != 0) { + if (fchown(dfd, uid, gid) != 0) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO, - "%s: unable to chown %d:%d %s", __func__, - (int)uid, (int)gid, path); + "%s: unable to chown %d:%d %.*s", __func__, + (int)uid, (int)gid, (int)(ep - path), path); } } } else { if (errno == EEXIST) goto reopen; - if (!quiet) - sudo_warn(U_("unable to mkdir %s"), path); - goto bad; + if (!quiet) { + sudo_warn(U_("unable to mkdir %.*s"), + (int)(ep - path), path); + } + goto done; } } else { /* Already exists, make sure it is a directory. */ - int rc = fstat(dfd, &sb); - close(dfd); - if (rc != 0) { - if (!quiet) - sudo_warn(U_("unable to stat %s"), path); - goto bad; + if (fstat(dfd, &sb) != 0) { + if (!quiet) { + sudo_warn(U_("unable to stat %.*s"), + (int)(ep - path), path); + } + close(dfd); + goto done; } if (!S_ISDIR(sb.st_mode)) { - if (!quiet) - sudo_warnx(U_("%s exists but is not a directory (0%o)"), - path, (unsigned int) sb.st_mode); - goto bad; + if (!quiet) { + sudo_warnx(U_("%.*s exists but is not a directory (0%o)"), + (int)(ep - path), path, (unsigned int) sb.st_mode); + } + close(dfd); + goto done; } } - *slash = '/'; + close(parentfd); + parentfd = dfd; } + ret = true; - debug_return_bool(true); -bad: - /* We must restore the path before we return. */ - /* cppcheck-suppress nullPointerRedundantCheck */ - *slash = '/'; - debug_return_bool(false); +done: + if (parentfd != -1) + close(parentfd); + debug_return_bool(ret); } diff --git a/lib/util/mkdirat.c b/lib/util/mkdirat.c new file mode 100644 index 000000000..4dee92d10 --- /dev/null +++ b/lib/util/mkdirat.c @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2015, 2019-2021 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. + */ + +/* + * 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 + +#include +#include +#include + +#include "sudo_compat.h" + +#ifndef HAVE_MKDIRAT +int +sudo_mkdirat(int dfd, const char *path, mode_t mode) +{ + int ret, odfd; + + if (dfd == (int)AT_FDCWD) + return mkdir(path, mode); + + /* Save cwd */ + if ((odfd = open(".", O_RDONLY)) == -1) + return -1; + + if (fchdir(dfd) == -1) { + close(odfd); + return -1; + } + + ret = mkdir(path, mode); + + /* Restore cwd */ + if (fchdir(odfd) == -1) { + /* Should not happen */ + ret = -1; + } + close(odfd); + + return ret; +} +#endif /* HAVE_MKDIRAT */ diff --git a/logsrvd/logsrvd.c b/logsrvd/logsrvd.c index 63df2ce26..5a412e50b 100644 --- a/logsrvd/logsrvd.c +++ b/logsrvd/logsrvd.c @@ -1750,7 +1750,7 @@ write_pidfile(void) int fd; bool success; mode_t oldmask; - char *pid_file = (char *)logsrvd_conf_pid_file(); + const char *pid_file = logsrvd_conf_pid_file(); debug_decl(write_pidfile, SUDO_DEBUG_UTIL); if (pid_file == NULL) @@ -1759,7 +1759,6 @@ write_pidfile(void) /* Default logsrvd umask is more restrictive (077). */ oldmask = umask(S_IWGRP|S_IWOTH); - /* sudo_mkdir_parents() modifies the path but restores it before return. */ success = sudo_mkdir_parents(pid_file, ROOT_UID, ROOT_GID, S_IRWXU|S_IXGRP|S_IXOTH, false); if (success) { diff --git a/plugins/sudoers/timestamp.c b/plugins/sudoers/timestamp.c index 0a321bd84..50d7a02bf 100644 --- a/plugins/sudoers/timestamp.c +++ b/plugins/sudoers/timestamp.c @@ -185,7 +185,7 @@ ts_find_record(int fd, struct timestamp_entry *key, struct timestamp_entry *entr * Returns false on failure and displays a warning to stderr. */ static bool -ts_mkdirs(char *path, uid_t owner, gid_t group, mode_t mode, +ts_mkdirs(const char *path, uid_t owner, gid_t group, mode_t mode, mode_t parent_mode, bool quiet) { bool ret; diff --git a/scripts/mkdep.pl b/scripts/mkdep.pl index 25624b4e3..1c12c109d 100755 --- a/scripts/mkdep.pl +++ b/scripts/mkdep.pl @@ -116,7 +116,7 @@ sub mkdep { # XXX - fill in AUTH_OBJS from contents of the auth dir instead $makefile =~ s:\@AUTH_OBJS\@:afs.lo aix_auth.lo bsdauth.lo dce.lo fwtk.lo getspwuid.lo kerb5.lo pam.lo passwd.lo rfc1938.lo secureware.lo securid5.lo sia.lo:; $makefile =~ s:\@DIGEST\@:digest.lo digest_openssl.lo digest_gcrypt.lo:; - $makefile =~ s:\@LTLIBOBJS\@:arc4random.lo arc4random_buf.lo arc4random_uniform.lo cfmakeraw.lo closefrom.lo dup3.lo explicit_bzero.lo fchmodat.lo freezero.lo fstatat.lo fnmatch.lo getaddrinfo.lo getcwd.lo getentropy.lo getgrouplist.lo getdelim.lo getopt_long.lo getusershell.lo glob.lo gmtime_r.lo inet_ntop_lo inet_pton.lo isblank.lo localtime_r.lo memrchr.lo mksiglist.lo mksigname.lo mktemp.lo nanosleep.lo openat.lo pipe2.lo pread.lo pwrite.lo pw_dup.lo reallocarray.lo sha2.lo sig2str.lo siglist.lo signame.lo snprintf.lo str2sig.lo strlcat.lo strlcpy.lo strndup.lo strnlen.lo strsignal.lo timegm.lo unlinkat.lo utimens.lo:; + $makefile =~ s:\@LTLIBOBJS\@:arc4random.lo arc4random_buf.lo arc4random_uniform.lo cfmakeraw.lo closefrom.lo dup3.lo explicit_bzero.lo fchmodat.lo freezero.lo fstatat.lo fnmatch.lo getaddrinfo.lo getcwd.lo getentropy.lo getgrouplist.lo getdelim.lo getopt_long.lo getusershell.lo glob.lo gmtime_r.lo inet_ntop_lo inet_pton.lo isblank.lo localtime_r.lo memrchr.lo mkdirat.lo mksiglist.lo mksigname.lo mktemp.lo nanosleep.lo openat.lo pipe2.lo pread.lo pwrite.lo pw_dup.lo reallocarray.lo sha2.lo sig2str.lo siglist.lo signame.lo snprintf.lo str2sig.lo strlcat.lo strlcpy.lo strndup.lo strnlen.lo strsignal.lo timegm.lo unlinkat.lo utimens.lo:; # Parse OBJS lines my %objs;